/* ==========================================================================
   Ahmed Babay — Room stages
   rooms.css : full-viewport 3D interiors.

   The cutaway house is the map. When an act belongs to a room, a stage takes
   over the whole viewport and you are standing in it.

   Each stage is a shadow box: several planes sharing one drawing space, pushed
   apart along Z. A plane at depth z is scaled by (persp - z) / persp, which
   exactly cancels the perspective divide — so head-on the layers register as
   one picture, and a couple of degrees of camera rotation separates them into
   real parallax.

   Moving between rooms is driven by the scrollbar, not a timer: the room you
   are leaving comes toward you and dissolves while the next one walks up out
   of the dark.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The stage
   -------------------------------------------------------------------------- */

.stages {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
}

.stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  perspective: 1300px;
  perspective-origin: 40% 50%;
  background: var(--wall-in);
  opacity: var(--vis, 0);
  visibility: hidden;
  /* Short, so the fade tracks the scrollbar instead of lagging behind it. */
  transition: opacity 140ms linear;
}

.stage.is-on { visibility: visible; }

/* Idle stages must not burn frames. */
.stage:not(.is-on) * { animation-play-state: paused !important; }

.stage__cam {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform:
    translateZ(var(--push, 0px))
    rotateX(var(--rx, 0deg))
    rotateY(var(--ry, 0deg));
  transition: transform 140ms linear;
  will-change: transform;
}

/* A second rig, so the slow idle drift composes with the pointer camera
   instead of fighting it. */
.stage__drift {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  animation: drift 26s ease-in-out infinite;
}

@keyframes drift {
  0%, 100% { transform: rotateY(-0.55deg) rotateX(0.30deg)  translateZ(0px); }
  33%      { transform: rotateY(0.50deg)  rotateX(-0.25deg) translateZ(14px); }
  66%      { transform: rotateY(0.25deg)  rotateX(0.40deg)  translateZ(-10px); }
}

/* A plane in the box. --zn is depth in px, negative is further away. */
.layer {
  position: absolute;
  inset: -10%;
  transform:
    translateZ(calc(var(--zn, 0) * 1px))
    scale(calc((1300 - var(--zn, 0)) / 1300));
  transform-style: preserve-3d;
}

.layer > svg { display: block; width: 100%; height: 100%; }

/* Depth of field: the nearest plane softens, as a real lens would. */
.layer--fore { filter: blur(3px); }
.layer--far  { filter: blur(1px); }

/* --------------------------------------------------------------------------
   2. Cinematic overlays
   -------------------------------------------------------------------------- */

/* Warm grade, so the rooms feel lit rather than coloured. */
.stage__grade {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(72% 64% at 42% 50%, transparent 30%, rgba(0,0,0,.58) 100%);
  opacity: calc(.16 + var(--night-op) * .22);
  transition: opacity 1s var(--ease);
}

/* Room-wide light modulation — the whole scene breathes with the fire or lamp. */
.stage__flicker {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: screen;
  background: rgba(255,168,88,.028);
  opacity: var(--night-op);
  animation: roomFlicker 5.2s ease-in-out infinite;
}

@keyframes roomFlicker {
  0%, 100% { opacity: calc(var(--night-op) * .30); }
  28%      { opacity: calc(var(--night-op) * .48); }
  47%      { opacity: calc(var(--night-op) * .36); }
  71%      { opacity: calc(var(--night-op) * .54); }
}

/* Film grain. One tiny tile, nudged around — cheap, and it kills the banding
   in the big soft gradients. */
.stage__grain {
  position: absolute;
  inset: -50px;
  pointer-events: none;
  opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain 900ms steps(4) infinite;
}

@keyframes grain {
  0%   { transform: translate(0,0); }
  25%  { transform: translate(-14px,9px); }
  50%  { transform: translate(11px,-12px); }
  75%  { transform: translate(-8px,-6px); }
  100% { transform: translate(0,0); }
}

