* {
  -webkit-tap-highlight-color: transparent;
}

/* ==== Root variables ==== */

:root {
  /* Fonts */
  --font-main: Arial, BlinkMacSystemFont, Roboto, sans-serif;
  --font-mini: 11px;
  --font-small: 13px;
  --font-base: 14px;
  --font-large: 17px;
  --font-title: 20px;
  --a4-width: 794px;
  --a4-height: 1123px;

  /* Colors */
  --bg: #f8f9fa;
  --bg-rev: #050505;
  --bg2: #e6e7e8;
  --text: #333;
  /* Card text colours with alpha baked in (instead of opacity on the element):
     an opacity < 1 promotes the text to its own layer, which the sticky category
     menu's backdrop-filter can't sample — so the text wouldn't blur under it
     while images did. Baked-in alpha keeps the look without the extra layer. */
  --card-title-color: rgba(51, 51, 51, 0.9);
  --card-sub-color: rgba(51, 51, 51, 0.7);
  --muted: #ccc;
  --accent: #2d5280;
  /* Liquid-glass fill for the star glyph (flying intro/PTR star + header logo),
     so all three look identical. Rotationally symmetric (centred radial sheen)
     over an opaque accent base; var(--accent) resolves per theme at use. */
  --star-fill:
    radial-gradient(circle at 50% 50%,
      rgba(255, 255, 255, 0.55) 0%,
      rgba(255, 255, 255, 0.12) 34%,
      rgba(0, 0, 0, 0.16) 72%,
      rgba(0, 0, 0, 0.34) 100%),
    linear-gradient(var(--accent), var(--accent));
  --error: #7d1e1e;
  --starlight: #e6ce11;

  /* Gradients & Overlay Defaults */
  --bg-grad-2: var(--bg);
  --bg-grad-3: var(--muted);
  --overlay-bg: var(--bg);
  --btn-bg-active: var(--accent);
  --btn-text-active: var(--bg);

  /* Background Image Settings */
  --bg-image: url('https://cloud.echovestia.org/imgs/other/background.jpg');
  --bg-image-opacity: 0.25;
   /* Tarot */
  --tarot-back-image: url('https://cloud.echovestia.org/imgs/tarot-cards/back.jpg');

  /* Layout & Spacing */
  --header-height: 60px;
  --footer-height: 20px;
  --margin-top: 32px;
  --margin: 12px;
  --margin-plus: 24px;
  --gap: 12px;
  --padding: 16px;
  --padding-max: 32px;
  --radius: 50%;
  --radius-base: 8px;
  --max-width: 1080px;
  --max-width-l0: 900px;
  --max-width-l1: 720px;
  --max-width-l2: 540px;
  --max-width-l3: 380px;
  --max-width-l4: 180px;
  /* Card grids (welcome + every "related" section) get their own, wider cap:
     --max-width is a reading-width value, tuned for prose pages (info/meta/
     sitemap) and the mobile-first stacked album/project layout — stretching
     it would make paragraphs uncomfortably wide. The grid instead should keep
     using more of a real desktop monitor as it gets wider (more columns of
     content, like Pinterest), independent of that reading width. */
  --max-width-grid: 1720px;
  /* Tighter than --gap specifically for the card grid: Pinterest's density
     comes as much from small margins as from column count — narrower gutters
     between cards (and against the screen edge, see .screen's side padding)
     let images themselves be bigger at the same viewport width, rather than
     spending that width on whitespace. --gap stays untouched everywhere else
     (buttons, layouts, unrelated spacing) since tightening it globally would
     ripple into things nobody asked to change. */
  --grid-gap: 6px;
  --btn-size: 42px;
  --anno-height: 0px;
  --anno-transition: padding-top 0.4s ease-in-out;
  --glass-blur: blur(10px) saturate(180%);
  --glass-bg: linear-gradient(120deg, rgba(255,255,255,0.05), rgba(255,255,255,0.15));
  --shadow-light: 0 3px 12px rgba(0, 0, 0, 0.15);
  --shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
  --shadow-max: 0 20px 50px rgba(0, 0, 0, 0.5);
  --shadow-invert: inset 0 0 50px rgba(0,0,0,0.5);
  --mini-player-bg: rgba(255, 255, 255, 0.8);
  --shimmer:  rgba(255, 255, 255, 0.5);
  --skeleton: rgba(120, 130, 150, 0.10);
  /* Liquid-glass tint (category menu, mini-player) — page background colour */
  --glass-tint: rgba(248, 249, 250, 0.33);
  --border: 1px solid var(--muted);
  --gradient: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(0, 0, 0, 0.15) 10%,
    rgba(0, 0, 0, 0.15) 30%,
    transparent 50%,
    rgba(0, 0, 0, 0.15) 70%,
    rgba(0, 0, 0, 0.15) 90%,
    transparent 100%);
  /* Transforms & Opacity */
  --scale-min: scale(0.9);
  --scale-base: scale(0.97);
  --scale-max: scale(1.05);
  --opacity-base: 0.9;
  --opacity-min: 0.7;
  --opacity-mini: 0.5;
  --opacity-micro: 0.3;
  --sparkle-c1: rgba(100, 160, 255, 0.9);
  --sparkle-c2: rgba(70,  130, 255, 0.4);
  --sparkle-c3: rgba(50,  100, 230, 0.1);

  /* Transitions */
  --transition-fast: 0.25s ease;
  --transition-medium: 0.4s ease;
  --transition-slow: 0.6s ease;
}

/* ==== Dark theme override ==== */
:root[data-theme="dark"] {
  --bg: #050505;
  --bg-rev: #f8f9fa;
  --bg2: #1e1f20;
  --bg-grad-2: var(--bg);
  --bg-grad-3: #000;
  --bg-image-opacity: 0.15;
  --overlay-bg: var(--bg);
  --text: #eeeeee;
  --card-title-color: rgba(238, 238, 238, 0.9);
  --card-sub-color: rgba(238, 238, 238, 0.7);
  --muted: #666;
  --accent: #e5e5e5;
  --shadow-light: 0 3px 12px rgba(255, 255, 255, 0.15);
  --shadow: 0 12px 32px rgba(255, 255, 255, 0.25);
  --shadow-max: 0 20px 50px rgba(255, 255, 255, 0.5);
  --shadow-invert: inset 0 0 50px rgba(255, 255, 255, 0.5);
  --mini-player-bg: rgba(0, 0, 0, 0.8);
  --btn-bg-active: var(--text);
  --btn-text-active: var(--bg);
  --shimmer: rgba(255, 255, 255, 0.15);
  --skeleton: rgba(255, 255, 255, 0.06);
  --glass-tint: rgba(5, 5, 5, 0.55);
  --gradient: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(255, 255, 255, 0.15) 10%,
    rgba(255, 255, 255, 0.15) 30%,
    transparent 50%,
    rgba(255, 255, 255, 0.15) 70%,
    rgba(255, 255, 255, 0.15) 90%,
    transparent 100%);
  --sparkle-c1: rgba(255, 230, 120, 0.9);
  --sparkle-c2: rgba(255, 220,   0, 0.4);
  --sparkle-c3: rgba(255, 200,   0, 0.1);
}

/* ==== Reset & base styles ==== */

html {
  height: 100%;
  background-color: var(--bg);
}

body {
  margin: 0;
  padding: 0;
  height: 100%;
  font-family: var(--font-main);
  color: var(--text);
  transition: color var(--transition-medium);
  overflow: hidden;
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image: var(--bg-image);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  opacity: var(--bg-image-opacity);
  -webkit-mask-image: radial-gradient(circle at center, black 40%, transparent 100%);
  mask-image: radial-gradient(circle at center, black 40%, transparent 100%);
  pointer-events: none;
  will-change: transform;
  transition: opacity 800ms ease, filter 800ms ease;
}

[data-theme="dark"] body::before {
  filter: invert(1) hue-rotate(180deg) brightness(2) contrast(0.7);
}

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  background: radial-gradient(circle at 0% 0%, var(--bg-grad-2), transparent 60%),
              radial-gradient(circle at 120% 120%, var(--bg-grad-3), transparent 60%),
              var(--bg);
  pointer-events: none;
  transition: background 800ms ease;
}

/* App wrapper */
.app {
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
  z-index: 1;
  transition: background 800ms ease;
  background: transparent;
}

/* input, button, textarea, select {
  font-family: inherit;
} */

/* ==== Announcement bar ==== */
.top-announcement {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 101;
  background-color: var(--bg);
  color: var(--text);
  font-size: var(--font-small);
  overflow: hidden;
  opacity: 1;
  /* Collapsed by default (instead of display:none) so opening can grow the bar
     smoothly in step with the header's own push-down transition below, rather
     than popping to full height instantly while the header is still sliding —
     that mismatch was the "ugly line" briefly cutting across the header. */
  max-height: 0;
  transition: max-height 0.4s ease-in-out;
}

.announcement-content {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 8px 40px 8px 16px;
  text-align: center;
  display: block;
  opacity: 0;
  transform: translateY(-5px);
  transition: opacity 0.8s ease, transform 0.8s ease;
}

.announcement-close {
  position: absolute;
  right: 10px; top: 50%;
  transform: translateY(-50%);
  background: none; border: none;
  color: inherit; opacity: 0;
  font-size: var(--font-title); cursor: pointer; padding: 5px;
  transition: opacity 0.8s ease;
}

.top-announcement.text-visible .announcement-content {
  opacity: 1;
  transform: translateY(0);
}
.top-announcement.text-visible .announcement-close {
  opacity: var(--opacity-min);
}
.top-announcement.text-visible .announcement-close:hover {
  opacity: 1;
}

.top-announcement.is-closed {
  transform: translateY(-100%);
  pointer-events: none;
}

.anno-link {
  color: var(--accent);
  border-bottom: var(--border);
  cursor: pointer;
  transition: opacity 0.2s;
}
.anno-link:hover { opacity: var(--opacity-base); }
.anno-link .ext-icon { margin-left: 3px; }

/* ==== Utilities ==== */

   .no-ptr {
  touch-action: pan-x;
}

.disabled {
  opacity: var(--opacity-mini);
  pointer-events: none;
  filter: grayscale(1);
}

.is-hidden { display: none !important; }

.info-wrap, .project-about, .memory-desc, .video-desc {
  font-size: var(--font-small);
  opacity: var(--opacity-min);
  margin-bottom: var(--gap);
  line-height: 1.6;
  text-align: justify;
  text-align-last: left;
  -webkit-hyphens: auto;
  -ms-hyphens: auto;
  hyphens: auto;
}

a {
  color: var(--accent);
  text-decoration: none;
  transition: opacity 0.2s ease;
}
a:hover { opacity: 1; }

.section-title {
  font-size: var(--font-base);
  border-bottom: var(--border);
  text-align: left;
  color: var(--text);
  padding: var(--gap);
  margin-top: 24px;
}

.meta-line {
  padding: var(--padding);
  font-size: var(--font-small);
  text-align: center;
  opacity: var(--opacity-min);
}
.meta-line a {
  color: var(--accent);
  cursor: pointer;
  text-align: center;
  opacity: 1;
}

.meta-artist-link {
  color: var(--accent);
  cursor: pointer;
  text-align: center;
    opacity: 1;
}

/* ==== Screen transitions ==== */
.screen {
  display: none;
  flex-direction: column;
  align-items: stretch;
  padding: calc(60px + var(--anno-height)) 6px 32px;

  transition: var(--anno-transition);
  gap: var(--gap);
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
   /* overscroll-behavior-y: none; */
  opacity: 0;
  transform: translateY(10px);
  scrollbar-width: none;
  /* Content itself dissolves into transparency at the top (and bottom) so the
     background image stays visible behind it — instead of a solid --bg band
     painting over the image. Long, eased multi-stop fade (slow start, then
     ramps) keeps it soft rather than the abrupt cut the old 14px mask had. */
  mask-image: linear-gradient(
    to bottom,

    transparent 0,
    rgba(0,0,0,.04) calc(var(--anno-height) + 4px),
    rgba(0,0,0,.10) calc(var(--anno-height) + 8px),
    rgba(0,0,0,.18) calc(var(--anno-height) + 16px),
    rgba(0,0,0,.30) calc(var(--anno-height) + 28px),
    rgba(0,0,0,.48) calc(var(--anno-height) + 42px),
    rgba(0,0,0,.66) calc(var(--anno-height) + 60px),
    rgba(0,0,0,.82) calc(var(--anno-height) + 80px),
    rgba(0,0,0,.94) calc(var(--anno-height) + 100px),
    #000 calc(var(--anno-height) + 112px),
    #000 calc(100% - 80px),
    rgba(0,0,0,.98) calc(100% - 68px),
    rgba(0,0,0,.94) calc(100% - 56px),
    rgba(0,0,0,.87) calc(100% - 45px),
    rgba(0,0,0,.76) calc(100% - 35px),
    rgba(0,0,0,.61) calc(100% - 26px),
    rgba(0,0,0,.43) calc(100% - 18px),
    rgba(0,0,0,.25) calc(100% - 11px),
    rgba(0,0,0,.11) calc(100% - 5px),
    transparent 100%
);
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    rgba(0,0,0,.04) calc(var(--anno-height) + 4px),
    rgba(0,0,0,.10) calc(var(--anno-height) + 8px),
    rgba(0,0,0,.18) calc(var(--anno-height) + 16px),
    rgba(0,0,0,.30) calc(var(--anno-height) + 28px),
    rgba(0,0,0,.48) calc(var(--anno-height) + 42px),
    rgba(0,0,0,.66) calc(var(--anno-height) + 60px),
    rgba(0,0,0,.82) calc(var(--anno-height) + 80px),
    rgba(0,0,0,.94) calc(var(--anno-height) + 100px),
    #000 calc(var(--anno-height) + 112px),
    #000 calc(100% - 80px),
    rgba(0,0,0,.98) calc(100% - 68px),
    rgba(0,0,0,.94) calc(100% - 56px),
    rgba(0,0,0,.87) calc(100% - 45px),
    rgba(0,0,0,.76) calc(100% - 35px),
    rgba(0,0,0,.61) calc(100% - 26px),
    rgba(0,0,0,.43) calc(100% - 18px),
    rgba(0,0,0,.25) calc(100% - 11px),
    rgba(0,0,0,.11) calc(100% - 5px),
    transparent 100%
);
}
.screen::-webkit-scrollbar { display: none; }

