/* ============================================================
   The garage — landing scene.

   One fixed viewport, never scrolls. Four aligned renders of the
   same room (day/night x headlight off/on) stack on top of each
   other and crossfade by opacity. Everything clickable is a real
   <button> positioned in percentages of the image, so hotspots
   stay locked to objects at any viewport size.
   ============================================================ */

:root {
  /* Scene geometry, measured off the 1672x941 render. */
  --scene-w: 1672;
  --scene-h: 941;

  /* Inner edge of the garage door frame — the hole the door fills. */
  --op-x: 7.0%;
  --op-y: 11.0%;
  --op-w: 86.0%;
  --op-h: 83.6%;

  --door-face: #cfccc2;
  --hud: #efece4;
}

html.garage,
html.garage body {
  height: 100%;
  overflow: hidden;
  margin: 0;
  overscroll-behavior: none;
}

html.garage body {
  background: #0c0b09;
  font-family: var(--mono);
  color: var(--hud);
}

/* --- stage & scene ------------------------------------------ */

.stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
}

/* Fit the whole render, letterboxed. The composition is framed on purpose —
   cropping it would cut the ivy border and can push hotspots off-screen on
   extreme aspect ratios. The dark surround reads as a frame. */
.scene {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(100vw, calc(100svh * var(--scene-w) / var(--scene-h)));
  height: min(100svh, calc(100vw * var(--scene-h) / var(--scene-w)));
}

/* Separate node so the engine shake doesn't fight .scene's centring.
   `isolation` matters: the sunlight and lamp overlays use mix-blend-mode, and
   without an explicit group here they can blend against the near-black page
   background instead of the renders, which paints the whole scene black. */
.shake {
  position: absolute;
  inset: 0;
  isolation: isolate;
  transform-origin: 50% 60%;
}

.layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 900ms ease;
}
.layer img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: fill;
  user-select: none;
  -webkit-user-drag: none;
}
.layer[data-layer='day'] { opacity: 1; }          /* base plate, always on */
.layer.is-on { opacity: 1; }

/* Headlight fades faster than the sky does. */
.layer[data-layer$='moto'] { transition-duration: 260ms; }


/* --- hotspots & outlines ------------------------------------
   The hit area is the object's silhouette, not a rectangle: JS sets each
   button's clip-path from the same polygon that draws its outline. Hovering
   the wall beside the bike does nothing.
   ------------------------------------------------------------ */

.hotspot {
  position: absolute;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  z-index: 4;
  -webkit-tap-highlight-color: transparent;
}
/* The traced outline is the focus indicator — a box here would defeat the point. */
.hotspot:focus { outline: none; }

.outlines {
  position: absolute;
  inset: 0;
  /* Without explicit dimensions the svg takes its 1:1 viewBox as an intrinsic
     ratio and renders square, which throws every y coordinate off. */
  width: 100%;
  height: 100%;
  z-index: 3;
  pointer-events: none;
  overflow: visible;
}

.outline {
  fill: none;
  stroke: var(--clay);
  stroke-width: 2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  opacity: 0;
  transition: opacity 220ms ease;
  filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.6));
}
.outline.is-on { opacity: 1; }

.labels {
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
}

.olabel {
  position: absolute;
  transform: translate(-50%, calc(-100% - 10px));
  white-space: nowrap;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
  /* A tight halo for edge contrast against light walls, then progressively
     wider shadows that darken whatever the type happens to land on. */
  text-shadow:
    0 0 3px rgba(0, 0, 0, 0.95),
    0 1px 2px rgba(0, 0, 0, 0.95),
    0 2px 12px rgba(0, 0, 0, 0.9),
    0 0 44px rgba(0, 0, 0, 0.75);
  opacity: 0;
  transition: opacity 220ms ease, transform 220ms ease;
}
.olabel.is-on { opacity: 1; }
.olabel small {
  display: block;
  font-size: 0.8em;
  letter-spacing: 0.12em;
  opacity: 0.85;
  margin-top: 2px;
}

