/* ==========================================================================
   SILENT SHRINE — LAYERED HERO
   ==========================================================================

   HOW THIS FILE IS ORGANISED
   --------------------------
    1.  Design tokens ....... colours, fonts, sizes, timings  (EDIT HERE FIRST)
    2.  Reset and base ...... sensible defaults
    3.  Shared pieces ....... buttons, brand
    5.  Hero scene .......... the three stacked images
    6.  Atmosphere .......... sky, glow, fog, vignette, grain
    7.  Hero text
    8.  Edge labels
    9.  Story section
   9f.  Immersive WebGL sections
   10.  Footer
   11.  Reveal animations ... the slide-up sequence
   12.  Responsive .......... tablet, then phone
   13.  Reduced motion ...... accessibility fallback

   Almost everything you would want to change lives in section 1.
   ========================================================================== */


/* ==========================================================================
   1. DESIGN TOKENS
   --------------------------------------------------------------------------
   Every colour, size and timing used on the page is defined once, here.
   Change a value in this block and it updates everywhere it is used.
   ========================================================================== */

:root {

  /* ---- Colours -------------------------------------------------------- */
  --colour-ink:              #05070d;   /* darkest background            */
  --colour-night:            #0b1120;   /* main background               */
  --colour-night-soft:       #131c30;   /* raised surfaces, cards        */

  --colour-text:             #f3efe7;   /* main text on dark             */
  --colour-text-muted:       #a3aec4;   /* secondary text                */

  --colour-accent:           #d2452f;   /* shrine vermilion — used sparingly */
  --colour-accent-warm:      #e8a75c;   /* lamp light / doorway glow     */

  --colour-line:             rgba(243, 239, 231, 0.14);  /* hairline borders */

  /* ---- Typography ------------------------------------------------------
     System fonts only, so there is no webfont download and nothing to wait
     for. The serif stack picks a Japanese "mincho" face where one exists,
     which suits the subject, and falls back to Georgia elsewhere.

     To use a webfont instead, add its <link> to index.html and put the font
     name at the front of the matching stack below.
     --------------------------------------------------------------------- */
  --font-display: "Hiragino Mincho ProN", "Yu Mincho", "YuMincho",
                  "Songti SC", Georgia, "Times New Roman", serif;
  --font-body:    ui-sans-serif, -apple-system, BlinkMacSystemFont,
                  "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Fluid type: grows with the viewport between a floor and a ceiling,
     so it never needs a separate font-size for every screen width. */
  --size-section-title: clamp(1.9rem, 3.6vw, 3rem);
  --size-lede: clamp(1rem, 1.25vw, 1.175rem);
  --size-body: 1rem;
  --size-small: 0.8125rem;

  --tracking-wide: 0.22em;   /* letter-spacing for small caps labels */

  /* ---- Spacing ---------------------------------------------------------
     A simple scale. Sticking to these keeps the rhythm of the page even. */
  --space-xs: 0.5rem;
  --space-sm: 0.875rem;
  --space-md: 1.5rem;
  --space-lg: 2.5rem;
  --space-xl: 4rem;
  --space-2xl: clamp(4rem, 9vw, 8rem);

  --page-max-width: 1240px;
  --page-gutter: clamp(1.25rem, 4vw, 3rem);

  --radius-sm: 6px;
  --radius-md: 14px;
  --radius-pill: 999px;

  /* ---- Motion ----------------------------------------------------------
     THE SLIDE-UP SEQUENCE IS TUNED HERE.

     Each layer waits for its "delay" to pass, then takes "duration" to slide
     up into place. Because the delays are staggered the layers arrive one
     after another: shrine, then guardians, then forest.

     Want it slower? Raise --reveal-duration and spread the delays further
     apart. Want it snappier? Lower them all by the same proportion.
     --------------------------------------------------------------------- */
  --reveal-duration: 2000ms;
  --reveal-ease: cubic-bezier(0.16, 1, 0.3, 1);   /* fast start, long soft landing */

  --delay-layer-1: 250ms;    /* the shrine arrives first        */
  --delay-wordmark: 1000ms;  /* the word climbs up behind it    */
  --delay-layer-2: 1950ms;   /* then the guardians              */
  --delay-layer-3: 2600ms;   /* then the forest edge            */

  --wordmark-duration: 2400ms;   /* the word moves slower than the layers */

  --delay-text: 3200ms;      /* the edge labels come last     */
  --stagger-text: 160ms;     /* gap between the two of them   */

  --ease-ui: cubic-bezier(0.4, 0, 0.2, 1);   /* for buttons and links */

  /* ---- Layering order --------------------------------------------------
     Written out by name so the stacking order is obvious at a glance. */
  --z-sky: 1;
  --z-stars: 2;       /* stars, star signs and shooting stars */
  --z-wordmark: 3;    /* behind the shrine, so the building can hide it */
  --z-layer-1: 4;
  --z-fog-far: 5;
  --z-layer-2: 6;
  --z-fog-near: 7;
  --z-layer-3: 8;
  --z-dust: 9;
  --z-vignette: 10;
  --z-grain: 11;
  --z-hero-text: 12;
}


/* ==========================================================================
   2. RESET AND BASE
   ========================================================================== */

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

html {
  scroll-behavior: smooth;
  /* Stops iOS from silently inflating text in landscape. */
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background-color: var(--colour-ink);
  color: var(--colour-text);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* The hero layers are wider than the screen on purpose, so hide the
     sideways overflow they would otherwise create. */
  overflow-x: hidden;
}

h1, h2, h3, p, ul, figure {
  margin: 0;
}

ul {
  padding: 0;
  list-style: none;
}

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

a {
  color: inherit;
  text-decoration: none;
}

/* One consistent focus ring everywhere, shown only for keyboard users. */
:focus-visible {
  outline: 2px solid var(--colour-accent-warm);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}


/* ==========================================================================
   3. SHARED PIECES
   ========================================================================== */

/* ---- Brand -------------------------------------------------------------- */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
}

.brand__mark {
  width: 9px;
  height: 9px;
  background-color: var(--colour-accent);
  border-radius: 2px;
  transform: rotate(45deg);
}

.brand__name {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  letter-spacing: 0.04em;
}

/* ---- Buttons ------------------------------------------------------------ */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.95rem 1.85rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 260ms var(--ease-ui),
              border-color 260ms var(--ease-ui),
              color 260ms var(--ease-ui),
              transform 260ms var(--ease-ui);
}

.button:hover {
  transform: translateY(-2px);
}

.button:active {
  transform: translateY(0);
}

.button--primary {
  background-color: var(--colour-accent);
  color: #fff;
}

.button--primary:hover {
  background-color: #e05138;
}

/* The outline button sits over the scene, where the background behind it is
   both dark and busy. It needs a visible edge and a frosted panel of its own,
   otherwise it disappears into the night sky. */