.screen.active {
  display: flex;
  animation: screenFadeIn 0.3s ease forwards;
}

.screen.active.is-back {
  animation: screenFadeBack 0.3s ease forwards;
}

@keyframes screenFadeIn {
 from { opacity: 0; filter: blur(4px); transform: scale(0.95); }
  to   { opacity: 1; filter: blur(0);  transform: scale(1); }
}

@keyframes screenFadeBack {
  from { opacity: 0; filter: blur(4px);  transform: scale(1.05); }
  to   { opacity: 1; filter: blur(0);    transform: scale(1); }
}

/* ==== Header, footer & menu ==== */

.ptr-overlay {
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
  z-index: 10000;
  pointer-events: none;
}

.ptr-backdrop {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  background: var(--bg);
  opacity: 0;
  will-change: opacity;
}

.ptr-logo {
  position: absolute; top: 0; left: 0;
  width: 120px; height: 120px;
  display: flex; align-items: center; justify-content: center;
  margin: 0; padding: 0;

  font-size: 96px; line-height: 1;
  color: var(--accent);
  filter: drop-shadow(0 6px 18px rgba(0,0,0,0.5));
  opacity: 0;
  transform-origin: center center;
  will-change: transform;
}

.ptr-logo.spinning {
  animation: ptr-spin-final 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}

@keyframes ptr-spin-final {
  0% { transform: translate(var(--cx), var(--cy)) scale(1) rotate(0deg); }
  100% { transform: translate(var(--cx), var(--cy)) scale(1) rotate(60deg); }
}

/* Header */
header.bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--header-height);
  display: flex;
  align-items: center;
  padding: 0 var(--padding);
  /* Solid background only at the very edge, then fades — content shows higher
     up. The .top-veil progressive blur keeps the title readable (below) 
  background: linear-gradient(to bottom,
    var(--bg) 1%,
    rgb(from var(--bg) r g b / 0.15) 40%,
    rgb(from var(--bg) r g b / 0) 100%); */
  background: linear-gradient(
    to bottom,
    var(--bg) 0%,
    rgb(from var(--bg) r g b / .94) 6%,
    rgb(from var(--bg) r g b / .82) 12%,
    rgb(from var(--bg) r g b / .66) 20%,
    rgb(from var(--bg) r g b / .48) 30%,
    rgb(from var(--bg) r g b / .30) 42%,
    rgb(from var(--bg) r g b / .18) 58%,
    rgb(from var(--bg) r g b / .10) 72%,
    rgb(from var(--bg) r g b / .04) 86%,
    transparent 100%
);
  z-index: 10;

   top: var(--anno-height);
  transition: top 0.4s ease-in-out;
}

/* Top veil: content blurs progressively as it scrolls up and fully fades at
   the top (transparency from the .screen mask, blur from three backdrop-filter
   layers of increasing strength) */
.top-veil {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: calc(var(--header-height) + var(--anno-height));
  /* --anno-height itself isn't animatable (a plain custom property), but the
     header's `top` and the announcement bar's `max-height` both key off it and
     transition over 0.4s ease-in-out — without a matching transition here,
     this height used to snap to its new value in a single frame while those
     two were still easing into place, a beat of desync right at the
     boundary between the bar and the header that could read as a seam. */
  transition: height 0.4s ease-in-out;
  pointer-events: none;
  z-index: 9;
}

.top-veil > div { position: absolute; inset: 0; }

.tv-soft {
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  mask-image: linear-gradient(to bottom, black 0%, black 50%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 50%, transparent 100%);
}

.tv-mid {
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  mask-image: linear-gradient(to bottom, black 0%, black 30%, transparent 80%);
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 30%, transparent 80%);
}

.tv-strong {
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  mask-image: linear-gradient(to bottom, black 0%, black 12%, transparent 55%);
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 12%, transparent 55%);
}

/* Bottom veil: mirror of the top effect at the bottom edge (behind the
   footer). Short — doesn't reach the mini-player */
.bottom-veil {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  height: 16px;
  pointer-events: none;
  z-index: 9;
}

.bottom-veil > div { position: absolute; inset: 0; }

.bv-soft {
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  mask-image: linear-gradient(to top, black 0%, black 50%, transparent 100%);
  -webkit-mask-image: linear-gradient(to top, black 0%, black 50%, transparent 100%);
}

.bv-mid {
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  mask-image: linear-gradient(to top, black 0%, black 30%, transparent 80%);
  -webkit-mask-image: linear-gradient(to top, black 0%, black 30%, transparent 80%);
}

.bv-strong {
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  mask-image: linear-gradient(to top, black 0%, black 12%, transparent 55%);
  -webkit-mask-image: linear-gradient(to top, black 0%, black 12%, transparent 55%);
}

/* Without backdrop-filter support only the mask fade remains — clean and
   cheap */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .top-veil, .bottom-veil { display: none; }
}

.back-btn {
  position: fixed;
  font-size: var(--font-large);
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  z-index: 20;
  left: var(--padding);
  padding: 12px;
}

.back-btn:hover {opacity: var(--opacity-min);}
.back-btn:active { transform: var(--scale-min); }

/* Logo */
.logo {
  margin-left: 40px;
  display: flex;
  gap: 4px;
  cursor: pointer;
}

.logo:hover {opacity: var(--opacity-min);}
.logo:active { transform: var(--scale-base); }
.logo .icon {
   font-size: var(--font-large);
  /* Same liquid-glass star as the flying intro/PTR logo */
  background-image: var(--star-fill);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  filter:
    drop-shadow(0 0 1px rgba(255, 255, 255, 0.4))
    drop-shadow(0 1px 4px rgba(0, 0, 0, 0.3));
  transform-origin: 50% 50%;
  animation: logoPulse 20s linear infinite;
}
.logo .text {
  font-size: var(--font-large);
  letter-spacing: 0.05em;
  color: var(--text);
}
/* Seamless continuous rotation: a full turn loops with no jump (the old 0→120deg
   keyframe snapped back to 0 each cycle — the visible seam). The star is
   rotationally symmetric, so a slow full spin reads as a gentle constant turn. */
