/* ---------- The stairwell (Read) ---------- */
/* Through the portrait and up the stairs toward the door of light. The
   staircase is flat pseudo-perspective (see .sw-stairs); scrolling walks you
   up. Sits under the letter viewer (z 20). */
#ascent {
  position: fixed;
  inset: 0;
  z-index: 18;
  background: #05070d;
  transition: opacity 0.5s ease;
}

#ascent[hidden] { display: none; }

/* Held invisible while the portrait door swings open, then released to fade up
   (the opacity transition above) as you step through into the stairwell. */
#ascent.behind-door { opacity: 0; }

/* stepping back out — the stairwell dissolves as the vigil returns */
#ascent.closing { opacity: 0; }

/* While the stairwell covers the screen, the vigil beneath stops painting —
   a hundred burning candles behind an opaque scene is wasted work. */
body.in-ascent #candle-field,
body.in-ascent .center-stack,
body.in-ascent .actions { visibility: hidden; }

/* The walk-through (Read → stairwell). Everything that moves each frame is
   promoted to its own layer up front, and every ambient animation — flames,
   glows, stars, the door's breathing — holds its breath. With their contents
   frozen, the compositor slides cached textures instead of re-rastering a
   hundred candles per frame; on phones this is the difference between a walk
   and a slideshow. js/stairwell.js toggles the class around the transition. */
body.walking #candle-field,
body.walking .center-stack { will-change: transform, opacity; }
body.walking #ascent { will-change: clip-path; }
body.walking .sw-stage { will-change: transform; }
body.walking .flame,
body.walking .glow,
body.walking #ascent .star,
body.walking .sw-door { animation-play-state: paused; }

.ascent-close {
  position: absolute;
  top: calc(0.7rem + env(safe-area-inset-top, 0px));
  right: 1.1rem;                  /* clear of the slim climb scrollbar */
  z-index: 3;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: rgba(10, 12, 16, 0.45);
  border: 1px solid var(--line);
  color: var(--ink);
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(3px);
}

.ascent-close:hover { background: rgba(10, 12, 16, 0.7); border-color: var(--line-strong); }
.ascent-close:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 2px; }

/* Scrolling is walking. The stage stays put (sticky) while the spacer below
   gives the scroll its length; each frame maps scrollTop to strides. */
.sw-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  background: #05070d;   /* rubber-banding and resizes expose night, not white */
  scrollbar-width: thin;                                   /* Firefox */
  scrollbar-color: rgba(255, 224, 165, 0.28) transparent;
}

/* The climb's scrollbar — a slim warm thread in the dark, not a white slab.
   The track stays invisible so the night shows through; the thumb is a faint
   candle-lit amber that brightens on hover. (WebKit/Blink; Firefox uses the
   scrollbar-* properties above.) */
.sw-scroll::-webkit-scrollbar { width: 9px; }
.sw-scroll::-webkit-scrollbar-track { background: transparent; }
.sw-scroll::-webkit-scrollbar-thumb {
  background: rgba(255, 224, 165, 0.24);
  border-radius: 9px;
  border: 3px solid transparent;      /* insets the thumb, leaving a thin rail */
  background-clip: content-box;
}
.sw-scroll::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 231, 183, 0.45);
  background-clip: content-box;
}

.sw-stage {
  position: sticky;
  top: 0;
  height: 100%;
  overflow: hidden;
}

/* the ground fade — masks the stair passing underfoot as it leaves the world */
.sw-stage::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 13%;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(4, 6, 11, 0), rgba(4, 6, 11, 0.9));
}

.sw-spacer { width: 1px; }

/* --- The night around the stairs --- */
.sw-sky {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(60% 45% at 50% 34%, rgba(255, 226, 160, 0.1), rgba(255, 226, 160, 0) 70%),
    linear-gradient(to bottom, #04060b 0%, #0a0e18 45%, #10141e 100%);
}

#ascent .star {
  position: absolute;
  border-radius: 50%;
  background: #fff;
  pointer-events: none;
  animation: star-tw var(--tw, 3s) ease-in-out infinite;
  animation-delay: var(--twd, 0s);
}