.button--ghost {
  border-color: rgba(243, 239, 231, 0.45);
  color: var(--colour-text);
  background-color: rgba(11, 17, 32, 0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.button--ghost:hover {
  border-color: rgba(243, 239, 231, 0.62);
  background-color: rgba(24, 34, 56, 0.75);
}

.button--small {
  padding: 0.6rem 1.25rem;
  font-size: var(--size-small);
}


/* ==========================================================================
   5. HERO SCENE — the three stacked images
   --------------------------------------------------------------------------
   POSITIONING A LAYER
   Each layer sets three values, and those three values are all you need to
   touch to move an image around:

       --layer-width     how wide the image is, as a % of the screen width.
                         Over 100% means it runs off the sides on purpose.

       --layer-shift-x   nudges the image sideways, as a % of its OWN width.
                         Used to visually centre artwork whose subject is not
                         in the middle of its own file.

       --layer-shift-y   pushes the image down, as a % of its OWN height.
                         Higher numbers sink it further below the bottom edge.

   Both shifts are percentages of the image itself, never of the screen. That
   distinction matters: an image's height here is decided by the screen WIDTH
   (the width above, held to the picture's proportions), so a shift measured
   against the screen height would slide out of place the moment the window
   changed shape. Measured against the image, the framing holds everywhere.

   These are re-declared at the bottom of the file for tablet and phone, so
   each screen size gets a composition that was chosen for it.
   ========================================================================== */

.hero {
  position: relative;
  /* svh = "small viewport height": the correct unit on mobile, because it
     ignores the browser chrome that slides away as you scroll. */
  min-height: 100svh;
  overflow: hidden;
  isolation: isolate;
}

/* Holds every visual layer. */
.hero__scene {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

/* The outer box of one layer. JavaScript writes its transform for parallax.
   Deliberately no "will-change" here. These layers are larger than the
   screen — the foreground is around 2150x1200 and carries a blur — and
   will-change would force the browser to hold a texture that size for all
   three, permanently. The parallax is written as translate3d, which already
   hands the work to the graphics card while it is actually moving, without
   the standing memory cost or the slow first paint. */
.scene__layer {
  position: absolute;
  inset: 0;
}

.scene__layer--shrine     { z-index: var(--z-layer-1); }
.scene__layer--warriors   { z-index: var(--z-layer-2); }
.scene__layer--foreground { z-index: var(--z-layer-3); }

/* The middle box. This is the one that slides up on load. */
.scene__layer-inner {
  position: absolute;
  inset: 0;
  opacity: 0;   /* invisible until the reveal animation runs */
}

/* The image itself. Positioned once and then left alone. */
.scene__image {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: var(--layer-width, 100%);
  height: auto;
  /* The base reset caps every image at the width of its container. These
     layers are meant to run wider than the screen, so that cap has to be
     lifted here or the widths below silently collapse to 100%. */
  max-width: none;
  /* Centre the image, then apply the two artwork nudges. */
  transform:
    translateX(-50%)
    translateX(var(--layer-shift-x, 0%))
    translateY(var(--layer-shift-y, 0%));
}

/* ---- Layer 1 — the shrine ------------------------------------------------
   Sized to leave the top of the screen clear, because that is where the
   headline sits. Its subject is slightly left of centre inside the file,
   so it is nudged right to look properly centred once on screen. */
.scene__layer--shrine {
  --layer-width: 78%;
  --layer-shift-x: 3.4%;
  --layer-shift-y: 3%;
}

/* ---- Layer 2 — the guardians ---------------------------------------------
   Wider than the shrine so the group spreads either side of the doorway
   rather than blocking it. Its subject sits slightly right inside the file,
   so it is nudged left. Sunk a little further than the shrine, which puts
   the nearest boots past the bottom edge and reads as closer to camera. */
.scene__layer--warriors {
  --layer-width: 80%;
  --layer-shift-x: -3.2%;
  --layer-shift-y: 5%;
}

/* ---- Layer 3 — the forest edge -------------------------------------------
   Wider than the screen so the foliage anchors to both edges, and sunk far
   enough that it frames the lower corners instead of burying the scene.

   The blur does two jobs: it mimics a camera focused on the shrine rather
   than on the leaves, and it hides the fact that this is the smallest of the
   three source files being scaled up the most. */
.scene__layer--foreground {
  --layer-width: 112%;
  --layer-shift-x: 0%;
  --layer-shift-y: 43%;
}

.scene__layer--foreground .scene__image {
  filter: blur(3px);
}


/* ==========================================================================
   6. ATMOSPHERE — sky, glow, fog, vignette, grain
   --------------------------------------------------------------------------
   None of this is an image. It is all gradients, which cost nothing to
   download and scale perfectly to any screen.
   ========================================================================== */

/* ---- Night sky ---------------------------------------------------------- */
.scene__sky {
  position: absolute;
  inset: 0;
  z-index: var(--z-sky);
  background:
    /* cool light lifting off the horizon */
    radial-gradient(120% 78% at 50% 100%,
      rgba(40, 62, 104, 0.72) 0%,
      rgba(15, 24, 44, 0.32) 45%,
      transparent 72%),
    /* deep sky overhead */
    linear-gradient(to bottom,
      #05070d 0%,
      #0a1020 38%,
      #101a30 70%,
      #16223c 100%);
}

/* ---- Star field ----------------------------------------------------------
   Each star is one tiny radial gradient. They cost nothing to download and
   stay pin-sharp on any screen.

   EDITABLE: a star is written as
       radial-gradient(SIZE SIZE at ACROSS% DOWN%, COLOUR, transparent)
   Add, remove or move them freely. They are kept in the upper part of the
   sky because everything below is covered by the scene.

   The two sets twinkle on different clocks so the sky never pulses as one. */
.scene__stars {
  position: absolute;
  inset: 0;
  z-index: var(--z-stars);
  opacity: 0;
  transition: opacity 2200ms ease-out 200ms;
}

body.is-ready .scene__stars {
  opacity: 1;
}

.scene__stars::before,
.scene__stars::after {
  content: "";
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
}

/* Set one — the brighter stars. */
.scene__stars::before {
  background-image:
    radial-gradient(2px 2px at 8% 14%,  rgba(255, 255, 255, 0.95), transparent),
    radial-gradient(1.5px 1.5px at 17% 32%, rgba(255, 252, 240, 0.75), transparent),
    radial-gradient(2px 2px at 29% 9%,  rgba(255, 255, 255, 0.85), transparent),
    radial-gradient(1.5px 1.5px at 38% 27%, rgba(226, 236, 255, 0.7), transparent),
    radial-gradient(2.5px 2.5px at 52% 12%, rgba(255, 255, 255, 0.9), transparent),
    radial-gradient(1.5px 1.5px at 63% 30%, rgba(255, 248, 232, 0.72), transparent),
    radial-gradient(2px 2px at 76% 16%, rgba(255, 255, 255, 0.88), transparent),
    radial-gradient(1.5px 1.5px at 87% 34%, rgba(226, 236, 255, 0.7), transparent),
    radial-gradient(2px 2px at 94% 11%, rgba(255, 255, 255, 0.8), transparent);
  animation: star-twinkle 5.5s ease-in-out infinite;
}

/* Set two — fainter, and further down the sky. */
.scene__stars::after {
  background-image:
    radial-gradient(1.5px 1.5px at 5% 44%, rgba(255, 255, 255, 0.6), transparent),
    radial-gradient(1px 1px at 14% 22%, rgba(255, 255, 255, 0.5), transparent),
    radial-gradient(1.5px 1.5px at 23% 48%, rgba(226, 236, 255, 0.55), transparent),
    radial-gradient(1px 1px at 34% 40%, rgba(255, 255, 255, 0.45), transparent),
    radial-gradient(1.5px 1.5px at 45% 22%, rgba(255, 252, 240, 0.6), transparent),
    radial-gradient(1px 1px at 57% 45%, rgba(255, 255, 255, 0.5), transparent),
    radial-gradient(1.5px 1.5px at 69% 8%,  rgba(226, 236, 255, 0.55), transparent),
    radial-gradient(1px 1px at 81% 42%, rgba(255, 255, 255, 0.45), transparent),
    radial-gradient(1.5px 1.5px at 91% 24%, rgba(255, 255, 255, 0.6), transparent),
    radial-gradient(1px 1px at 97% 47%, rgba(255, 252, 240, 0.5), transparent);
  animation: star-twinkle 7.5s ease-in-out 1.4s infinite;
}

/* Shallow on purpose. Stars that blink hard look like broken pixels. */
@keyframes star-twinkle {
  0%, 100% { opacity: 0.62; }
  50%      { opacity: 1; }
}

/* ---- Star signs ----------------------------------------------------------
   Sits in the top band of the sky, well clear of the wordmark below it. */
.scene__constellations {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--z-stars);
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 2600ms ease-out 700ms;
}

body.is-ready .scene__constellations {
  opacity: 1;
}

/* The joining lines are kept very faint — they should read as a hint of a
   shape you half-recognise, not as drawn-on diagram lines. */
.scene__constellations polyline {
  fill: none;
  stroke: rgba(214, 228, 255, 0.22);
  stroke-width: 1;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.scene__constellations circle {
  fill: rgba(255, 255, 255, 0.85);
}

/* ---- Shooting stars ------------------------------------------------------
   A thin streak, brightest at its leading edge. Each crosses the sky in
   about a second and a half, then waits out the rest of its cycle, so they
   stay an occasional surprise rather than a steady stream.

   EDITABLE: --spark-travel sets how far it flies, --spark-angle the
   direction. Change the top/left on each one to move where it starts. */
.scene__spark {
  --spark-travel-x: 440px;
  --spark-travel-y: 150px;
  --spark-angle: 19deg;

  position: absolute;
  z-index: var(--z-stars);
  width: 130px;
  height: 1px;
  border-radius: 1px;
  background: linear-gradient(to right,
    transparent,
    rgba(255, 255, 255, 0.15) 55%,
    rgba(255, 255, 255, 0.9) 100%);
  opacity: 0;
}

body.is-ready .scene__spark {
  animation: spark-streak 11s linear infinite;
}

.scene__spark--one {
  top: 12%;
  left: 6%;
}

.scene__spark--two {
  top: 26%;
  left: 54%;
  --spark-travel-x: 340px;
  --spark-travel-y: 105px;
  --spark-angle: 17deg;
  width: 100px;
  animation-delay: 6.5s;
}

@keyframes spark-streak {
  0% {
    opacity: 0;
    transform: translate(0, 0) rotate(var(--spark-angle));
  }
  3% {
    opacity: 1;
  }
  13% {
    opacity: 0;
    transform: translate(var(--spark-travel-x), var(--spark-travel-y))
               rotate(var(--spark-angle));
  }
  /* Held off-stage for the rest of the cycle. */
  100% {
    opacity: 0;
    transform: translate(var(--spark-travel-x), var(--spark-travel-y))
               rotate(var(--spark-angle));
  }
}

/* ---- Fog between the layers ---------------------------------------------
   This is the trick that sells the depth. Each panel of haze slightly veils
   the layer behind it, so the layers read as being at different distances
   instead of simply overlapping. */
.scene__fog {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  pointer-events: none;
}

.scene__fog--far {
  z-index: var(--z-fog-far);
  height: 62%;
  background: linear-gradient(to top,
    rgba(24, 38, 66, 0.5) 0%,
    rgba(24, 38, 66, 0.24) 38%,
    transparent 100%);
}

.scene__fog--near {
  z-index: var(--z-fog-near);
  height: 42%;
  background: linear-gradient(to top,
    rgba(16, 27, 50, 0.62) 0%,
    rgba(16, 27, 50, 0.22) 45%,
    transparent 100%);
}

/* ---- Drifting dust ------------------------------------------------------ */
.scene__dust {
  position: absolute;
  inset: 0;
  z-index: var(--z-dust);
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 1600ms ease-out 2200ms;
}

body.is-ready .scene__dust {
  opacity: 1;
}

/* ---- Vignette: darkens the corners, pulls the eye to the middle --------- */
.scene__vignette {
  position: absolute;
  inset: 0;
  z-index: var(--z-vignette);
  background:
    /* A wide, generous ellipse. It has to be large enough to leave the middle
       of the scene — the shrine and its doorway — completely untouched, and
       only bite at the far corners. A tighter ellipse drags its darkest edge
       across the doorway and puts out the one light in the picture. */
    radial-gradient(120% 105% at 50% 52%,
      transparent 55%,
      rgba(3, 5, 10, 0.42) 88%,
      rgba(3, 5, 10, 0.72) 100%),
    /* Darkens the upper part of the sky so the headline always has something
       dark behind it, and blends the bottom of the scene into the section
       below it. */
    linear-gradient(to bottom,
      rgba(5, 7, 13, 0.72) 0%,
      rgba(5, 7, 13, 0.38) 30%,
      transparent 52%,
      transparent 86%,
      var(--colour-ink) 100%);
}

/* ---- Film grain ---------------------------------------------------------
   An inline SVG noise texture. No file to download, and it stops the large
   flat gradients above from looking banded. */
.scene__grain {
  position: absolute;
  inset: 0;
  z-index: var(--z-grain);
  opacity: 0.16;
  mix-blend-mode: overlay;
  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='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}


/* ==========================================================================
   7. THE WORDMARK
   --------------------------------------------------------------------------
   The single piece of type in the hero. It rises from below and stops while
   the shrine is still covering its lower half, so the building and the word
   are locked together — you cannot read one without the other.

   HOW THE RESTING HEIGHT IS SET
   --wordmark-bottom decides where the word settles. It is measured in vw (a
   share of the screen WIDTH) for the same reason the layers are: the shrine's
   height comes from the screen width, so the roofline the word has to tuck
   behind moves with the width too. Using vw keeps the overlap identical on a
   tall window and a short one.

   Raise --wordmark-bottom to reveal more of the letters, lower it to bury
   them deeper behind the shrine.
   ========================================================================== */

.scene__wordmark {
  --wordmark-bottom: 30vw;

  position: absolute;
  left: 50%;
  bottom: var(--wordmark-bottom);
  z-index: var(--z-wordmark);

  margin: 0;
  width: 100%;
  text-align: center;
  white-space: nowrap;

  font-family: var(--font-body);
  font-size: 16.5vw;
  font-weight: 800;
  line-height: 0.8;
  letter-spacing: 0.02em;
  text-transform: uppercase;

  /* Brightest at the top, falling away toward the bottom. The faded lower
     part is what the shrine covers, so the letters seem to sink into the
     building rather than stopping dead behind it.

     The fade is held back until well past halfway on purpose. It has to
     begin roughly where the roofline crosses the letters — start it any
     higher and the visible part of the word goes grey for no reason. */
  background: linear-gradient(to bottom,
    rgba(255, 253, 249, 1) 0%,
    rgba(248, 243, 233, 0.96) 46%,
    rgba(226, 213, 191, 0.72) 74%,
    rgba(178, 160, 134, 0.3) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;

  /* The reveal animation adds its own translate, so the horizontal centring
     lives on a wrapper transform that the animation will not overwrite. */
  transform: translateX(-50%);
}


/* ==========================================================================
   8. EDGE LABELS
   --------------------------------------------------------------------------
   Vertical type running down the left and right margins.
   ========================================================================== */

.edge-label {
  position: absolute;
  top: 50%;
  z-index: var(--z-hero-text);
  display: flex;
  align-items: center;
  gap: 1.1rem;

  /* Sets the type top-to-bottom instead of left-to-right. */
  writing-mode: vertical-rl;

  font-size: 0.75rem;
  color: var(--colour-text-muted);
}

.edge-label--left {
  left: var(--page-gutter);
  transform: translateY(-50%);
}

.edge-label--right {
  right: var(--page-gutter);
  transform: translateY(-50%);
  /* Turns the right-hand label to read bottom-to-top, so the pair mirror
     each other around the scene instead of both leaning the same way. */
  rotate: 180deg;
}

.edge-label__japanese {
  font-family: "Hiragino Sans", "Hiragino Kaku Gothic ProN",
               "Yu Gothic", "Noto Sans JP", var(--font-body);
  font-size: 0.9375rem;
  letter-spacing: 0.28em;
  color: var(--colour-text);
}

.edge-label__roman {
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  font-size: 0.6875rem;
}

/* A short rule below each label, to anchor it to the edge of the frame. */
.edge-label::after {
  content: "";
  width: 1px;
  height: 3.5rem;
  background: linear-gradient(to bottom,
    rgba(243, 239, 231, 0.35), transparent);
}


/* ==========================================================================
   9. STORY SECTION
   ========================================================================== */

.section {
  position: relative;
  z-index: 1;
  overflow: hidden;   /* keeps the aura below from spilling onto the hero */
  background-color: var(--colour-ink);
  padding: var(--space-2xl) var(--page-gutter);
  /* A hairline where the section meets the hero, so the join is deliberate
     rather than the two just running into each other. */
  border-top: 1px solid var(--colour-line);
}

/* ---- The aura ------------------------------------------------------------
   One very soft warm ellipse behind the heading, drifting on a slow loop. It
   gives the flat panel a light source and stops the section reading as a
   plain black box.

   Only opacity and transform are animated. Those two are the cheap ones —
   the browser can hand them to the graphics card and never repaint, which is
   what keeps a minute-long ambient loop off the main thread entirely. */
.section__aura {
  position: absolute;
  top: -20%;
  left: 50%;
  /* Not a negative z-index. The section sets its own z-index, which makes it
     a stacking context, and anything negative inside would be painted behind
     the section's background colour and never seen. Zero sits above the
     background but still below .section__inner, which comes later. */
  z-index: 0;
  width: min(60rem, 120%);
  aspect-ratio: 3 / 2;
  pointer-events: none;

  background: radial-gradient(closest-side,
    rgba(232, 167, 92, 0.13) 0%,
    rgba(181, 55, 42, 0.07) 45%,
    transparent 100%);

  transform: translateX(-50%);
  animation: aura-drift 26s ease-in-out infinite alternate;
}

@keyframes aura-drift {
  from { transform: translateX(-56%) translateY(-3%) scale(1);    opacity: 0.75; }
  to   { transform: translateX(-44%) translateY(4%)  scale(1.14); opacity: 1; }
}

.section__inner {
  position: relative;
  max-width: var(--page-max-width);
  margin: 0 auto;
}

.section__header {
  max-width: 42rem;
  margin-bottom: var(--space-xl);
}

/* ---- Eyebrow -------------------------------------------------------------
   Japanese first, its reading second, matching the kicker used by every
   section further down the page. */
.section__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: var(--space-md);
  font-size: 1rem;
  color: var(--colour-accent);
}

.section__eyebrow [lang="ja"] {
  font-family: "Hiragino Sans", "Hiragino Kaku Gothic ProN",
               "Yu Gothic", "Noto Sans JP", var(--font-body);
  letter-spacing: 0.18em;
}

.section__eyebrow-roman {
  font-size: var(--size-small);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.section__eyebrow-roman::before {
  content: "";
  display: inline-block;
  width: 1.75rem;
  height: 1px;
  margin-right: 0.9rem;
  vertical-align: middle;
  background-color: currentColor;
  opacity: 0.45;
}

.section__title {
  font-family: var(--font-display);
  font-size: var(--size-section-title);
  font-weight: 400;
  line-height: 1.15;
}

/* The second line, picked out so the phrase has a turn in it rather than
   reading as one flat run of type. */
.section__title-accent {
  background: linear-gradient(to right,
    var(--colour-accent-warm) 0%,
    rgba(243, 239, 231, 0.92) 60%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-style: italic;
}

.section__lede {
  margin-top: var(--space-md);
  font-size: var(--size-lede);
  color: var(--colour-text-muted);
}

/* ---- Cards ---------------------------------------------------------------
   Three panels naming the three layers of the hero, in the same order.
   ------------------------------------------------------------------------- */
.card-grid {
  display: grid;
  /* Cards wrap on their own once they drop under 17rem — no media query. */
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--space-md);
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: var(--space-lg);
  background-color: var(--colour-night-soft);
  border: 1px solid var(--colour-line);
  border-radius: var(--radius-md);
  transition: translate 500ms var(--ease-ui),
              border-color 500ms var(--ease-ui),
              background-color 500ms var(--ease-ui),
              box-shadow 500ms var(--ease-ui);
}

/* A warm bloom that fades up from the bottom of the card on hover, as though
   the lantern light in the hero has reached it. */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(120% 80% at 50% 118%,
    rgba(232, 167, 92, 0.16) 0%,
    transparent 70%);
  opacity: 0;
  transition: opacity 500ms var(--ease-ui);
}

/* A hairline along the top edge that draws in from the left on hover. */
.card::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 1px;
  background: linear-gradient(to right,
    var(--colour-accent-warm), transparent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 620ms var(--ease-ui);
}

.card:hover,
.card:focus-within {
  /* `translate` rather than `transform`, so this lift and the scroll reveal
     above can run at the same time without overwriting one another. */
  translate: 0 -6px;
  border-color: rgba(232, 167, 92, 0.32);
  background-color: #172136;
  box-shadow: 0 22px 48px -24px rgba(0, 0, 0, 0.9);
}

.card:hover::before,
.card:focus-within::before { opacity: 1; }

.card:hover::after,
.card:focus-within::after  { transform: scaleX(1); }

/* ---- The watermark kanji -------------------------------------------------
   One large character in the bottom corner of each card, running off the
   edge. It has to bleed off two sides — bottom and right — for the crop to
   read as deliberate. Clipped on one side only, it just looks like a glyph
   that failed to draw.

   Decoration, so it is hidden from screen readers and never announced. */
.card__glyph {
  position: absolute;
  right: -0.06em;
  bottom: -0.3em;
  font-family: "Hiragino Mincho ProN", "Yu Mincho", var(--font-display);
  font-size: 7.5rem;
  line-height: 1;
  color: rgba(243, 239, 231, 0.05);
  pointer-events: none;
  user-select: none;
  transition: color 600ms var(--ease-ui),
              transform 600ms var(--ease-ui);
}

.card:hover .card__glyph,
.card:focus-within .card__glyph {
  color: rgba(232, 167, 92, 0.14);
  transform: translateY(-5px) scale(1.05);
}

/* ---- Index ---------------------------------------------------------------
   The number with a short rule beside it, which gives the three cards a
   shared horizontal line to sit on. */
.card__index {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-bottom: var(--space-md);
  font-family: var(--font-display);
  font-size: 0.875rem;
  letter-spacing: 0.12em;
  color: var(--colour-accent-warm);
}

.card__index::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(to right,
    rgba(232, 167, 92, 0.45), transparent);
}

.card__title {
  position: relative;
  margin-bottom: var(--space-sm);
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 400;
}

.card__title-japanese {
  display: block;
  margin-top: 0.3rem;
  font-family: "Hiragino Sans", "Hiragino Kaku Gothic ProN",
               "Yu Gothic", "Noto Sans JP", var(--font-body);
  font-size: 0.8125rem;
  letter-spacing: 0.22em;
  color: var(--colour-text-muted);
}

.card__text {
  position: relative;
  color: var(--colour-text-muted);
  font-size: 0.9375rem;
}

/* Pushed to the bottom by the auto margin, so this line sits on the same
   baseline across all three cards however long the text above it runs. */
.card__meta {
  position: relative;
  margin-top: auto;
  padding-top: var(--space-md);
  font-size: 0.6875rem;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: rgba(243, 239, 231, 0.4);
}


/* ==========================================================================
   9b. SHARED SECTION PIECES
   --------------------------------------------------------------------------
   The three sections below the story all use the same small kicker label:
   a Japanese word with its English reading underneath.
   ========================================================================== */

/* Japanese needs its own stack — the body font has no kana or kanji, so
   without this the browser substitutes whatever it can find and the two
   languages end up looking unrelated. */
.band__kicker [lang="ja"],
.paper__kicker [lang="ja"],
.split__kicker [lang="ja"],
.paper__lede,
.paper__fact-label,
.split__list [lang="ja"],
.band__title-japanese {
  font-family: "Hiragino Sans", "Hiragino Kaku Gothic ProN",
               "Yu Gothic", "Noto Sans JP", var(--font-body);
}

.band__kicker,
.paper__kicker,
.split__kicker {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: var(--space-md);
  font-size: 1rem;
  letter-spacing: 0.18em;
}

.band__kicker-roman,
.paper__kicker-roman,
.split__kicker-roman {
  font-family: var(--font-body);
  font-size: var(--size-small);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* A short rule between the Japanese and the English. */
.band__kicker-roman::before,
.paper__kicker-roman::before,
.split__kicker-roman::before {
  content: "";
  display: inline-block;
  width: 1.75rem;
  height: 1px;
  margin-right: 0.9rem;
  vertical-align: middle;
  background-color: currentColor;
  opacity: 0.45;
}

/* ---- Scroll reveal -------------------------------------------------------
   Elements start slightly low and transparent. JavaScript adds "is-visible"
   the first time each one scrolls into view, and it eases into place.

   This is a transition rather than an animation on purpose: a transition
   only runs when the class is added, so nothing moves until the reader has
   actually reached it.

   To stagger a row of elements, set --reveal-delay on each one in the HTML:

       <article class="card reveal-on-scroll" style="--reveal-delay: 120ms">

   The delay applies only on the way in. Removing it from the resting state
   matters — otherwise a card that is hovered while still fading would wait
   out its delay again before responding. */
.reveal-on-scroll {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 900ms var(--reveal-ease) var(--reveal-delay, 0ms),
              transform 900ms var(--reveal-ease) var(--reveal-delay, 0ms);
}

.reveal-on-scroll.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* A card is both a reveal target and a hover target, so it needs one
   transition covering both. They are kept on separate properties — the
   reveal moves it with `transform`, the hover lifts it with `translate` —
   because a single property cannot hold two independent movements, and a
   card can easily be hovered while it is still fading in. */
.card.reveal-on-scroll {
  transition:
    opacity 900ms var(--reveal-ease) var(--reveal-delay, 0ms),
    transform 900ms var(--reveal-ease) var(--reveal-delay, 0ms),
    translate 500ms var(--ease-ui),
    border-color 500ms var(--ease-ui),
    background-color 500ms var(--ease-ui),
    box-shadow 500ms var(--ease-ui);
}


/* ==========================================================================
   9c. IMAGE BAND  —  full-bleed photograph with text over it
   ========================================================================== */

.band {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 88svh;
  padding: var(--space-2xl) var(--page-gutter);
  overflow: hidden;
  isolation: isolate;
}

/* The photograph. Deliberately taller than the section so there is room for
   it to drift as you scroll without ever exposing an edge. */
.band__image {
  position: absolute;
  top: -12%;
  right: 0;
  left: 0;
  height: 124%;
  z-index: 1;
  background-position: center;
  background-size: cover;
  /* JavaScript writes a translate here to make it drift. */
  will-change: transform;
}

/* EDITABLE: swap the photograph here. */
.band--path .band__image {
  background-image: url("../images/section-torii-path.webp");
}

/* Dark wash over the photograph. Without it the text has no reliable
   contrast, because a photograph is light in some places and dark in
   others and you cannot style around that. */
.band__wash {
  position: absolute;
  inset: 0;
  z-index: 2;
  background:
    linear-gradient(to right,
      rgba(5, 7, 13, 0.92) 0%,
      rgba(5, 7, 13, 0.72) 42%,
      rgba(5, 7, 13, 0.35) 100%),
    linear-gradient(to bottom,
      var(--colour-ink) 0%,
      transparent 18%,
      transparent 82%,
      var(--colour-ink) 100%);
}

.band__inner {
  position: relative;
  z-index: 3;
  max-width: 34rem;
  margin: 0 auto 0 0;
}

.band__kicker {
  color: var(--colour-accent-warm);
}

.band__title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4.4vw, 3.5rem);
  font-weight: 400;
  line-height: 1.16;
}

.band__title-japanese {
  display: inline-block;
  margin-top: 0.35em;
  font-weight: 500;
  font-size: 0.72em;
  letter-spacing: 0.14em;
  color: var(--colour-accent-warm);
}

.band__text {
  margin-top: var(--space-md);
  font-size: var(--size-lede);
  color: var(--colour-text-muted);
}


/* ==========================================================================
   9d. PAPER SECTION  —  the light break
   --------------------------------------------------------------------------
   Warm paper rather than plain white. A pure white block against this palette
   is a shock; paper reads as a page being turned.
   ========================================================================== */

.paper {
  position: relative;
  z-index: 1;
  padding: var(--space-2xl) var(--page-gutter);
  background-color: #f4efe4;
  background-image: url("../images/section-washi-paper.webp");
  background-size: cover;
  background-position: center;
  color: #1d1a15;
}

.paper__inner {
  max-width: var(--page-max-width);
  margin: 0 auto;
}

.paper__header {
  max-width: 44rem;
  margin-bottom: var(--space-xl);
}

.paper__kicker {
  color: #8a1f10;   /* the vermilion, darkened to hold up on paper */
}

.paper__title {
  font-family: var(--font-display);
  font-size: clamp(2.1rem, 4.8vw, 3.75rem);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.01em;
}

.paper__columns {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: 1px solid rgba(29, 26, 21, 0.14);
}

.paper__lede {
  font-size: clamp(1.15rem, 1.7vw, 1.4rem);
  line-height: 1.9;
  letter-spacing: 0.06em;
}

.paper__lede-roman {
  display: block;
  margin-top: 0.6rem;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: rgba(29, 26, 21, 0.55);
}

.paper__body p {
  color: rgba(29, 26, 21, 0.78);
}

.paper__body p + p {
  margin-top: var(--space-md);
}

/* ---- The three facts ----------------------------------------------------- */
.paper__facts {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
  margin: var(--space-xl) 0 0;
  padding-top: var(--space-lg);
  border-top: 1px solid rgba(29, 26, 21, 0.14);
}

.paper__fact-label {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  font-size: 0.9375rem;
  letter-spacing: 0.1em;
}

.paper__fact-roman {
  font-family: var(--font-body);
  font-size: 0.6875rem;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: rgba(29, 26, 21, 0.5);
}

.paper__fact-value {
  margin: 0.35rem 0 0;
  font-family: var(--font-display);
  font-size: clamp(2rem, 3.6vw, 2.9rem);
  line-height: 1;
}


/* ==========================================================================
   9e. SPLIT SECTION  —  image beside text
   ========================================================================== */

.split {
  position: relative;
  z-index: 1;
  padding: var(--space-2xl) var(--page-gutter);
  background-color: var(--colour-ink);
}

.split__inner {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(2rem, 5vw, 4.5rem);
  max-width: var(--page-max-width);
  margin: 0 auto;
}

.split__media {
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-line);
}

.split__media img {
  width: 100%;
  height: auto;
  /* A slow push-in on hover. Scale only, so it stays cheap to draw. */
  transition: transform 900ms var(--ease-ui);
}

.split__media:hover img {
  transform: scale(1.04);
}

.split__kicker {
  color: var(--colour-accent-warm);
}

.split__title {
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 3.4vw, 2.9rem);
  font-weight: 400;
  line-height: 1.16;
}

.split__text {
  margin-top: var(--space-md);
  color: var(--colour-text-muted);
}

.split__list {
  display: grid;
  gap: 0.85rem;
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--colour-line);
  font-size: 0.9375rem;
}

