html, body { margin: 0; padding: 0; }
body { font-family: 'Space Grotesk', system-ui, sans-serif; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0ms !important;
    transition-duration: 0ms !important;
  }
}

/* ─────────────────────────  Sun & Moon (full circles) ───────────────────────── */
.sun-wrap, .moon-wrap {
  position: absolute;
  z-index: 0;
  will-change: filter, transform;
  transition: filter 600ms ease-out, transform 700ms ease-out, top 700ms ease-out, left 700ms ease-out, right 700ms ease-out, width 700ms ease-out, height 700ms ease-out;
}
.sun-wrap  { filter: drop-shadow(0 0 28px rgba(255, 178, 96, 0.6)) drop-shadow(0 0 80px rgba(255, 200, 100, 0.35)); }
.moon-wrap { filter: drop-shadow(0 0 22px rgba(170, 210, 255, 0.55)) drop-shadow(0 0 60px rgba(110, 231, 255, 0.3)); }
/* Moon sits above the starfield (.scenery z=1) but below shooting stars. */
#night-side .moon-wrap { z-index: 2; }

#sun, #moon {
  position: absolute;
  inset: 0;
  border-radius: 50%;
}
#moon { overflow: hidden; }
#sun {
  background: radial-gradient(circle at 32% 30%, #fff8d8 0%, #ffd66e 35%, #ff9b3d 70%, #c4581d 100%);
  box-shadow: inset -10px -14px 40px rgba(196, 88, 30, 0.35);
}
#moon {
  background: radial-gradient(circle at 30% 30%, #f4f7ff 0%, #c5cce8 50%, #6f7799 100%);
  box-shadow: inset 8px 8px 22px rgba(70, 80, 110, 0.5);
}
#moon::before, #moon::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  background: rgba(80, 90, 120, 0.25);
}
#moon::before { width: 28%; height: 28%; top: 28%; left: 62%; }
#moon::after  { width: 16%; height: 16%; top: 56%; left: 72%; }

/* Day full mode: sun BIG at top-right of day-side (initial fallback —
   `placeCelestials()` overrides this with an arc position based on the hour). */
#hero[data-time="day"] #day-side .sun-wrap {
  top: 4rem; right: 5rem;
  width: 220px; height: 220px;
}
/* Day full mode: moon SMALL at top of night sidebar.
   Halo is reduced to match the smaller disc, so it doesn't bleed past the
   collapsed bar into the day side. */
#hero[data-time="day"] #night-side .moon-wrap {
  top: 1.2rem; left: 50%; transform: translateX(-50%);
  width: 56px; height: 56px;
  filter: drop-shadow(0 0 8px rgba(170, 210, 255, 0.5)) drop-shadow(0 0 16px rgba(110, 231, 255, 0.25));
}
/* Night full mode: moon BIG at top-left of night-side (initial fallback —
   `placeCelestials()` overrides this with an arc position based on the hour). */
#hero[data-time="night"] #night-side .moon-wrap {
  top: 4rem; left: 5rem;
  width: 220px; height: 220px;
}
/* Night full mode: sun SMALL at top of day sidebar.
   Halo is reduced to match the smaller disc, symmetric with the small moon. */
#hero[data-time="night"] #day-side .sun-wrap {
  top: 1.2rem; left: 50%; transform: translateX(-50%);
  width: 56px; height: 56px;
  filter: drop-shadow(0 0 10px rgba(255, 178, 96, 0.55)) drop-shadow(0 0 20px rgba(255, 200, 100, 0.3));
}

/* Active-mode pulse on the dominant celestial */
#hero[data-time="day"]   #day-side .sun-wrap   { animation: sun-pulse 5s ease-in-out infinite; }
#hero[data-time="night"] #night-side .moon-wrap { animation: moon-drift 7s ease-in-out infinite; }

/* Sidebar mode dims the small celestial slightly */
#hero[data-time="day"]   #night-side #moon { opacity: 0.78; }
#hero[data-time="night"] #day-side  #sun  { opacity: 0.78; }

/* Sidebar celestial = clickable toggle */
#hero[data-time="day"]   #night-side .moon-wrap,
#hero[data-time="night"] #day-side  .sun-wrap {
  cursor: pointer;
}
#hero[data-time="day"]   #night-side .moon-wrap:hover #moon,
#hero[data-time="night"] #day-side  .sun-wrap:hover  #sun {
  opacity: 1;
  filter: brightness(1.1);
}
#hero[data-time="day"]   #night-side .moon-wrap:focus-visible,
#hero[data-time="night"] #day-side  .sun-wrap:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 6px;
  border-radius: 50%;
}

@keyframes sun-pulse {
  0%, 100% { filter: drop-shadow(0 0 28px rgba(255, 178, 96, 0.6)) drop-shadow(0 0 80px rgba(255, 200, 100, 0.35)); }
  50%      { filter: drop-shadow(0 0 44px rgba(255, 178, 96, 0.85)) drop-shadow(0 0 120px rgba(255, 200, 100, 0.55)); }
}
@keyframes moon-drift {
  0%, 100% { filter: drop-shadow(0 0 22px rgba(170, 210, 255, 0.55)) drop-shadow(0 0 60px rgba(110, 231, 255, 0.3)) translateY(-2px); }
  50%      { filter: drop-shadow(0 0 32px rgba(170, 210, 255, 0.75)) drop-shadow(0 0 90px rgba(110, 231, 255, 0.45)) translateY(2px); }
}

/* ─────────────────────────  Scenery  ───────────────────────── */

:root {
  --sky-1: #fde2c2;
  --sky-2: #ffd1a3;
  --sky-3: #ffb56b;
  --sky-4: #ff9558;
  --sky-5: #f0703a;
  --hill-far: #6fa66b;
  --hill-mid: #4a8950;
  --hill-near: #2e5d34;
  --hill-pine: #0f2114;
  --rays-opacity: 0.8;
}
#day-side {
  background: linear-gradient(180deg, var(--sky-1) 0%, var(--sky-2) 28%, var(--sky-3) 56%, var(--sky-4) 78%, var(--sky-5) 100%);
}
#night-side {
  background: linear-gradient(180deg, #050816 0%, #0c1238 30%, #142a5a 55%, #0e5460 80%, #0a7864 100%);
}

/* Custom JS-driven lightsaber cursor — bypasses the browser's CSS-cursor size cap
   so we can use the original 310×310 GIFs. Hide the system cursor everywhere
   (the universal selector + !important beats every cursor: pointer rule).
   Pseudo-elements (range thumb/track, etc.) need explicit overrides because
   the universal selector doesn't match them. */
/* Some browsers (notably WebKit on form-control pseudo-elements) ignore
   `cursor: none`, so we also pass a 1×1 transparent GIF as a fallback URL.
   The url() takes precedence; `none` is the keyword fallback. */
html, body, * {
  cursor: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0, none !important;
}
input[type="range"],
input[type="range"]::-webkit-slider-thumb,
input[type="range"]::-webkit-slider-runnable-track,
input[type="range"]::-moz-range-thumb,
input[type="range"]::-moz-range-track,
select, option, textarea, input {
  cursor: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0, none !important;
}
.cursor-saber {
  position: fixed;
  top: 0;
  left: 0;
  width: 64px;
  height: 64px;
  pointer-events: none;
  z-index: 10000;
  transform: translate3d(-9999px, -9999px, 0);
  will-change: transform;
  opacity: 0;
  transition: opacity 200ms ease-out;
}
.cursor-saber.is-visible { opacity: 1; }
.cursor-saber img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  transition: opacity 120ms ease-out;
  /* Hotspot offset baked in via translate inside JS; image origin is top-left. */
}
.cursor-saber .saber-idle   { opacity: 1; }
.cursor-saber .saber-active { opacity: 0; }
.cursor-saber.is-active .saber-idle   { opacity: 0; }
.cursor-saber.is-active .saber-active { opacity: 1; }
@media (hover: none), (pointer: coarse) {
  html, body, * { cursor: auto !important; }
  .nav-link, button, a, [role="button"] { cursor: pointer !important; }
  .cursor-saber { display: none; }
}