@keyframes logoPulse { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

.header-btns {
 right: var(--padding);
 position: fixed;
  cursor: pointer;
  z-index: 20;

 }

.header-btn {
  padding: 10px;
  background: none;
  border: none;
  font-size: var(--font-large);
  color: var(--accent);
  cursor: pointer;
  transition: opacity var(--transition-fast);
}

.header-btn:hover, .footer-btn:hover {opacity: var(--opacity-min);}

.header-btn:active, .footer-btn:active {
 transform: var(--scale-min);
 }

/* Resume-mini-player icon: always occupies its normal header-btn footprint
   (same padding/size as the other icons) so the OTHER icons never shift
   position when it appears/disappears — only its own opacity/interactivity
   toggle. Shown for as long as something is playing, regardless of whether
   the mini-player itself is open or swiped away (core/mini-player.js), via
   .is-visible; tapping it shows/hides the mini-player. */
.mp-resume-btn {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease;
}
.mp-resume-btn.is-visible {
  opacity: 1;
  pointer-events: auto;
}

.eq-bars {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 13px;
  vertical-align: baseline;
}

.eq-bars span {
  width: 2px;
  border-radius: 1px;
  background: currentColor;
  animation: eqBounce 0.9s ease-in-out infinite;
}

.eq-bars span:nth-child(2) { animation-delay: 0.3s; }
.eq-bars span:nth-child(3) { animation-delay: 0.6s; }

/* Paused: bars stop and drop to the bottom */
.eq-bars.is-paused span { animation: none; height: 4px; }

@keyframes eqBounce {
  0%, 100% { height: 4px; }
  50%      { height: 13px; }
}

@media (prefers-reduced-motion: reduce) {
  .eq-bars span { animation: none; height: 9px; }
}

/* Footer */
.footer {
  gap: 12px;
  position: fixed;
  bottom: 0; left: 0; right: 0;
  padding: 0 24px;
/*  background: linear-gradient(to top,
    var(--bg) 0%,
    rgb(from var(--bg) r g b / 0.45) 40%,
    rgb(from var(--bg) r g b / 0) 100%); */
background: linear-gradient(
    to top,
    var(--bg) 0%,
    rgb(from var(--bg) r g b / .92) 8%,
    rgb(from var(--bg) r g b / .76) 18%,
    rgb(from var(--bg) r g b / .58) 30%,
    rgb(from var(--bg) r g b / .38) 44%,
    rgb(from var(--bg) r g b / .22) 58%,
    rgb(from var(--bg) r g b / .10) 74%,
    rgb(from var(--bg) r g b / .04) 88%,
    transparent 100%
);
  display: flex;
  justify-content: flex-end;
  align-items: center;
  color: var(--muted);
  font-size: var(--font-mini);
  z-index: 99;
  height: var(--footer-height);
}

.footer-btn {
  letter-spacing: 0.5px;
  padding: 12px;
  background: none;
  border: none;
  font-size: var(--font-mini);
  color: var(--accent);
  cursor: pointer;
  transition: opacity var(--transition-fast);
}

/* ==== Category menu ==== */
/* No own glass/blur (the fixed header already provides the frosted band). The
   menu is fixed and its left/top/width are interpolated by JS against the scroll
   position (see initMenuMorph in cards.js): at the top of the page it is a full
   row just under the header; as you scroll it shrinks and slides into the gap
   between the logo and the header buttons, becoming part of the header while
   staying fully functional (scroll, category select, search). It is never
   hidden — only repositioned. Sides fade to transparency; horizontal scroll
   stays. #contentGrid reserves its resting footprint via --menu-h. */
.category-menu {
  display: none;               /* shown only on the welcome screen (rule below) */
  position: fixed;
  z-index: 11;                 /* sits in the header band, above the header bg */
  margin: 0;
  padding: var(--padding) 8px var(--padding) var(--padding);
  /* left / top / width are driven by JS per scroll frame */
  left: 0;
  top: 0;
  width: 100%;
  will-change: left, top, width;
}
/* Present on every screen: on welcome it morphs with the scroll, elsewhere JS
   keeps it fully tucked so only the search box shows (see applyScreenState in
   cards.js). The menu is an app-level sibling placed after the screens. */
.category-menu { display: block; }

.category-scroll {
  display: flex;
  gap: 3px;
  width: 100%;
  overflow-x: auto;
  white-space: nowrap;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* keep the sides fading into transparency (scroll affordance) */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 7%, #000 93%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, #000 7%, #000 93%, transparent 100%);
}
.category-scroll::-webkit-scrollbar { display: none; }
.category-scroll > *:last-child { margin-right: var(--padding); }

/* The menu is fixed (out of flow); reserve its resting footprint above the grid */
#contentGrid { margin-top: var(--menu-h, 40px); }

.category-btn {
  display: inline-block;
  height: 28px;
  padding: 6px 14px;
  font-size: var(--font-base);
  background: none;
  color: var(--menu-btn-text);
  border: none;
  cursor: pointer;
  transition: color var(--transition-fast);
  /* JS fades + blurs these out as the menu collapses to just the search
     (vars on .category-menu, eased per-frame — see initMenuMorph in cards.js) */
  opacity: var(--cat-op, 1);
  filter: blur(var(--cat-blur, 0px));
  pointer-events: var(--cat-pe, auto);
}
.category-btn.active {
  background: var(--btn-bg-active);
  color: var(--btn-text-active);
  -webkit-mask: radial-gradient(
    ellipse 50% 50% at center,
    #000 70%,
    transparent 100%
  );
  mask: radial-gradient(
    ellipse 50% 50% at center,
    #000 70%,
    transparent 100%
  );
}
/* ==== Search ==== */
/* Search input field */
.search-wrapper {
  display: inline-flex;
  height: 28px;
  align-items: center;
  cursor: text;
  transition: all 0.3s ease;
}
.search-icon {
opacity: var(--opacity-min);
 padding: 6px;
}
.search-field {
  background: transparent;
  border: none;
  outline: none;
  color: inherit;
  font-size: var(--font-base);
  width: 50px;
  transition: width 0.3s ease;
}
.search-field::placeholder { color: inherit; opacity: var(--opacity-min); }
.search-wrapper:focus-within .search-field { width: 120px; }

/* ==== Search popup (core/search.js) ==== */

/* While the popup is open the search field must stay above the backdrop so it
   remains typable (the menu otherwise sits at z-index 11, below the backdrop).
   The .search-closing state keeps the lift through the fade-out (see below). */
body.search-open .category-menu,
body.search-closing .category-menu { z-index: 160; }

/* Freeze the page while searching: only the suggestions list scrolls, so on a
   phone the content behind the keyboard can't be dragged around. */
body.search-open .screen.active,
body.search-closing .screen.active { overflow: hidden; touch-action: none; }

/* Full-screen frost behind the dropdown: it blurs the whole viewport (content,
   including the areas under the header and footer). On top of the blur it lays a
   faint wash of the theme background colour — so everything that gets frosted
   reads lighter in the light theme and darker in the dark theme. That wash is
   what carries the suggestion text's legibility (no per-item backing), since it
   lifts contrast against whatever bright/busy content is blurred behind.
   The header bar, footer, category menu and search field are lifted ABOVE this
   layer while searching (rules below), so they stay sharp and their
   transparency→theme-colour gradients sit on top — the blur dissolves into the
   page→browser-chrome transition at the very top and bottom. It still captures
   taps everywhere (pointer-events span the whole element). */
.search-backdrop {
  position: fixed;
  inset: 0;
  z-index: 140;
  pointer-events: none;
  background: transparent;
  backdrop-filter: blur(0px);
  -webkit-backdrop-filter: blur(0px);
  transition: backdrop-filter var(--transition-fast),
              -webkit-backdrop-filter var(--transition-fast),
              background var(--transition-fast);
}
.search-backdrop.is-open {
  pointer-events: auto;
  background: color-mix(in srgb, var(--bg), transparent 75%);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
}

/* Lift the chrome above the blur so it stays sharp and its bg-colour gradients
   stay on top (the blur fades into them at the very top/bottom — seamless with
   the browser). The header's own progressive veil blur is dropped meanwhile, so
   the frosting under the header matches the rest of the page. The lift is held
   through .search-closing so the chrome doesn't dip below the still-fading blur
   on the way out (which made the header flicker blurred for a frame). */
body.search-open header.bar,
body.search-closing header.bar,
body.search-open .footer,
body.search-closing .footer,
body.search-open .top-announcement,
body.search-closing .top-announcement { z-index: 160; }
/* Hand the header/footer blur off to the full-page frost without a gap.
   The veils carry their own backdrop-filter; the frost (.search-backdrop) ramps
   its blur in over --transition-fast. opacity animates instantly but the frost's
   backdrop-filter ramps slowly/late (especially on iOS Safari), so crossfading
   the two left a beat where the veils were gone but the frost hadn't arrived —
   the header/footer flashed sharp. Instead we HOLD the veils fully on (opacity 1)
   for the whole ramp, then snap them off once the frost fully covers the region:
   `0s linear <delay>` keeps opacity at 1 through the delay, then jumps to 0. The
   region therefore stays continuously blurred (briefly doubled, which is
   invisible) with no gap. On close the class is removed and the base
   .top-veil/.bottom-veil rule (no transition) snaps the veils back instantly.
   opacity, not display: toggling display:none would tear down the veils'
   backdrop-filter layer and make it cold-start a frame later on return. */
body.search-open .top-veil,
body.search-open .bottom-veil {
  opacity: 0;
  transition: opacity 0s linear var(--transition-fast);
}

/* Dropdown anchored under the search field (top/left/max-width/max-height set
   by JS). The whole page is already frosted, so the panel itself carries no
   card, border, tint or blur — just the (sharp) list floating over the frost.
   Its width follows the content (max-content), so a short result set leaves the
   space beside it as backdrop — tapping there closes the search. */
.search-panel {
  position: fixed;
  z-index: 150;
  width: max-content;
  pointer-events: none;
}
.search-panel.is-open { pointer-events: auto; }

.search-panel-inner {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;   /* don't chain scroll to the page underneath */
  /* no-ptr (set in search.js) disables pull-to-refresh while scrolling results,
     but its .no-ptr { touch-action: pan-x } would also block vertical scroll —
     restore it here so the list still scrolls up/down. */
  touch-action: pan-y;
  scrollbar-width: none;
  padding: 14px 10px;
  /* No backing of its own — the list floats directly over the frosted, washed
     backdrop (see .search-backdrop), which is what carries legibility. */
  /* Fade the scroll edges into transparency instead of hard-clipping the list
     against the panel bounds — same soft edge-dissolve the rest of the site
     uses (top/bottom veils, marquee fades). The fade band is a touch wider than
     the 14px padding so results melt out as they scroll past the top/bottom. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%);
  transform: translateY(-8px) scale(0.99);
  transform-origin: top center;
  opacity: 0;
  transition: transform var(--transition-fast), opacity var(--transition-fast);
}
.search-panel-inner::-webkit-scrollbar { display: none; }
.search-panel.is-open .search-panel-inner { transform: none; opacity: 1; }

.search-section-title {
  font-size: var(--font-mini);
  letter-spacing: 0.08em;
  color: var(--text);
  opacity: var(--opacity-min);
  padding: 12px 8px 4px;
}

.search-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: fit-content;
  padding: 10px 8px;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  color: var(--text);
  transition: background var(--transition-fast);
}

.search-item:active { transform: var(--scale-base); }

.search-item-ic {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-large);
  color: var(--accent);
  opacity: var(--opacity-base);
}
/* Programs / service pages show their own artwork instead of a glyph */
.search-item-ic.is-img { opacity: 1; }
.search-item-img {
  width: 30px;
  height: 30px;
  object-fit: cover;
  display: block;
}
.search-item-body { display: flex; flex-direction: column; min-width: 0; }
.search-item-title {
  font-size: var(--font-base);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.search-item-sub {
  font-size: var(--font-mini);
  opacity: var(--opacity-min);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Trailing badge on external links — signals the item opens in a new tab */
.search-item-ext {
  flex: 0 0 auto;
  margin-left: 6px;
  font-size: var(--font-small);
  color: var(--accent);
  opacity: var(--opacity-base);
}

.search-empty {
  padding: 18px 8px;
  font-size: var(--font-small);
  color: var(--text);
  opacity: var(--opacity-min);
  text-align: center;
}

/* "Here it is!" pulse when search jumps to a track on the album page. The
   highlight lives on ::after behind the (crisp) text and fades out at the left
   and right edges into transparency — the mini-player edge-fade language. */
.track-row.track-flash::after { animation: trackFlash 2s ease; }
@keyframes trackFlash {
  0%, 100% { opacity: 0; }
  18%, 55% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .track-row.track-flash::after { animation: none; }
}

/* ==== Cards & grid ==== */

.content-grid {
  max-width: var(--max-width-grid);
  margin: 0 auto;
  padding-top: 16px;
  padding-bottom: 16px;
  column-gap: var(--grid-gap);
  column-fill: balance;
  display: masonry; /* Note: Experimental/Polyfilled */
}

@media (min-width: 360px)   { .content-grid { column-count: 2; } }
@media (min-width: 600px)   { .content-grid { column-count: 3; } }
@media (min-width: 840px)   { .content-grid { column-count: 4; } }
@media (min-width: 1200px)  { .content-grid { column-count: 5; } }
@media (min-width: 1550px)  { .content-grid { column-count: 6; } }

.card {
  position: relative;
  display: block;
  overflow: hidden;
  break-inside: avoid;
}
.card:hover {
  z-index: 2;
}

.card:active {opacity: var(--opacity-min); transform: var(--scale-base);}

/* Shimmer */

.card, .album-cover, .project-cover {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-base);
}


.card::after,
.album-cover::after,
.project-cover::after,
.science-cover-wrapper::after,
.carousel-item::after,
.tarot-front::after,
.tarot-back::after  {
  content: "";
  position: absolute;
  top: 0; left: 0; width: 100%; height: 100%;
  z-index: 2;

  background: linear-gradient(
    110deg,
    transparent 35%,
    var(--shimmer) 50%,
    transparent 65%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite linear;

  opacity: 1;
  transition: opacity 0.5s ease;
  pointer-events: none;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.card.loaded::after,
.album-cover.loaded::after,
.project-cover.loaded::after,
.science-cover-wrapper.loaded::after,
.carousel-item.loaded::after,
.tarot-front.loaded::after,
.tarot-back.loaded::after  {
  opacity: 0;
  animation: none;
}

/* Lazy loading */
img.lazy-load {
  opacity: 0;
  transition: opacity 0.5s ease-in;
  background-color: rgba(255, 255, 255, 0.05);
}

img.lazy-load.is-loaded {
  opacity: 1;
}

.card img {
  width: 100%;
  display: block;
  object-fit: cover;
  border: none !important;
  outline: none !important;
  color: transparent !important;
  border-radius: var(--radius-base);
}

.card.square img  { aspect-ratio: 1 / 1; }
.card.rect34 img  { aspect-ratio: 3 / 4; }
.card.rect43 img  { aspect-ratio: 4 / 3; }

.card-title {
  padding: 8px 10px;
  font-size: var(--font-base);
  /* alpha baked into the colour (no opacity) so the menu's backdrop-filter
     blurs this text like it blurs the images — see --card-title-color */
  color: var(--card-title-color);
}
.card-subtitle {
  padding: 0 10px 10px;
  font-size: var(--font-small);
  color: var(--card-sub-color);
}

/* Hidden while the cover image is loading, so the shimmer plays over a clean
   surface instead of text peeking through it — same fade-in timing as the
   image (both classes get added together, see cards.js MIN_SHIMMER_MS), so
   title/subtitle and photo appear as one synchronized reveal. */
.card-title, .card-subtitle {
  opacity: 0;
  transition: opacity 0.5s ease-in;
}
.card.loaded .card-title, .card.loaded .card-subtitle {
  opacity: 1;
}

/* ==== Shared page layouts ==== */

/* Common Two-Column Page Structure */
.memory-main, .project-main, .album-main, .download-main, .library-main {
  margin-top: var(--gap);
  width: 100%;
}

/* Mobile First: Stacked Layout */
.memory-layout, .project-layout, .album-layout, .library-layout {
  width: 100%;
  max-width: var(--max-width);
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Left Column */
.memory-col-left, .project-col-left, .album-col-left, .library-col-left {
  text-align: center;
  width: 100%;
}

/* Right Column */
.memory-col-right, .project-col-right, .album-col-right, .library-col-right {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  margin-top: 24px;
  text-align: center;
}

/* Titles */
.memory-title, .project-title, .album-title {
  color: var(--text);
  margin: var(--padding) 0 12px;
  text-align: center;
  font-size: var(--font-large);
}

/* Desktop layout */
@media (min-width: 840px) {
  .memory-layout, .project-layout, .album-layout, .library-layout {
    width: auto;
    margin: 0 auto;
    display: grid;

    grid-template-columns: 420px minmax(0, 420px);
    column-gap: calc(var(--gap) * 3);
    align-items: flex-start;
    justify-content: center;
  }

.library-layout {
grid-template-columns: 540px minmax(0, 540px);
}

  .project-col-right, .memory-col-right, .album-col-right {
    text-align: left;
  }

  }

/* ==== Memory page ==== */
.carousel-container {
  position: relative;
   margin: 0 auto;
  max-width: var(--max-width-l2);
  overflow: hidden;
  box-shadow: var(--shadow-max);
  border-radius: var(--radius-base);
}
.carousel-track {
  display: flex;
  width: 100%;
  height: 100%;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.carousel-track::-webkit-scrollbar { display: none; }
.carousel-item {
  flex: 0 0 100%;
  width: 100%;
  object-fit: cover;
  display: block;
  scroll-snap-align: center;
  color: transparent !important;
  border: none !important;
  outline: none !important;
  position: relative;
  overflow: hidden;
}

.carousel-item.is-zooming {
  overflow: visible !important;
  z-index: 99;
}

.carousel-item img, .carousel-item video {
  transition: transform 0.3s ease-out;
  transform-origin: center center;
  position: relative;
}

.carousel-item.is-zooming img,
.carousel-item.is-zooming video {
  transition: none !important;
}

.carousel-item::after {
  pointer-events: none;
}

.carousel-dots {
   position: absolute;
   width: 100%;
  bottom: 12px;
  display: flex;
  justify-content: center;
  gap: 6px;
  z-index: 2;
  pointer-events: none;
}
.dot {
  width: 6px; height: 6px;
  background: var(--muted);
  border-radius: var(--radius);
  transition: transform 0.2s, background 0.2s;
  box-shadow: var(--shadow-light);
}
.dot.active { background: #fff; transform: scale(1.3); }

/* ==== Project page ==== */
.project-cover {
  width: 100%;
  max-width: var(--max-width-l2);
  aspect-ratio: 3 / 4;
 box-shadow: var(--shadow);
  overflow: hidden;
  margin: 0 auto;
}
.project-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
   color: transparent !important;
  border: none !important;
  outline: none !important;
}

.project-desc {
opacity: var(--opacity-base);
font-size: var(--font-base);
white-space: pre-wrap;
}

/* ==== Album page ==== */
.album-cover {
  width: 100%;
  max-width: var(--max-width-l2);
  aspect-ratio: 1/1;
  overflow: hidden;
  margin: 0 auto;
  box-shadow: var(--shadow-max);
}
.album-cover img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
color: transparent !important;
border: none !important;
outline: none !important;}

.album-buttons {
  display: flex;
  justify-content: center;
  gap: var(--gap);
  margin-bottom: var(--gap);
  margin-top: var(--gap) ;
}
.album-btn {
  cursor: pointer;

  width: 72px;
  height: 32px;

  border: none;
  background: var(--btn-bg-active);
  color: var(--btn-text-active);

  box-shadow: var(--shadow-light);

  font-size: var(--font-base);
  opacity: var(--opacity-base);

  transition:
    transform var(--transition-fast),
    opacity var(--transition-fast);

  -webkit-mask:
    radial-gradient(
      ellipse 50% 50% at center,
      #000 75%,
      transparent 100%
    );

  mask:
    radial-gradient(
      ellipse 50% 50% at center,
      #000 75%,
      transparent 100%
    );
}

.album-btn:active { transform: var(--scale-min); opacity: var(--opacity-min); }

.tracklist {
  width: 100%;
  max-width: var(--max-width-l2);
  margin: auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.track-row {
   display: flex;
  align-items: flex-end;
  gap: var(--gap);
  grid-template-columns: 28px 1fr 48px;
  align-items: center;
  padding: 0 var(--gap);
  cursor: pointer;
  position: relative;
}
.track-row:active { transform: var(--scale-base); }
/* Row content sits above the search-flash highlight (::after, z-index 0) */
.track-row > * { position: relative; z-index: 1; }
.track-row::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: color-mix(in srgb, var(--accent), transparent 78%);
  mask-image: linear-gradient( to right, transparent 0%, #000 12%, #000 88%, transparent 100% ), linear-gradient( to bottom, transparent 0%, #000 20%, #000 80%, transparent 100% );
  mask-composite: intersect;
  -webkit-mask-composite: source-in;
  opacity: 0;
  pointer-events: none;
}

.track-index, .track-duration {
  margin: var(--gap) 0;
  color: var(--text);
  font-size: var(--font-small);
  opacity: var(--opacity-min);
}
/* Fixed size: switching the index 01 to ▶/❚❚ doesn't change the row height
   or shift neighbouring tracks and the content below */
.track-index {
  width: 24px;
  line-height: 1.5;
  text-align: left;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.track-title {
  color: var(--text);
  font-size: var(--font-base);
  text-align: left;
}

.track-dots {
   flex-grow: 1;
   font-size: var(--font-large);
  border-bottom: 1px dotted;
  color: var(--muted);
   position: relative;
  top: 4px;

}

.track-index.is-playing-icon {
color: var(--accent);
opacity: 1;
 }

.album-summary {
  padding-top: 24px;
  width: 100%;
  color: var(--text);
  opacity: var(--opacity-mini);
  font-size: var(--font-small);
  text-align: center;
}

.track-row.is-locked {
    opacity: var(--opacity-mini);
    cursor: default;
    filter: grayscale(1);
    pointer-events: none !important;
}

.track-row.is-locked .track-duration {
    font-style: italic;
    font-size: var(--font-mini);
}

/* ==== Scientific page ==== */
.science-cover-wrapper {
  position: relative;
  width: 100%;
  aspect-ratio: 210 / 297;
  max-width: var(--max-width-l2);
  overflow: hidden;
  margin: 0 auto;
  box-shadow: var(--shadow-max);
}
.download-pdf {
  font-size: var(--font-base);
  color: var(--accent);
  cursor: pointer;
  opacity: var(--opacity-min);
  transition: opacity 0.2s ease;
  text-align: center;
  display: block;
  margin-top: 10px;
}

/* ==== Video page ==== */
.video-page-container {
  width: 100%;
  max-width: var(--max-width-l0);
  margin: 0 auto 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;

}
/* Custom video-player controls — mini-player visual language */
.vp-controls {
  display: flex;
  align-items: center;
  gap: var(--gap);
  width: 100%;
  margin: 10px auto 0;
  padding: 0 4px;
  box-sizing: border-box;
}

.vp-center { flex: 1; display: flex; flex-direction: column; }

.vp-time-row {
  display: flex; justify-content: space-between;
  font-size: var(--font-mini); opacity: var(--opacity-min); margin-bottom: 3px;
  color: var(--text);
}

.vp-progress-wrap {
  position: relative;
  width: 100%;
  height: 5px;
  background: var(--muted);
  opacity: var(--opacity-min);
  overflow: hidden;
  border-radius: var(--radius-base);
}

#vpProgressBar {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 0%;
  background: var(--accent);
}

#vpSeek {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  border: none;
  outline: none;
  opacity: 0;
  cursor: pointer;
}

.vp-btn {
  opacity: var(--opacity-base);
  background: none;
  border: none;
  font-size: var(--font-large);
  color: var(--text);
  cursor: pointer;
  padding: 6px;
}

.vp-btn:active { transform: var(--scale-min); }

.vp-btn.is-muted { opacity: var(--opacity-micro); }

.video-player-wrapper {
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  background: #000;
  overflow: hidden;
  box-shadow: var(--shadow-max);
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: var(--radius-base);
}
#mainVideoPlayer {
  width: auto;
  height: auto;
  display: block;
  outline: none;
}
.video-info-block {
  margin-top: 10px;
  text-align: left;
}
.video-title {
  color: var(--text);
  margin: 24px 0 6px 0;
  line-height: 1.2;
  font-size: var(--font-large);
}

/* ==== Download page ==== */
.download-main {
  display: flex;
  justify-content: center;
  padding-top: 16px;
}
.download-layout {
  width: 100%;
  max-width: var(--max-width-l4);
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  align-items: stretch;
}
.download-cover {
  width: 100%;
  overflow: hidden;
}
.download-cover img { width: 100%; height: auto; display: block; object-fit: cover; }
.download-info { text-align: left; color: var(--text); }
.download-title { font-size: var(--font-base); margin: 12px 0 4px; }
.download-artist { font-size: var(--font-base); opacity: var(--opacity-base); margin: 0 0 12px; }
.download-row {
  display: flex;
  justify-content: space-between;
  font-size: var(--font-small);
  opacity: var(--opacity-base);
  margin-bottom: 6px;
}
.download-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: var(--padding);
  color: var(--accent);
  text-decoration: none;
  font-size: var(--font-base);
  cursor: pointer;
  transition: transform var(--transition-fast), opacity var(--transition-fast);
}
.download-btn:active { transform: var(--scale-min); opacity: var(--opacity-min); }

.password-reveal {
  font-family: monospace;   cursor: pointer;
  transition: filter 0.3s ease, opacity 0.3s;
  user-select: none;
}

.password-reveal.is-blurred {
  filter: blur(3px);
  opacity: var(--opacity-min);
}

.password-reveal:not(.is-blurred) {
  filter: blur(0);
  opacity: 1;
  user-select: text;
  color: var(--accent);   }

/* ==== Settings page ==== */
.settings-container {
  width: 100%;
  max-width: var(--max-width-l2);
  padding: 0 var(--padding);
  margin: var(--margin-top) auto 0;
  box-sizing: border-box;
}
.setting-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--padding) 0;
}
.setting-label {
  font-size: var(--font-base);
  opacity: var(--opacity-base);
  flex: 1;
  white-space: nowrap;
}
.setting-value-btn {
  background: none;
  border: none;
  padding: 0;
  font-size: var(--font-base);
  color: var(--text);
  cursor: pointer;
  opacity: var(--opacity-min);
  transition: opacity 0.2s ease;
  text-align: right;
}

/* ==== Info pages ==== */
.info-wrap {
  margin: auto;
  color: var(--text);
  max-width: var(--max-width);

}

.meta-wrap { margin: auto; text-align: center; max-width: var(--max-width);}
.meta-title {
color: var(--accent);
 text-align: left;
 margin-bottom: var(--gap);
 font-size: var(--font-large);
 opacity: var(--opacity-base);
 max-width: var(--max-width);
 }
.meta-text {
  text-align: left;
  font-size: var(--font-base);
  opacity: var(--opacity-base);
  margin-bottom: var(--gap);
}

.links-block {
line-height: 3;
align-items: center;
}

/* ==== Game of Life program ==== */
.game-container {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--max-width-l1);
   margin: 0 auto 0;
  box-sizing: border-box;
}

.life-canvas-wrapper {
  width: 100%;
  aspect-ratio: 16/9;
  background: none;
  overflow: hidden;
  cursor: crosshair;
  touch-action: none;
  box-shadow: var(--shadow);
  border-radius: var(--radius-base);
}

.game-controls {
display: flex;
  justify-content: center;
  gap: var(--gap);
  margin-bottom: var(--padding);
  margin-top: var(--padding);
}

.game-btn {
  cursor: pointer;
  width: 72px;
  height: 32px;
  border: none;
  background: var(--btn-bg-active);
  color: var(--btn-text-active);
  box-shadow: var(--shadow-light);
  font-size: var(--font-base);
  opacity: var(--opacity-base);
  transition:
    transform var(--transition-fast),
    opacity var(--transition-fast);
  -webkit-mask:
    radial-gradient(
      ellipse 50% 50% at center,
      #000 75%,
      transparent 100%
    );
  mask:
    radial-gradient(
      ellipse 36px 16px at center,
      #000 75%,
      transparent 100%
    );
}

.game-btn:active { transform: var(--scale-min); opacity: var(--opacity-min); }

.program-title {
text-align: left;
margin-top: var(--padding);
font-size: var(--font-large);
}

.prog-desc {
  margin-top: 8px;
  font-size: var(--font-small);
  color: var(--text);
  text-align: justify;
  opacity: var(--opacity-min);
  line-height: 1.5;
}

/* Shared "leaves the site" badge — used after external link text (radio
   description, announcement banner). Sized in em so it scales with whatever
   text it's inline with; vertical-align only matters in plain-inline contexts
   like .anno-link (flex contexts like .radio-full-anchor use align-items
   instead and simply ignore it). */
.ext-icon {
  width: 0.85em;
  height: 0.85em;
  fill: currentColor;
  flex: 0 0 auto;
  vertical-align: -0.1em;
}

/* "…full version available at radio.echovestia.org [icon]" — inline link at
   the end of the radio description paragraph (radioDesc / fpRadioDesc) */
.radio-full-anchor {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--accent);
  white-space: nowrap;
  text-decoration: none;
  transition: opacity var(--transition-fast);
}
.radio-full-anchor:hover { opacity: var(--opacity-min); }