.split__list li {
  display: flex;
  align-items: baseline;
  gap: 1rem;
}

.split__list [lang="ja"] {
  min-width: 3.5rem;
  letter-spacing: 0.12em;
  color: var(--colour-text);
}

.split__list span:last-child {
  color: var(--colour-text-muted);
}


/* ==========================================================================
   9f. IMMERSIVE SCROLL SECTIONS  (the WebGL ones)
   --------------------------------------------------------------------------
   Each section is roughly two and a half screens tall, and holds a child
   that is exactly one screen tall and sticky. The child pins itself to the
   top of the window and stays there while the rest of the section scrolls
   past behind it — which is what gives you a still canvas and a moving
   scroll position at the same time.

   Make a section taller to slow its scene down, shorter to speed it up.
   ========================================================================== */

.immersive {
  position: relative;
  z-index: 1;
  height: 260svh;
  background-color: var(--colour-ink);
}

.immersive__sticky {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  isolation: isolate;
}

/* ---- Base of the frame ---------------------------------------------------
   A fade to the section's own background along the bottom edge.

   It earns its place twice over. It grounds the scene, so the 3D sits in the
   dark rather than floating in a lit box. More importantly it fixes the
   handoff at the end of the page: a pinned canvas stops scrolling when its
   section runs out, so whatever happens to be in the lower part of that last
   frame is what butts up against the footer. Without this, the ink scene
   ended on a long flat band of near-black that read as a gap in the page. */