.scenery {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}
#day-side > :not(.scenery):not(.sun-wrap):not(.moon-wrap):not(.sidebar-nav):not(.shooting-stars):not(.city):not(.lamps):not(.side-header):not(.side-footer):not(.logo):not(.hero-weather),
#night-side > :not(.scenery):not(.sun-wrap):not(.moon-wrap):not(.sidebar-nav):not(.shooting-stars):not(.city):not(.lamps):not(.side-header):not(.side-footer):not(.logo):not(.hero-weather) { position: relative; z-index: 2; }

/* Day clouds */
.cloud {
  position: absolute;
  opacity: 0.85;
  will-change: transform;
  transform: translate3d(var(--mx, 0), 0, 0);
  transition: transform 600ms ease-out;
}
.cloud svg { display: block; }
@keyframes cloud-drift-1 {
  0%   { left: -200px; }
  100% { left: 110%; }
}
@keyframes cloud-drift-2 {
  0%   { left: -300px; }
  100% { left: 110%; }
}
.cloud-a { top: 14%; animation: cloud-drift-1 90s linear infinite; }
.cloud-b { top: 22%; animation: cloud-drift-2 130s linear infinite; animation-delay: -45s; }
.cloud-c { top: 30%; animation: cloud-drift-1 110s linear infinite; animation-delay: -70s; }
.cloud-d { top: 9%;  animation: cloud-drift-2 150s linear infinite; animation-delay: -110s; }

/* Day hills */
.day-hills {
  position: absolute;
  inset: auto 0 0 0;
  width: 100%;
  height: 55%;
  will-change: transform;
  transform: translate3d(calc(var(--mx, 0px) * -0.3), 0, 0);
  transition: transform 800ms ease-out;
}
.day-hills svg { width: 100%; height: 100%; display: block; }
.day-hills svg > path:nth-of-type(1) { fill: var(--hill-far); transition: fill 800ms ease-out; }
.day-hills svg > path:nth-of-type(2) { fill: var(--hill-mid); transition: fill 800ms ease-out; }
.day-hills svg > path:nth-of-type(3) { fill: var(--hill-near); transition: fill 800ms ease-out; }
.day-hills svg > g                    { fill: var(--hill-pine); transition: fill 800ms ease-out; }

/* Sun rays from horizon */
.sun-rays {
  position: absolute;
  left: 50%;
  bottom: 18%;
  width: 600px;
  height: 600px;
  transform: translate(-50%, 50%);
  background: radial-gradient(ellipse at center, rgba(255, 230, 150, 0.45) 0%, rgba(255, 200, 110, 0.15) 30%, rgba(255, 200, 110, 0) 60%);
  pointer-events: none;
  opacity: var(--rays-opacity, 0.8);
  transition: opacity 800ms ease-out;
}

/* Bird specks — animated, spawned by JS */
.birds {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

.bird-flight {
  position: absolute;
  top: var(--bird-top, 30%);
  left: 0;
  width: 100%;
  height: 0;
  will-change: transform;
  animation-name: bird-fly-ltr;
  animation-duration: var(--bird-duration, 14s);
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}

.bird-flight.rtl {
  animation-name: bird-fly-rtl;
}

.bird {
  position: absolute;
  top: 0;
  left: 0;
  transform: scale(var(--bird-scale, 1));
  transform-origin: center;
  opacity: 0.85;
}

.bird-svg {
  display: block;
  transform-origin: center;
  animation: bird-flap 320ms ease-in-out infinite alternate;
}

@keyframes bird-fly-ltr {
  0%   { transform: translate3d(-10vw, 0, 0); }
  100% { transform: translate3d(110vw, var(--bird-drift, -40px), 0); }
}

@keyframes bird-fly-rtl {
  0%   { transform: translate3d(110vw, 0, 0) scaleX(-1); }
  100% { transform: translate3d(-10vw, var(--bird-drift, -40px), 0) scaleX(-1); }
}

@keyframes bird-flap {
  0%   { transform: scaleY(1)   translateY(0); }
  50%  { transform: scaleY(0.5) translateY(-1px); }
  100% { transform: scaleY(0.25) translateY(-2px); }
}

/* Night stars */
.stars {
  position: absolute;
  inset: 0;
  will-change: transform;
  transform: translate3d(calc(var(--mx, 0px) * 0.15), calc(var(--my, 0px) * 0.15), 0);
  transition: transform 600ms ease-out;
}
.star {
  position: absolute;
  background: #ffffff;
  border-radius: 50%;
  box-shadow: 0 0 4px rgba(255, 255, 255, 0.7);
  animation: twinkle 3s ease-in-out infinite;
}
@keyframes twinkle {
  0%, 100% { opacity: 0.3; transform: scale(0.8); }
  50%      { opacity: 1;   transform: scale(1.1); }
}

/* Shooting stars (spawned by JS) — render above the moon */
.shooting-stars {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 3;
}
.shooting-star {
  position: absolute;
  top: var(--ss-top, 20%);
  left: var(--ss-left, 50%);
  width: 2px;
  height: 2px;
  background: #ffffff;
  border-radius: 50%;
  box-shadow:
    0 0 6px 1px rgba(255, 255, 255, 0.9),
    0 0 18px 4px rgba(180, 220, 255, 0.45);
  transform: rotate(var(--ss-angle, 25deg));
  transform-origin: 0 50%;
  opacity: 0;
  animation: shoot var(--ss-duration, 1100ms) ease-out forwards;
}
.shooting-star::before {
  content: "";
  position: absolute;
  top: 50%;
  right: 0;
  width: var(--ss-tail, 140px);
  height: 1px;
  background: linear-gradient(to left, rgba(255,255,255,0.95), rgba(180,220,255,0.5) 40%, transparent);
  transform: translateY(-50%);
  filter: blur(0.4px);
}
@keyframes shoot {
  0%   { opacity: 0; transform: rotate(var(--ss-angle, 25deg)) translateX(0); }
  12%  { opacity: 1; }
  55%  { opacity: 1; }
  85%  { opacity: 0; }
  100% { opacity: 0; transform: rotate(var(--ss-angle, 25deg)) translateX(var(--ss-distance, 70vw)); }
}

/* Aurora ribbon */
.aurora {
  position: absolute;
  top: 18%;
  left: -10%;
  width: 130%;
  height: 25%;
  background: radial-gradient(ellipse at 30% 50%, rgba(110, 231, 255, 0.2) 0%, rgba(167, 139, 250, 0.15) 30%, transparent 60%),
              radial-gradient(ellipse at 70% 50%, rgba(110, 231, 255, 0.15) 0%, rgba(34, 197, 94, 0.1) 35%, transparent 65%);
  filter: blur(30px);
  opacity: 0.8;
  animation: aurora-shift 18s ease-in-out infinite alternate;
}
@keyframes aurora-shift {
  0%   { transform: translateX(-30px) skewY(-2deg); }
  100% { transform: translateX(30px)  skewY(2deg);  }
}

/* City silhouette — sits in front of the moon (.moon-wrap z=2). */
.city {
  position: absolute;
  inset: auto 0 0 0;
  width: 100%;
  height: 38%;
  z-index: 3;
  pointer-events: none;
  will-change: transform;
  transform: translate3d(calc(var(--mx, 0px) * -0.15), 0, 0);
  transition: transform 800ms ease-out;
}
.city svg { width: 100%; height: 100%; display: block; }
.city-window {
  animation: window-flicker 6s ease-in-out infinite;
}
@keyframes window-flicker {
  0%, 100% { opacity: 0.85; }
  25%      { opacity: 0.4;  }
  50%      { opacity: 1;    }
  75%      { opacity: 0.6;  }
}

/* Lampposts — also in front of the moon, otherwise the moon would float between
   the city silhouette (z=3) and the lamps. */
.lamps {
  position: absolute;
  inset: auto 0 0 0;
  width: 100%;
  height: 48%;
  z-index: 3;
  pointer-events: none;
}
.lamp {
  position: absolute;
  bottom: 0;
}
.lamp svg { display: block; }
.lamp-glow {
  position: absolute;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 230, 130, 0.35) 0%, rgba(255, 200, 110, 0.15) 40%, transparent 70%);
  transform: translate(-50%, -50%);
  animation: lamp-pulse 4s ease-in-out infinite;
}
@keyframes lamp-pulse {
  0%, 100% { opacity: 0.7; }
  50%      { opacity: 1;   }
}