/* ==== Sound program ==== */

.sound-visual-wrapper {
  width: 100%;
  aspect-ratio: 16/9;
  background: transparent;
  margin-bottom: var(--padding);
  position: relative;
  overflow: hidden;
  box-shadow: var(--shadow);
  border-radius: var(--radius-base);
}
}

#soundCanvas {
  display: block;
  width: 100%;
  height: 100%;
  background: transparent;

}

.sound-type-selector {
  display: flex;
  justify-content: center;
  gap: var(--gap);
  margin-bottom: var(--padding);
  flex-wrap: wrap;
}

.sound-type-btn {
  background: transparent;
  border: var(--border);
  color: var(--muted);
  padding: 3px 6px;
  font-size: var(--font-small);
  cursor: pointer;
  transition: all 0.2s;
  border-radius: var(--radius);
  aspect-ratio: 7 / 1;
}

.sound-type-btn.active {
  border-color: var(--accent);
  color: var(--accent);
  font-weight: bold;
}

.sound-play-row {
margin-top: var(--padding);
margin-bottom: var(--padding);
display: flex;
gap: var(--gap);
justify-content: center;
}

.game-btn.disabled {
  opacity: var(--opacity-mini);
  pointer-events: none;
  filter: grayscale(1);
}

/* ==== Tarot program ==== */

.tarot-grid {
  display: flex;
  justify-content: center;
  gap: 20px;
  flex-wrap: wrap;
  width: 100%;
  margin-top: 20px;
  perspective: 1000px;
}

#tarotGrid {
  opacity: 0;
  transition: opacity 0.8s ease-in-out;
}
#tarotGrid.is-ready {
  opacity: 1;
}

.tarot-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 24%;
  max-width: 200px;
  min-width: 100px;
}

.tarot-card {
  width: 100%;
  aspect-ratio: 2 / 3.5;
  position: relative;
  cursor: pointer;
  background: transparent;
}