.immersive__sticky::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 2;
  height: 26svh;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(5, 7, 13, 0) 0%,
    rgba(5, 7, 13, 0.72) 55%,
    var(--colour-ink) 100%);
}

.immersive__canvas {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
}

/* Shown when WebGL is unavailable or motion is turned down. A plain wash in
   the same colours, so the section still has a floor for its text. */
.immersive__canvas.is-unavailable {
  background:
    radial-gradient(80% 60% at 50% 62%,
      rgba(181, 55, 42, 0.22) 0%,
      transparent 70%),
    linear-gradient(to bottom, #05070d 0%, #10182a 100%);
}

/* ---- Text over the canvas ------------------------------------------------ */
.immersive__overlay {
  position: absolute;
  inset: 0;
  /* Above the base fade, or the words would be dimmed by it. */
  z-index: 3;
  display: flex;
  align-items: center;
  padding: var(--space-2xl) var(--page-gutter);
  pointer-events: none;
}

.immersive__overlay--centred {
  justify-content: center;
  text-align: center;
}

/* Each block is stacked in the same place and faded through one at a time,
   so they never push each other around as they swap over. */
.immersive__step {
  position: absolute;
  max-width: 32rem;
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 800ms var(--reveal-ease),
              transform 800ms var(--reveal-ease);
}

.immersive__overlay--centred .immersive__step {
  max-width: 46rem;
}

.immersive__step.is-active {
  opacity: 1;
  transform: translateY(0);
}

.immersive__kicker {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: var(--space-md);
  font-size: 1rem;
  letter-spacing: 0.18em;
  color: var(--colour-accent-warm);
}

.immersive__overlay--centred .immersive__kicker {
  justify-content: center;
}

.immersive__kicker [lang="ja"] {
  font-family: "Hiragino Sans", "Hiragino Kaku Gothic ProN",
               "Yu Gothic", "Noto Sans JP", var(--font-body);
}

.immersive__kicker-roman {
  font-size: var(--size-small);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.immersive__kicker-roman::before {
  content: "";
  display: inline-block;
  width: 1.75rem;
  height: 1px;
  margin-right: 0.9rem;
  vertical-align: middle;
  background-color: currentColor;
  opacity: 0.45;
}

.immersive__title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4.2vw, 3.4rem);
  font-weight: 400;
  line-height: 1.14;
  /* The canvas behind is bright in places, so the type needs its own shadow
     to stay readable wherever it happens to fall. */
  text-shadow: 0 2px 30px rgba(5, 7, 13, 0.85);
}