/* Heading polish on the new backgrounds */
#day-side h1 {
  color: #3a1a08;
  text-shadow: 0 1px 0 rgba(255, 245, 220, 0.5), 0 12px 40px rgba(196, 88, 30, 0.25);
}
#night-side h1 {
  color: #e8ecff;
  text-shadow: 0 0 30px rgba(110, 231, 255, 0.35), 0 0 60px rgba(167, 139, 250, 0.25);
}
.day-nav { color: #3a1a08 !important; }
.day-nav::after { background: #c4581d !important; }
#day-side header .text-day-muted { color: #6b3a18 !important; }
#day-side .day-footer .text-day-muted {
  color: #fffaf0 !important;
  font-weight: 700;
  letter-spacing: 0.25em;
}

/* ─────────────────────────  Logo (funnie / dev) ───────────────────────── */
.logo {
  position: absolute;
  z-index: 5;
  font-family: 'Space Grotesk', system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(3.5rem, 9vw, 7rem);
  line-height: 0.85;
  letter-spacing: -0.05em;
  display: flex;
  flex-direction: column;
  pointer-events: none;
  /* Entry animation: surges up from below while fading in. Re-fires whenever
     the side toggles from hidden to visible (display flips from none → flex
     restart the animation), so swapping day/night re-plays the reveal. */
  animation: funnie-logo-surge 900ms cubic-bezier(0.2, 0.8, 0.2, 1) 200ms both;
}
@keyframes funnie-logo-surge {
  from { opacity: 0; transform: translateY(48px); }
  to   { opacity: 1; transform: translateY(0); }
}
.logo-line {
  background-size: 200% 200%;
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  animation: gradient-shift 8s ease-in-out infinite alternate;
}
.logo-line:nth-child(2) { animation-direction: alternate-reverse; }
.logo-day .logo-line {
  background-image: linear-gradient(90deg, #c4581d 0%, #ff8a3d 25%, #ffd66e 50%, #ff8a3d 75%, #c4581d 100%);
}
.logo-night .logo-line {
  background-image: linear-gradient(90deg, #6ee7ff 0%, #a78bfa 30%, #ec4899 55%, #a78bfa 80%, #6ee7ff 100%);
}
@keyframes gradient-shift {
  0%   { background-position: 0% 50%; }
  100% { background-position: 100% 50%; }
}

/* ─────────────────────────  Sidebar icon nav  ───────────────────────── */
.sidebar-nav {
  position: absolute;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  padding: 0;
}
.icon-btn {
  position: relative;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 10px;
  background: transparent;
  cursor: pointer;
  color: inherit;
  transition: background 200ms ease-out, transform 200ms ease-out;
}
.icon-btn svg { width: 22px; height: 22px; }
.icon-btn:hover { transform: scale(1.08); }
.icon-day { color: #3a1a08; }
.icon-day:hover { background: rgba(196, 88, 30, 0.18); }
.icon-night { color: #e8ecff; }
.icon-night:hover { background: rgba(167, 139, 250, 0.22); }
.icon-btn:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

/* Tooltip beside the icon */
.icon-btn::after {
  content: attr(data-tooltip);
  position: absolute;
  top: 50%;
  transform: translateY(-50%) translateX(0);
  font-family: 'Space Mono', ui-monospace, monospace;
  font-size: 0.65rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  background: #050507;
  color: #ffffff;
  padding: 0.45rem 0.7rem;
  border-radius: 4px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms ease-out, transform 250ms ease-out;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
  z-index: 30;
}
.icon-day::after  { left: calc(100% + 12px); }
.icon-night::after { right: calc(100% + 12px); }
.icon-btn:hover::after, .icon-btn:focus-visible::after {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
}
.icon-day:hover::after, .icon-day:focus-visible::after {
  transform: translateY(-50%) translateX(2px);
}
.icon-night:hover::after, .icon-night:focus-visible::after {
  transform: translateY(-50%) translateX(-2px);
}

/* ─────────────────────────  Section layout: full vs sidebar  ───────────────────────── */
#day-side, #night-side { transition: flex-grow 700ms ease-out, flex-basis 700ms ease-out; }
#hero[data-time="day"]   #day-side  { flex: 1 1 auto; }
#hero[data-time="day"]   #night-side { flex: 0 0 88px; }
#hero[data-time="night"] #night-side { flex: 1 1 auto; }
#hero[data-time="night"] #day-side  { flex: 0 0 88px; }

/* visibility helpers */
#hero[data-time="day"]   #day-side  .collapsed-only { display: none; }
#hero[data-time="day"]   #night-side .full-only     { display: none; }
#hero[data-time="night"] #night-side .collapsed-only { display: none; }
#hero[data-time="night"] #day-side  .full-only      { display: none; }

/* common: full-mode positioning helpers.
   Headers and the collapsed icon nav sit above .weather-fx (z=10) so rain/snow/
   fog never paints over the menu items. `isolation: isolate` guarantees the
   pill backdrop-filters compose against the scene behind the header rather
   than escaping into a higher stacking context. */
.side-header {
  position: absolute;
  top: 2rem;
  z-index: 10;
  isolation: isolate;
  display: flex;
  align-items: flex-start;
  padding: 0.5rem 2rem;
}
.side-footer {
  position: absolute;
  bottom: 2rem;
  z-index: 5;
  display: flex;
  align-items: flex-end;
  padding: 0.5rem 2rem;
}
.big-heading {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
}

/* Day full mode: nav top-left, logo bottom-left (above the footer), footer left */
#hero[data-time="day"] #day-side .side-header { left: 0; }
#hero[data-time="day"] #day-side .side-footer { left: 0; }
#hero[data-time="day"] #day-side .logo        { bottom: 4rem; left: 2.5rem; text-align: left; }
#hero[data-time="day"] #day-side .big-heading { display: none; }

/* Night full mode: nav top-right, logo bottom-right (above the footer), footer right */
#hero[data-time="night"] #night-side .side-header { right: 0; }
#hero[data-time="night"] #night-side .side-footer { right: 0; }
#hero[data-time="night"] #night-side .logo        { bottom: 4rem; right: 2.5rem; text-align: right; align-items: flex-end; }
#hero[data-time="night"] #night-side .big-heading { display: none; }

/* Hamburger toggle — hidden on desktop, shown on mobile by the breakpoint
   block below. The button mimics the nav-link pill styling for consistency. */
.nav-toggle { display: none; }

/* Each menu link gets its own slightly rounded, very-transparent frosted pill.
   The buttons stay visually separated (no shared block) and the underlying
   scenery / rain remains visible through them.
   `.side-header .nav-link` raises specificity above Tailwind preflight's
   `button { padding: 0 }` reset, which the Play CDN injects after main.css. */
/* Hero header pills + sticky-bar pills share one set of styles so a button
   in the home header looks and behaves identically to one in the post-page
   sticky bar. The active/current pill (only used by the bar) just pins the
   white-fill hover state on. */
.side-header .nav-link,
.bar-nav .nav-link {
  position: relative;
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  font-family: 'Space Mono', ui-monospace, monospace;
  font-size: 0.75rem;
  line-height: 1 !important;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.18);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 12px;
  padding: 0.65rem 1.1rem !important;
  overflow: hidden;
  isolation: isolate;
  backdrop-filter: blur(6px) saturate(140%);
  -webkit-backdrop-filter: blur(6px) saturate(140%);
  transition: color 200ms ease-out, border-color 200ms ease-out;
  text-decoration: none;
}
.side-header .nav-link::after,
.bar-nav .nav-link::after { display: none; }

/* Fill that grows up from the bottom on hover/focus/current. */
.side-header .nav-link::before,
.bar-nav .nav-link::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0;
  background: rgba(255, 255, 255, 0.95);
  transition: height 280ms ease-out;
  z-index: -1;
}
.side-header .nav-link:hover::before,
.side-header .nav-link:focus-visible::before,
.bar-nav .nav-link:hover::before,
.bar-nav .nav-link:focus-visible::before,
.bar-nav .nav-link.is-current::before {
  height: 100%;
}
/* When the white fill is up, flip text dark. !important needed because
   .day-nav / .night-nav use !important to set their own colors. */
.side-header .nav-link:hover,
.side-header .nav-link:focus-visible,
.bar-nav .nav-link:hover,
.bar-nav .nav-link:focus-visible,
.bar-nav .nav-link.is-current {
  color: #0f1b2d !important;
  border-color: rgba(255, 255, 255, 0.95);
}

.nav-link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -2px;
  height: 1px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 300ms ease-out;
}
.nav-link:hover::after,
.nav-link:focus-visible::after { transform: scaleX(1); }
.day-nav { color: #0f1b2d; }
.day-nav::after { background: #7cb6ff; }
.night-nav { color: #e8ecff; }
.night-nav::after { background: #a78bfa; }
.nav-link:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
  border-radius: 2px;
}

/* ─────────────────────────  In-page sections (formerly modal panels)  ───────────────────────── */
.page-content { display: block; }
.content-block { position: relative; }
/* Only the active side's block is rendered — hide the opposite side so the
   page only shows sections that match the current day/night mode. */
body[data-time="day"]   .content-block-night { display: none; }
body[data-time="night"] .content-block-day   { display: none; }
.panel {
  scroll-margin-top: 4.5rem; /* leave room under the sticky bar when scrolled-to */
  padding: 5rem 2rem 5rem;
  font-family: 'Space Grotesk', system-ui, sans-serif;
}
@media (min-width: 768px) {
  .panel { padding: 6rem 4rem; }
}
.panel-day { background: #f4f7fb; color: #0f1b2d; }
.panel-night { background: #0a0d1a; color: #e8ecff; }
/* On no-hero pages the body itself paints the day/night sky gradient — let
   it show through by removing the panel's solid background. */
body.no-hero .panel { background: transparent; }

/* Top bar — pinned to the viewport top via position:fixed so it can slide
   in over the hero. Hidden by default; revealed once `body.hero-passed`
   is set (JS toggles this when the hero's side-header scrolls out of view).
   Living inside .content-block means `display:none` on the inactive side's
   block also removes that side's bar from the visual tree.

   On single-post pages the bar also hosts a mini scenery (hills/city) and
   weather particles, so it's a positioned container with absolute children
   underneath the flex content (logo / nav). */
.sticky-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1.5rem;
  min-height: 90px;
  overflow: hidden;
  isolation: isolate;
  border-bottom: 1px solid transparent;
  transform: translateY(-100%);
  transition: transform 280ms ease-out;
  will-change: transform;
}
/* Scenery sits behind nav text — semi-transparent backdrop blur replaced by
   the actual sky gradient bleeding from body.no-hero. Day uses the live
   --sky-* vars (set by JS); night uses its own dark gradient. */
/* Stretch the body to at least the viewport and lay it out as a flex column
   so the article grows to fill remaining space and the footer pins to the
   bottom on short posts (classic sticky-footer pattern). */
body.no-hero {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
body.no-hero .content-block { flex: 1 0 auto; }
body.no-hero .site-footer   { flex-shrink: 0; }

/* Day post: solid panel-day surface (#f4f7fb) so the article sits on the
   same off-white as the home day-side panels — sky tones are reserved for
   the bar at top and the footer scenery at bottom. */
body.no-hero[data-time="day"] { background: #f4f7fb; }

/* Night post: solid panel-night surface so the article sits on the same
   deep navy as the home night-side panels — sky tones are reserved for
   the bar at top and the starry footer at bottom. */
body.no-hero[data-time="night"] { background: #0B0D1A; }

/* Scenery layer: hills (day) or city silhouette (night), pinned bottom. */
.bar-scenery {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.bar-hills, .bar-city {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Star field for the night sticky bar. Sits between the city scenery and
   the weather-fx so meteors paint above twinkling stars but particles still
   read on top. Inherits the global .star twinkle animation. */
.bar-stars {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
.sticky-bar .shooting-stars {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
}

/* Weather-fx layer: above scenery, below logo/nav text. */
.bar-weather-fx {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
}
.bar-weather-fx > div { position: absolute; inset: 0; }

/* The hero cloud/fog overlays use a 6px blur tuned for a full-viewport
   sky. Inside the bar (90px) and the footer (240px) that swallows the
   silhouette — drop the blur and trim opacity so the effect reads as
   "there are clouds" without smearing the whole strip. */
.bar-weather-fx .fx-clouds-extra { filter: blur(2px); }
.bar-weather-fx .fx-fog { backdrop-filter: blur(1px); -webkit-backdrop-filter: blur(1px); }

/* Override the hero's 110vh rain-fall so drops loop within the bar's
   small height; particles still come from the same JS that spawns them. */
.bar-weather-fx .rain-drop { animation-name: bar-rain-fall; }
.bar-weather-fx .snow-flake { animation-name: bar-snow-fall; }
@keyframes bar-rain-fall {
  0%   { transform: translateY(-30px); }
  100% { transform: translateY(110px); }
}
@keyframes bar-snow-fall {
  0%   { transform: translateY(-20px) translateX(0); }
  100% { transform: translateY(110px) translateX(8px); }
}
/* Hide the .fx-rain / .fx-snow hosts inside the bar by default; the same
   body[data-weather] selectors that flip them on for the hero also flip
   them on here. */
body[data-weather^="drizzle"] .bar-weather-fx .fx-rain,
body[data-weather^="rain"]    .bar-weather-fx .fx-rain,
body[data-weather^="showers"] .bar-weather-fx .fx-rain,
body[data-weather="storm"]    .bar-weather-fx .fx-rain { opacity: 1; }
body[data-weather^="snow"]    .bar-weather-fx .fx-snow { opacity: 1; }
.bar-weather-fx .fx-rain, .bar-weather-fx .fx-snow {
  opacity: 0;
  transition: opacity 800ms ease-out;
}

body.hero-passed .sticky-bar,
body.no-hero      .sticky-bar { transform: translateY(0); }

/* Sticky-bar palette per side. The day bar tracks the live --sky-* tokens
   so it shows the SUNRISE peach band before 08:00 and the DAYLIGHT blue
   from 08:00 onward (and the sunset blend in the evening). The night bar
   stays the iconic deep-blue regardless of clock hour. */
.sticky-bar-day {
  background: linear-gradient(180deg, var(--sky-1, #9fd3ff) 0%, var(--sky-2, #6fbcef) 55%, var(--sky-3, #5ea7d8) 100%);
  color: #0f1b2d;
  border-bottom-color: rgba(15, 27, 45, 0.22);
}
.sticky-bar-night {
  background: linear-gradient(180deg, #050816 0%, #142a5a 60%, #0a7864 100%);
  color: #e8ecff;
  border-bottom-color: rgba(232, 236, 255, 0.18);
}
.bar-celestial {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 0;
}
.sun-mini, .moon-mini {
  display: block;
  width: 32px;
  height: 32px;
  border-radius: 50%;
}
.sun-mini {
  background: radial-gradient(circle at 32% 30%, #fff8d8 0%, #ffd66e 35%, #ff9b3d 75%, #c4581d 100%);
  box-shadow: 0 0 18px rgba(255, 200, 80, 0.55);
}
.moon-mini {
  position: relative;
  overflow: hidden;
  background: radial-gradient(circle at 30% 30%, #f4f7ff 0%, #c5cce8 50%, #6f779b 100%);
  box-shadow: 0 0 18px rgba(167, 139, 250, 0.45);
}
.bar-logo {
  flex: 0 0 auto;
  display: inline-flex;
  flex-direction: column;
  font-family: 'Space Grotesk', system-ui, sans-serif;
  font-weight: 800;
  font-size: 1rem;
  line-height: 0.95;
  letter-spacing: -0.02em;
  text-decoration: none;
  color: inherit;
}
.bar-logo-line:first-child { color: inherit; }
.sticky-bar-day .bar-logo-line:nth-child(2) { color: #c4581d; }
.sticky-bar-night .bar-logo-line:nth-child(2) { color: #a78bfa; }
.bar-nav {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.bar-celestial, .bar-logo, .bar-page-title { position: relative; z-index: 2; }

/* Night sticky bar: push the weather widget to the far right edge so the
   nav sits in the middle and weather hugs the right side. The .bar-nav's
   own margin-left: auto plus this auto split the free space evenly between
   "before nav" and "before weather". Day side keeps weather flush against
   the nav (no extra auto). */
.sticky-bar-night .bar-weather { margin-left: auto; }

/* Page-section label that sits next to the logo (e.g. "Blog" on a post).
   Plain text in the same family as the post title, just smaller — not a
   pill or button. */
.bar-page-title {
  font-family: 'Space Grotesk', system-ui, sans-serif;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
}
.sticky-bar-day  .bar-page-title { color: #0f1b2d; }
.sticky-bar-night .bar-page-title { color: #e8ecff; }

/* ─────────────────────────  Site footer (every page)  ───────────────────────── */
.site-footer {
  position: relative;
  z-index: 5;
  padding: 1.25rem 1.5rem;
  height: 150px;
  text-align: center;
  border-top: 1px solid transparent;
  background: transparent;
  overflow: hidden;
  isolation: isolate;
  display: flex;
  align-items: center;
  justify-content: center;
}
.site-footer-inner {
  position: relative;
  z-index: 2;
  max-width: 60rem;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem 1rem;
  font-family: 'Space Mono', ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}
.site-footer-mark { font-weight: 700; }
.site-footer-tagline { opacity: 0.85; }

/* Scenery layers — fill the footer behind the text. The day variant gets
   rolling hills + scattered trees; the night variant gets a star field plus
   a host for the JS-spawned shooting stars. Hidden by default; the active
   side is revealed via body[data-time]. */
.site-footer-scenery {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  display: none;
  overflow: hidden;
}
body[data-time="day"]   .site-footer-scenery-day   { display: block; }
body[data-time="night"] .site-footer-scenery-night { display: block; }

/* Day footer: hills hug the bottom of the footer; trees overlay the closest
   ridge so the silhouette reads layered like the hero. Both SVGs use the
   live --hill-* tokens so dawn/day/sunset palettes carry through. */
.site-footer .footer-hills,
.site-footer .footer-trees {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 70%;
  display: block;
}
.site-footer .footer-trees { height: 60%; }

/* Night footer: star field spans the visible sky area; shooting-stars host
   covers the same region so meteors arc across before fading. */
.site-footer .footer-stars,
.site-footer .shooting-stars {
  position: absolute;
  inset: 0;
}
.site-footer .footer-stars .star {
  position: absolute;
  background: #ffffff;
  border-radius: 50%;
  box-shadow: 0 0 4px rgba(255, 255, 255, 0.7);
  animation: twinkle 3s ease-in-out infinite;
}

/* Day side: high-contrast dark navy brand line with muted slate tagline.
   Sky gradient pulls from the live --sky-* tokens so before 08:00 the
   footer reads as a peachy sunrise band (matching the hero) and switches
   to daylight blue from 08:00 onward. */
body[data-time="day"] .site-footer {
  background: linear-gradient(180deg, var(--sky-1, #9fd3ff) 0%, var(--sky-2, #cfe7fb) 45%, var(--sky-3, #e9d8b8) 90%, var(--sky-4, #d6bf8f) 100%);
  color: #0f1b2d; /* --day-text for legibility against pale sky */
  border-top-color: rgba(15, 27, 45, 0.15);
}
body[data-time="day"] .site-footer-mark { color: #0f1b2d; /* --day-text */ }
body[data-time="day"] .site-footer-tagline { color: #2b3a52; }

/* Night side: deep starry gradient that fades from the body's night sky
   to a near-black ground line — gives the shooting stars something to
   streak across without losing the dark-bg feel of the previous footer. */
body[data-time="night"] .site-footer {
  background: linear-gradient(180deg, #050816 0%, #0c1238 45%, #142a5a 80%, #0a0d1a 100%);
  color: #8a90b8; /* --night-muted */
  border-top-color: rgba(232, 236, 255, 0.12);
}
body[data-time="night"] .site-footer-mark { color: #e8ecff; /* --night-text */ }

/* Push the article down so the fixed bar (90px) doesn't crowd the date/title.
   Only on no-hero pages — front-page panels already start far below the bar. */
body.no-hero .content-block { padding-top: 4rem; }
@media (max-width: 600px) {
  .sticky-bar { padding: 0.6rem 1rem; gap: 0.5rem; }
  .bar-celestial { width: 32px; height: 32px; }
  .sun-mini, .moon-mini { width: 26px; height: 26px; }
  .bar-logo { font-size: 0.85rem; }
  .bar-nav .nav-link { font-size: 0.6rem; padding: 0.35rem 0.65rem; letter-spacing: 0.12em; }
}
.panel-title {
  font-family: 'Space Grotesk', system-ui, sans-serif;
  font-size: 2.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0 0 2rem;
}
.panel-day .panel-title { color: #0f1b2d; }
.panel-night .panel-title { color: #e8ecff; }

.tag {
  display: inline-block;
  font-family: 'Space Mono', ui-monospace, monospace;
  font-size: 0.65rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  padding: 0.2rem 0.5rem;
  border-radius: 0.25rem;
  margin-right: 0.4rem;
  margin-top: 0.4rem;
}
.panel-day .tag { background: #e6eefb; color: #0f1b2d; }
.panel-night .tag { background: #1a2046; color: #e8ecff; }

/* Re-theme blog content (which uses hard-coded night utility classes) when the
   blog panel is opened from the day side. */
.panel-day .text-night-muted   { color: #5b6b82; }
.panel-day .text-night-accent  { color: #c4581d; }
.panel-day .border-night-border { border-color: #dbe4f0; }
.panel-day .bg-night-surface   { background-color: #ffffff; }
.panel-day [data-blog-section="list"] button:hover {
  box-shadow: 0 0 30px rgba(255, 175, 82, 0.28);
}
.timeline {
  position: relative;
  padding-left: 1.5rem;
}
.timeline::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.5rem;
  bottom: 0.5rem;
  width: 2px;
  background: #7cb6ff;
}
.timeline-item { position: relative; padding-bottom: 2rem; }
.timeline-item::before {
  content: '';
  position: absolute;
  left: -1.85rem;
  top: 0.5rem;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 999px;
  background: #7cb6ff;
}

.copy-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: 'Space Mono', ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  padding: 0.35rem 0.65rem;
  border-radius: 0.35rem;
  cursor: pointer;
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  transition: background 200ms ease-out;
}
.copy-btn:hover { background: rgba(167, 139, 250, 0.12); }
.copy-btn[data-copied="true"] { background: rgba(110, 231, 255, 0.15); }

/* Blog / archive post-link hover — side-aware tint + accent text.
 * Centralised here (rather than via Tailwind utilities) so the hover
 * colours are obvious in one place and we avoid Play CDN's quirks
 * with PHP-concatenated dynamic class names. */
.post-link {
  color: inherit;
  text-decoration: none;
  border-radius: 0.4rem;
  transition: background-color 220ms ease, color 220ms ease;
}
.post-link .post-link-title {
  transition: color 220ms ease;
}
.post-link .post-link-date {
  transition: color 220ms ease, opacity 220ms ease;
}

.panel-day .post-link:hover,
.panel-day .post-link:focus-visible {
  background-color: rgba(124, 182, 255, 0.10);
}
.panel-day .post-link:hover .post-link-title,
.panel-day .post-link:focus-visible .post-link-title {
  color: #c4581e;
}
.panel-day .post-link:hover .post-link-date,
.panel-day .post-link:focus-visible .post-link-date {
  color: #0f1b2d;
}

.panel-night .post-link:hover,
.panel-night .post-link:focus-visible {
  background-color: rgba(167, 139, 250, 0.12);
}
.panel-night .post-link:hover .post-link-title,
.panel-night .post-link:focus-visible .post-link-title {
  color: #6ee7ff;
}
.panel-night .post-link:hover .post-link-date,
.panel-night .post-link:focus-visible .post-link-date {
  color: #e8ecff;
}

/* Social cards — bottom-up brand fill on hover. */
.social-card {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.social-card::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0;
  z-index: 0;
  pointer-events: none;
  transition: height 420ms cubic-bezier(0.22, 1, 0.36, 1);
}
.social-card:hover::before,
.social-card:focus-visible::before { height: 100%; }
.social-card > * {
  position: relative;
  z-index: 1;
  transition: color 180ms ease-out 240ms;
}

.social-card--instagram::before {
  background: linear-gradient(0deg, #feda75 0%, #fa7e1e 22%, #d62976 50%, #962fbf 75%, #4f5bd5 100%);
}
.social-card--github::before { background: #ffffff; }
.social-card--discord::before { background: #5865F2; }

.social-card--instagram:hover .social-card__title,
.social-card--instagram:hover .social-card__handle,
.social-card--instagram:hover .social-card__icon,
.social-card--discord:hover .social-card__title,
.social-card--discord:hover .social-card__handle,
.social-card--discord:hover .social-card__icon { color: #ffffff; }

.social-card--github:hover .social-card__title,
.social-card--github:hover .social-card__handle,
.social-card--github:hover .social-card__icon { color: #0a0d1a; }

.modal-shell {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.6);
}
.modal-shell[hidden] { display: none; }
.modal-card {
  background: #11162b;
  color: #e8ecff;
  border: 1px solid #222743;
  border-radius: 0.75rem;
  padding: 2rem;
  max-width: 26rem;
  width: calc(100% - 2rem);
  text-align: center;
}

@media (max-width: 767px) {
  #hero[data-time="day"]   #night-side { flex: 0 0 64px; }
  #hero[data-time="night"] #day-side  { flex: 0 0 64px; }

  /* The ACTIVE big celestial (sun when data-time="day", moon when "night")
     sits centered horizontally inside its OWN side near the top — so the
     sun centers within the day scene and the moon centers within the night
     scene. `position: absolute` (the celestial's default) anchors it to the
     parent side; `left: calc(50% - half-width)` centers it within that side.
     `transform`-based centering can't be used because `sun-pulse` /
     `moon-drift` keyframes set `transform` directly and would override it.
     The collapsed-bar (small) celestial keeps its original rules — untouched. */
  #hero[data-time="day"] #day-side .sun-wrap,
  #hero[data-time="night"] #night-side .moon-wrap {
    position: absolute;
    top: 2.5rem;
    left: 0;
    right: 0;
    margin-inline: auto;
    width: 130px;
    height: 130px;
  }
  #hero[data-time="day"] #night-side .moon-wrap,
  #hero[data-time="night"] #day-side .sun-wrap { width: 40px; height: 40px; top: 0.8rem; }

  .logo { font-size: clamp(2.6rem, 14vw, 4.5rem); }
  #hero[data-time="day"] #day-side .logo    { bottom: 9rem; left: 1.5rem; }
  #hero[data-time="night"] #night-side .logo { bottom: 9rem; right: 1.5rem; }
  .side-header { padding: 0.5rem 1.25rem; top: 1.25rem; }
  .side-footer { padding: 0.5rem 1.25rem; bottom: 1.25rem; }

  /* Mobile: collapse the inline nav into a hamburger dropdown.
     The header lays out as a column so the toggle sits above the (initially
     hidden) stacked nav. On the night side everything right-aligns. */
  .side-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }
  #hero[data-time="night"] #night-side .side-header {
    align-items: flex-end;
    justify-content: flex-start !important;
    flex-direction: column-reverse;
  }

  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    background: rgba(255, 255, 255, 0.18);
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: 12px;
    backdrop-filter: blur(6px) saturate(140%);
    -webkit-backdrop-filter: blur(6px) saturate(140%);
    cursor: pointer;
    color: #0f1b2d;
    padding: 0;
  }
  .nav-toggle.nav-toggle-night { color: #e8ecff; }
  .nav-toggle svg { width: 20px; height: 20px; }
  .nav-toggle:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

  /* Stacked dropdown — hidden until the header is marked open. */
  .side-header nav {
    display: none;
    flex-direction: column;
    gap: 0.5rem;
    align-items: stretch;
  }
  .side-header[data-open="true"] nav,
  .side-header[data-open="closing"] nav { display: flex; }
  .side-header .nav-link { text-align: center; }

  /* Staggered reveal: each link fades + slides in slightly delayed from the
     previous so the menu unfolds top-to-bottom. The collapsing animation
     reverses the stagger (bottom-most disappears first) and JS holds the
     `closing` state long enough for it to play before snapping to hidden. */
  @keyframes funnie-nav-reveal {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  @keyframes funnie-nav-collapse {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(-6px); }
  }
  .side-header[data-open="true"] .nav-link {
    animation: funnie-nav-reveal 280ms ease-out both;
  }
  .side-header[data-open="true"] .nav-link:nth-child(1) { animation-delay: 40ms; }
  .side-header[data-open="true"] .nav-link:nth-child(2) { animation-delay: 130ms; }
  .side-header[data-open="true"] .nav-link:nth-child(3) { animation-delay: 220ms; }
  .side-header[data-open="true"] .nav-link:nth-child(4) { animation-delay: 310ms; }

  .side-header[data-open="closing"] .nav-link {
    animation: funnie-nav-collapse 220ms ease-in both;
  }
  .side-header[data-open="closing"] .nav-link:nth-last-child(1) { animation-delay: 0ms; }
  .side-header[data-open="closing"] .nav-link:nth-last-child(2) { animation-delay: 70ms; }
  .side-header[data-open="closing"] .nav-link:nth-last-child(3) { animation-delay: 140ms; }
  .side-header[data-open="closing"] .nav-link:nth-last-child(4) { animation-delay: 210ms; }

  .icon-btn { width: 36px; height: 36px; }
  .icon-btn svg { width: 18px; height: 18px; }
  .icon-btn::after { font-size: 0.55rem; padding: 0.3rem 0.55rem; }

  .panel { padding: 4rem 1.25rem; }
}

/* Brighten footer monos on dark backgrounds */
#night-side .side-footer .text-night-muted {
  color: #e8ecff !important;
  font-weight: 700;
  letter-spacing: 0.25em;
  text-shadow: 0 0 12px rgba(110, 231, 255, 0.45);
}

:focus-visible { outline-offset: 3px; }
button:focus-visible, a:focus-visible {
  outline: 2px solid currentColor;
  border-radius: 4px;
}

/* ─────────────────────────  Floating debug box (left middle)  ───────────────────────── */
.debug-box { display: none; }
body.debug .debug-box {
  position: fixed;
  top: 50%;
  left: 1rem;
  transform: translateY(-50%);
  z-index: 50;
  width: 220px;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  padding: 1rem 1.1rem;
  background: rgba(8, 12, 30, 0.45);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 0.75rem;
  font-family: 'Space Mono', ui-monospace, monospace;
  color: #ffffff;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  opacity: 0.85;
  transition: opacity 200ms ease-out;
}
.debug-box:hover, .debug-box:focus-within { opacity: 1; }

.debug-section {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.debug-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
}
.debug-label {
  font-size: 0.55rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.6;
}
.debug-value {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.1em;
}

.weather {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.8rem;
  letter-spacing: 0.08em;
}
.weather-icon { font-size: 1.1rem; opacity: 0.95; }
.weather-temp { font-weight: 700; }
.weather-condition { font-size: 0.65rem; letter-spacing: 0.18em; text-transform: uppercase; opacity: 0.75; }
.weather-location { flex-basis: 100%; font-size: 0.6rem; letter-spacing: 0.18em; text-transform: uppercase; opacity: 0.55; margin-top: 0.1rem; }

/* Day widget and the night sticky-bar widget are right-anchored — pack all
   rows against the right edge so the icon/temp/condition row and the
   location row share the same right margin. The hero-corner night widget
   is pinned top-left of #night-side, so it stays left-aligned. */
.hero-weather-day,
.sticky-bar-day  .bar-weather,
.sticky-bar-night .bar-weather { justify-content: flex-end; }
.hero-weather-day .weather-location,
.sticky-bar-day  .bar-weather .weather-location,
.sticky-bar-night .bar-weather .weather-location { text-align: right; }
.hero-weather-night .weather-location { text-align: left; }

/* Hero corner widgets — day pinned top-right of #day-side, night pinned
   top-left of #night-side, so they meet at the top-center divider. */
.hero-weather {
  position: absolute;
  top: 1.5rem;
  z-index: 50;
  font-family: 'Space Grotesk', system-ui, sans-serif;
  pointer-events: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.hero-weather-day  { right: 1.5rem; color: #2a1d10; text-shadow: 0 0 6px rgba(255, 255, 255, 0.95), 0 1px 2px rgba(255, 255, 255, 0.85); }
.hero-weather-night {  left: 1.5rem; color: #e8ecff; }

/* Invert day-side text when the sky darkens (overcast / storm / heavy rain
   / snow / showers / dense drizzle / rime fog) — dark text on a dark sky
   stops reading, so swap to light text with a dark halo. Applies to both
   the hero-corner and sticky-bar day widgets. */
body[data-weather="overcast"] .hero-weather-day,
body[data-weather="overcast"] .sticky-bar-day .bar-weather,
body[data-weather="storm"]    .hero-weather-day,
body[data-weather="storm"]    .sticky-bar-day .bar-weather,
body[data-weather="rime"]     .hero-weather-day,
body[data-weather="rime"]     .sticky-bar-day .bar-weather,
body[data-weather="drizzle-3"] .hero-weather-day,
body[data-weather="drizzle-3"] .sticky-bar-day .bar-weather,
body[data-weather="rain-2"]    .hero-weather-day,
body[data-weather="rain-2"]    .sticky-bar-day .bar-weather,
body[data-weather="rain-3"]    .hero-weather-day,
body[data-weather="rain-3"]    .sticky-bar-day .bar-weather,
body[data-weather="showers-2"] .hero-weather-day,
body[data-weather="showers-2"] .sticky-bar-day .bar-weather,
body[data-weather="showers-3"] .hero-weather-day,
body[data-weather="showers-3"] .sticky-bar-day .bar-weather,
body[data-weather="snow-2"]    .hero-weather-day,
body[data-weather="snow-2"]    .sticky-bar-day .bar-weather,
body[data-weather="snow-3"]    .hero-weather-day,
body[data-weather="snow-3"]    .sticky-bar-day .bar-weather {
  color: #f4f7fb;
  text-shadow: 0 0 6px rgba(0, 0, 0, 0.9), 0 1px 2px rgba(0, 0, 0, 0.8);
}

/* Sticky-bar widget — sits just to the LEFT of .bar-nav on both bars.
   Markup order is celestial → logo → title → nav → bar-weather, so we use
   flex `order` to slot weather between the title and the nav, and move the
   auto-margin from .bar-nav onto .bar-weather so the weather widget is the
   one that pushes itself + nav against the right edge. */
.bar-weather {
  flex: 0 0 auto;
  position: relative;
  z-index: 2;
  order: 1;
  margin-left: auto;
}
.sticky-bar .bar-nav { order: 2; margin-left: 0; }
.sticky-bar-day  .bar-weather { color: #0f1b2d; }
.sticky-bar-night .bar-weather { color: #e8ecff; }

@media (max-width: 720px) {
  .hero-weather { top: 1rem; }
  .hero-weather-day  { right: 1rem; }
  .hero-weather-night {  left: 1rem; }
  .hero-weather .weather-condition { display: none; }
  .bar-weather .weather-condition { display: none; }
}

.debug-box input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  background: linear-gradient(90deg, rgba(255,178,96,0.7), rgba(255,255,255,0.35) 50%, rgba(167,139,250,0.7));
  border-radius: 2px;
  outline: none;
  margin: 0;
}
.debug-box input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: linear-gradient(135deg, #ffd66e, #a78bfa);
  border: 2px solid #ffffff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
}
.debug-box input[type="range"]::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: linear-gradient(135deg, #ffd66e, #a78bfa);
  border: 2px solid #ffffff;
}
.debug-box input[type="range"]:focus-visible { outline: 2px solid #ffffff; outline-offset: 3px; border-radius: 4px; }

#phase-slider {
  background: linear-gradient(90deg, #050816 0%, #050816 5%, #c5cce8 50%, #050816 95%, #050816 100%);
}

@media (max-width: 767px) {
  /* Match the desktop selector's specificity (`body.debug .debug-box`) so
     these mobile overrides actually win — otherwise the box stays in its
     220px-wide fixed-left desktop position. */
  body.debug .debug-box {
    width: auto;
    max-width: calc(100vw - 2rem);
    left: 1rem; right: 1rem;
    top: auto; bottom: 1rem; transform: none;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.8rem;
    padding: 0.75rem 0.9rem;
  }
  body.debug .debug-section { flex: 1 1 calc(50% - 0.4rem); min-width: 0; }
  /* Sliders inside the wrapped sections shouldn't overflow their column. */
  body.debug .debug-box input[type="range"] { max-width: 100%; }
  /* Long labels (e.g. "Waxing gibbous") need to wrap inside their cell. */
  body.debug .debug-value { white-space: normal; word-break: break-word; }
}

/* ─────────────────────────  Moon phase shadow  ───────────────────────── */
.moon-phase-shadow {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: rgba(8, 12, 30, 0.88);
  transform: translateX(var(--moon-shadow-x, -100%));
  transition: transform 900ms ease-out;
  pointer-events: none;
}

/* ─────────────────────────  Weather effects  ───────────────────────── */
/* Sits above every scene layer (.sidebar-nav z=5 is currently the highest in
   the hero) but below panels (z=50), so opening a panel still covers it. */
.weather-fx {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 10;
}
.weather-fx > div { position: absolute; inset: 0; }

/* Hidden by default — body[data-weather] turns the right ones on */
.fx-clouds-extra, .fx-rain, .fx-snow, .fx-fog, .fx-storm-tint, .fx-lightning {
  opacity: 0;
  transition: opacity 800ms ease-out;
}

/* Clear & mainly-clear: fade existing clouds */
body[data-weather="clear"] #day-side .cloud { opacity: 0; }
body[data-weather="mainly-clear"] #day-side .cloud { opacity: 0.4; }

/* Cloudy variants — `.fx-clouds-extra` uses stacked radial-gradients so the
   number of cloud puffs and their darkness scale with precipitation intensity.
   The bucket attribute is `<kind>-<1|2|3>` (light/moderate/heavy). Overcast
   and storm pin to the level-3 sky. */
body[data-weather="overcast"] .fx-clouds-extra,
body[data-weather="storm"] .fx-clouds-extra,
body[data-weather$="-1"] .fx-clouds-extra,
body[data-weather$="-2"] .fx-clouds-extra,
body[data-weather$="-3"] .fx-clouds-extra { opacity: 1; }
body[data-weather="storm"] #day-side .cloud { opacity: 0.4; filter: brightness(0.6); }

.fx-clouds-extra {
  filter: blur(6px);
}

/* Level 1 — light precipitation. A few medium-grey puffs. */
body[data-weather$="-1"] .fx-clouds-extra {
  background-image:
    radial-gradient(ellipse 35% 24% at 15% 12%, rgba(140, 148, 165, 0.55) 0%, rgba(140, 148, 165, 0.18) 55%, transparent 80%),
    radial-gradient(ellipse 32% 22% at 52% 8%,  rgba(140, 148, 165, 0.50) 0%, rgba(140, 148, 165, 0.16) 55%, transparent 80%),
    radial-gradient(ellipse 38% 26% at 82% 14%, rgba(140, 148, 165, 0.55) 0%, rgba(140, 148, 165, 0.18) 55%, transparent 80%),
    radial-gradient(ellipse 30% 20% at 35% 22%, rgba(140, 148, 165, 0.42) 0%, rgba(140, 148, 165, 0.12) 55%, transparent 80%);
}

/* Level 2 — moderate. More puffs, darker grey, broader coverage. */
body[data-weather$="-2"] .fx-clouds-extra {
  background-image:
    radial-gradient(ellipse 38% 26% at 12% 10%, rgba(105, 112, 130, 0.62) 0%, rgba(105, 112, 130, 0.22) 55%, transparent 80%),
    radial-gradient(ellipse 32% 22% at 32% 6%,  rgba(105, 112, 130, 0.58) 0%, rgba(105, 112, 130, 0.20) 55%, transparent 80%),
    radial-gradient(ellipse 36% 24% at 55% 12%, rgba(105, 112, 130, 0.62) 0%, rgba(105, 112, 130, 0.22) 55%, transparent 80%),
    radial-gradient(ellipse 30% 22% at 75% 6%,  rgba(105, 112, 130, 0.55) 0%, rgba(105, 112, 130, 0.18) 55%, transparent 80%),
    radial-gradient(ellipse 38% 26% at 92% 14%, rgba(105, 112, 130, 0.62) 0%, rgba(105, 112, 130, 0.22) 55%, transparent 80%),
    radial-gradient(ellipse 34% 22% at 22% 24%, rgba(105, 112, 130, 0.50) 0%, rgba(105, 112, 130, 0.16) 55%, transparent 80%),
    radial-gradient(ellipse 36% 22% at 50% 28%, rgba(105, 112, 130, 0.50) 0%, rgba(105, 112, 130, 0.16) 55%, transparent 80%),
    radial-gradient(ellipse 34% 22% at 78% 26%, rgba(105, 112, 130, 0.50) 0%, rgba(105, 112, 130, 0.16) 55%, transparent 80%);
}

/* Level 3 — heavy / storm / overcast. Dense dark-grey blanket across the top half. */
body[data-weather$="-3"] .fx-clouds-extra,
body[data-weather="overcast"] .fx-clouds-extra,
body[data-weather="storm"] .fx-clouds-extra {
  background-image:
    radial-gradient(ellipse 42% 30% at 8% 8%,  rgba(60, 65, 80, 0.78) 0%, rgba(60, 65, 80, 0.32) 55%, transparent 82%),
    radial-gradient(ellipse 36% 26% at 28% 4%, rgba(60, 65, 80, 0.72) 0%, rgba(60, 65, 80, 0.28) 55%, transparent 82%),
    radial-gradient(ellipse 40% 28% at 50% 10%, rgba(60, 65, 80, 0.78) 0%, rgba(60, 65, 80, 0.32) 55%, transparent 82%),
    radial-gradient(ellipse 36% 26% at 72% 4%, rgba(60, 65, 80, 0.72) 0%, rgba(60, 65, 80, 0.28) 55%, transparent 82%),
    radial-gradient(ellipse 42% 30% at 92% 8%, rgba(60, 65, 80, 0.78) 0%, rgba(60, 65, 80, 0.32) 55%, transparent 82%),
    radial-gradient(ellipse 38% 26% at 18% 22%, rgba(60, 65, 80, 0.70) 0%, rgba(60, 65, 80, 0.26) 55%, transparent 82%),
    radial-gradient(ellipse 40% 28% at 42% 26%, rgba(60, 65, 80, 0.74) 0%, rgba(60, 65, 80, 0.30) 55%, transparent 82%),
    radial-gradient(ellipse 38% 26% at 64% 22%, rgba(60, 65, 80, 0.70) 0%, rgba(60, 65, 80, 0.26) 55%, transparent 82%),
    radial-gradient(ellipse 40% 28% at 86% 26%, rgba(60, 65, 80, 0.74) 0%, rgba(60, 65, 80, 0.30) 55%, transparent 82%),
    radial-gradient(ellipse 42% 28% at 30% 40%, rgba(60, 65, 80, 0.65) 0%, rgba(60, 65, 80, 0.22) 55%, transparent 82%),
    radial-gradient(ellipse 44% 30% at 60% 44%, rgba(60, 65, 80, 0.68) 0%, rgba(60, 65, 80, 0.24) 55%, transparent 82%),
    radial-gradient(ellipse 42% 28% at 88% 40%, rgba(60, 65, 80, 0.65) 0%, rgba(60, 65, 80, 0.22) 55%, transparent 82%);
}

/* Fog overlay */
body[data-weather="fog"] .fx-fog { opacity: 0.55; }
body[data-weather="rime"] .fx-fog { opacity: 0.7; }
.fx-fog {
  background:
    linear-gradient(180deg, rgba(220,225,235,0.5) 0%, rgba(200,210,225,0.4) 60%, rgba(180,195,215,0.5) 100%);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

/* Rain particles (added by JS as <span class="rain-drop">) */
.rain-drop {
  position: absolute;
  width: 1px;
  height: 14px;
  background: linear-gradient(transparent 0%, rgba(190, 220, 255, 0.85) 100%);
  top: -20px;
  animation-name: rain-fall;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform;
}
@keyframes rain-fall {
  0%   { transform: translateY(0); }
  100% { transform: translateY(110vh); }
}
body[data-weather^="drizzle"] .fx-rain,
body[data-weather^="rain"]    .fx-rain,
body[data-weather^="showers"] .fx-rain,
body[data-weather="storm"]    .fx-rain {
  opacity: 1;
}
body[data-weather^="drizzle"] .rain-drop {
  height: 6px;
  background: linear-gradient(transparent 0%, rgba(200, 220, 245, 0.55) 100%);
}

/* Snow particles (JS-added <span class="snow-flake">) */
.snow-flake {
  position: absolute;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 4px rgba(255, 255, 255, 0.6);
  top: -10px;
  animation-name: snow-fall;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform;
}
@keyframes snow-fall {
  0%   { transform: translateY(0) translateX(0); }
  50%  { transform: translateY(55vh) translateX(20px); }
  100% { transform: translateY(110vh) translateX(0); }
}
body[data-weather^="snow"] .fx-snow { opacity: 1; }

/* Storm tint + lightning */
body[data-weather="storm"] .fx-storm-tint { opacity: 0.55; }
.fx-storm-tint {
  background: linear-gradient(180deg, rgba(15, 18, 35, 0.7) 0%, rgba(40, 45, 70, 0.4) 60%, rgba(15, 18, 35, 0.55) 100%);
}
body[data-weather="storm"] .fx-lightning {
  opacity: 1;
  background: rgba(255, 255, 255, 0);
  animation: lightning-flash 7s infinite;
}
@keyframes lightning-flash {
  0%, 92%, 100% { background: rgba(255, 255, 255, 0); }
  93%           { background: rgba(255, 255, 255, 0.85); }
  94%           { background: rgba(255, 255, 255, 0); }
  95%           { background: rgba(255, 255, 255, 0.6); }
  96%           { background: rgba(255, 255, 255, 0); }
}

/* Reduced motion: no falling particles, no lightning */
@media (prefers-reduced-motion: reduce) {
  .rain-drop, .snow-flake { animation: none; opacity: 0.4; }
  .fx-lightning { animation: none; }
}

/* Debug select */
.debug-select {
  width: 100%;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #ffffff;
  padding: 0.45rem 0.6rem;
  border-radius: 6px;
  font-family: 'Space Mono', ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  outline: none;
}
.debug-select:focus-visible { border-color: #ffffff; }
.debug-select option { background: #11162b; color: #ffffff; }

/* ─────────────────────────  Hardware lightbox  ─────────────────────────
   Click any hardware card to open. The image scales to fit the viewport;
   the caption sits at the bottom over a dark scrim that fades in from the
   image, so the description stays readable on light or busy product photos. */
.hardware-lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}
.hardware-lightbox[hidden] { display: none; }
.hardware-lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(5, 8, 22, 0.88);
  backdrop-filter: blur(4px);
}
.hardware-lightbox-frame {
  position: relative;
  margin: 0;
  max-width: min(1100px, 92vw);
  max-height: 92vh;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
  animation: hardware-lightbox-pop 220ms ease-out;
}
.hardware-lightbox-img {
  display: block;
  max-width: 92vw;
  max-height: 92vh;
  width: auto;
  height: auto;
  object-fit: contain;
  background: #11162b;
}
.hardware-lightbox-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 2.25rem 1.75rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  color: #ffffff;
  /* Scrim sits BELOW the text — opaque under the type, fading toward the
     image above so the photo is uninterrupted at the top. */
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.92) 0%,
    rgba(0, 0, 0, 0.78) 55%,
    rgba(0, 0, 0, 0) 100%
  );
  pointer-events: none;
}
.hardware-lightbox-kind,
.hardware-lightbox-name,
.hardware-lightbox-note {
  /* Keep the caption legible where the scrim thins out near the top of the
     gradient (e.g. light product photos like the white-background mic). Hard
     offset, no blur — reads as a solid drop shadow rather than a glow. */
  text-shadow: 0 2px 0 #000;
}
.hardware-lightbox-kind {
  font-family: 'Space Mono', ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #a78bfa;
}
.hardware-lightbox-name {
  font-family: 'Space Grotesk', system-ui, sans-serif;
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.hardware-lightbox-note {
  font-size: 0.95rem;
  color: rgba(232, 236, 255, 0.82);
}
.hardware-lightbox-close {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 1;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: #ffffff;
  font-size: 1.6rem;
  line-height: 1;
  padding: 0 0 2px;
  transition: background 160ms ease-out, transform 160ms ease-out;
}
.hardware-lightbox-close:hover { background: rgba(255, 255, 255, 0.22); transform: scale(1.05); }
.hardware-lightbox-close:focus-visible { outline: 2px solid #ffffff; outline-offset: 2px; }
@keyframes hardware-lightbox-pop {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}
@media (max-width: 640px) {
  .hardware-lightbox { padding: 0.75rem; }
  .hardware-lightbox-caption { padding: 1.75rem 1rem 1rem; }
  .hardware-lightbox-name { font-size: 1.15rem; }
  .hardware-lightbox-note { font-size: 0.85rem; }
}