.tarot-inner {
  position: relative;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  transform-style: preserve-3d;
}

.tarot-card.flipped .tarot-inner {
  transform: rotateY(180deg);
}

.tarot-front, .tarot-back {
  position: absolute;
  width: 100%;
  height: 100%;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  border-radius: var(--radius-base);
  overflow: hidden;
  box-shadow: var(--shadow);
  border: 1px solid rgba(255,255,255,0.1);
}

.tarot-back {
  background-color: var(--accent);
  background-image: var(--tarot-back-image);
  background-size: cover;
  background-position: center;
}

.tarot-front {
  background-color: var(--accent);
  transform: rotateY(180deg);
}

.tarot-front img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.4s ease;
}

.tarot-front img.is-loaded {
  opacity: 1;
}

.tarot-meta {
  margin-top: 15px;
  text-align: center;
  min-height: 40px;
}

.tarot-pos {
  font-size: var(--font-small);
  opacity: var(--opacity-min);
  letter-spacing: 1px;
  margin-bottom: 4px;
}

.tarot-name {
  font-size: var(--font-base);
  color: var(--accent);
  opacity: 0;
  transform: translateY(-5px);
  transition: all 0.5s ease 0.2s;
}

.tarot-card.flipped + .tarot-meta .tarot-name {
  opacity: 1;
  transform: translateY(0);
}

#btnTarotReset, #btnTarotShare {
    transition: opacity 0.6s ease, transform 0.4s ease;
    opacity: 0;
}

#btnTarotReset.is-visible, #btnTarotShare.is-visible {
opacity: 1;
pointer-events: auto;

}

/* ---- Oracle block ---- */
.tarot-oracle {
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: max-height 1.2s ease, opacity var(--transition-slow);
  text-align: center;
  padding: 0 var(--padding-max);
  margin-top: var(--margin-plus);
  margin-bottom: var(--margin-plus);
}

.tarot-oracle.is-visible {
  max-height: 600px;
  opacity: 1;
}

.oracle-spinner {
  font-size: var(--font-large);
  display: inline-block;
  opacity: 0;
  transition: opacity var(--transition-slow);
  margin-bottom: var(--margin);
  color: var(--accent);
}

.tarot-oracle.is-loading .oracle-spinner {
  opacity: 1;
  animation: oracleSpin 6s linear infinite;
}

.tarot-oracle.is-done .oracle-spinner {
  opacity: var(--opacity-micro);
}

@keyframes oracleSpin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Text wrapper */
.oracle-text {
  position: relative;
  font-size: var(--font-base);
  line-height: 1.8;
  color: var(--text);
  opacity: var(--opacity-base);
}

/* ---- Per-word reveal ---- */
.oracle-word {
  display: inline;
  opacity: 0;
  animation: wordReveal 0.5s ease forwards;
}

@keyframes wordReveal {
  0% {
    opacity: 0;
    text-shadow: 0 0 12px rgba(100, 180, 255, 0.9), 0 0 24px rgba(100, 180, 255, 0.5);
    color: #9ecfff;
  }
  60% {
    opacity: 1;
    text-shadow: 0 0 6px rgba(100, 180, 255, 0.4);
    color: #b8d9ff;
  }
  100% {
    opacity: 1;
    text-shadow: none;
    color: inherit;
  }
}

.oracle-word.is-settled {
  text-shadow:
    0 0 6px  rgba(230, 206, 17, 0.15),
    0 0 12px rgba(100, 180, 255, 0.1);
  transition: text-shadow 0.6s ease;
}

/* ---- Sparkle stars ---- */
.oracle-sparkle {
  position: absolute;
  border-radius: var(--radius);
  pointer-events: none;
  z-index: 1;
  background: radial-gradient(circle,
  var(--sparkle-c1)  0%,
  var(--sparkle-c2) 40%,
  var(--sparkle-c3) 65%,
  transparent       75%
);
  animation: sparkleFade var(--sparkle-duration, 1.6s) ease-out forwards;
  transform: translate(-50%, -50%);
}

@keyframes sparkleFade {
  0%   { opacity: 0;    transform: translate(-50%, -50%) scale(0.3); }
  20%  { opacity: 1;    transform: translate(-50%, -50%) scale(1);   }
  70%  { opacity: 0.8;  transform: translate(-50%, -50%) scale(0.9); }
  100% { opacity: 0;    transform: translate(-50%, -50%) scale(0.5); }
}
/* ==== Library program ==== */

.library-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: var(--max-width-l2);
  margin: 0 auto;
}

.lib-sheet-wrapper {
  width: 100%;
  display: flex;
  justify-content: center;
}

.lib-sheet {
  aspect-ratio: 210 / 297;
  width: 100%;
  max-width: 100%;
  background: var(--bg);
  color: var(--text);
  padding: 25px 30px;
  box-sizing: border-box;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  font-family: 'Courier New', Courier, monospace;
  font-size: var(--lib-font-size, 12px);
  line-height: 1.5;
  text-align: justify;
  overflow: hidden;
  position: relative;
  height: auto;
}

.lib-sheet-header {
  border-bottom: var(--border);
  padding-bottom: 8px;
  margin-bottom: 10px;
  font-size: var(--lib-font-size, 10px);
  display: flex;
  gap: 10px;
  align-items: center;
  color: var(--text);
}

.lib-hex-container {
  display: flex;
  align-items: center;
  gap: 8px;
  overflow: hidden;
  flex: 1;
}

.lib-copy-btn {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  display: flex;
  color: var(--muted);
  align-items: center;
  justify-content: center;
  transition: color 0.2s;
}
.lib-copy-btn:hover { color: var(--text); }
.lib-copy-btn.success { color: var(--accent); }

.lib-highlight {
  background-color: var(--accent);
  color: var(--bg);
  padding: 0 2px;
}

.lib-volume-title {
  height: 2.4em;
  font-weight: bold;
  text-transform: uppercase;
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.lib-content {
  flex: 1;
  word-break: break-all;
  overflow: hidden;
  opacity: var(--opacity-base);
}

.lib-sheet-footer {
  margin-top: 10px;
  border-top: var(--border);
  padding-top: 8px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--lib-font-size, 11px);
  color: var(--text);
}

.lib-nav-arrow {
  background: transparent;
  border: none;
  font-size: var(--lib-font-size, 14px);
  cursor: pointer;
  color: var(--muted);
  padding: 0 10px;
}
.lib-nav-arrow:hover { color: var(--text); }

.lib-controls {
  width: 100%;
  margin-bottom: 30px;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.lib-toggle-group {
  display: flex;
  gap: var(--gap);
  margin-bottom: 5px;
}

.lib-toggle-btn {
  background: transparent;
  border: var(--border);
  color: var(--muted);
   font-size: var(--font-small);
  padding: 6px 12px;
  cursor: pointer;
}
.lib-toggle-btn.active {
  border-color: var(--accent);
  color: var(--accent);
}

.lib-search-row {
  display: flex;
  gap: var(--gap);
}

.lib-input {
  flex: 1;
  background: rgba(255,255,255,0.05);
  border: var(--border);
  color: var(--text);
  padding: 10px;
  font-family: monospace;
  font-size: var(--font-small);
}

.lib-action-btn {
  background: var(--accent);
  color: var(--bg);
  border: none;
  width: 44px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}

.lib-text-btn {
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  padding: 8px;
  cursor: pointer;
  font-size: var(--font-small)
}

/* Print */
@media print {

  /* 1. Kill all backgrounds — iOS Safari prints them even through visibility:hidden. */
  *, *::before, *::after {
    background:       transparent !important;
    background-color: transparent !important;
    background-image: none !important;
    box-shadow:       none !important;
  }

  html, body {
    background: white !important;
    margin: 0 !important;
    padding: 0 !important;
    overflow: visible !important;
  }

  /* 2. Hide all persistent visible chrome.
        Other screens stay hidden via their own .is-hidden {display:none !important}. */
  header,
  footer,
  .category-menu,
  .top-veil,
  .bottom-veil,
  #topAnnouncement,
  #ptrOverlay,
  #miniPlayer,
  #fullPlayer,
  .intro-overlay,
  .cookie-banner,
  #toastContainer,
  .ascii-controls { display: none !important; }

  /* 3. Hide non-sheet parts of the library page. */
  .library-col-right,
  .meta-line,
  h3.section-title,
  #libraryRelatedGrid,
  .lib-copy-btn,
  .lib-sheet-footer { display: none !important; }

  /* 4. Flatten the layout chain. */
  #libraryPage {
    display: block !important;
    position: static !important;
    height: auto !important;
    overflow: visible !important;
    opacity: 1 !important;
    transform: none !important;
    mask-image: none !important;
    -webkit-mask-image: none !important;
    padding: 0 !important;
  }

  .library-main,
  .library-layout,
  .library-col-left,
  .lib-sheet-wrapper {
    display: block !important;
    height: auto !important;
    overflow: visible !important;
    padding: 0 !important;
    margin: 0 !important;
    width: 100% !important;
  }

  /* 5. Sheet — static flow, no absolute box. */
  #libSheet {
    position: static !important;
    width: 100% !important;
    height: auto !important;
    min-height: 100vh !important;
    background: white !important;
    color: black !important;
    margin: 0 !important;
    box-sizing: border-box !important;
    transform: none !important;
    overflow: visible !important;
    aspect-ratio: unset !important;
    border: none !important;
    border-radius: 0 !important;
  }

  #libSheet * { color: black !important; }
  #libSheet .lib-content { overflow: visible !important; }
  #libSheet .lib-sheet-header { border-color: rgba(0, 0, 0, 0.15) !important; }

  @page {
    size: A4 portrait;
    margin: 0;
  }
}

/* ==== Radio program ==== */

.radio-fx-layer {
  position: fixed;
  top: 0; left: 0; width: 100%; height: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 3s ease-in-out;

  background:
    radial-gradient(circle at 20% 30%, rgba(var(--accent), 0.15), transparent 50%),
    radial-gradient(circle at 80% 70%, rgba(100, 100, 255, 0.1), transparent 50%);

  filter: blur(40px);
}

#radioPage.active-play .radio-fx-layer {
  opacity: 1;
  animation: radio-drift 20s infinite alternate ease-in-out;
}

@keyframes radio-drift {
  0% { transform: scale(1); }
  100% { transform: scale(1.2); }
}

.radio-visual-wrapper {
  width: 100%;
  aspect-ratio: 1;
  max-width: var(--max-width-l2);
  margin: 20px auto;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

.radio-visual-wrapper:active .radio-circle {
  transform: var(--scale-base);
  opacity: var(--opacity-mini);
}

.radio-circle {
  position: absolute;
  top: 0; left: 0; width: 100%; height: 100%;
  border-radius: var(--radius);

  background: radial-gradient(circle, var(--accent) 0%, transparent 70%);
  box-shadow: 0 0 20px rgba(var(--accent), 0.05);

  opacity: var(--opacity-micro);
  transition: all 1s ease;
  transform: scale(0.8);
}

.radio-circle.live {
  opacity: var(--opacity-mini);
  animation: radio-pulse 6s ease-in-out infinite;
}

@keyframes radio-pulse {
  0%, 100% { transform: scale(0.9); box-shadow: 0 0 20px rgba(var(--accent), 0.1); }
  50% { transform: scale(1.05); box-shadow: 0 0 50px rgba(var(--accent), 0.3); }
}

.radio-center-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.radio-play-icon {
  font-size: var(--font-large);
  color: var(--accent);
  opacity: var(--opacity-min);
  transition: opacity 0.3s;
}

.radio-status {
  font-family: monospace;
  font-size: var(--font-small);
  letter-spacing: 2px;
  opacity: var(--opacity-mini);
}

.radio-info-container {
  min-height: 20px;
  display: flex;
  justify-content: center;
}
.radio-info-text {
  margin-botton: 30px;
  font-size: var(--font-small);
  opacity: var(--opacity-mini);
}

/* ==== Sonata (piano) program ==== */
.piano-visual-wrapper {
  width: 100%;
  max-width: var(--max-width-l2);
  aspect-ratio: 4/3;
  margin: 20px auto 0;
  background: var(--accent);
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;

    -webkit-mask-image: radial-gradient(ellipse at center, black 0%, transparent 70%);
  mask-image: radial-gradient(ellipse at center, black 0%, transparent 70%);

}

#pianoCanvas {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scaleX(-1);
  opacity: var(--opacity-base);
  filter: grayscale(100%);
  transition: opacity 1s ease;
}

#pianoStatus {
  position: absolute;
  color: var(--muted);
  opacity: var(--opacity-mini);
  pointer-events: none;
}

/* ==== Natal chart program ==== */

.natal-wrapper {
  width: 90%;
  max-width: var(--max-width-l2);
  aspect-ratio: 1;
  margin: 20px auto;
  position: relative;
  border-radius: var(--radius);
  box-shadow: var(--shadow-invert);
  overflow: visible;

}

.zodiac-separator {
  position: absolute;
  top: 0; left: 50%;
  width: 1px;
  height: 100%;
  background: var(--gradient);
  pointer-events: none;
  z-index: 0;
}