/* The guided tour speaks up. */
.olabel.is-big {
  font-size: clamp(18px, 2.2vw, 32px);
  font-weight: 700;
  letter-spacing: 0.15em;
  text-shadow:
    0 0 4px rgba(0, 0, 0, 0.95),
    0 2px 4px rgba(0, 0, 0, 0.95),
    0 4px 22px rgba(0, 0, 0, 0.9),
    0 0 70px rgba(0, 0, 0, 0.7);
}
.olabel.is-big small { font-size: 0.44em; margin-top: 0.4em; }


/* --- tour arrows --------------------------------------------- */

.arrows {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 5;
  pointer-events: none;
  overflow: visible;
}

.arrow {
  fill: none;
  stroke: #fff;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.9));
  opacity: 0;
  transition: opacity 380ms ease;
}
.arrow.is-on { opacity: 1; }
.arrow.is-out { opacity: 0; }

@media (max-width: 40rem) {
  .olabel { font-size: 10px; }
  .olabel.is-big { font-size: clamp(12px, 3.6vw, 17px); letter-spacing: 0.12em; }
  .arrow { stroke-width: 2; }
}


/* --- coffee steam ------------------------------------------- */

.steam {
  position: absolute;
  left: 25.3%;
  top: 49.5%;
  width: 3.8%;
  height: 11.5%;
  pointer-events: none;
  opacity: 1;
}
.steam i {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 46%;
  height: 26%;
  margin-left: -23%;
  border-radius: 50%;
  background: #fffdf7;
  filter: blur(6px);
  opacity: 0;
  animation: steam 5.2s ease-in-out infinite;
}
.steam i:nth-child(2) { animation-delay: 1.7s; }
.steam i:nth-child(3) { animation-delay: 3.4s; }

@keyframes steam {
  0%   { transform: translate(0, 0) scale(0.5); opacity: 0; }
  20%  { opacity: 0.62; }
  60%  { transform: translate(-40%, -190%) scale(1.7); opacity: 0.34; }
  100% { transform: translate(32%, -340%) scale(2.4); opacity: 0; }
}


/* --- now playing chip --------------------------------------- */

.nowplaying {
  position: absolute;
  /* Below the shelf, clear of the hover label that pops above the hotspot. */
  left: 25%;
  top: 49.5%;
  transform: translate(-50%, 0) scale(0.9);
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 9px;
  background: rgba(14, 13, 11, 0.82);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 3px;
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  opacity: 0;
  pointer-events: none;
  transition: opacity 300ms ease, transform 300ms ease;
}
.nowplaying.is-on { opacity: 1; transform: translate(-50%, 0) scale(1); }

.eq { display: flex; align-items: flex-end; gap: 2px; height: 11px; }
.eq i {
  width: 2px;
  background: var(--clay);
  animation: eq 900ms ease-in-out infinite;
  /* Paused unless the card is actually up. It hides itself a few seconds after
     a track starts, and an animation running behind `opacity: 0` for the rest
     of the side is a compositor waking up for nothing. Play state rather than
     the `animation` shorthand, which would reset the per-bar delays below and
     march all four in step. */
  animation-play-state: paused;
}
.nowplaying.is-on .eq i { animation-play-state: running; }
.eq i:nth-child(1) { height: 40%; animation-delay: -200ms; }
.eq i:nth-child(2) { height: 90%; animation-delay: -600ms; }
.eq i:nth-child(3) { height: 60%; animation-delay: -400ms; }
.eq i:nth-child(4) { height: 100%; animation-delay: -800ms; }

@keyframes eq { 0%, 100% { transform: scaleY(0.35); } 50% { transform: scaleY(1); } }



/* --- ambient life -------------------------------------------
   Two cheap loops, neither of which touches the four big renders:
   sun drifting across the room, and the lantern breathing at night.
   ------------------------------------------------------------ */

.sunlight {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(120% 90% at 42% 16%, rgba(255, 214, 150, 0.55), transparent 62%);
  mix-blend-mode: soft-light;
  opacity: 0.1;
  animation: sunlight 19s ease-in-out infinite;
}
@keyframes sunlight {
  0%, 100% { opacity: 0.08; }
  50%      { opacity: 0.32; }
}