@keyframes star-tw {
  50% { opacity: 0.06; }
}

/* photographs hung in the dark, drifting past at half your pace */
.sw-photoband {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  will-change: transform;
}

.step-photo {
  position: absolute;
  width: clamp(90px, 26vw, 150px);
  border: 4px solid rgba(255, 255, 255, 0.55);
  border-radius: 3px;
  transform: translateX(-50%) rotate(var(--tilt, 0deg));
  opacity: 0.55;
  filter: sepia(0.35) contrast(0.9);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.4);
  pointer-events: none;
}

/* wide screens: the walls are further away — photos larger, less faded */
@media (min-width: 1160px) {
  .step-photo {
    width: clamp(150px, 13vw, 230px);
    opacity: 0.68;
  }
}

/* --- The staircase (flat pseudo-perspective) --- */
/* Every stair is a plain rectangle anchored to one ground line; JS scales
   each by 0.8 per stride and stacks them toward the vanishing line. Nothing
   but 2D translate/scale — identical in every engine, desktop and mobile. */
.sw-stairs {
  position: absolute;
  inset: 0;
}

/* One stair, tapered stone: the tread's sides angle in toward the stair
   above — a true staircase silhouette. The shading reads as rounded 3D stone:
   the BACK of the tread sinks into a soft corner-shadow (the ambient occlusion
   where the next riser rises — no more bright seam line), the tread's front
   lip catches a highlight, and the vertical riser face is dimmer than the
   top surface and falls into its own form-shadow toward the floor. The tread
   is drawn 2px taller than the geometric pitch, tucking behind the stair
   above — no hairline gaps. One clip-path shapes the silhouette, so the shade
   veil inside can never show corners past it (engines without clip-path show
   a plain block). */
.sw-step {
  position: absolute;
  left: 50%;
  bottom: 9%;                     /* the ground line every stair stands on */
  width: min(94vw, 620px);        /* drawn close — the flight fills the frame */
  transform-origin: 50% 100%;
  will-change: transform, opacity;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.3em;
  padding: 0.5em 4%;
  padding-top: calc(var(--tread, 28px) + 0.3em);
  background:
    /* the stone's ends fall away into the night, giving each stair its width
       and volume in the dark (night colour so they dissolve, not grey out).
       --side (driven by JS) tapers the reach as the light fills the stairwell:
       full murk while it's night, retreating to nothing by the peak. */
    linear-gradient(to right,
      rgba(5, 7, 13, calc(0.36 * var(--side, 1))) 0%,
      rgba(5, 7, 13, 0) calc(22% * var(--side, 1)),
      rgba(5, 7, 13, 0) calc(100% - 22% * var(--side, 1)),
      rgba(5, 7, 13, calc(0.36 * var(--side, 1))) 100%),
    linear-gradient(to bottom,
    hsl(41 26% 66%) 0px,                                    /* corner shadow, back of tread */
    hsl(41 26% 78%) 5.4px,
    hsl(41 26% 90%) 12px,                                   /* lit tread surface */
    hsl(41 26% 90%) calc(var(--tread, 28px) - 18px),
    hsl(41 26% 94%) calc(var(--tread, 28px) - 1px),        /* bright nosing lip */
    hsl(41 26% 80%) calc(var(--tread, 28px) + 2px),        /* riser face */
    hsl(41 26% 71%) 100%);                                 /* riser form-shadow toward the floor */
  border: none;
  clip-path: polygon(10% 0, 90% 0, 100% var(--tread, 28px), 100% 100%, 0 100%, 0 var(--tread, 28px));
  font-family: inherit;
  text-align: center;
  color: var(--paper-ink);
  cursor: pointer;
}

.sw-step:hover, .sw-step:focus-visible { filter: brightness(1.05); }
.sw-step:focus-visible { outline: 2px solid var(--flame-2); outline-offset: -3px; }