.zodiac-ring {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 2;
}

.planet-marker {
  position: absolute;
  transform: translate(-50%, -50%);

  display: flex; align-items: center; justify-content: center;
  z-index: 10;
  transition: top 4s cubic-bezier(0.2, 0.8, 0.2, 1), left 1.5s cubic-bezier(0.2, 0.8, 0.2, 1);
  will-change: top, left;
}

.planet-marker span {
  font-size: clamp(14px, 4vw, 20px);
  background: var(--bg);
  box-shadow: 0 0 4px var(--accent);
  border-radius: var(--radius);
  padding: 2px;
  line-height: 1;
  color: var(--accent);
   background: transparent;
   box-shadow: none;
  animation: natal-float 5s ease-in-out infinite alternate;
  animation-delay: var(--delay);
}

@keyframes natal-float {
  0% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(0, -3px) scale(1.05); }
  100% { transform: translate(0, 0) scale(1); }
}

.natal-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 30px;
  width: 100%;
}

.natal-input-row {
  padding: 8px 15px;
  display: inline-flex;

}

.natal-date-input {
  background: transparent;
  border: none;

  color: var(--text);
  font-family: monospace;
  font-size: 14px;
  padding: 0;
  outline: none;
  text-align: center;
  color-scheme: dark;
}

.natal-actions-row {
  display: flex;
  gap: var(--gap);
  align-items: center;
  justify-content: center;
  width: 100%;
}

.natal-text-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--accent);
  font-size: var(--font-small);
  letter-spacing: 1px;
  padding: 5px;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: opacity 0.2s;
}
.natal-text-btn:not(:disabled):hover {
opacity: var(--opacity-min);
}

.natal-text-btn:not(:disabled):active { transform: var(--scale-base); }

/* While a reading is generating the Calculate button and the date are locked
   (see natal-ui.js). Grey them out with --muted and kill the press/hover/tap
   feedback so it's clearly inert — no transform "push", no cursor, no taps. */
.natal-text-btn:disabled {
  color: var(--muted);
  cursor: default;
  pointer-events: none;
}
.natal-date-input:disabled {
  color: var(--muted);
  -webkit-text-fill-color: var(--muted);  /* Safari greys disabled inputs via text-fill, not color */
  opacity: 1;
  cursor: default;
}

.natal-data-table {
  width: 100%;
  max-width: var(--max-width-l3);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr 60px;
  gap: 8px 15px;
  font-family: monospace;
  font-size: var(--font-small);
  opacity: var(--opacity-base);
}

.natal-row {
  display: contents;
}

.natal-cell {
  padding: 4px 0;
  border-bottom: var(--border);
}

.natal-cell.degree {
 text-align: right;
 }

/* ==== ASCII program ==== */
.ascii-main-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  margin: 20px auto ;
  gap: var(--padding-max);
}

@media (min-width: 720px) {
  .ascii-main-grid { grid-template-columns: repeat(3, 1fr); }
  .ascii-grid-display {gap: 2px;}
}

@media (min-width: 900px) {
  .ascii-main-grid { grid-template-columns: repeat(4, 1fr); }
  .ascii-grid-display {gap: 3px;}
}

.ascii-unit {
  --cell-size: var(--padding);
  position: relative;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  transition: all 0.3s ease;
}

.ascii-unit.is-active {
  border-color: var(--accent);
}

.ascii-grid-display {
  display: grid;
  grid-template-columns: repeat(8, var(--cell-size));
  grid-template-rows: repeat(8, var(--cell-size));
  gap: 1px;
  font-family: 'Courier New', Courier, monospace;
  font-size: var(--font-large);
  cursor: crosshair;
  user-select: none;
  touch-action: none;
}

.ascii-grid-display span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--cell-size);
  height: var(--cell-size);
  transition: all 0.2s ease;
  color: var(--muted);
  opacity: var(--opacity-mini);
}

.ascii-grid-display span.is-probe {

  color: var(--accent) !important;
  opacity: 1;
  transform: scale(1.1);
  z-index: 5;
}

.ascii-grid-display span.is-near {
  opacity: 1;

}

.ascii-fader {
  margin-top: var(--padding);
  height: 2px;
  -webkit-appearance: none;
  background-color: var(--muted);
  background-image: linear-gradient(to right, #489a7e 0%, #d3d92c 100%);
  background-repeat: no-repeat;
  background-size: 0% 100%;

  outline: none;
  cursor: pointer;
    position: absolute;
  bottom: calc(-1 * var(--padding));
  left: 50%;
  transform: translateX(-50%);
  width: 80%;
}

.ascii-fader::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--font-base);
  height: var(--font-base);
  background: var(--starlight);
  cursor: pointer;
  clip-path: polygon(
    50% 0%,
    65% 35%,
    100% 50%,
    65% 65%,
    50% 100%,
    35% 65%,
    0% 50%,
    35% 35%
  );
}

/* Firefox */
.ascii-fader::-moz-range-track {
  height: 2px;
  background: transparent;
}

.ascii-fader::-moz-range-thumb {
  width: var(--font-base);
  height: var(--font-base);
  border: none;

  background: var(--starlight);
  cursor: pointer;

  clip-path: polygon(
    50% 0%,
    65% 35%,
    100% 50%,
    65% 65%,
    50% 100%,
    35% 65%,
    0% 50%,
    35% 35%
  );
}

.ascii-controls {
  display: flex;
  gap: 20px;
  margin: auto;
  margin: var(--margin-plus) auto;
}

.ascii-unit.is-muted {
  filter: grayscale(1);
  opacity: var(--opacity-micro);
  pointer-events: none;
}

.ascii-unit:nth-child(3n+1) { --accent-mod: #44aaff; }
.ascii-unit:nth-child(3n+2) { --accent-mod: #ffaa44; }
.ascii-unit:nth-child(3n+3) { --accent-mod: #aa44ff; }

.ascii-grid-display span.is-near {
  color: var(--accent-mod);
}

/* ==== Theorem program ==== */

.theorem-screen {
  padding-top: 0 !important;
  padding-left: 0 !important;
  padding-right: 0 !important;
  gap: 0;
}

.theorem-content,
#theoremPage .section-title,
#theoremPage .content-grid {
  padding-left: 16px;
  padding-right: 16px;
}

.theorem-hero {
  position: relative;
  width: 100%;
  height: 100dvh;
  flex-shrink: 0;
  overflow: hidden;
  touch-action: pan-y;
  background: transparent;
  mask-image:
    linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, black 12%, black 88%, transparent 100%);
  mask-composite: intersect;
  -webkit-mask-image:
    linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, black 12%, black 88%, transparent 100%);
  -webkit-mask-composite: source-in;
}

#theoremVideo,
#theoremCanvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scaleX(-1);
  opacity: 0;
  transition: opacity 0.5s ease;
  pointer-events: none;
}

.theorem-hero--active #theoremVideo  { opacity: 0.9; }
.theorem-hero--active #theoremCanvas { opacity: 1; }

#theoremIdle {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  transition: opacity 0.5s ease;
}
.theorem-hero--active #theoremIdle { opacity: 0; }

.theorem-idle-ui {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: 46px;
  z-index: 4;
  opacity: 1;
  transition: opacity 0.3s ease;
  pointer-events: auto;
}

.theorem-hero--active .theorem-idle-ui {
  opacity: 0;
  pointer-events: none;
}

.theorem-loading {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  z-index: 5;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.theorem-loading.is-visible {
  opacity: 1;
}

.theorem-loading-text {
  font-size: var(--font-small);
  opacity: var(--opacity-min);
  color: var(--text);
  letter-spacing: 0.1em;
}

.theorem-loading-bar {
  width: 120px;
  height: 1px;
  background: color-mix(in srgb, var(--text) 15%, transparent);
  overflow: hidden;
}

.theorem-loading-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  transition: width 0.3s ease;
  animation: theorem-load-pulse 1.8s ease-in-out infinite;
}

@keyframes theorem-load-pulse {
  0%   { width: 0%;   opacity: 1; }
  60%  { width: 85%;  opacity: 1; }
  80%  { width: 95%;  opacity: 0.6; }
  100% { width: 100%; opacity: 0; }
}

.theorem-active-ui {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding-bottom: 54px;
  z-index: 7;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.theorem-hero--active .theorem-active-ui {
  opacity: 1;
  pointer-events: auto;
}

.theorem-hero-debug {
  text-align: center;
  padding: 0 16px;
}

.theorem-hero-debug .prog-desc {
  font-size: var(--font-mini);
  opacity: var(--opacity-mini);
  color: var(--text);
  background: color-mix(in srgb, var(--bg) 50%, transparent);
  padding: 4px 10px;
  display: inline-block;
}

.theorem-hero-controls,
.theorem-idle-controls {
  display: flex;
  justify-content: center;
  gap: 12px;
}

.theorem-ctrl-btn {

  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: var(--border) !important;
  touch-action: manipulation;
}

.theorem-ctrl-btn--start {
  min-width: 56px !important;
}

.theorem-ctrl-btn.is-open {
  opacity: 1 !important;
}

.theorem-settings-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  touch-action: pan-y;
}

.theorem-settings-overlay.is-open {
  pointer-events: auto;
}

.theorem-settings-panel {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: flex-end;
  padding-bottom: 84px;
  pointer-events: none;
}

.theorem-settings-panel.is-open .theorem-settings-inner {
  pointer-events: auto;
  opacity: 1;
  transform: translateY(0);
}

.theorem-settings-inner {
  width: 100%;
  padding: 20px 20px 42px;
  /* Fade on the inner itself (its own opacity/transform don't break its own
     backdrop-filter), so the blur appears together with the panel — not late */
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  display: flex;
  flex-direction: column;
  gap: calc(var(--gap) * 1.5);
  max-height: 72dvh;
  overflow-y: auto;
   background: transparent;
  mask-image:
    linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, black 12%, black 88%, transparent 100%);
  mask-composite: intersect;
  -webkit-mask-image:
    linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, black 12%, black 88%, transparent 100%);
  -webkit-mask-composite: source-in;
}

.theorem-setting-block {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.theorem-setting-label {
  font-size: var(--font-small);
  opacity: var(--opacity-min);
  padding-bottom: 4px;
  letter-spacing: 0.06em;
  font-size: 10px;
}

.theorem-scale-grid,
.theorem-root-grid,
.theorem-instrument-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.theorem-chip {
  padding: 5px 12px;
  font-size: var(--font-small);
  background: transparent;
  border: none;
  color: var(--text);
  cursor: pointer;
  opacity: var(--opacity-min);
  touch-action: manipulation;
}

.theorem-chip.active {
  border-color: transparent;
  cursor: pointer;
  width: 72px;
  height: 32px;
  border: none;
  background: var(--btn-bg-active);
  color: var(--btn-text-active);
  box-shadow: var(--shadow-light);
  font-size: var(--font-small);
  opacity: var(--opacity-base);
  transition:
    transform var(--transition-fast),
    opacity var(--transition-fast);
  -webkit-mask:
    radial-gradient(
      ellipse 50% 50% at center,
      #000 75%,
      transparent 100%
    );
  mask:
    radial-gradient(
      ellipse 36px 16px at center,
      #000 75%,
      transparent 100%
    );
}

.theorem-chip--symbol {
  font-size: 18px;
  padding: 6px 10px;
  min-width: 34px;
  text-align: center;
  line-height: 1;
}

.theorem-fx-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.theorem-fx-row {
  display: flex;
  align-items: center;
  gap: var(--gap);
  font-size: var(--font-small);
}

.theorem-fx-axis {
  flex: 1.2;
  opacity: var(--opacity-min);
  font-size: var(--font-small);
}

.theorem-fx-axis--symbol {
  font-size: 15px;
  min-width: 28px;
}

.theorem-fx-arrow {
  opacity: var(--opacity-micro);
  font-size: var(--font-mini);
}

.theorem-fx-select {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--text);
  font-size: var(--font-small);
  padding: 4px 6px;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  text-align: center;
  touch-action: manipulation;
}

.theorem-fx-select:focus { outline: none; }

.theorem-meta-wrap {
  padding: 0 16px;
}

/* ==== Chroma program ==== */

.chroma-portal-frame {
    cursor: pointer;
    position: relative;
    width: 90%;
    max-width: var(--max-width-l2);
    aspect-ratio: 7 / 7;
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: var(--shadow-max);
    margin: 20px auto;
}

#chromaCanvas {
    width: 100%;
    height: 100%;
    display: block;
}

#chromaStatusSymbol {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 20px;
    color: var(--accent);
    pointer-events: none;
    transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1), transform 0.8s ease;
    text-shadow: 0 0 20px var(--accent);
    opacity: 0.9;
}

.chroma-portal-frame.is-playing #chromaStatusSymbol {
    opacity: 0;
    transform: translate(-50%, -50%) scale(1.5);
}

.chroma-faders-row {
    display: flex;
    justify-content: space-around;
    width: 80%;
    height: 160px;
    margin: 32px auto;
}

