:root {
  --bg: #0b0d12;
  --card: #141823;
  --line: #232a38;
  --track: #1c2230;
  --text: #e8eaf0;
  --muted: #8a93a6;

  --limb: #eef0f5;
  --limb-dim: #a7aebd;
  --face: #1a1d26;
  --chair: #3a4152;
  --wood: #7a5a40;
  --wood-dark: #553d2b;

  --green: #3ddc84;
  --yellow: #ffd166;
  --red: #ff4d5a;

  --heat: 0; /* 0–1, set from script.js */
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

body {
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: 32px 16px;
  overflow-x: hidden;
  position: relative;
  background: radial-gradient(1100px 600px at 50% -10%, #182033 0%, var(--bg) 60%);
}

/* red vignette that grows with the reading */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background: radial-gradient(ellipse at center, transparent 40%, rgba(255, 50, 65, .6) 100%);
  opacity: calc(var(--heat) * var(--heat) * .9);
  transition: opacity .25s linear;
}

.wrap {
  width: 100%;
  max-width: 640px;
  position: relative;
  z-index: 2;
}

.hdr { text-align: center; margin-bottom: 20px; }

.title {
  margin: 0;
  font-size: clamp(30px, 6vw, 42px);
  font-weight: 800;
  letter-spacing: -.02em;
  position: relative;
}

.sub { margin: 4px 0 0; color: var(--muted); }

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 20px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, .45);
}

.stage {
  width: 100%;
  aspect-ratio: 480 / 300;
  will-change: transform;
}

#scene { width: 100%; height: 100%; display: block; overflow: visible; }

/* --- scene styling --- */
.chair rect { fill: var(--chair); }