/* night lying on the farther stairs — JS drives the opacity */
.sw-shade {
  position: absolute;
  inset: 0;
  background: #05070d;
  opacity: 0;
  pointer-events: none;
}

/* The words are cut into the riser, same carve as the media engravings: a
   recessed stone tone, a bright lower lip where light catches the groove, and a
   soft shadow in the recess above. */
.sw-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
  font-size: clamp(13px, 2.2vh, 17px);
  line-height: 1.35;
  min-height: 2.7em;   /* reserve two lines so a blank note keeps the author tag in place */
  color: hsl(41 25% 37%);
  text-shadow:
    0 1.4px 0.5px rgba(255, 255, 255, 0.7),
    0 -1px 0.6px rgba(46, 38, 26, 0.5),
    0 0 0.5px rgba(52, 42, 26, 0.4);
}

.sw-from {
  font-style: italic;
  font-size: clamp(10px, 1.6vh, 12px);
  color: hsl(41 20% 40%);
  text-shadow:
    0 1.3px 0.5px rgba(255, 255, 255, 0.6),
    0 -0.9px 0.5px rgba(46, 38, 26, 0.45),
    0 0 0.5px rgba(52, 42, 26, 0.35);
}

/* Media engraving: the note's media type carved into the right of the stair, in
   a reserved column slot (recording / picture / video, top to bottom). The fill
   is the recess (carve depth 30 → the ~80% stone dropped to 50%); the bright
   lower-lip highlight (55) is what reads as light hitting a carved groove.
   Size and inset (right) are here to tune in place; it scales with the stair. */
.sw-engrave {
  position: absolute;
  right: 2%;
  width: var(--eng, 22px);      /* sized in JS to the riser so 3 marks never collide */
  height: var(--eng, 22px);
  transform: translateY(-50%);
  pointer-events: none;
  color: hsl(41 24% 43%);
  filter:
    drop-shadow(0 1px 0.4px rgba(255, 255, 255, 0.55))
    drop-shadow(0 -0.6px 0.4px rgba(46, 38, 26, 0.39));
}
.sw-engrave svg { width: 100%; height: 100%; display: block; fill: currentColor; }
/* three evenly-spaced slots down the riser (below the tread) — a note can carry
   all three at once, so they must clear each other at every riser height. */
.sw-engrave-audio { top: calc(var(--tread, 28px) + (100% - var(--tread, 28px)) * 0.2); }
.sw-engrave-image { top: calc(var(--tread, 28px) + (100% - var(--tread, 28px)) * 0.5); }
.sw-engrave-video { top: calc(var(--tread, 28px) + (100% - var(--tread, 28px)) * 0.8); }

/* On phones the stairs are ~88vw, so the text fills them at the sizes above.
   On wider screens the stairs cap at 540px while the window is large, leaving
   that text small and faint — so bump it up here to match the roomier stair. */
@media (min-width: 640px) {
  .sw-text { font-size: clamp(17px, 2.3vh, 21px); }
  .sw-from { font-size: clamp(12px, 1.6vh, 14px); }
}

/* --- The narrow door of light at the top --- */
/* Unseen for most of the climb; it fades in once you're a few strides out
   (JS drives the opacity) and comes down to meet you. */
.sw-door {
  position: absolute;
  left: 50%;
  bottom: 9%;                     /* stands on the same ground line */
  z-index: 1;                     /* behind every stair */
  width: min(32vw, 140px);
  height: min(38vh, 300px);
  opacity: 0;
  transform-origin: 50% 100%;
  background: linear-gradient(to bottom, #fff9e8, #ffe9b3 60%, #f7cf8d);
  border-radius: 3px 3px 0 0;
  box-shadow:
    /* the reaching haze — grows blurrier as you arrive */
    0 0 calc(30px + var(--arrive, 0) * 140px) rgba(255, 236, 180, calc(0.55 + var(--arrive, 0) * 0.45)),
    /* the swelling core — spread makes the light physically pour outward */
    0 0 calc(40px + var(--arrive, 0) * 60px) calc(20px + var(--arrive, 0) * 46px) rgba(255, 224, 158, calc(0.25 + var(--arrive, 0) * 0.35));
  animation: door-breathe 5s ease-in-out infinite;
  pointer-events: none;
}

@keyframes door-breathe {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.12); }
}

