/* ==========================================================================
   CRAWLLOWEEN entrance gate
   A haunted house you walk into. Load last.

   WHAT IT IS: footage of a Second Empire mansion at night, fog moving across the
   lawn, windows lit, burning orange sky behind it. The wordmark sits over the
   top. Press ENTER and the camera finishes walking up the steps, the front door
   swings open, and it lands inside a moonlit hall before the page arrives.

   TWO FILES, ONE MOVE. The idle clip holds wide and loops as a palindrome, so the
   fog never visibly cuts. The entry clip is the approach tail joined to the door
   opening. Both source clips pass through a black frame at the threshold, which
   is what makes the join invisible. `_recon/build_gate_video.py` cuts them and
   documents the timings.

   WHY IT IS NO LONGER A DRAWING. This replaced a hand-built SVG house whose
   windows flickered in CSS and whose transform-origin had to be measured off the
   doorway at runtime so the scale would push through the door rather than through
   the parlour wall. All of that is gone: the camera move is in the footage, so
   there is nothing to measure and nothing to fake. The tradeoff is bytes, which
   is why the renditions are split at 700px and the entry clip is not fetched
   until the idle clip is playing.

   HOW IT IS BUILT, AND WHY THAT ORDER MATTERS

   The gate markup ships `hidden`. `gate.js` removes `hidden` only after it has
   confirmed JS is running, the visitor has not entered before, and reduced
   motion is off. So the default state of this site is "no gate":

     - JS broken or blocked  -> no gate, straight to the page
     - a search engine       -> no gate, all content immediately crawlable
     - reduced motion        -> no gate
     - a returning visitor   -> no gate
     - deep link to a crawl  -> no gate, only the homepage has one

   That is the opposite of how a splash screen usually gets built, and it is the
   only version that cannot hide the site from a customer or from Google. Never
   invert it by hiding the page and revealing it with script.
   ========================================================================== */

.gate {
  position: fixed;
  inset: 0;
  z-index: var(--z-gate);
  overflow: hidden;
  /* Black, not the page background. The footage letterboxes on extreme aspect
     ratios and black is the only colour that reads as intentional there. */
  background: var(--gate-letterbox);
}

.gate[hidden] { display: none; }

/* Lock the page behind the gate. Set by gate.js rather than here, so a CSS-only
   load can never strand the page unscrollable. */
.gate-locked { overflow: hidden; }

/* ----------------------------------------------------------------- scene --- */

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

.gate__vid {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* cover, so the house fills any viewport. The composition has generous
     headroom and the subject is centred, so cropping is safe in both
     directions. */
  object-fit: cover;
  /* The interesting third of the frame is the door, slightly below centre.
     Biasing the crop down keeps the door on screen on tall phone viewports,
     where a centred crop pushes it under the fold. */
  object-position: 50% 62%;
  display: block;
  pointer-events: none;
}

/* The entry clip sits on top of the idle clip and is invisible until it plays,
   so the swap is a single class change with no reflow and no black frame. */
.gate__vid--enter {
  opacity: 0;
  z-index: 2;
}

.gate.is-playing .gate__vid--enter { opacity: 1; }

/* Hide the idle loop the instant the entry clip takes over. Both are the same
   shot at the same moment, so there is nothing to cross-fade. */
.gate.is-playing .gate__vid--idle { opacity: 0; }

/* Vignette and top veil. The footage has one warm light source and the type sits
   over the dark half of it, so this buys separation for the wordmark without
   flattening the shot the way a full scrim would. */
.gate__vignette {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: var(--gate-topveil), var(--gate-vignette);
  transition: opacity var(--dur-slow) var(--ease-exit);
}

.gate.is-playing .gate__vignette { opacity: 0.35; }

/* --------------------------------------------------------------- the plate - */

/* Type sits high so the house, the steps and the doorway stay visible under it. */
.gate__inner {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: grid;
  place-items: start center;
  padding: clamp(var(--sp-6), 7vh, var(--sp-9)) var(--gutter) 0;
  text-align: center;
  transition:
    opacity var(--dur-slow) var(--ease-exit),
    transform var(--dur-slow) var(--ease-exit);
}