.chroma-fader-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    position: relative;
    width: 40px;
}

.chroma-fader {
    -webkit-appearance: none;
    appearance: none;
    width: 120px;
    height: 2px;
    background: var(--muted);
    outline: none;
    transform: rotate(-90deg);
    position: absolute;
    top: 50px;
    background-image: linear-gradient(to right, var(--accent) 0%, var(--muted) 100%);
    background-repeat: no-repeat;
    background-size: 50% 100%;
}

.chroma-fader::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--font-base);
  height: var(--font-base);
  background: var(--accent);
  cursor: pointer;
  clip-path: polygon(
    50% 0%,
    65% 35%,
    100% 50%,
    65% 65%,
    50% 100%,
    35% 65%,
    0% 50%,
    35% 35%
  );
}

/* Firefox */
.chroma-fader::-moz-range-track {
  height: 2px;
  background: transparent;
}

.chroma-fader::-moz-range-thumb {
  width: var(--font-base);
  height: var(--font-base);
  border: none;

  background: var(--accent);
  cursor: pointer;

  clip-path: polygon(
    50% 0%,
    65% 35%,
    100% 50%,
    65% 65%,
    50% 100%,
    35% 65%,
    0% 50%,
    35% 35%
  );
}

.chroma-label {
    position: absolute;
    bottom: 0;
    font-size: 20px;
    color: var(--muted);
    opacity: 0.8;
}

/* ==== Mini player ==== */
.mini-player {
  position: fixed;
  bottom: var(--margin-plus);
  left: 0;
  right: 0;
  max-width: var(--max-width-l2);
  margin: 0 auto;
  padding: 8px;
  display: flex;
  align-items: center;
  gap: var(--gap);
  z-index: 98;
  /* Liquid glass is on ::before (below); the player content stays sharp.
     KEY: keep .mini-player free of transform / filter / opacity<1 / will-change
     — any of those makes it a "backdrop root" and ::before's backdrop-filter
     stops blurring the background (blur vanishes, then snaps in at the end).
     So: center via margin, slide on show/hide via bottom (not transform), and
     animate the blur/opacity reveal on ::before itself (its own backdrop-filter
     and opacity don't break the frost) and on the content (opacity). This keeps
     the frost alive through the whole animation. */
  background: none;
  box-shadow: none;
  pointer-events: auto;
  cursor: pointer;                  /* tap anywhere (except buttons) opens the full player */
  /* Only "bottom" is transitioned here (plain show/hide via MP.show()/hide()).
     The free-drag gesture below (core/mini-player.js) repositions via left/top
     instead and manages its own transition timing/easing per-gesture (a quick
     "fly away" on dismiss, a springier one on snap-back) — never transform,
     per the backdrop-filter note above. */
  transition: bottom 0.42s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* Mini-player liquid glass — same recipe as the category menu: translucent
   tint + background blur, with edge fade */
.mini-player::before {
  content: "";
  position: absolute;
  z-index: -1;
inset: -8px 0;
  background: var(--glass-tint);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  mask-image:
    linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, black 22%, black 78%, transparent 100%);
  mask-composite: intersect;
  -webkit-mask-image:
    linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, black 22%, black 78%, transparent 100%);
  -webkit-mask-composite: source-in;
  pointer-events: none;
  /* --drag-fade: set inline on .mini-player during the free-drag gesture
     (core/mini-player.js) to preview the dismiss fade while held. Driven here
     (and on the content below) rather than the root's own opacity — the root
     must stay opacity:1 always, or it becomes a "backdrop root" and this very
     backdrop-filter stops blurring the background (see the note above). */
  opacity: var(--drag-fade, 1);
  /* Glass reveal: blur ramps from 0 to full, tint from transparent to colour.
     These are ::before's own properties, so backdrop-filter keeps seeing the
     background throughout the animation. */
  transition:
    opacity 0.42s ease,
    backdrop-filter 0.42s ease,
    -webkit-backdrop-filter 0.42s ease;
}

/* Hide: the container slides down via bottom (no transform) */
.mini-player.is-hidden {
  display: flex !important;
  bottom: calc(var(--margin-plus) - 18px);
  pointer-events: none;
}

/* Glass fades to transparent and loses blur (mirror of the reveal) */
.mini-player.is-hidden::before {
  opacity: 0;
  backdrop-filter: blur(0px) saturate(1);
  -webkit-backdrop-filter: blur(0px) saturate(1);
}

/* Content (cover, text, buttons) fades out together with the glass, and
   previews the same way while being drag-held (--drag-fade, see ::before above) */
.mini-player > * { opacity: var(--drag-fade, 1); transition: opacity 0.42s ease; }
.mini-player.is-hidden > * { opacity: 0; }

.mp-left { display: flex; align-items: center; gap: var(--gap); flex: 1; min-width: 0; padding-left: 16px;}

.mp-cover {
  position: relative;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  overflow: visible;
  cursor: pointer;
}

.mp-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 2px;
}

.mp-text {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
overflow: hidden;
}

.mp-title {
color: var(--text);
font-size: var(--font-base);
opacity: var(--opacity-base);
cursor: pointer;
white-space: nowrap;
}

.mp-artist {
color: var(--text);
font-size: var(--font-small);
opacity: var(--opacity-min);
cursor: pointer;
white-space: nowrap;
}

/* Marquee: when a line doesn't fit, JS duplicates it into .mq-move and it
   loops continuously left with a gap, so the start follows the end seamlessly */
.mq-move {
  display: inline-flex;
  flex-wrap: nowrap;
  will-change: transform;
  animation: mp-marquee linear infinite;
}
.mq-seg {
  display: inline-block;
  white-space: nowrap;
  padding-right: 28px; /* the gap between the end and the repeating start */
}
/* Soft edges only while scrolling: text fades in/out instead of a hard cut.
   Static lines that fit (e.g. the artist name) keep crisp, unfaded edges. */
.mp-title.mq-on, .mp-artist.mq-on {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
}
@keyframes mp-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .mq-move { animation: none; }
}

/* Thin, non-interactive progress line along the bottom edge of the mini-player.
   It fades out at both horizontal ends (dissolving into the glass) so it reads
   as a soft hint, not a control. Seeking/time live in the full-screen player. */
.mp-progress-line {
  position: absolute;
  left: 32px; right: 32px; bottom: 2px;
  height: 2px;
  pointer-events: none;
}
#mpProgressBar {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--accent);
  opacity: var(--opacity-min);
  transition: width 0.25s linear;
}

.mp-controls {padding-right: 16px;}

.mp-btn {
opacity: var(--opacity-base);
background: none;
border: none;
font-size: var(--font-large);
color: var(--text);
cursor: pointer; }

.mp-btn:active {
transform: var(--scale-min);
opacity: var(--opacity-min);
}

/* ==== Full-screen player — morphs up from the mini-player ==== */
.full-player {
  position: fixed;
  /* Closed = a bottom strip roughly matching the mini-player footprint */
  left: var(--margin-plus);
  right: var(--margin-plus);
  bottom: var(--margin-plus);
  top: calc(100% - var(--margin-plus) - 56px);
  z-index: 300;                                 /* above header and mini-player */
  border-radius: 0;
  background: var(--glass-tint);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  /* Morph via insets (layout) — not transform — so the page blur behind stays
     alive throughout the open/close animation */
  transition:
    top 0.46s cubic-bezier(0.2, 0.8, 0.2, 1),
    left 0.46s cubic-bezier(0.2, 0.8, 0.2, 1),
    right 0.46s cubic-bezier(0.2, 0.8, 0.2, 1),
    bottom 0.46s cubic-bezier(0.2, 0.8, 0.2, 1),
    opacity 0.32s ease;
  /* The player's frosted top edge dissolves into the page above (bottom is the
     screen edge, so only the top fades). Content gets its own fade on .fp-scroll. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16px, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 16px, #000 100%);
}
.full-player.is-open {
  /* Open = full-bleed blur over the whole page, only a gap at the top */
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  opacity: 1;
  pointer-events: auto;
  padding-bottom: 72px;
}
.full-player.is-dragging { transition: none; }

/* The bottom edge dissolves into the page background colour, so the panel never
   forms a hard seam against the browser's bottom UI bar (address/gesture bar). */
.full-player::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 110px;
  background: linear-gradient(to bottom, transparent 0%, var(--bg) 88%, var(--bg) 100%);
  pointer-events: none;
  z-index: 1;            /* above the scrolling content, below the grabber (z-index:2) */
}

.fp-scroll {
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;     /* native rubber-band bounce on iOS */
  overscroll-behavior: contain;          /* keep the bounce here; don't scroll/PTR the page behind */
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap);
  padding: 48px 24px 14px;
  /* Content dissolves into transparency at the top and bottom as it scrolls.
     The grabber sits above this (unaffected); the page blur behind is untouched. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 64px, #000 calc(100% - 64px), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 64px, #000 calc(100% - 64px), transparent 100%);
}
.fp-scroll::-webkit-scrollbar { display: none; }
.fp-grabber {
  position: absolute;
  top: 20px; left: 0; right: 0;     /* just below the panel's top fade, always crisp */
  width: 44px; height: 4px; margin: 0 auto;
  border-radius: 2px;
  background: var(--text);
  opacity: var(--opacity-min);
  z-index: 2;                       /* fixed in place, above the scrolling content */
  cursor: grab;
  touch-action: none;               /* the grabber drives the collapse gesture */
}
/* Radio mode: hide the track controls and show the radio circle instead */
.fp-radio { display: none; width: 100%; flex-direction: column; align-items: center; gap: var(--gap); }
.full-player.mode-radio .fp-radio { display: flex; }
.full-player.mode-radio .fp-cover,
.full-player.mode-radio .fp-meta,
.full-player.mode-radio .fp-progress,
.full-player.mode-radio .fp-controls { display: none; }
.fp-radio-visual { width: min(62vw, 280px); max-width: 280px;}
.fp-radio-info {
  width: 100%; max-width: 480px; margin: 0 auto; text-align: center;
  /* Reserve the slot up-front so the title/desc below never jump when the
     now-playing line appears or disappears */
  min-height: 2.6em;
  display: flex; align-items: center; justify-content: center;
}
.fp-radio-info .radio-info-text {
  margin: 0;                       /* wrapper reserves the space, not a margin */
  font-size: var(--font-small); letter-spacing: 1px; line-height: 1.4;
}
.fp-radio-meta {max-width: 480px; width: 100%; }
.fp-radio-desc {
  font-size: var(--font-small); color: var(--text); opacity: var(--opacity-min);
  line-height: 1.6; margin-top: 8px;text-align: justify;
}

.fp-cover {
  width: min(64vw, 300px);
  aspect-ratio: 1 / 1;
  flex-shrink: 0;
  overflow: hidden;
  box-shadow: var(--shadow);
  cursor: default;                 /* cover is neutral (no navigation) */
  border-radius: var(--radius-base);
}
.fp-cover img { width: 100%; height: 100%; object-fit: cover; display: block; opacity: 1; transition: opacity 0.35s ease; }
.fp-meta { text-align: center; cursor: pointer; max-width: 480px; width: 100%; }
/* Title/artist stay on one line (ellipsis) so length never shifts the layout below */
.fp-title { font-size: var(--font-large); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; text-align: center;}
.fp-artist { font-size: var(--font-base); color: var(--text); opacity: var(--opacity-min); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.fp-progress { width: 100%; max-width: 360px; }
.fp-time-row { display: flex; justify-content: space-between; font-size: var(--font-mini); opacity: var(--opacity-min); margin-bottom: 6px; }
.fp-progress-wrap { position: relative; width: 100%; height: 5px; background: var(--muted); opacity: var(--opacity-min); overflow: hidden; border-radius: 2px;}
#fpProgressBar { position: absolute; left: 0; top: 0; height: 100%; width: 0%; background: var(--accent); }
#fpSeek { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; -webkit-appearance: none; appearance: none; background: transparent; border: none; outline: none; opacity: 0; cursor: pointer; }
.fp-controls { display: flex; align-items: center; justify-content: center; gap: 28px; margin-top: 4px; }
.fp-btn { background: none; border: none; color: var(--text); font-size: var(--font-large); cursor: pointer; opacity: var(--opacity-base); }
.fp-btn--play { font-size: calc(var(--font-large) * 1.4); }
.fp-btn:active { transform: var(--scale-min); opacity: var(--opacity-min); }
.fp-extra { width: 100%; max-width: 480px; margin-top: 10px; }
.fp-tracklist { display: flex; flex-direction: column; }
.fp-track {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 4px;
  font-size: var(--font-base);
  color: var(--text);
  cursor: pointer;
}
.fp-track:active { transform: var(--scale-base); }
.fp-track-i { width: 24px; flex-shrink: 0; font-size: var(--font-small); opacity: var(--opacity-min); }
.fp-track-t { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fp-track-d { flex-shrink: 0; font-size: var(--font-small); opacity: var(--opacity-min); }
.fp-track.is-current .fp-track-t { color: var(--accent); }
.fp-track.is-current .fp-track-i { color: var(--accent); opacity: 1; }

/* Project info (duplicated from the project page), below the tracklist */
.fp-project {
  width: 100%;
  display: flex; flex-direction: column;
  gap: 16px;
  margin-top: 28px; padding-top: 22px;
  border-top: var(--border);
}
.fp-project-head { display: flex; align-items: center; gap: 14px; text-align: left; cursor: pointer; }
.fp-project-head:active { transform: var(--scale-base); }
.fp-project-cover { width: 96px; flex-shrink: 0; aspect-ratio: 1 / 1; overflow: hidden;border-radius: 4px;}
.fp-project-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fp-project-headtext { flex: 1; min-width: 0; }
.fp-project-title { font-size: var(--font-large); color: var(--text); }
.fp-project-desc  { font-size: var(--font-small); color: var(--text); opacity: var(--opacity-min); margin-top: 3px; }
.fp-project-about { font-size: var(--font-small); color: var(--text); opacity: var(--opacity-min); line-height: 1.6; text-align: left; margin-bottom: 32px;}

/* ==== Mini player — radio mode ==== */

.mp-radio-circle {
  width: 100%;
  height: 100%;
  border-radius: var(--radius);

  background: radial-gradient(circle, var(--accent) 0%, transparent 70%);

  opacity: var(--opacity-mini);
  transform: scale(0.6);
  display: none;
}

.mp-radio-circle.live {
  animation: mp-pulse-mini 3s ease-in-out infinite;
}

@keyframes mp-pulse-mini {
  0%, 100% { transform: scale(0.6); opacity: var(--opacity-mini); }
  50% { transform: scale(0.85); opacity: 1; box-shadow: 0 0 15px var(--accent); }
}

#miniPlayer.mode-radio #mpCoverImg {
  display: none;
}

#miniPlayer.mode-radio .mp-radio-circle {
  display: block;
}