.lampglow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(27% 35% at 49.4% 24%, rgba(255, 203, 138, 0.42), transparent 72%);
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 900ms ease;
}

.is-night .sunlight { animation: none; opacity: 0; }
/* A dark room makes the wisps read as a glowing orb unless they come down. */
.is-night .steam { opacity: 0.45; }
.is-night .lampglow { animation: lamp 7.5s ease-in-out infinite; }

/* A wide, soft pool rather than a bright core, so it is felt more than seen —
   but with enough swing that it visibly breathes. */
@keyframes lamp {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 0.8; }
}


/* --- close the door ------------------------------------------ */

.door-close {
  position: absolute;
  left: 50%;
  top: 3.4%;
  transform: translateX(-50%);
  z-index: 6;
  width: 2.6%;
  min-width: 22px;
  height: 2.6%;
  min-height: 12px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transition: opacity 400ms ease 500ms, visibility 400ms 500ms;
}
.is-open .door-close { opacity: 0.16; visibility: visible; }
.door-close:hover { opacity: 0.6; }
.door-close svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: #fff;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- engine shake ------------------------------------------- */

.shake.is-cranking { animation: crank 1.5s ease-out; }

@keyframes crank {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg); }
  6%   { transform: translate3d(-2.5px, 1.5px, 0) rotate(0.08deg); }
  14%  { transform: translate3d(2px, -2px, 0) rotate(-0.07deg); }
  24%  { transform: translate3d(-2px, -1px, 0) rotate(0.06deg); }
  38%  { transform: translate3d(1.5px, 1.5px, 0) rotate(-0.05deg); }
  54%  { transform: translate3d(-1px, -1px, 0) rotate(0.03deg); }
  72%  { transform: translate3d(0.8px, 0.6px, 0) rotate(-0.02deg); }
  100% { transform: translate3d(0, 0, 0) rotate(0deg); }
}

/* A much smaller, endless tremble while it idles. */
.shake.is-idling { animation: idle 260ms linear infinite; }

@keyframes idle {
  0%, 100% { transform: translate3d(0, 0, 0); }
  25%      { transform: translate3d(0.45px, -0.35px, 0); }
  50%      { transform: translate3d(-0.3px, 0.4px, 0); }
  75%      { transform: translate3d(0.25px, 0.3px, 0); }
}


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

.doorway {
  position: absolute;
  left: var(--op-x);
  top: var(--op-y);
  width: var(--op-w);
  height: var(--op-h);
  overflow: hidden;
  z-index: 5;
  /* Purely scenery — it must never intercept clicks meant for the room,
     including while it is still sliding up. */
  pointer-events: none;
}

.door {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  transform: translateY(0);
  transition: transform 2000ms cubic-bezier(0.5, 0.02, 0.2, 1);
  will-change: transform;
}
.is-open .door { transform: translateY(-100.5%); }

.door__panel {
  position: relative;
  flex: 1;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--door-face) 100%, #fff 8%) 0%,
      var(--door-face) 38%,
      color-mix(in srgb, var(--door-face) 88%, #000 12%) 100%);
  border-bottom: 2px solid rgba(0, 0, 0, 0.34);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
}
.door__panel:last-child { border-bottom: 0; }

/* Faint pressed ribs, the way a real sectional panel is stamped. */
.door__panel::before {
  content: '';
  position: absolute;
  inset: 14% 3%;
  border: 1px solid rgba(0, 0, 0, 0.07);
  border-radius: 2px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}

/* Top panel carries the classic row of lights. */
.door__lights {
  position: absolute;
  inset: 22% 8%;
  display: flex;
  gap: 4%;
}
.door__lights span {
  flex: 1;
  border-radius: 2px;
  background: linear-gradient(160deg, #6f6a5c, #322f28 60%, #45403a);
  box-shadow:
    inset 0 0 0 2px rgba(255, 255, 255, 0.35),
    inset 0 6px 14px rgba(0, 0, 0, 0.5);
}

.door__handle {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 8%;
  height: 12%;
  min-height: 8px;
  transform: translate(-50%, -50%);
  border-radius: 2px;
  background: linear-gradient(180deg, #6d6858, #413d34);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

/* Panels darken as they slide up into the ceiling track. */
.doorway::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 22%;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.62), transparent);
  z-index: 2;
}