/* The reading column needs a floor to sit on. */
.stages::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    transparent 0%,
    transparent 44%,
    color-mix(in srgb, var(--bg) 58%, transparent) 58%,
    var(--bg) 78%
  );
  opacity: 1;
}


/* --------------------------------------------------------------------------
   3. Shared room fittings
   -------------------------------------------------------------------------- */

.stage svg [fill], .stage svg [stroke] {
  transition: fill 1s var(--ease), stroke 1s var(--ease), opacity 1s var(--ease);
}

/* Lamps, fires and window light only glow after dark. */
.glow-night { opacity: var(--night-op); }
.glow-day   { opacity: calc(1 - var(--night-op)); }

/* EVERY animated shape inside a stage pivots on its own box.
   Without this, transform-origin resolves against the SVG viewport and the
   shape swings in from somewhere out in space — which is exactly what made
   the fire look unstable. */
.stage svg .fl-back, .stage svg .fl-mid, .stage svg .fl-core, .stage svg .fl-tip,
.stage svg .cat, .stage svg .cat-ear, .stage svg .cat-tail, .stage svg .cat-chest,
.stage svg .sway, .stage svg .hang-swing, .stage svg .tick,
.stage svg .ember, .stage svg .mote, .stage svg .vapour, .stage svg .rain,
.stage svg .firelight, .stage svg .screen-flick, .stage svg .blink-led,
.stage svg .pulse-soft, .stage svg .moth, .stage svg .code-scroll,
.stage svg .build-bar, .stage svg .wave, .stage svg .fan, .stage svg .print-page,
.stage svg .ripple, .stage svg .glint, .stage svg .bob,
.stage svg .dog, .stage svg .dog-tail, .stage svg .dog-ear, .stage svg .canopy,
.stage svg .rgb-wave rect {
  transform-box: fill-box;
}

/* --------------------------------------------------------------------------
   4. Fire — anchored at the base, small amplitude, no skew
   -------------------------------------------------------------------------- */

.fl-back, .fl-mid, .fl-core, .fl-tip {
  transform-origin: 50% 100%;
  will-change: transform;
}

.fl-back { animation: flBack 2.30s ease-in-out infinite alternate; }
.fl-mid  { animation: flMid  1.70s ease-in-out infinite alternate; }
.fl-core { animation: flCore 1.30s ease-in-out infinite alternate; }
.fl-tip  { animation: flTip  0.95s ease-in-out infinite alternate; }

@keyframes flBack {
  from { transform: scale(1.00, 0.97); opacity: .42; }
  to   { transform: scale(0.97, 1.04); opacity: .55; }
}
@keyframes flMid {
  from { transform: scale(1.02, 0.96); opacity: .82; }
  to   { transform: scale(0.98, 1.05); opacity: .95; }
}
@keyframes flCore {
  from { transform: scale(0.99, 1.03); opacity: .92; }
  to   { transform: scale(1.03, 0.96); opacity: 1; }
}
@keyframes flTip {
  from { transform: scale(1.00, 0.92); opacity: .70; }
  to   { transform: scale(0.96, 1.08); opacity: .95; }
}

.firelight {
  transform-origin: 50% 50%;
  animation: firelight 3.4s ease-in-out infinite alternate;
}
@keyframes firelight {
  from { opacity: .22; transform: scale(0.99); }
  to   { opacity: .36; transform: scale(1.02); }
}

.ember { animation: ember 5s linear infinite; opacity: 0; }
.ember:nth-of-type(2) { animation-delay: -1.7s; animation-duration: 6.2s; }
.ember:nth-of-type(3) { animation-delay: -3.4s; animation-duration: 4.3s; }
.ember:nth-of-type(4) { animation-delay: -2.5s; animation-duration: 6.8s; }
.ember:nth-of-type(5) { animation-delay: -4.6s; animation-duration: 5.5s; }