.gate__plate { max-width: 44ch; }

.gate__eyebrow {
  font-family: var(--font-type);
  font-size: var(--fs-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--bone);
  margin-bottom: var(--sp-4);
  /* The typewriter face is the label voice borrowed from the reference site.
     It only ever carries a few words, never a sentence: see tokens.css. */
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.9);
}

/* THE WORDMARK.

   The commissioned logo, carved pumpkins and all, and it carries the correct
   two-L spelling. It replaced live stencil type that only existed because the
   flyer artwork spells the brand with one L.

   CAPPED AT 34rem, WHICH IS DELIBERATE AND LOOKS SMALLER THAN YOU EXPECT.
   The source is 640x314 and that is the largest that exists. Displaying it at hero
   width means upscaling past 1.2x, where the dimensional bevel on the letters goes
   soft and the whole thing reads as a low-resolution image rather than as artwork.
   544px (34rem) keeps it at or below native size on a 1x screen. It holds the frame
   anyway because the logo is far denser than type was. Raise this only when a
   larger export exists.

   The glow sits behind the image with a drop-shadow filter rather than a box
   shadow, because the artwork has a transparent background and a box shadow would
   draw a rectangle in mid-air around it. */
.gate__word {
  margin: 0;
  line-height: 0;
}

.gate__word picture,
.gate__word img {
  display: block;
  width: min(78vw, 34rem);
  height: auto;
  margin-inline: auto;
  filter:
    drop-shadow(0 6px 18px rgba(0, 0, 0, 0.85))
    drop-shadow(0 0 54px rgba(255, 122, 24, 0.34));
}

.gate__sub {
  margin: var(--sp-4) auto 0;
  max-width: 34ch;
  color: var(--bone);
  font-size: var(--fs-sm);
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.92);
}

.gate__btn { margin-top: var(--sp-5); min-width: 15rem; }

/* THE AGE NOTICE, AND WHY IT IS NOT A QUIET CAPTION.

   This was --text-dim with a soft shadow, which was legible over the landscape
   framing and became unreadable the moment the vertical rendition put the lit face
   of the house directly behind it. It is the one line on this plate that is a legal
   requirement rather than a design element, so it gets --bone and a shadow heavy
   enough to hold over any part of the footage.

   Two shadows on purpose: a tight dark one for edge definition against light
   masonry, and a wide one to darken the area around it. */
.gate__id {
  font-family: var(--font-type);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--bone);
  margin: var(--sp-3) 0 0;
  text-shadow:
    0 0 4px rgba(0, 0, 0, 0.95),
    0 1px 16px rgba(0, 0, 0, 0.9);
}

/* --text-faint over footage is not a real colour, it is a guess that happens to
   work on a flat background. Over a lit window it disappeared entirely, so this
   steps up a level and carries its own shadow like everything else on the plate. */
.gate__skip {
  display: block;
  width: 100%;
  min-height: var(--tap-min);
  text-align: center;
  margin-top: var(--sp-1);
  background: none;
  border: 0;
  color: var(--text-dim);
  font-size: var(--fs-micro);
  font-weight: var(--fw-semi);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  cursor: pointer;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.95), 0 0 3px rgba(0, 0, 0, 0.8);
}
.gate__skip:hover { color: var(--text); }

/* --------------------------------------------------- walking through it --- */

/* While the entry clip runs, the type clears out of the way. It leads the camera
   slightly rather than fading with it, so the frame is clean by the time the door
   is open. */
.gate.is-playing .gate__inner {
  opacity: 0;
  transform: translate3d(0, -1.5rem, 0) scale(1.06);
  transition:
    opacity var(--dur-base) var(--ease-exit),
    transform var(--dur-slower) var(--ease-exit);
  pointer-events: none;
}

/* The final dissolve. The entry clip already fades to black in the file, so this
   only has to hand off from black to the page. */
.gate.is-open { opacity: 0; transition: opacity var(--dur-slow) linear; }