/* --- leaving through an object ------------------------------
   The photograph is wiped away by a soft diagonal sweep while the ink is wiped
   in behind it, so the room appears to be drawn rather than cross-faded. Then
   the sheet turns and the next page is already on paper underneath.
   ------------------------------------------------------------ */

/* Nothing behind the photograph. There used to be a `.paperplate` in here — a
   second sheet in the reading pages' paper, inside the scene, fading in over
   200ms while `.nextsheet` took 900ms to do the same to the letterbox around it.
   Two sheets in one colour on two schedules is what put a hard rectangle around
   the room every time you left it in light mode: for most of a second the scene
   was already paper and the surround was still nearly black, and the boundary
   between them — invisible at rest, because the ivy down each edge of the render
   is as dark as the letterbox it sits in — became the brightest edge on screen.

   So there is one sheet now, `.nextsheet`, behind everything and covering the
   whole viewport. As the photograph masks away, what shows through inside the
   scene is the same pixels as the surround, at the same moment, by construction:
   no arithmetic to keep in step, and nothing to line up. The only edge left is
   the mask's own gradient, which is a 25%-wide ramp and soft by design. */
.photo { position: absolute; inset: 0; }

.linework {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
}

@media (prefers-color-scheme: dark) {
  /* Black ink is invisible on dark paper — invert it into a light pencil. */
  .linework { filter: invert(1); }
}

/* A soft diagonal band, three times the width of the scene. Sliding its
   position is what makes the drawing arrive gradually — gradient stops can't be
   animated, but mask-position can.

   The sizing matters: the opaque run is 40% of a 300% image, i.e. 120% of the
   scene, so at position 0% the whole scene sits inside it and nothing is masked
   at rest. At 100% the scene maps past the 65% stop, so nothing shows at all.
   Get this wrong and the right-hand edge of the room is quietly faded the whole
   time the page is just sitting there. */