@keyframes ember {
  0%   { opacity: 0;  transform: translate(0,0) scale(1); }
  14%  { opacity: .9; }
  100% { opacity: 0;  transform: translate(-18px,-165px) scale(.25); }
}

/* --------------------------------------------------------------------------
   5. The cat
   -------------------------------------------------------------------------- */

.cat-chest { transform-origin: 50% 100%; animation: breathe 4.2s ease-in-out infinite; }
@keyframes breathe {
  0%, 100% { transform: scale(1, 1); }
  50%      { transform: scale(1.008, 1.045); }
}

.cat-ear { transform-origin: 50% 100%; animation: earTwitch 9.5s ease-in-out infinite; }
.cat-ear--r { animation-delay: -4.2s; }
@keyframes earTwitch {
  0%, 90%, 100% { transform: rotate(0deg); }
  92%           { transform: rotate(-11deg); }
  95%           { transform: rotate(5deg); }
}

.cat-tail { transform-origin: 100% 50%; animation: tailFlick 7.4s ease-in-out infinite; }
@keyframes tailFlick {
  0%, 74%, 100% { transform: rotate(0deg); }
  81%           { transform: rotate(-4.5deg); }
  88%           { transform: rotate(2.5deg); }
}

/* --------------------------------------------------------------------------
   6. Atmosphere
   -------------------------------------------------------------------------- */

.mote { animation: mote 15s linear infinite; opacity: 0; }
.mote:nth-of-type(2) { animation-delay: -2.4s;  animation-duration: 18s; }
.mote:nth-of-type(3) { animation-delay: -5.8s;  animation-duration: 12s; }
.mote:nth-of-type(4) { animation-delay: -8.1s;  animation-duration: 20s; }
.mote:nth-of-type(5) { animation-delay: -11.3s; animation-duration: 16s; }
.mote:nth-of-type(6) { animation-delay: -6.6s;  animation-duration: 22s; }
.mote:nth-of-type(7) { animation-delay: -3.2s;  animation-duration: 17s; }
.mote:nth-of-type(8) { animation-delay: -9.7s;  animation-duration: 13s; }
.mote:nth-of-type(9) { animation-delay: -14.1s; animation-duration: 19s; }

@keyframes mote {
  0%   { opacity: 0;   transform: translate(0,0); }
  12%  { opacity: .5; }
  50%  { opacity: .22; transform: translate(30px,-110px); }
  88%  { opacity: .45; transform: translate(-16px,-210px); }
  100% { opacity: 0;   transform: translate(10px,-290px); }
}

.vapour { animation: vapour 5.8s ease-out infinite; opacity: 0; }
.vapour:nth-of-type(2) { animation-delay: -1.9s; }
.vapour:nth-of-type(3) { animation-delay: -3.8s; }
@keyframes vapour {
  0%   { opacity: 0;   transform: translateY(0) scale(.5); }
  22%  { opacity: .40; }
  100% { opacity: 0;   transform: translateY(-92px) scale(2); }
}

.sway { transform-origin: 50% 100%; animation: swayProp 8.5s ease-in-out infinite; }
.sway--slow { animation-duration: 12s; animation-delay: -3s; }
@keyframes swayProp {
  0%, 100% { transform: rotate(-1.4deg); }
  50%      { transform: rotate(1.4deg); }
}

.hang-swing { transform-origin: 50% 0%; animation: hangSwing 6.4s ease-in-out infinite; }
@keyframes hangSwing {
  0%, 100% { transform: rotate(-1.8deg); }
  50%      { transform: rotate(1.8deg); }
}

.tick { transform-origin: 50% 100%; animation: tick 60s steps(60) infinite; }
@keyframes tick { to { transform: rotate(360deg); } }

.screen-flick { animation: screenFlick 7s ease-in-out infinite; }
@keyframes screenFlick {
  0%, 100% { opacity: .58; }
  38%      { opacity: .92; }
  61%      { opacity: .70; }
}

.code-scroll { animation: codeScroll 11s linear infinite; }
@keyframes codeScroll {
  0%   { transform: translateY(0); }
  100% { transform: translateY(-52px); }
}