/* The dawn: the night gives way as you climb — an overlay across the whole
   sky (stars, photographs and all soften into it); JS raises its opacity
   from SW_DAWN_START (js/stairwell.js) to the top. Colors here set how bright the
   light gets and how far down the screen it reaches. */
.sw-dawn {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(to bottom,
    #f6e9c9 0%,      /* warm cream — the layer of light right under the peak bloom */
    #edd3a0 11%,     /* gold… */
    #d4a878 22%,     /* …warm tan… */
    #b08468 33%,     /* …into a dusty mauve… */
    #856a6a 45%,     /* …the warm-to-cool turn… */
    #574f60 57%,     /* …dusty mauve-slate… */
    #3c3f54 68%,     /* …the cool slate that made the old sky sing… */
    #262c42 80%,     /* …deep dusk blue carrying down… */
    rgba(20, 26, 40, 0) 100%);   /* …fading into the night at the floor */
}

/* The arrival bloom: as you crest the climb, the doorway's light floods the
   sky. It sits behind the stairs (so the near letter stays readable) and uses
   `screen` blending to ADD light rather than veil the scene — JS ramps its
   opacity in over the last stretch (SW_PEAK_START in js/stairwell.js). Center, reach,
   and colors here decide where the light pools and how fierce it gets. */
.sw-bloom {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  mix-blend-mode: screen;
  will-change: opacity;
  background:
    radial-gradient(115% 78% at 50% 47%,
      rgba(255, 249, 230, 0.98) 0%,
      rgba(255, 238, 196, 0.62) 26%,
      rgba(255, 228, 165, 0.22) 48%,
      rgba(255, 220, 150, 0) 68%);
}

/* --- The foot of the stairs, where you arrive --- */
.ascent-foot {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  text-align: center;
  padding: 0 1.5rem calc(1.6rem + env(safe-area-inset-bottom, 0px));
  pointer-events: none;
}

/* The clickable "climb to the top" cue — pinned in the upper stairwell, above
   where the stairs recede to their vanishing point. It's a flat overlay (NOT in
   the stairs' perspective, or it would shrink to a dot), present the whole climb
   and faded out by swUpdate only as you crest. The stairwell build (js/stairwell.js)
   sets the real `top` from the staircase's own geometry so the gap keeps its
   ratio on every screen; this is only the pre-build fallback. */
.sw-climb-top {
  position: absolute;
  top: 30vh;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;                     /* over the stairs and the door glow */
  white-space: nowrap;
  opacity: 0;                     /* swUpdate raises it while there's room to climb */
  font-family: inherit;
  background: none;
  border: 0;
  padding: 0.3em 0.6em;
  color: rgba(236, 231, 220, 0.9);
  font-size: 0.74rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.85);
  cursor: pointer;
  transition: opacity 0.4s ease;
  animation: float-hint 2.8s ease-in-out infinite;   /* a gentle drift up and back */
}
/* keeps the -50% centering while bobbing on the Y axis */
@keyframes float-hint {
  0%, 100% { transform: translate(-50%, 0); }
  50%      { transform: translate(-50%, -7px); }
}
.sw-climb-top:hover { color: #fff; }
.sw-climb-top:focus-visible { outline: 2px solid var(--flame-2); outline-offset: 2px; border-radius: 6px; }

.foot-count {
  margin-top: 0.5rem;
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  color: var(--ink-dim);
}

.sw-climb-top .up {
  display: inline-block;
  animation: rise-hint 2.4s ease-in-out infinite;
}

@keyframes rise-hint {
  0%, 100% { transform: translateY(2px); opacity: 0.55; }
  50%      { transform: translateY(-3px); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  #ascent .star, .sw-climb-top, .sw-climb-top .up, .sw-door { animation: none; }
}

.letters-empty { color: var(--ink-dim); font-style: italic; }