.photo,
.linework {
  -webkit-mask-image: linear-gradient(100deg, #000 0 40%, rgba(0, 0, 0, 0) 65%);
  mask-image: linear-gradient(100deg, #000 0 40%, rgba(0, 0, 0, 0) 65%);
  -webkit-mask-size: 300% 300%;
  mask-size: 300% 300%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.photo { -webkit-mask-position: 0% 50%; mask-position: 0% 50%; }          /* fully shown */
.linework { -webkit-mask-position: 100% 50%; mask-position: 100% 50%; }   /* fully hidden */

html.is-departing .linework {
  opacity: 1;
  -webkit-mask-position: 0% 50%;
  mask-position: 0% 50%;
  transition: -webkit-mask-position 1150ms cubic-bezier(0.4, 0.05, 0.35, 1),
              mask-position 1150ms cubic-bezier(0.4, 0.05, 0.35, 1),
              opacity 200ms ease;
}
html.is-departing .photo {
  -webkit-mask-position: 100% 50%;
  mask-position: 100% 50%;
  transition: -webkit-mask-position 1150ms cubic-bezier(0.4, 0.05, 0.35, 1),
              mask-position 1150ms cubic-bezier(0.4, 0.05, 0.35, 1);
}

/* Everything that isn't the room gets out of the way immediately. */
html.is-departing .sunlight,
html.is-departing .lampglow,
html.is-departing .steam,
html.is-departing .nowplaying,
html.is-departing .outlines,
html.is-departing .arrows,
html.is-departing .labels,
html.is-departing .hud,
html.is-departing .door-close,
html.is-departing .doorway { opacity: 0; transition: opacity 220ms ease; }

/* Then the page actually turns. The sheet swings about its own left edge, far
   enough to go nearly edge-on, uncovering a blank sheet in the reading pages'
   paper. By the time the navigation fires the viewport is already that paper,
   so the document swap has nothing visible to give away — the blog just fades
   its words onto the page that was already turned to. */
.nextsheet {
  position: fixed;
  inset: 0;
  background: var(--paper);
  opacity: 0;
  pointer-events: none;
  /* Quick but not a snap. This is now the sheet the drawing lands on as well as
     the letterbox around it, so it wants to be paper early — ink on a sheet
     still halfway to black is ink nobody can read — while still taking long
     enough that the surround is seen to brighten rather than to cut. Nothing
     lines up against it any more, so the duration is free to be chosen for how
     it reads instead of for what it has to match. */
  transition: opacity 700ms cubic-bezier(0.2, 0.75, 0.3, 1);
}
.nextsheet::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image: var(--grain);
  opacity: 0.07;
}

html.is-departing .nextsheet { opacity: 1; }

/* The finished drawing is lifted away rather than swung like a book cover: it
   drifts up a little and fades, uncovering the blank sheet underneath. A 3D
   turn read as a page-flip gimmick; this reads as setting a sheet aside. */
html.is-flipping .scene {
  transform: translate(-50%, -50%) translateY(-2.5%);
  opacity: 0;
  transition: transform 640ms cubic-bezier(0.32, 0, 0.22, 1),
              opacity 540ms ease 40ms;
}

@media (prefers-reduced-motion: reduce) {
  html.is-departing .linework,
  html.is-departing .photo { transition: none; }
  html.is-flipping .scene { transition: opacity 200ms ease; transform: translate(-50%, -50%); opacity: 0; }
  html.is-departing .nextsheet { transition: none; }
}


/* --- intro overlay ------------------------------------------ */

.intro {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: grid;
  place-content: center;
  /* Shifts the centred block down so the name lands mid-panel instead of
     across the seam between two door sections. */
  padding-top: 15svh;
  gap: 1.25rem;
  justify-items: center;
  text-align: center;
  transition: opacity 700ms ease, visibility 700ms;
}
.is-open .intro { opacity: 0; visibility: hidden; }

.intro__name {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: #24221c;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
}

.open-btn {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #16150f;
  background: var(--hud);
  border: 1px solid #16150f;
  padding: 0.85rem 1.6rem;
  cursor: pointer;
  border-radius: 2px;
  box-shadow: 4px 4px 0 rgba(20, 19, 15, 0.5);
  transition: transform 120ms ease, box-shadow 120ms ease, background-color 120ms ease;
}
.open-btn:hover {
  background: #fff;
  transform: translate(1px, 1px);
  box-shadow: 3px 3px 0 rgba(20, 19, 15, 0.5);
}
.open-btn:active { transform: translate(4px, 4px); box-shadow: 0 0 0 rgba(0, 0, 0, 0); }


/* --- hud ---------------------------------------------------- */

/* Reuse the site's paper grain over the photo so the garage and the
   writing pages feel like one surface. Sits above the scene, below the HUD. */
html.garage body::before {
  z-index: 7;
  opacity: 0.05;
  mix-blend-mode: overlay;
}

.hud {
  position: fixed;
  z-index: 8;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  pointer-events: none;
  opacity: 0;
  transition: opacity 600ms ease 400ms;
}
.is-open .hud { opacity: 1; }
.hud > * { pointer-events: auto; }

.hud a,
.hud button {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hud);
  background: rgba(12, 11, 9, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.2);
  padding: 6px 10px;
  border-radius: 2px;
  text-decoration: none;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: background-color 140ms ease, border-color 140ms ease;
}
.hud a:hover,
.hud button:hover {
  background: rgba(12, 11, 9, 0.8);
  border-color: color-mix(in srgb, var(--clay) 80%, transparent);
}

.hud__nav { display: flex; gap: 6px; flex-wrap: wrap; }
.hud__right { display: flex; gap: 6px; align-items: center; }

/* Type, not a control: same size and tracking as the buttons beside it, but no
   box and no hover, so it reads as a caption on the room rather than something
   else to press. */
.hud__hint {
  margin: 0;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(239, 236, 228, 0.5);
  padding: 6px 2px;
}
/* Which word, on the same test garage.js makes and written the same way on
   purpose: a laptop with a touchscreen reports `hover: hover` and a coarse
   pointer both, and if the two disagreed this would say "hover the objects" and
   then ask permission anyway. */
.hud__hint--hover, .hud__hint--tap { display: none; }
@media (hover: hover) and (pointer: fine) {
  .hud__hint--hover { display: inline; }
}
@media not all and (hover: hover) and (pointer: fine) {
  .hud__hint--tap { display: inline; }
}

/* --- mute ---------------------------------------------------- */

/* Doubled to beat `.hud button`'s padding, which is set for a word rather than a
   glyph and leaves the speaker floating in a wide box. */
.hud__mute,
.hud .hud__mute {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 5px 7px;
  line-height: 0;
}
.hud__mute svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.hud__mute-body { fill: currentColor; stroke-width: 1; }

/* Muted is the stroke arriving and the waves leaving. Drawing the slash on with
   a dash offset rather than swapping opacity means it reads as a pen going
   through the speaker, which is what the gesture is. */
.hud__mute-slash {
  stroke-dasharray: 20;
  stroke-dashoffset: 20;
  transition: stroke-dashoffset 180ms ease;
}
.hud__mute-wave { transition: opacity 140ms ease; }

.hud__mute[aria-pressed='true'] .hud__mute-slash { stroke-dashoffset: 0; }
.hud__mute[aria-pressed='true'] .hud__mute-wave { opacity: 0; }

/* --- leaving, on a touch screen ------------------------------ */

.leaveask {
  position: fixed;
  z-index: 12;
  left: 50%;
  bottom: 4.5rem;
  transform: translateX(-50%);
  width: min(19rem, calc(100vw - 2.5rem));
  padding: 0.9rem 1rem 0.8rem;
  text-align: center;
  background: rgba(12, 11, 9, 0.88);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 3px;
  backdrop-filter: blur(6px);
}
.leaveask[hidden] { display: none; }

.leaveask__text {
  margin: 0 0 0.7rem;
  font-size: 11px;
  line-height: 1.5;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--hud);
}