.immersive__title--large {
  font-size: clamp(2.4rem, 6vw, 4.8rem);
}

.immersive__text {
  margin-top: var(--space-md);
  font-size: var(--size-lede);
  color: var(--colour-text-muted);
  text-shadow: 0 2px 24px rgba(5, 7, 13, 0.9);
}

.immersive__text--wide {
  max-width: 34rem;
  margin-inline: auto;
}

/* ---- The closing quote --------------------------------------------------- */
.immersive__quote {
  font-family: "Hiragino Mincho ProN", "Yu Mincho", var(--font-display);
  font-size: clamp(1.5rem, 3.4vw, 2.6rem);
  line-height: 1.7;
  letter-spacing: 0.14em;
  text-shadow: 0 2px 30px rgba(5, 7, 13, 0.9);
}

.immersive__quote-roman {
  margin-top: var(--space-md);
  font-size: var(--size-small);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--colour-text-muted);
}


/* ==========================================================================
   10. FOOTER
   ========================================================================== */

.site-footer {
  position: relative;
  z-index: 1;
  overflow: hidden;   /* keeps the aura and the watermark inside the footer */
  background-color: var(--colour-night);
  border-top: 1px solid var(--colour-line);
  padding: var(--space-2xl) var(--page-gutter) var(--space-lg);
}