.blink-led { animation: led 3.2s steps(1, end) infinite; }
@keyframes led { 0%, 62% { opacity: 1; } 63%, 100% { opacity: .16; } }

.pulse-soft { transform-origin: 50% 50%; animation: pulseSoft 6.5s ease-in-out infinite; }
@keyframes pulseSoft {
  0%, 100% { opacity: calc(var(--night-op) * .7); }
  50%      { opacity: var(--night-op); }
}

.rain { animation: rain 2.8s linear infinite; }
.rain:nth-of-type(2) { animation-delay: -0.9s; animation-duration: 3.6s; }
.rain:nth-of-type(3) { animation-delay: -1.8s; animation-duration: 2.3s; }
.rain:nth-of-type(4) { animation-delay: -2.4s; animation-duration: 4.1s; }
@keyframes rain {
  0%   { opacity: 0;   transform: translateY(-40px); }
  20%  { opacity: .45; }
  100% { opacity: 0;   transform: translateY(180px); }
}

.moth { animation: moth 7.5s ease-in-out infinite; }
.moth:nth-of-type(2) { animation-delay: -2.6s; animation-duration: 9.2s; }
.moth:nth-of-type(3) { animation-delay: -5.1s; animation-duration: 6.4s; }
@keyframes moth {
  0%, 100% { transform: translate(0,0); }
  25%      { transform: translate(26px,-18px); }
  50%      { transform: translate(-14px,-30px); }
  75%      { transform: translate(-30px,-6px); }
}


/* --------------------------------------------------------------------------
   6b. Room-specific machinery
   -------------------------------------------------------------------------- */

/* Studio: a build bar creeping across, an audio waveform, backlit keys. */
.build-bar { transform-origin: 0% 50%; animation: buildBar 9s ease-in-out infinite; }
@keyframes buildBar {
  0%   { transform: scaleX(0);   opacity: .9; }
  70%  { transform: scaleX(.82); opacity: 1; }
  92%  { transform: scaleX(1);   opacity: 1; }
  100% { transform: scaleX(1);   opacity: 0; }
}

.wave { animation: wave 2.6s ease-in-out infinite alternate; }
@keyframes wave {
  from { transform: scaleY(.55); }
  to   { transform: scaleY(1.25); }
}

.rgb-wave rect { animation: rgbKey 2.4s ease-in-out infinite; opacity: .35; }
.rgb-wave rect:nth-of-type(2)  { animation-delay: -2.25s; }
.rgb-wave rect:nth-of-type(3)  { animation-delay: -2.10s; }
.rgb-wave rect:nth-of-type(4)  { animation-delay: -1.95s; }
.rgb-wave rect:nth-of-type(5)  { animation-delay: -1.80s; }
.rgb-wave rect:nth-of-type(6)  { animation-delay: -1.65s; }
.rgb-wave rect:nth-of-type(7)  { animation-delay: -1.50s; }
.rgb-wave rect:nth-of-type(8)  { animation-delay: -1.35s; }
.rgb-wave rect:nth-of-type(9)  { animation-delay: -1.20s; }
.rgb-wave rect:nth-of-type(10) { animation-delay: -1.05s; }
.rgb-wave rect:nth-of-type(11) { animation-delay: -0.90s; }
.rgb-wave rect:nth-of-type(12) { animation-delay: -0.75s; }
.rgb-wave rect:nth-of-type(13) { animation-delay: -0.60s; }
.rgb-wave rect:nth-of-type(14) { animation-delay: -0.45s; }
@keyframes rgbKey { 0%, 100% { opacity: .28; } 40% { opacity: 1; } }

/* Office: a fan actually turning, and a page leaving the printer. */
.fan { transform-origin: 50% 50%; animation: fanSpin 1.1s linear infinite; }
@keyframes fanSpin { to { transform: rotate(360deg); } }