.leaveask__row { display: flex; gap: 6px; justify-content: center; }

/* Same chrome as the HUD's own buttons — it is the same panel, just asking. */
.leaveask button {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hud);
  background: rgba(12, 11, 9, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.2);
  padding: 7px 14px;
  border-radius: 2px;
  cursor: pointer;
}
.leaveask button[data-leave-go] {
  border-color: color-mix(in srgb, var(--clay) 85%, transparent);
  color: #fff;
}

html.is-departing .leaveask { opacity: 0; transition: opacity 220ms ease; }

/* --- portrait ------------------------------------------------ */

.portrait-head { display: none; }

@media (max-width: 40rem), (orientation: portrait) {
  .hud {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    /* Sit in the band below the scene rather than at the screen edge. */
    bottom: auto;
    top: calc(50% + (100vw * var(--scene-h) / var(--scene-w)) / 2 + 1.1rem);
  }
  .hud__right { flex-wrap: wrap; justify-content: center; }
  
  .portrait-head {
    display: block;
    position: fixed;
    z-index: 8;
    left: 0;
    right: 0;
    bottom: calc(50% + (100vw * var(--scene-h) / var(--scene-w)) / 2 + 1.1rem);
    text-align: center;
    padding: 0 1.25rem;
    opacity: 0;
    transition: opacity 600ms ease 400ms;
  }
  .is-open .portrait-head { opacity: 1; }

  .portrait-head__name {
    margin: 0 0 0.5rem;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--hud);
  }
  .portrait-head__note {
    margin: 0;
    font-size: 10px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: rgba(239, 236, 228, 0.55);
  }

  /* Where the note above the scene is showing, it has already said this, and the
     bar is the crowded end of the screen. The hint stays in the HUD everywhere
     else — including a landscape phone, which gets no note because there is no
     band above the scene to put one in. */
  .hud__hint { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .door { transition-duration: 600ms; }
  .shake.is-cranking,
  .shake.is-idling { animation: none; }
  .steam i { animation: none; opacity: 0.25; }
}