.limb {
  fill: none;
  stroke: var(--limb);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.limb.dim { stroke: var(--limb-dim); }
.hand { fill: var(--limb); }
.hand.dim { fill: var(--limb-dim); }

.eye { fill: var(--face); stroke: var(--face); stroke-width: 1.6; }
.brow { stroke: var(--face); stroke-width: 2.4; stroke-linecap: round; }
.mouth { fill: var(--face); }

.sweat path { fill: #7cc8ff; }

.table-shadow { fill: #07090e; }
.table-dust circle { fill: #8a93a6; }
.table rect { fill: var(--wood-dark); }
.table .top { fill: var(--wood); }

.keyboard path { fill: #a9b2c3; }
.keyboard .keys { fill: none; stroke: #6d778a; stroke-width: 1.6; stroke-linecap: round; }
.monitor rect, .monitor path { fill: #4a5264; }
.monitor .screen { fill: #2f5f94; }
.monitor .screen-hot { fill: #6e2230; }
.monitor .code {
  fill: none;
  stroke: #8ed0ff;
  stroke-width: 1.4;
  stroke-linecap: round;
  opacity: .82;
  animation: codePulse 2.8s ease-in-out infinite alternate;
}
.monitor .code-err {
  fill: none;
  stroke: #ff6b76;
  stroke-width: 1.5;
  stroke-linecap: round;
}
@keyframes codePulse { from { opacity: .68; } to { opacity: .95; } }
.mug rect { fill: #8ed0ff; }
.mug path { fill: none; stroke: #8ed0ff; stroke-width: 2.4; stroke-linecap: round; }
.mug .coffee-spill path,
.mug .coffee-spill circle { fill: #6f432a; stroke: none; }
.mug .steam {
  stroke: #7f8aa3;
  stroke-width: 1.6;
  opacity: .7;
  transform-box: fill-box;
  transform-origin: bottom center;
  animation: steamRise 2.6s ease-in-out infinite alternate;
}
@keyframes steamRise {
  from { transform: translateY(0) scaleX(.95); opacity: .52; }
  to   { transform: translateY(-2.2px) scaleX(1.06); opacity: .82; }
}

/* --- meter --- */
.meter { margin-top: 18px; }

.meter-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 12px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .1em;
}

.meter-pct {
  font-variant-numeric: tabular-nums;
  font-size: 24px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: -.01em;
}

.track {
  position: relative;
  height: 14px;
  margin-top: 8px;
  border-radius: 999px;
  overflow: hidden;
  background: linear-gradient(90deg, var(--green), var(--yellow) 55%, var(--red));
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .06),
    0 0 calc(var(--heat) * 26px) rgba(255, 77, 90, calc(var(--heat) * .65));
  transition: box-shadow .25s linear;
}

.cover {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 100%;
  background: var(--track);
}

.status {
  margin: 10px 0 0;
  min-height: 1.5em;
  color: var(--muted);
  font-size: 14px;
}

.foot {
  margin-top: 18px;
  text-align: center;
  color: var(--muted);
  font-size: 12px;
  opacity: .7;
}

/* --- overlays --- */
#flash {
  position: fixed;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
  z-index: 4;
}
#flash.go { animation: flash .55s ease-out forwards; }
@keyframes flash { from { opacity: 1; } to { opacity: 0; } }

#fx {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  overflow: hidden;
}

.confetti, .debris {
  position: absolute;
  left: 0; top: 0;
  will-change: transform, opacity;
  animation: burst var(--dur, 1.8s) cubic-bezier(.15, .7, .3, 1) forwards;
}
.confetti { width: 10px; height: 10px; border-radius: 2px; }
.debris {
  font-weight: 800;
  font-size: 28px;
  color: #fff;
  white-space: nowrap;
  text-shadow: 0 2px 14px rgba(0, 0, 0, .6);
}

@keyframes burst {
  0%   { transform: translate(var(--x), var(--y)) rotate(0deg); opacity: 1; }
  55%  { transform: translate(calc(var(--x) + var(--dx)), calc(var(--y) + var(--dy))) rotate(var(--rot)); opacity: 1; }
  100% { transform: translate(calc(var(--x) + var(--dx) * 1.25), calc(var(--y) + var(--dy) + var(--fall))) rotate(calc(var(--rot) * 1.7)); opacity: 0; }
}

/* --- 100%: everything goes wrong --- */
.flipped .wrap { animation: shake .09s infinite; }
@keyframes shake {
  0%   { transform: translate(0, 0) rotate(0); }
  25%  { transform: translate(-9px, 6px) rotate(-1.2deg); }
  50%  { transform: translate(8px, -7px) rotate(1deg); }
  75%  { transform: translate(-6px, -8px) rotate(.8deg); }
  100% { transform: translate(7px, 7px) rotate(-1deg); }
}

.flipped .card {
  animation: rave .5s linear infinite;
  border-color: var(--red);
}
@keyframes rave { to { filter: hue-rotate(360deg); } }

.flipped .title { animation: glitch .22s steps(1) infinite; }
.flipped .title::before,
.flipped .title::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  opacity: .8;
  mix-blend-mode: screen;
}
.flipped .title::before { color: #ff2d55; transform: translate(-3px, -2px); clip-path: inset(10% 0 55% 0); animation: glitch-a .3s steps(1) infinite; }
.flipped .title::after  { color: #2df5ff; transform: translate(3px, 2px);  clip-path: inset(60% 0 5% 0);  animation: glitch-b .27s steps(1) infinite; }
@keyframes glitch   { 0% { transform: translate(0); } 50% { transform: translate(-2px, 1px) skewX(-6deg); } 100% { transform: translate(2px, -1px) skewX(4deg); } }
@keyframes glitch-a { 0% { clip-path: inset(10% 0 55% 0); } 50% { clip-path: inset(40% 0 20% 0); } 100% { clip-path: inset(0 0 80% 0); } }
@keyframes glitch-b { 0% { clip-path: inset(60% 0 5% 0); } 50% { clip-path: inset(20% 0 60% 0); } 100% { clip-path: inset(75% 0 0 0); } }

.flipped .status {
  color: var(--red);
  font-weight: 800;
  font-size: 18px;
  animation: blink .18s steps(1) infinite;
}
@keyframes blink { 50% { color: #fff; } }

.flipped .meter-pct { animation: blink .18s steps(1) infinite; }

@media (prefers-reduced-motion: reduce) {
  .flipped .wrap, .flipped .card, .flipped .title, .flipped .title::before, .flipped .title::after,
  .flipped .status, .flipped .meter-pct, .monitor .code, .mug .steam { animation: none; }
  .confetti, .debris { display: none; }
}

/* --- long-form pages (e.g. /legal/) --- */
.prose .card { padding: 28px; }
.prose .hdr { margin-bottom: 24px; }
.prose h2 { font-size: 20px; margin: 28px 0 8px; letter-spacing: -.01em; }
.prose h2:first-child { margin-top: 0; }
.prose h3 { font-size: 15px; margin: 18px 0 4px; color: var(--muted); text-transform: uppercase; letter-spacing: .08em; }
.prose p, .prose li { color: #c9ceda; font-size: 15px; line-height: 1.65; }
.prose p { margin: 8px 0; }
.prose ul { padding-left: 20px; margin: 8px 0; }
.prose li { margin: 6px 0; }
.prose a { color: #8ed0ff; }
.prose .cmd { margin: 10px 0; padding: 12px 14px; border-radius: 10px; background: #0b0d12; border: 1px solid var(--line); overflow-x: auto; }
.prose .cmd code { font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: #e8eaf0; white-space: pre; }
.prose .btn { font: inherit; font-size: 14px; padding: 8px 14px; border-radius: 8px; border: 1px solid var(--line); background: #1c2230; color: var(--text); cursor: pointer; }
.prose .btn:hover { border-color: #8ed0ff; }
.prose .muted { color: var(--muted); font-size: 13px; }
.notfound { text-align: center; }
.notfound .flip { font-size: clamp(28px, 7vw, 44px); font-weight: 800; color: var(--red); margin: 8px 0 12px; letter-spacing: -.02em; }
.notfound h2 { margin-top: 0; }
.notfound .btn { display: inline-block; text-decoration: none; margin-top: 6px; }

/* --- Konami walk (journey mode) and the void --- */
/* The stage is a window into the world: everything in it is clipped to the
   viewBox at all times, or the room's floor and far wall (world x up to 600)
   spill out past the card. The overlays that should be able to leave the
   stage — the speech bubble, and the confetti and debris in #fx — are not
   inside #viewport. */
#viewport { clip-path: url(#stageClip); }
.kiss { fill: #ff5d8f; }
.kiss .sparkle { fill: #ffd166; }
.smile { fill: none; stroke: var(--face); stroke-width: 2.2; stroke-linecap: round; }
.bubble path { fill: #f4f6fb; stroke: #c9ceda; stroke-width: 1.5; }
.bubble text { font: 700 14px system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; fill: #1a1d26; }

/* --- outdoor scenery (Konami walk) --- */
/* Belt and braces with the clip above: keep the outdoors (world x 600+) out of
   the scene entirely until the walk starts; script.js puts .journey on <body> for the walk and
   .void for the void, which shares the garden. */
body:not(.journey):not(.void) .outdoors,
body:not(.journey):not(.void) .building { display: none; }

/* --- the void (the anticlockwise-corners egg) ---
   The same garden, drained of colour under an overcast sky: no sun, no fish;
   the waterfall and its cliffs are hidden where the mountain he climbs would
   cover them. The mountain, the trees and the drop are the .void-art groups. */
body:not(.void) .void-art { display: none; }
/* (the colour is drained by a tinted overlay in #voidFront, not a filter: a
   filter over the whole garden costs a long frame whenever it reappears) */
body.void .outdoors .sky, body.void .outdoors .sun, body.void .outdoors .stream,
body.void .outdoors .ground, body.void .outdoors .path,
body.void .outdoors .waterfall, body.void .outdoors .cliffs,
body.void .outdoors .trees-sunny, body.void .outdoors-over,
body.void .building .sun-spill { display: none; }
.void-art .mist ellipse {
  transform-box: fill-box;
  transform-origin: center;
  opacity: .1;
  animation: voidMist 9s ease-in-out infinite;
}
.void-art .mist ellipse:nth-child(2) { animation-delay: -3s; animation-duration: 11s; }
.void-art .mist ellipse:nth-child(3) { animation-delay: -6s; animation-duration: 8s; }
.void-art .mist ellipse:nth-child(4) { animation-delay: -4.5s; animation-duration: 12s; }
.void-art .mist ellipse:nth-child(5) { animation-delay: -1.8s; animation-duration: 10s; }
.void-art .mist ellipse:nth-child(6) { animation-delay: -7.2s; animation-duration: 13s; }
@keyframes voidMist {
  0%, 100% { transform: translateX(-10px) scale(.9); opacity: .06; }
  50%      { transform: translateX(12px) scale(1.1); opacity: .14; }
}
.void-art .rain-splashes ellipse {
  transform-box: fill-box;
  transform-origin: center;
  animation: rainSplash 1.1s ease-out infinite;
}
.void-art .rain-splashes ellipse:nth-child(3n+1) { animation-delay: -.35s; animation-duration: .95s; }
.void-art .rain-splashes ellipse:nth-child(3n+2) { animation-delay: -.7s; animation-duration: 1.25s; }
@keyframes rainSplash {
  0%   { transform: scale(.3); opacity: .85; }
  100% { transform: scale(1.45); opacity: 0; }
}
/* the rain (#rain, in screen coordinates): streaks sliding down and a little
   to the left; staggered so the sheet never pulses */
.rain line {
  stroke: #b4bccb;
  stroke-width: 1.2;
  stroke-linecap: round;
  opacity: .32;
  animation: rainFall .75s linear infinite;
}
.rain line:nth-child(5n+1) { animation-delay: -.15s; }
.rain line:nth-child(5n+2) { animation-delay: -.3s; }
.rain line:nth-child(5n+3) { animation-delay: -.45s; }
.rain line:nth-child(5n+4) { animation-delay: -.6s; }
.rain line:nth-child(3n) { animation-duration: .95s; stroke-width: .85; opacity: .2; }
.rain line:nth-child(4n) { animation-duration: .62s; stroke-width: 1.55; opacity: .42; }
@keyframes rainFall { to { transform: translate(-54px, 330px); } }
@media (prefers-reduced-motion: reduce) {
  .void-art .mist ellipse, .void-art .rain-splashes ellipse, .rain line { animation: none; }
}

.outdoors .sun-rays {
  transform-box: fill-box;
  transform-origin: center;
  animation: sunRays 48s linear infinite;
}
@keyframes sunRays { to { transform: rotate(360deg); } }

.outdoors .cloud { animation: cloudDrift 16s ease-in-out infinite alternate; }
.outdoors .cloud:nth-child(2) { animation-duration: 21s; animation-delay: -7s; }
.outdoors .cloud:nth-child(3) { animation-duration: 13s; animation-delay: -3s; }
.outdoors .cloud:nth-child(4) { animation-duration: 24s; animation-delay: -11s; }
.outdoors .cloud:nth-child(5) { animation-duration: 19s; animation-delay: -5s; }
.outdoors .cloud:nth-child(6) { animation-duration: 27s; animation-delay: -14s; }
@keyframes cloudDrift { from { transform: translateX(-8px); } to { transform: translateX(10px); } }

/* dash patterns all repeat every 44 units; a growing offset slides them toward
   the polyline's start (leftwards = downhill, away from the waterfall pool) */
.outdoors .flow polyline { animation: streamFlow 2.4s linear infinite; }
.outdoors .flow polyline:nth-child(2) { animation-duration: 3.1s; }
.outdoors .flow polyline:nth-child(3) { animation-duration: 1.9s; }
@keyframes streamFlow { to { stroke-dashoffset: 44; } }

/* pattern repeats every 32 units; a shrinking offset slides it toward the line's end (down) */
.outdoors .fall line { animation: waterfallFlow 1.1s linear infinite; }
.outdoors .fall line:nth-child(2) { animation-duration: .85s; animation-delay: -.3s; }
.outdoors .fall line:nth-child(3) { animation-duration: 1.3s; animation-delay: -.6s; }
.outdoors .fall line:nth-child(4) { animation-duration: .95s; animation-delay: -.45s; }
.outdoors .fall line:nth-child(5) { animation-duration: 1.2s; animation-delay: -.75s; }
.outdoors .fall line:nth-child(6) { animation-duration: 1.05s; animation-delay: -.2s; }
@keyframes waterfallFlow { to { stroke-dashoffset: -32; } }

.outdoors .pool .ripple {
  transform-box: fill-box;
  transform-origin: center;
  animation: poolRipple 3.2s ease-out infinite;
}
.outdoors .pool .ripple:nth-of-type(3) { animation-delay: -1.1s; animation-duration: 3.6s; }
.outdoors .pool .ripple:nth-of-type(4) { animation-delay: -2.2s; animation-duration: 2.9s; }
@keyframes poolRipple {
  0%   { transform: scale(.7); opacity: .75; }
  100% { transform: scale(1.22); opacity: 0; }
}

.outdoors .mist circle {
  transform-box: fill-box;
  transform-origin: center;
  opacity: .45;
  animation: mistPulse 2.8s ease-in-out infinite;
}
.outdoors .mist circle:nth-child(2) { animation-delay: -.9s; }
.outdoors .mist circle:nth-child(3) { animation-delay: -1.7s; }
.outdoors .mist circle:nth-child(4) { animation-delay: -2.3s; animation-duration: 2.2s; }
.outdoors .mist .mist-drift {
  animation: mistDrift 3.8s ease-out infinite;
}
.outdoors .mist .mist-drift:nth-child(6) { animation-delay: -1.3s; animation-duration: 4.3s; }
.outdoors .mist .mist-drift:nth-child(7) { animation-delay: -2.6s; animation-duration: 3.5s; }
@keyframes mistPulse {
  0%, 100% { transform: scale(.85); opacity: .3; }
  50%      { transform: scale(1.15); opacity: .6; }
}
@keyframes mistDrift {
  0%   { transform: translate(0, 4px) scale(.65); opacity: 0; }
  30%  { opacity: .45; }
  100% { transform: translate(-18px, -24px) scale(1.35); opacity: 0; }
}

.outdoors .garden, .outdoors .garden-front, .outdoors-over .reeds, .outdoors .bushes .ferns {
  transform-box: fill-box;
  transform-origin: bottom center;
  animation: breezeSway 4.8s ease-in-out infinite alternate;
}
.outdoors .garden-front { animation-duration: 3.9s; animation-delay: -1.7s; }
.outdoors-over .reeds { animation-duration: 3.4s; animation-delay: -2.5s; }
.outdoors .bushes .ferns { animation-duration: 4.2s; animation-delay: -1.1s; }
@keyframes breezeSway {
  from { transform: skewX(-1.8deg); }
  to   { transform: skewX(2.2deg); }
}

.outdoors .butterfly path {
  transform-box: fill-box;
  transform-origin: center;
  animation: butterflyFloat 3.6s ease-in-out infinite alternate;
}
.outdoors .butterfly:nth-of-type(15) path { animation-duration: 4.3s; animation-delay: -1.4s; }
.outdoors .butterfly:nth-of-type(16) path { animation-duration: 3.9s; animation-delay: -2.6s; }
@keyframes butterflyFloat {
  0%   { transform: translate(-5px, 2px) rotate(-8deg) scaleY(.82); }
  50%  { transform: translate(2px, -6px) rotate(4deg) scaleY(1.08); }
  100% { transform: translate(8px, -2px) rotate(10deg) scaleY(.78); }
}

/* jumping fish: each .fish group is placed on the stream surface by its transform
   attribute; the inner .fish-body leaps in a forward arc (nose up, then nose down)
   during the first ~16% of the cycle and is invisible ("underwater") the rest of
   the time. The .splash ring flashes where the fish re-enters. Per-fish period
   and phase come from --leap / --leap-delay so body and splash stay in sync. */
.outdoors .fish { --leap: 6.4s; --leap-delay: -1s; }
.outdoors .fish:nth-child(2) { --leap: 7.6s; --leap-delay: -4.1s; }
.outdoors .fish:nth-child(3) { --leap: 5.8s; --leap-delay: -2.7s; }
.outdoors .fish:nth-child(4) { --leap: 8.4s; --leap-delay: -6.3s; }
.outdoors .fish-body,
.outdoors .splash {
  transform-box: fill-box;
  transform-origin: center;
  opacity: 0;
  animation-duration: var(--leap);
  animation-delay: var(--leap-delay);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.outdoors .fish-body { animation-name: fishLeap; }
.outdoors .splash { animation-name: fishSplash; }
@keyframes fishLeap {
  0%   { opacity: 0; transform: translate(0, 2px) rotate(-40deg); }
  1.5% { opacity: 1; }
  4%   { transform: translate(6px, -9px) rotate(-32deg); }
  8%   { transform: translate(12px, -15px) rotate(0deg); }
  12%  { transform: translate(18px, -9px) rotate(34deg); }
  15%  { opacity: 1; transform: translate(23px, 0) rotate(46deg); }
  16.5%, 100% { opacity: 0; transform: translate(25px, 4px) rotate(50deg); }
}
@keyframes fishSplash {
  0%, 14% { opacity: 0; transform: scale(.3); }
  16%     { opacity: .85; transform: scale(.7); }
  24%, 100% { opacity: 0; transform: scale(1.7); }
}

@media (prefers-reduced-motion: reduce) {
  .outdoors .sun-rays, .outdoors .cloud, .outdoors .flow polyline,
  .outdoors .fall line, .outdoors .pool .ripple, .outdoors .mist circle,
  .outdoors .garden, .outdoors .garden-front, .outdoors-over .reeds,
  .outdoors .bushes .ferns, .outdoors .butterfly path,
  .outdoors .fish-body, .outdoors .splash { animation: none; }
  .outdoors .fish-body { opacity: 1; }   /* fish rest at the surface instead of leaping */
}

/* --- nightclub lights (clublights.js) ---
   A fixed full-viewport canvas behind .wrap (z-index 2) and the heat vignette (z-index 1),
   shown only while the full song plays on wide screens. Fades via opacity. */
.clublights {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity .5s ease;
}
.clublights.on { opacity: 1; }
.clublights[hidden] { display: none; }
@media (max-width: 899.98px) {
  .clublights { display: none; }
}

/* --- enlightenment (the relief easter egg) ---
   Three states live here. The achiever gets the aura (.aura.lit), the broken
   meter (#meter.broken) and bubbles (body.enlightened); everyone else who finds
   the level already at zero gets the sleeping z's (body.calm). script.js owns
   placement and class flipping; everything that moves is a keyframe below. */
:root {
  --calm-green: #3ddc84;     /* the site's green, the left end of the calm gauge */
  --calm-blue: #4fb4ff;      /* the right end: the gauge runs green -> blue */
  --calm-ink: #8fc9b6;       /* labels and status while the meter is broken */
  --zen-gold: #ffd166;       /* = --yellow; named for what it means here */
}

/* --- 1. the aura: glow, ripples, halo --- */
/* All of this is inside the SVG, so radii, strokes and blurs are viewBox units
   (480x300) and scale with the card. script.js positions #aura on the figure. */
.aura {
  --halo-y: -32px;           /* the halo's lift above the aura origin, in viewBox units */
  pointer-events: none;
}

/* while he meditates: a warm smudge of light and three rings you can barely see */
.aura .glow {
  fill: var(--zen-gold);
  opacity: .06;
  filter: blur(9px);
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 1.2s ease;
}
.aura .ring {
  fill: none;
  stroke: var(--zen-gold);
  stroke-width: 1.6;
  opacity: .05;
  transform-box: fill-box;
  transform-origin: center;
}
.aura .halo {
  fill: none;
  stroke: #fff6e0;
  stroke-width: 2.2;
  opacity: .05;
  transform-box: fill-box;
  transform-origin: center;
  transform: translateY(var(--halo-y));
}

/* enlightenment: the light comes up and the rings start travelling outward */
.aura.lit .glow {
  opacity: .2;
  animation: auraBreathe 7s ease-in-out infinite;
}
.aura.lit .ring { animation: auraRipple 3s linear infinite; }
/* one second apart, so it reads as calm ripples rather than one pulse */
.aura.lit .ring2 { animation-delay: 1s; stroke: #fff4d6; }
.aura.lit .ring3 { animation-delay: 2s; stroke: var(--calm-green); }
.aura.lit .halo { animation: haloPulse 4.5s ease-in-out infinite; }

/* the ring grows away from the figure and thins out as it fades, so the stroke
   does not fatten with the scale */
@keyframes auraRipple {
  0%   { transform: scale(.4); opacity: 0; stroke-width: 3; }
  15%  { opacity: .5; }
  100% { transform: scale(2.15); opacity: 0; stroke-width: .7; }
}
@keyframes auraBreathe {
  0%, 100% { transform: scale(.94); opacity: .14; }
  50%      { transform: scale(1.06); opacity: .26; }
}
@keyframes haloPulse {
  0%, 100% { transform: translateY(var(--halo-y)) scale(1);    opacity: .6; }
  50%      { transform: translateY(var(--halo-y)) scale(1.07); opacity: .95; }
}

/* --- 2. asleep: the z's, and the dimmed room --- */
.zzz text {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 700;
  fill: #b4bccb;             /* muted, but the stage shrinks these to a few px */
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  animation: zDrift 2.5s ease-out infinite;
}
/* sizes step up as they get further from the head (viewBox units: the head is r 17) */
.zzz .z1 { font-size: 12px; }
.zzz .z2 { font-size: 14.5px; animation-delay: .8s; }
.zzz .z3 { font-size: 17px; animation-delay: 1.6s; }

@keyframes zDrift {
  0%   { opacity: 0; transform: translate(0, 4px) scale(.8); }
  20%  { opacity: .85; }
  70%  { opacity: .5; }
  100% { opacity: 0; transform: translate(8px, -22px) scale(1.15); }
}

/* The room dims a few percent while he sleeps. A translucent overlay rather
   than a filter on .card: a filter would re-rasterise the animating SVG every
   frame, and this leaves the #viewport clip-path alone. It is always in the
   DOM at opacity 0 so the state can blend in and out. */
.card { position: relative; }
.card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: rgba(6, 8, 13, .5);
  opacity: 0;
  pointer-events: none;
  transition: opacity 1.2s ease;
}
body.calm .card::after { opacity: .28; }

/* --- 3. the broken meter --- */
/* No longer a flip gauge: a calm green-to-blue fill that grows in from the
   right edge, with the heat glow replaced by a soft cool one. */
.broken .track {
  background-color: var(--track);
  background-image: linear-gradient(90deg, var(--calm-green), var(--calm-blue));
  background-repeat: no-repeat;
  background-position: right center;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .06),
    0 0 20px rgba(79, 180, 255, .25);
  animation: calmFill 1.8s ease-out forwards;
}
@keyframes calmFill {
  from { background-size: 0 100%; }
  to   { background-size: 100% 100%; }
}

/* script.js keeps setting .cover's width for the normal reading; the broken
   meter has no "unfilled part" to show, so it stops being drawn. */
.broken .cover { display: none; }

/* the crack: a jagged dark band across the bar, traced down one edge and back
   up the other so the zigzag keeps an even width. Lighter along the edges, as
   if the surface of the bar had broken away. */
.crack { display: none; }
.broken .crack {
  display: block;
  position: absolute;
  top: -1px;
  bottom: -1px;
  left: 58%;
  width: 15px;
  margin-left: -7px;
  background: linear-gradient(90deg,
    rgba(232, 234, 240, .3), var(--bg) 38%, var(--bg) 62%, rgba(232, 234, 240, .3));
  clip-path: polygon(
    46% 0, 74% 16%, 40% 34%, 78% 52%, 38% 70%, 70% 86%, 44% 100%,
    22% 100%, 54% 86%, 20% 70%, 60% 52%, 24% 34%, 56% 16%, 28% 0);
}

/* "(infinity) calm" is wider than "100%" and is a phrase, not a number:
   smaller, on one line, lower case against the shouting label, no tabular
   figures, and it still fits beside that label at 380px. */
.broken .meter-pct {
  font-size: clamp(17px, 4.4vw, 21px);
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  font-variant-numeric: normal;
  white-space: nowrap;
  color: #cdeede;
}
.broken .meter-name,
.broken .status { color: var(--calm-ink); }

/* the achiever's page: the red heat vignette gives way to a faint green one.
   (--heat is an inline style on <body>, so this overrides body::before itself
   rather than the variable.) */
body.enlightened::before {
  background: radial-gradient(ellipse at center, transparent 45%, rgba(61, 220, 132, .22) 100%);
  opacity: .6;
  transition: opacity 1.5s ease;
}

/* --- 4. soap bubbles (script.js makes them in #fx with --x/--dx/--dur/--size) --- */
.bubble-fx {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--size, 14px);
  height: var(--size, 14px);
  border-radius: 50%;
  /* soap film: a hairline rim (a border, so it survives a 10px bubble), a soft
     highlight up at the top left, a faint green/blue sheen in the body */
  border: 1px solid rgba(255, 255, 255, .3);
  background:
    radial-gradient(circle at 33% 27%, rgba(255, 255, 255, .5) 0 7%, rgba(255, 255, 255, .14) 20%, rgba(255, 255, 255, 0) 46%),
    radial-gradient(circle at 64% 74%, rgba(61, 220, 132, .14), rgba(79, 180, 255, .1) 55%, rgba(255, 255, 255, 0) 78%);
  box-shadow: 0 0 9px rgba(180, 220, 255, .1);
  will-change: transform, opacity;
  animation: bubbleFloat var(--dur, 7s) linear forwards;
}

/* up the page from the bottom, drifting by --dx with a little wobble either
   side of it, and gone before the top edge */
@keyframes bubbleFloat {
  0%   { transform: translate(var(--x), calc(100vh + 40px)) scale(.85); opacity: 0; }
  8%   { opacity: .9; }
  25%  { transform: translate(calc(var(--x) + var(--dx) * .3 + 7px), 72vh) scale(1); }
  50%  { transform: translate(calc(var(--x) + var(--dx) * .55 - 6px), 48vh) scale(1.02); }
  75%  { transform: translate(calc(var(--x) + var(--dx) * .85 + 5px), 24vh) scale(1); opacity: .8; }
  100% { transform: translate(calc(var(--x) + var(--dx)), -8vh) scale(1.07); opacity: 0; }
}

/* --- 5. the same states, held still --- */
@media (prefers-reduced-motion: reduce) {
  /* .lit in the selectors, not just .aura: these have to outrank the lit rules
     above, and a media query adds no specificity of its own */
  .aura.lit .glow, .aura.lit .ring, .aura.lit .halo,
  .zzz text, .broken .track, .card::after { animation: none; transition: none; }

  /* lit, but as a still picture: three concentric rings resting at different
     distances instead of travelling ones */
  .aura.lit .glow { opacity: .2; }
  .aura.lit .ring { opacity: .34; }
  .aura.lit .ring2 { opacity: .22; transform: scale(1.45); }
  .aura.lit .ring3 { opacity: .12; transform: scale(1.9); }
  .aura.lit .halo { opacity: .9; }

  .zzz text { opacity: .6; }                       /* the z's hang where they are */
  .broken .track { background-size: 100% 100%; }   /* filled, without the sweep */
  .bubble-fx { display: none; }
}