.print-page { transform-origin: 50% 0%; animation: printPage 11s ease-in-out infinite; }
@keyframes printPage {
  0%, 6%   { transform: scaleY(0);   opacity: 0; }
  22%      { transform: scaleY(1);   opacity: 1; }
  76%      { transform: scaleY(1);   opacity: 1; }
  86%, 100%{ transform: scaleY(1) translateY(40px); opacity: 0; }
}

/* Back garden: water, and a dog pleased to see you. */
.ripple { transform-origin: 50% 50%; animation: ripple 6s ease-out infinite; opacity: 0; }
.ripple:nth-of-type(2) { animation-delay: -2s; }
.ripple:nth-of-type(3) { animation-delay: -4s; }
@keyframes ripple {
  0%   { transform: scale(.25); opacity: 0; }
  20%  { opacity: .5; }
  100% { transform: scale(1.05); opacity: 0; }
}

.glint { animation: glint 4.5s ease-in-out infinite alternate; }
.glint:nth-of-type(2) { animation-delay: -1.6s; animation-duration: 5.8s; }
.glint:nth-of-type(3) { animation-delay: -3.1s; animation-duration: 3.9s; }
@keyframes glint {
  from { opacity: .12; transform: translateX(-14px) scaleX(.9); }
  to   { opacity: .38; transform: translateX(14px) scaleX(1.1); }
}

.bob { animation: bob 5.2s ease-in-out infinite; }
@keyframes bob {
  0%, 100% { transform: translate(0, 0) rotate(-1.5deg); }
  50%      { transform: translate(16px, -7px) rotate(1.5deg); }
}

.dog-tail { transform-origin: 0% 60%; animation: wag 700ms ease-in-out infinite alternate; }
@keyframes wag { from { transform: rotate(-16deg); } to { transform: rotate(16deg); } }

.dog-ear { transform-origin: 100% 0%; animation: dogEar 6s ease-in-out infinite; }
@keyframes dogEar {
  0%, 84%, 100% { transform: rotate(0deg); }
  88%           { transform: rotate(-13deg); }
  93%           { transform: rotate(6deg); }
}

.dog { transform-origin: 50% 100%; animation: dogBreathe 3.8s ease-in-out infinite; }
@keyframes dogBreathe { 0%, 100% { transform: scale(1,1); } 50% { transform: scale(1.006,1.03); } }

/* --------------------------------------------------------------------------
   6c. Case studies, out the back
   -------------------------------------------------------------------------- */


.act[data-act="yard"] .cases {
  display: grid;
  gap: var(--s-9);
  padding-block: 0;
}

/* work.css lays a case out in two columns on a full-width page. In here it is
   one column, and the screenshots stop trying to stick. */
.act[data-act="yard"] .case__body { grid-template-columns: minmax(0, 1fr); gap: var(--s-6); }
.act[data-act="yard"] .shots { position: static; top: auto; }
.act[data-act="yard"] .case { scroll-margin-top: var(--s-6); }
.act[data-act="yard"] .case__title { font-size: clamp(1.6rem, 3vw, 2.1rem); }
.act[data-act="yard"] .case--flagship .case__title { font-size: clamp(1.8rem, 3.4vw, 2.4rem); }

/* No cards out here — the write-ups sit straight on the garden, on the same
   scrim every other room uses. */

.act[data-act="yard"] .case {
  background: transparent;
  backdrop-filter: none;
  border: 0;
  padding: 0;
}

.act[data-act="yard"] .case,
.act[data-act="yard"] .case__title,
.act[data-act="yard"] .block__label { text-shadow: 0 1px 20px var(--bg), 0 0 3px var(--bg); }

.act[data-act="yard"] .case__kicker,
.act[data-act="yard"] .block p,
.act[data-act="yard"] .block__list li,
.act[data-act="yard"] .shots__hint { color: var(--text); }

.act[data-act="yard"] .case__head { border-bottom-color: var(--border-2); }
.act[data-act="yard"] .case__stack { border-top-color: var(--border-2); }