/* --------------------------------------------------------- no-video fallback

   gate.js adds this when the entry clip is not buffered by the time the visitor
   clicks, or when the browser refuses to play it. The still poster pushes in and
   dissolves: a fast door instead of a stalled one. Never leave the visitor
   waiting on a download to get into a ticket page.
   ------------------------------------------------------------------------ */

.gate.is-cutting .gate__scene {
  transform: scale(1.35);
  opacity: 0;
  transition:
    transform var(--dur-slower) var(--ease-exit),
    opacity var(--dur-slow) linear var(--dur-fast);
}

.gate.is-cutting .gate__inner {
  opacity: 0;
  transform: scale(1.08);
}

/* ------------------------------------------------------------ the page behind

   The hero sits over-scaled behind the gate and settles as the camera arrives, so
   the arrival feels like it landed somewhere.
   ------------------------------------------------------------------------ */

.gate-armed .hero__media { transform: scale(1.1); }
.hero__media { transition: transform 1.5s var(--ease-standard); }

/* The hero's own headline and the fixed nav both sit behind the gate and both
   showed through earlier versions of it. They fade in as the class comes off,
   which also means the type arrives a beat after the room does.

   Opacity only, never visibility or display: the text stays in the layout and in
   the accessibility tree, because a decorative gate must never be the reason a
   screen reader misses the H1. */
.hero__inner,
.site-nav {
  transition: opacity var(--dur-slower) var(--ease-entrance) 260ms;
}

.gate-armed .hero__inner { opacity: 0; }

.gate-armed .site-nav {
  opacity: 0;
  pointer-events: none;
}

/* gate.js parks focus on <main> after the gate opens so the next Tab starts at
   the top of the page. That is a programmatic focus on a non-interactive
   container, and Chromium paints it as focus-visible, which drew the accent
   outline right around the hero headline. Suppress the ring on this one marked
   target only. Real focus rings everywhere else are untouched. */
[data-gate-focus]:focus,
[data-gate-focus]:focus-visible {
  outline: none;
}

/* ------------------------------------------------------------------ phone ---
   Shorter plate on a narrow screen. The framing fix is separate and below,
   because it is not a width problem.
   ------------------------------------------------------------------------ */

@media (max-width: 700px) {
  .gate__inner { padding-top: clamp(var(--sp-5), 5vh, var(--sp-7)); }
  /* No font-size override here on purpose. The 11vw rule above already fits the
     wordmark at every width, and the override that used to live here is what
     pushed it off the right edge of a 390px screen. */
  .gate__sub { max-width: 26ch; }
}

/* ------------------------------------------------ TALL SCREENS: THE FRAMING ---

   Nothing needed here any more, and that is the point.

   A 16:9 file in a 390x844 viewport had to scale to about 1500px wide to cover the
   height, so `cover` threw away 74% of the frame: the house never read as a house
   and the walk-in became a run of unreadable close-ups.

   This was briefly fixed in CSS, by letterboxing the video with `contain` over a
   blurred copy of the poster. It worked, and it looked like a compromise, because it
   was one: the band was 26% of the screen and the fill read as dark bars.

   The real fix was upstream. The clips were re-rendered to 9:16 with the sky and the
   path outpainted, and index.html serves them behind
   `media="(max-aspect-ratio: 4/5)"`. Plain `cover` then works everywhere, so the
   blur layer, the contain override and the band hairlines are all gone.

   If a tall-screen problem ever comes back, check the <source> list first. It is far
   more likely that a rendition is missing than that the CSS is wrong.
   ------------------------------------------------------------------------ */

/* -------------------------------------------------------- reduced motion ---
   gate.js never arms the gate under reduced motion. These are a guard for the
   case where the preference flips mid-session while it is on screen.
   ------------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .gate__inner,
  .gate__scene,
  .gate__vignette,
  .hero__media,
  .hero__inner,
  .site-nav { transition: none; }

  .gate-armed .hero__media { transform: none; }
  .gate-armed .hero__inner { opacity: 1; }
  .gate-armed .site-nav { opacity: 1; pointer-events: auto; }
}