/* ---- Atmosphere ----------------------------------------------------------
   The same warm ellipse as the story section, so the page closes on the note
   it opened with. Only opacity and transform move, which the browser can
   hand to the graphics card and never repaint. */
.site-footer__aura {
  position: absolute;
  bottom: -45%;
  left: 50%;
  z-index: 0;
  width: min(52rem, 130%);
  aspect-ratio: 2 / 1;
  pointer-events: none;

  background: radial-gradient(closest-side,
    rgba(232, 167, 92, 0.11) 0%,
    rgba(181, 55, 42, 0.06) 50%,
    transparent 100%);

  transform: translateX(-50%);
  animation: footer-aura-drift 32s ease-in-out infinite alternate;
}

@keyframes footer-aura-drift {
  from { transform: translateX(-54%) scale(1);    opacity: 0.7; }
  to   { transform: translateX(-46%) scale(1.16); opacity: 1; }
}

/* One large character, 静 — "quiet".

   It runs off both the right and the bottom edge on purpose. Cropped on a
   single side it reads as a mistake; cropped on two it reads as a stamp that
   happens to be bigger than the paper. */
.site-footer__glyph {
  position: absolute;
  right: -0.1em;
  bottom: -0.4em;
  z-index: 0;
  font-family: "Hiragino Mincho ProN", "Yu Mincho", var(--font-display);
  font-size: clamp(9rem, 20vw, 20rem);
  line-height: 1;
  color: rgba(243, 239, 231, 0.028);
  pointer-events: none;
  user-select: none;
}