/* Chips and screenshots keep just enough body to read as objects. */
.act[data-act="yard"] .chip {
  background: color-mix(in srgb, var(--surface) 76%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.act[data-act="yard"] .shots__frame {
  background: color-mix(in srgb, var(--surface) 64%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--border-2);
  border-radius: var(--r-lg);
  overflow: hidden;
}

/* With the cards gone, a solid mint badge reads as a green block dropped on
   the garden. Outline it instead, and let the flagship match its neighbours. */
.act[data-act="yard"] .case__flag {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent-line);
  font-weight: 600;
}
.act[data-act="yard"] .case--flagship .case__head { border-bottom-color: var(--border-2); }
.act[data-act="yard"] .case--flagship .case__kicker { font-size: var(--fs-sm); }

/* Arriving at a case study scrolls you to it. That is the whole cue — no
   outline, no recolouring of the title. */
.act[data-act="yard"] .case.is-target { border: 0; box-shadow: none; }

/* The mark carries the house rather than initials. */
.hud__mark svg  { width: 21px; height: 21px; }
.boot__mark svg { width: 38px; height: 38px; }

/* --------------------------------------------------------------------------
   7. Reading column over a live room
   -------------------------------------------------------------------------- */

.act--room .panel .build,
.act--room .panel .cert,
.act--room .panel .shelf,
.act--room .panel .tome,
.act--room .panel .peg,
.act--room .panel .plaque,
.act--room .panel .postrow,
.act--room .panel .side-table {
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

/* --------------------------------------------------------------------------
   8. Narrow screens
   -------------------------------------------------------------------------- */

@media (max-width: 999px) {
  .stages { height: var(--scene-h); bottom: auto; z-index: 4; }
  .stage  { perspective-origin: 50% 50%; }
  /* The band met the text on a hard line. Instead the lower part of the scene
     is progressively blurred and faded into the page colour, so the room
     drifts out of focus rather than being cut off.

     The mask does double duty: it fades the gradient *and* ramps the
     backdrop-filter, which is what makes the blur build up gradually rather
     than switching on at an edge. */
  .stages::after {
    background: linear-gradient(
      180deg,
      transparent 44%,
      color-mix(in srgb, var(--bg) 45%, transparent) 72%,
      color-mix(in srgb, var(--bg) 88%, transparent) 90%,
      var(--bg) 100%
    );
    -webkit-backdrop-filter: blur(9px);
    backdrop-filter: blur(9px);
    -webkit-mask-image: linear-gradient(180deg, transparent 42%, #000 88%);
    mask-image: linear-gradient(180deg, transparent 42%, #000 88%);
  }

  /* A hairline of warmth where the two meet, so the seam reads as light
     rather than as an edge. */
  .stages::before {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--border-2) 25%, var(--border-2) 75%, transparent);
    opacity: .5;
    pointer-events: none;
  }
  .stage__grain { opacity: .035; }
}

/* --------------------------------------------------------------------------
   9. Reduced motion — the rooms stand still, but they are still rooms
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .stage, .stage__cam { transition-duration: 0.01ms !important; }
  .stage__drift,
  .fl-back, .fl-mid, .fl-core, .fl-tip, .firelight, .ember,
  .cat-chest, .cat-ear, .cat-tail, .mote, .vapour, .sway, .hang-swing,
  .tick, .screen-flick, .code-scroll, .blink-led, .pulse-soft, .rain, .moth,
  .build-bar, .wave, .fan, .print-page, .rgb-wave rect,
  .ripple, .glint, .bob, .dog, .dog-tail, .dog-ear, .canopy,
  .stage__grain, .stage__flicker {
    animation: none !important;
  }
  .layer--fore, .layer--far { filter: none; }
}

/* --------------------------------------------------------------------------
   10. No JavaScript — no stages at all, the document stands alone
   -------------------------------------------------------------------------- */

html:not(.js) .stages { display: none; }