#miniPlayer.mode-radio .mp-progress-line,
#miniPlayer.mode-radio #mpNext {
  display: none !important;
}

/* ==== Sitemap page ==== */

.sitemap-wrap {
margin: auto;
max-width: var(--max-width);
margin-top: var(--margin-top);
}

.sitemap-container {
  display: flex;
  flex-direction: column;
  gap: 30px;
  margin-top: var(--margin-top);

  text-align: left;
}

.sitemap-col {
  display: flex;
  flex-direction: column;
  gap: 30px;
  width: 100%;
}

@media (min-width: 840px) {
  .sitemap-container {
    flex-direction: row;
    align-items: flex-start;
    gap: 60px;
  }

  .sitemap-col {
    flex: 1;
  }
}

.sitemap-group {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.sitemap-header {
  color: var(--accent);
  font-size: var(--font-base);
  padding-bottom: var(--gap);
  border-bottom: var(--border);
  opacity: var(--opacity-min);
}

.sitemap-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-left: var(--padding);
}

.sitemap-link {
  color: var(--text);
  text-decoration: none;
  opacity: var(--opacity-min);
  font-size: var(--font-base);
  transition: opacity 0.2s, transform 0.2s;
  display: block;
  cursor: pointer;
  padding: 4px 0;
}

.sitemap-link:hover {
  opacity: var(--opacity-min);
  color: var(--accent);
}

/* ==== Echo guestbook ==== */

.guestbook-wrap {
width: 100%;
max-width: var(--max-width-l2);
margin: 0 auto;
margin-top: var(--margin-top);
}

.echo-form-container {
    margin-top: var(--margin-top);
    background: var(--bg2);
    border: none;
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    margin-bottom: calc(var(--margin) * 4);
    transition: var(--transition-medium);
    padding: 30px;
        mask-image:
        linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 10%, black 90%, transparent 100%);
    mask-composite: intersect;
    -webkit-mask-image:
        linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 10%, black 90%, transparent 100%);
    -webkit-mask-composite: source-in;
    visibility: hidden;
    animation-delay: 0.9s;
    animation: screenFadeIn 2.1s ease forwards;
}

.echo-input-group {
    display: flex;
    flex-direction: column;
    gap: calc(var(--gap) / 2);
    border: none;
}

.echo-name-row {
    position: relative;
    width: 100%;
    border: none;
}

.echo-input, .echo-textarea {
    width: 100%;
    background: var(--bg);

    color: var(--text);
    padding: 10px 32px;
    font-family: var(--font-main);
    font-size: var(--font-base);
    transition: var(--transition-fast);
    box-sizing: border-box;
    mask-image:
        linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
    mask-composite: intersect;
    -webkit-mask-image:
        linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
    -webkit-mask-composite: source-in;
    border: none;
}

#echoUsername {
    padding-right: 120px;
    cursor: pointer;
}

.echo-form-container::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    pointer-events: none;
    z-index: -1;
}

.echo-input:focus, .echo-textarea:focus {
    outline: none;
    border-color: var(--accent);
    background: var(--bg);
}

.user-badge {
    position: absolute;
    right: 42px;
    top: 50%;
    transform: translateY(-50%) translateX(10px);
    font-size: var(--font-mini);
    color: var(--accent);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    z-index: 1;
    transition: opacity var(--transition-medium),
                transform var(--transition-medium);
}

.user-badge:not(:empty) {
    opacity: 1;
    transform: translateY(-50%) translateX(0);
}

.user-badge.public {
    opacity: var(--opacity-min);
}

.user-badge.clickable {
opacity: var(--opacity-min);
    pointer-events: auto;
    z-index: 10;
}

.echo-user-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 4px;
    background: var(--bg);
    border: none;
    max-height: 0;
    overflow-y: auto;
    opacity: 0;
    z-index: 10;
    transition: max-height var(--transition-medium) ease,
                opacity var(--transition-medium) ease;

          mask-image:
        linear-gradient(to right, transparent 0%, black 5%, black 75%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
    mask-composite: intersect;
    -webkit-mask-image:
        linear-gradient(to right, transparent 0%, black 5%, black 75%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
    -webkit-mask-composite: source-in;
    border: none;

}

.echo-user-dropdown.is-open {
    max-height: 200px;
    opacity: 1;
}

.echo-user-option {
    padding: 15px 30px;
    cursor: pointer;
    transition: background var(--transition-fast);
    font-size: var(--font-small);
    border-bottom: var(--border);

      mask-image:
        linear-gradient(to right, transparent 0%, black 10%, black 95%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
    mask-composite: intersect;
    -webkit-mask-image:
        linear-gradient(to right, transparent 0%, black 10%, black 95%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
    -webkit-mask-composite: source-in;
    border: none;

}

.echo-user-option:last-child {
    border-bottom: none;
}

.echo-user-option:hover {
    background: var(--bg2);
}

.echo-user-option.is-selected {
    background: var(--bg);
    color: var(--accent);
}

.echo-password-section {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--transition-slow) ease;
    overflow: hidden;
}

.echo-password-section:not(.is-hidden) {
    grid-template-rows: 1fr;
}

.echo-password-wrapper {
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: calc(var(--gap) / 2);
}

.echo-password-section .echo-input,
.echo-password-section .echo-error-msg {
    opacity: 0;
    transform: translateY(-5px);
}

.echo-password-section:not(.is-hidden) .echo-input,
.echo-password-section:not(.is-hidden) .echo-error-msg {
    opacity: 1;
    transform: translateY(0);
}

.echo-password-section .is-hidden {
    display: none;
}

.echo-textarea {
    min-height: 80px;
    resize: vertical;
    line-height: 1.5;
}

.echo-textarea.is-disabled {
    pointer-events: none;
    opacity: var(--opacity-min);
}

.echo-error-msg {
    color: var(--error);
    font-size: var(--font-mini);
    padding: 6px 12px;
}

.echo-tarot-container {
    display: inline-flex;
    gap: 8px;
    margin: var(--padding);
    flex-wrap: wrap;
}

.echo-tarot-card {
    opacity: var(--opacity-min);
    cursor: pointer;
    width: 50px;
    object-fit: cover;
    border-radius: 4px;
    border: var(--border);
    box-shadow: var(--shadow-light);
    animation: fadeIn 0.5s ease both;
}

.echo-tarot-preview .echo-tarot-card {
    width: 50px;
}

.echo-tarot-preview, .echo-msg-tarot {
    display: flex;
    gap: 8px;
}

.echo-tarot-preview img, .echo-msg-tarot img {
    height: auto;
    border-radius: 4px;
    border: var(--border);
    box-shadow: var(--shadow-light);
    animation: fadeIn 0.5s ease both;
}

.echo-music-preview {
  opacity: var(--opacity-micro);
  padding: 12px 24px;
}

#echoSendBtn {
    transition: var(--transition-fast);
    margin-left: var(--margin);
}

#echoSendBtn:disabled {
    opacity: var(--opacity-micro);
    pointer-events: none;
}

.echo-badge {
    font-size: var(--font-small);
    opacity: var(--opacity-min);
    display: flex;
    align-items: center;

}

/* ---- Messages list ---- */
.echo-messages-list {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    animation-delay: 0.12s;

}

.echo-item {
    padding: 16px 24px ;
     mask-image:
        linear-gradient(to right, transparent 0%, black 10%, black 85%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
    mask-composite: intersect;
    -webkit-mask-image:
        linear-gradient(to right, transparent 0%, black 10%, black 85%, transparent 100%),
        linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
    -webkit-mask-composite: source-in;
     animation: screenFadeIn 0.9s ease forwards;
     transition: opacity 0.5s ease;
}

.echo-item-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 5px;
}

.echo-username {
    font-size: var(--font-base);
    color: var(--accent);
}

.echo-username.is-public {
    color: var(--muted);
}

.echo-date {
    font-size: var(--font-mini);
    opacity: var(--opacity-micro);
}

.echo-music-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 8px;
    margin-top: 5px;
    font-size: var(--font-mini);
    opacity: 0.6;
    cursor: pointer;
}

.echo-item-content {
    font-size: var(--font-small);
}

/* Animation for new messages - smooth transitions for all */

.echo-status {
    text-align: center;
    padding: 60px 0;
    opacity: var(--opacity-micro);
    font-size: var(--font-base);
}

.load-more-echoes {
    margin: 40px auto;
    display: block;
}

.echo-error-msg {
    color: var(--error);
    font-size: var(--font-mini);
    margin-top: -5px;
    margin-bottom: 5px;
    margin-left: var(--margin);
    transition: all 0.3s ease;
}
.echo-error-msg.is-hidden {
    display: none;
}

/* ==== Intro overlay ==== */
.intro-overlay {
  position: fixed; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);

  -webkit-backdrop-filter: blur(15px);
  backdrop-filter: blur(15px);
  z-index: 9999;
  pointer-events: none;
  opacity: 1;
  transition: opacity 1s ease;
}
.intro-overlay.hidden { opacity: 0; pointer-events: none; }

.intro-logo {
  font-size: 96px;
  color: var(--accent);
  opacity: var(--opacity-base);
  transform-origin: center;
  will-change: transform, opacity;
  filter: drop-shadow(0 6px 18px rgba(0,0,0,0.5));
  transition: transform 1.2s cubic-bezier(.4,.8,.2,1), opacity 0.6s ease;
}
.intro-logo.fly { transform: translate(var(--tx), var(--ty)) scale(var(--s)); opacity: 1; }

/* Liquid-glass treatment for the flying star (intro + pull-to-refresh).
   The star ROTATES in flight and while refreshing, and at the reload "seam" the
   two stars meet at different angles — so the glass must be rotationally
   symmetric or the light/dark sides won't line up. Hence a centred radial sheen
   (bright core → dark rim) over an opaque accent base, and centred (offset-free)
   shadows. Rotation no longer changes the look. */
.intro-logo, .ptr-logo {
  background-image: var(--star-fill);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  filter:
    drop-shadow(0 0 1px rgba(255, 255, 255, 0.45))
    drop-shadow(0 0 11px rgba(0, 0, 0, 0.38));
}

/* Firefox renders -webkit-background-clip: text as INVISIBLE while the element is
   transformed/animated (the flying star + the rotating logo) — the glyph vanishes
   and "doesn't animate". Give Firefox a solid accent star instead (visible and
   animating, just without the sheen). Targeted via -moz-appearance support. */
@supports (-moz-appearance: none) {
  .intro-logo, .ptr-logo, .logo .icon {
    background-image: none;
    -webkit-text-fill-color: var(--accent);
    color: var(--accent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .intro-logo, .intro-logo.fly, .intro-overlay { transition: none !important; }
}
/* ==== Snowfall animation ==== */
.snowflake {
  position: fixed; top: -2em; z-index: 100;
  color: var(--muted); font-size: var(--font-large);
  user-select: none; pointer-events: none;
  animation: fall linear infinite;
}
@keyframes fall {
  to { transform: translateY(110vh); opacity: 0; }
}

/* ==== Welcome screen fade-out text ==== */
.welcome-title {
  text-align: center; margin-left: 3px; color: var(--text);
  animation: fadeOut 5s forwards;
}
@keyframes fadeOut {
  0%, 30% { opacity: 1; }
  100% { opacity: 0; }
}