.site-footer__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: var(--space-xl);
  max-width: var(--page-max-width);
  margin: 0 auto;
}

/* ---- Brand column -------------------------------------------------------- */

/* The vertical Japanese phrase from the hero's left edge, repeated here to
   tie the top and bottom of the page together. */
.site-footer__seal {
  margin-top: var(--space-md);
  font-family: "Hiragino Mincho ProN", "Yu Mincho", var(--font-display);
  font-size: 1.0625rem;
  letter-spacing: 0.3em;
  color: var(--colour-accent-warm);
}

.site-footer__tagline {
  margin-top: var(--space-sm);
  max-width: 26rem;
  font-size: 0.9375rem;
  color: var(--colour-text-muted);
}

/* ---- Back to top --------------------------------------------------------- */
.site-footer__top {
  display: inline-flex;
  align-items: baseline;
  gap: 0.7rem;
  margin-top: var(--space-lg);
  color: var(--colour-text-muted);
  transition: color 260ms var(--ease-ui);
}

.site-footer__top [lang="ja"] {
  font-family: "Hiragino Sans", "Hiragino Kaku Gothic ProN",
               "Yu Gothic", "Noto Sans JP", var(--font-body);
  font-size: 0.9375rem;
  letter-spacing: 0.2em;
  color: var(--colour-accent);
}

.site-footer__top-roman {
  position: relative;
  font-size: 0.6875rem;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* A rule under the English that draws in from the left on hover. */
.site-footer__top-roman::after {
  content: "";
  position: absolute;
  bottom: -4px;
  left: 0;
  width: 100%;
  height: 1px;
  background-color: var(--colour-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 340ms var(--ease-ui);
}

.site-footer__top:hover {
  color: var(--colour-text);
}

.site-footer__top:hover .site-footer__top-roman::after {
  transform: scaleX(1);
}

/* ---- Column headings -----------------------------------------------------
   Japanese first, its English reading underneath, matching every kicker on
   the page above. */
.site-footer__heading {
  margin-bottom: var(--space-md);
  font-size: 1rem;
  font-weight: 400;
  color: var(--colour-text);
}

.site-footer__heading [lang="ja"] {
  font-family: "Hiragino Sans", "Hiragino Kaku Gothic ProN",
               "Yu Gothic", "Noto Sans JP", var(--font-body);
  letter-spacing: 0.2em;
  color: var(--colour-accent);
}

.site-footer__heading-roman {
  display: block;
  margin-top: 0.35rem;
  font-size: 0.6875rem;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: rgba(243, 239, 231, 0.45);
}

.site-footer__list {
  display: grid;
  gap: 0.7rem;
}

/* ---- Links ---------------------------------------------------------------
   Each link slides a little to the right on hover and grows a short rule in
   front of it, so the movement points the way the link goes. */
.site-footer__link {
  position: relative;
  display: inline-block;
  font-size: 0.9375rem;
  color: var(--colour-text-muted);
  transition: color 240ms var(--ease-ui),
              translate 300ms var(--ease-ui);
}

.site-footer__link::before {
  content: "";
  position: absolute;
  top: 50%;
  right: calc(100% + 0.45rem);
  width: 0.85rem;
  height: 1px;
  background-color: var(--colour-accent);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 300ms var(--ease-ui);
}

.site-footer__link:hover {
  color: var(--colour-text);
  translate: 0.85rem 0;
}

.site-footer__link:hover::before {
  transform: scaleX(1);
}

/* ---- Bottom bar: copyright and credit ----------------------------------- */
.site-footer__legal {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: 1px solid var(--colour-line);
  font-size: var(--size-small);
  color: var(--colour-text-muted);
}

.site-footer__credit-link {
  position: relative;
  color: var(--colour-text);
  font-weight: 600;
}

/* Underline that draws itself in from the left on hover. */
.site-footer__credit-link::after {
  content: "";
  position: absolute;
  bottom: -3px;
  left: 0;
  width: 100%;
  height: 1px;
  background-color: var(--colour-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 320ms var(--ease-ui);
}

.site-footer__credit-link:hover::after {
  transform: scaleX(1);
}


/* ==========================================================================
   11. REVEAL ANIMATIONS — the slide-up sequence
   --------------------------------------------------------------------------
   Nothing animates until JavaScript adds "is-ready" to the <body>, which it
   does once the three hero images have finished downloading. Waiting means
   the sequence always plays in full and never starts against a blank screen.

   Each layer starts lower down and fully transparent, then rises into place.
   The closer a layer is to the camera the further it travels, which is how
   depth behaves in real life and makes the arrival feel three-dimensional.
   ========================================================================== */

@keyframes layer-rise {
  from {
    opacity: 0;
    transform: translateY(var(--rise-distance)) scale(var(--rise-scale, 1));
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

body.is-ready .scene__layer-inner {
  animation-name: layer-rise;
  animation-duration: var(--reveal-duration);
  animation-timing-function: var(--reveal-ease);
  animation-fill-mode: forwards;
}

/* First: the shrine. Travels the least and eases in from very slightly
   larger, which reads as the camera settling. */
body.is-ready .scene__layer--shrine .scene__layer-inner {
  --rise-distance: 9%;
  --rise-scale: 1.035;
  animation-delay: var(--delay-layer-1);
}

/* Second: the guardians. */
body.is-ready .scene__layer--warriors .scene__layer-inner {
  --rise-distance: 16%;
  animation-delay: var(--delay-layer-2);
}

/* Third: the forest edge. Travels the furthest, being closest to camera. */
body.is-ready .scene__layer--foreground .scene__layer-inner {
  --rise-distance: 26%;
  animation-delay: var(--delay-layer-3);
}

/* ---- The wordmark --------------------------------------------------------
   Climbs up from behind the shrine. It starts far enough down to be entirely
   hidden by the building and finishes with its lower half still covered, so
   the movement reads as the word rising out from behind the shrine rather
   than sliding in over it.

   The horizontal centring has to be repeated in both keyframes: an animated
   transform replaces the element's own transform outright, so leaving it out
   would throw the word off to one side for the whole climb. */
@keyframes wordmark-rise {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(72%);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
}

body.is-ready .scene__wordmark {
  animation: wordmark-rise var(--wordmark-duration) var(--reveal-ease)
             var(--delay-wordmark) forwards;
}

.scene__wordmark {
  opacity: 0;
}

/* ---- Edge labels ---------------------------------------------------------
   The last thing to arrive, once the scene has fully settled.

   These keep their own keyframes because they are centred vertically with a
   transform of their own. An animated transform replaces that outright, so
   the centring has to be carried through both keyframes or the labels jump
   half their height down the screen as they fade in. */
@keyframes edge-label-rise {
  from { opacity: 0; transform: translateY(-50%) translateY(18px); }
  to   { opacity: 1; transform: translateY(-50%) translateY(0); }
}

[data-reveal] {
  opacity: 0;
}

body.is-ready [data-reveal] {
  animation: edge-label-rise 1100ms var(--reveal-ease) forwards;
}

body.is-ready [data-reveal="1"] { animation-delay: calc(var(--delay-text) + 0 * var(--stagger-text)); }
body.is-ready [data-reveal="2"] { animation-delay: calc(var(--delay-text) + 1 * var(--stagger-text)); }


/* ==========================================================================
   12. RESPONSIVE
   --------------------------------------------------------------------------
   The images are wide and low, so on a narrow screen they would shrink into
   a thin strip if simply scaled down. Instead each breakpoint scales them UP
   and crops the sides, keeping the shrine and the nearest guardians at a
   size that still feels cinematic.
   ========================================================================== */

/* ---- Tablet and small laptop -------------------------------------------- */
@media (max-width: 900px) {

  /* The shrine is taller here, so the word has to rest higher to keep the
     same amount of itself tucked behind the roof. */
  .scene__wordmark {
    --wordmark-bottom: 41vw;
  }

  .scene__layer--shrine {
    --layer-width: 112%;
    --layer-shift-y: 3%;
  }

  .scene__layer--warriors {
    --layer-width: 124%;
    --layer-shift-y: 6%;
  }

  .scene__layer--foreground {
    --layer-width: 150%;
    --layer-shift-y: 34%;
  }

  /* One column reads better than two narrow ones at this width. */
  .paper__columns,
  .split__inner {
    grid-template-columns: 1fr;
  }

  /* Image above the text, so the reader meets the picture first. */
  .split__media {
    order: -1;
  }

  .site-footer__inner {
    grid-template-columns: 1fr 1fr;
  }

  .site-footer__brand {
    grid-column: 1 / -1;
  }
}

/* ---- Phone --------------------------------------------------------------- */
@media (max-width: 600px) {

  /* Bigger relative to the screen, because 16.5vw of a phone is only about
     60px — far too timid to carry a hero on its own. */
  .scene__wordmark {
    --wordmark-bottom: 68vw;
    font-size: 22vw;
  }

  /* Smaller labels, tucked closer to the edges, so they never crowd the
     scene on a narrow screen. */
  .edge-label {
    gap: 0.75rem;
  }

  .edge-label__japanese {
    font-size: 0.8125rem;
    letter-spacing: 0.2em;
  }

  .edge-label__roman {
    display: none;   /* the Japanese alone is enough at this width */
  }

  .edge-label::after {
    height: 2.25rem;
  }

  /* Layers are scaled up hard and cropped at the sides. A phone screen is
     tall and narrow, so an image this wide would otherwise shrink to a thin
     strip across the middle. Better to lose the edges and keep the scale. */
  .scene__layer--shrine {
    --layer-width: 165%;
    --layer-shift-y: 4%;
  }

  .scene__layer--warriors {
    --layer-width: 205%;
    --layer-shift-y: 7%;
  }

  .scene__layer--foreground {
    --layer-width: 250%;
    --layer-shift-y: 22%;
  }

  /* Less blur, because the layer is scaled up so much that heavy blur here
     turns the foliage into an unreadable smear. */
  .scene__layer--foreground .scene__image {
    filter: blur(2px);
  }

  /* The wash is angled left-to-right on wide screens, where the text sits in
     the left half. On a phone the text spans the full width, so the wash has
     to be even across it or the right-hand words lose their contrast. */
  .band__wash {
    background:
      linear-gradient(to bottom,
        var(--colour-ink) 0%,
        rgba(5, 7, 13, 0.78) 25%,
        rgba(5, 7, 13, 0.78) 75%,
        var(--colour-ink) 100%);
  }

  .band {
    min-height: 76svh;
  }

  /* Stacking the three facts keeps the numbers at a readable size instead of
     squeezing them into three narrow columns. */
  .paper__facts {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
  }

  /* Shorter scroll runs on a phone. The same scene over two and a half
     screens of thumb-scrolling feels like it has stalled. */
  .immersive {
    height: 200svh;
  }

  .immersive__overlay {
    align-items: flex-end;
    padding-bottom: calc(var(--space-2xl) + 2rem);
    text-align: center;
  }

  .immersive__step {
    max-width: none;
  }

  .immersive__kicker {
    justify-content: center;
  }

  /* Explore and Get in touch sit side by side rather than stacking. Both
     lists are short and narrow, and stacking them made the footer taller
     than the phone screen — a full extra swipe of almost nothing. */
  .site-footer {
    padding-top: var(--space-xl);
  }

  .site-footer__inner {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-lg) var(--space-md);
  }

  .site-footer__brand {
    grid-column: 1 / -1;
  }

  .site-footer__legal {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.4rem;
    /* The uneven link columns already leave a gap above this rule, so it
       needs far less of its own. */
    margin-top: var(--space-md);
  }

  /* Roomier rows. These are the only links on the page a thumb has to hit,
     and 15px text on an 11px gap is a smaller target than it looks. */
  .site-footer__list {
    gap: 0.5rem;
  }

  .site-footer__link {
    padding: 0.35rem 0;
  }

  /* Smaller, fainter, and lifted clear of the copyright line. At the size it
     runs on a desktop it fills the empty half of a phone footer and starts
     reading as content rather than texture. */
  .site-footer__glyph {
    right: -1.2rem;
    bottom: 5.5rem;
    font-size: 9rem;
    color: rgba(243, 239, 231, 0.02);
  }

  /* A thumb cannot hover, so the links have no reason to shift sideways or
     to leave room for a rule that will never be drawn. */
  .site-footer__link:hover {
    translate: 0 0;
  }

  .site-footer__link::before {
    display: none;
  }
}


/* ==========================================================================
   13. REDUCED MOTION
   --------------------------------------------------------------------------
   Some people get motion sick from sliding and parallax, and the operating
   system lets them say so. When they have, we show the finished scene
   immediately: everything is still there, it simply does not move.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {

  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* Show the layers and the wordmark in their final positions straight away.
     The edge labels keep their centring transform — "none" would drop them
     half their own height down the screen. */
  .scene__layer-inner,
  .scene__wordmark {
    opacity: 1;
  }

  [data-reveal] {
    opacity: 1;
  }

  /* The dust field is not drawn at all in this mode. */
  .scene__dust {
    display: none;
  }

  /* Show the lower sections in place rather than waiting on a scroll cue
     that will never animate. */
  .reveal-on-scroll {
    opacity: 1;
    transform: none;
  }

  /* No canvas runs in this mode, so the tall scroll-driven sections have
     nothing to drive. Collapse them to a single screen and show the first
     block of text, which keeps the words without the empty scrolling. */
  .immersive {
    height: auto;
  }

  .immersive__sticky {
    position: relative;
    height: auto;
    min-height: 60svh;
  }

  .immersive__overlay {
    position: relative;
    display: grid;
    gap: var(--space-xl);
  }

  .immersive__step {
    position: relative;
    opacity: 1;
    transform: none;
  }

  /* With the sticky unpinned the sections flow normally, so there is no
     pinned frame left to ground — and a fade this tall would sit straight
     over the text. */
  .immersive__sticky::after {
    display: none;
  }
}
