/* ==========================================================================
   音纪 · 站点皮肤层
   --------------------------------------------------------------------------
   只消费 ../design-system/tokens.css 的令牌，不写死颜色 / 字号 / 圆角 / 间距。
   保留原站的「唱片 + 频谱 + 玻璃质感」，色相从紫粉霓虹收敛到夜蓝底 + 紫强调。

   圆角纪律：全站只用三档 —— --r-sm（控件）/ --r-md（面板）/ --r-full（药丸、
   唱片、播放键）。同一个元素不会同时出现第四档圆角。

   动效纪律：只有「回应用户操作」的动效（唱片转动、频谱、加载进度、页面切换），
   不做逐块 fade-up。
   ========================================================================== */

/* ==========================================================================
   1. 氛围舞台
   原站有粒子 + 渐变 + 噪点 + 侧边声波共 5 层 canvas。这里收敛成 1 层粒子 +
   2 团静态光晕 + 1 层颗粒，把 GPU 让给频谱，同时保住"深夜录音棚"的氛围。

   分层（关键）：
     .stage / .side-wave   z-index 0，fixed，纯装饰
     .app                  z-index 2，**背景必须透明**，否则氛围层永远看不见
   这块面板的底色由 body 提供；也正因为透明，business 逻辑里
   `showView` 隐藏 header 才不会连底色一起消失。
   ========================================================================== */
body { background: var(--bg-sunken); }

.stage {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.stage canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* 两团光晕：紫 + 蓝，静态（不做呼吸动画，避免与频谱抢注意力） */
.stage__orb {
  position: absolute;
  display: block;
  border-radius: var(--r-full);
  filter: blur(64px);
  opacity: .55;
}
.stage__orb--a {
  top: 4%;
  left: 50%;
  width: 300px;
  height: 300px;
  margin-left: -150px;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--accent) 24%, transparent) 0%,
    transparent 70%);
}
.stage__orb--b {
  top: 48%;
  left: -14%;
  width: 260px;
  height: 260px;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--info) 15%, transparent) 0%,
    transparent 70%);
}
/* 颗粒：极低不透明度，给暗色面板一点胶片质感 */
.stage__grain {
  position: absolute;
  inset: 0;
  opacity: .03;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* 播放时透出的封面模糊底（.playing 由业务脚本切换） */
.cover-backdrop {
  position: absolute;
  top: 14%;
  left: 50%;
  width: 300px;
  height: 300px;
  margin-left: -150px;
  border-radius: var(--r-full);
  overflow: hidden;
  filter: blur(64px) saturate(1.4);
  opacity: 0;
  transition: opacity var(--dur-4) var(--ease-out);
}
.cover-backdrop img { width: 100%; height: 100%; object-fit: cover; }
.cover-backdrop.playing { opacity: .3; }

/* 侧边声波：贴住 .app 画布外缘。窄屏放不下就整层隐藏，
   否则会压到内容上（原站在 375px 下正是这个问题） */
.side-wave {
  position: fixed;
  top: 0;
  bottom: 0;
  width: 44px;
  z-index: 1;
  pointer-events: none;
}
.side-wave--left { left: 0; }
.side-wave--right { right: 0; }
.side-wave canvas { width: 100%; height: 100%; }

.app {
  z-index: 2;
  background: transparent;
}

/* 画布比 .app 宽出 44px 以上才露得出声波；否则整层不要 */
@media (max-width: 559px) {
  .side-wave { display: none; }
}

/* .view 里包一层内容容器，负责 gutter 与底部留白 */
.view-body { padding: 0 var(--gutter); }

/* ==========================================================================
   2. 顶栏品牌
   ========================================================================== */
.topbar__brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-width: 0;
}
.brand-mark {
  width: 34px;
  height: 34px;
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent);
}
.brand-mark svg { width: 20px; height: 20px; }
.brand-text { min-width: 0; }
.brand-name {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: var(--fw-semi);
  letter-spacing: var(--ls-heading);
  line-height: 1.2;
  color: var(--ink);
}
.brand-sub {
  display: block;
  font-size: var(--t-micro);
  line-height: var(--lh-tight);
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 未配置密钥 / 服务未连接：状态同时用颜色和文字表达（不只靠颜色） */
.brand-sub[data-state='ok'] { color: var(--accent); }
.brand-sub[data-state='warn'] { color: var(--warn); }
.brand-sub[data-state='error'] { color: var(--danger); }

/* ==========================================================================
   3. 继续播放条
   ========================================================================== */
.resume-banner {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-5);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-md);
  background: var(--accent-soft);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-out);
}.resume-banner:hover { border-color: var(--accent); }
.resume-banner__icon {
  width: 34px;
  height: 34px;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--on-accent);
}
.resume-banner__icon svg { width: 15px; height: 15px; }
.resume-banner__text { flex: 1; min-width: 0; }
.resume-banner__title {
  display: block;
  font-size: var(--t-sm);
  font-weight: var(--fw-semi);
  color: var(--ink);
}
.resume-banner__desc {
  display: block;
  font-size: var(--t-xs);
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.resume-banner__close {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  margin-right: calc(var(--s-2) * -1);
  border-radius: var(--r-full);
  color: var(--muted);
  transition: color var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out);
}
.resume-banner__close:hover { color: var(--ink); background: var(--surface-inset); }
.resume-banner__close svg { width: 14px; height: 14px; }

/* ==========================================================================
   4. 创作表单
   ========================================================================== */
.icon-field {
  display: none; /* 原站用于隐藏的输入，语义已由 .chip 承担 */
}

/* 描述行：输入框 + 「歌词生成」按钮同排，按钮不参与 flex 收缩 */
.desc-row {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}
.desc-input { flex: 1; min-width: 0; }
.desc-row .btn { flex: 0 0 auto; }

.lyric-input {
  min-height: 168px;
  font-family: var(--font-text);
  line-height: var(--lh-body);
}

/* 时长 / 音量：原生 range，键盘可操作（方向键 + Home/End） */
.range-row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
}
.range-value {
  flex: 0 0 auto;
  min-width: 3ch;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--t-h3);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

.range {
  flex: 1;
  min-width: 0;
  height: var(--tap-min);
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  cursor: pointer;
}
.range::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: var(--r-full);
  background: var(--surface-inset);
  border: 1px solid var(--line);
}
.range::-moz-range-track {
  height: 4px;
  border-radius: var(--r-full);
  background: var(--surface-inset);
  border: 1px solid var(--line);
}
.range::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  margin-top: -9px;
  border-radius: var(--r-full);
  background: var(--accent);
  border: 2px solid var(--bg);
  transition: transform var(--dur-1) var(--ease-out);
}
.range::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: var(--r-full);
  background: var(--accent);
  border: 2px solid var(--bg);
}
.range:active::-webkit-slider-thumb { transform: scale(1.12); }
.range:focus-visible { outline: none; }
.range:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--line-focus) 30%, transparent);
}
.range:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--line-focus) 30%, transparent);
}
.range--sm::-webkit-slider-thumb { width: 16px; height: 16px; margin-top: -7px; }
.range--sm::-moz-range-thumb { width: 16px; height: 16px; }

/* 生成 CTA */
.gen-cta { margin-top: var(--s-7); }
.gen-cta__note {
  margin-top: var(--s-3);
  text-align: center;
  font-size: var(--t-xs);
  color: var(--faint);
}

/* 成品库卡片 */
.quick-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  text-align: left;
  transition: border-color var(--dur-2) var(--ease-out),
              background var(--dur-2) var(--ease-out),
              transform var(--dur-1) var(--ease-out);
}
.quick-card:hover {
  border-color: var(--accent-line);
  background: var(--surface-2);
  color: var(--ink);
  transform: translateY(-2px);
}
.quick-card:active { transform: translateY(0) scale(.985); }
.quick-card-cover {
  width: 40px;
  height: 40px;
  /* data-uri SVG 没有固有尺寸，缺了 aspect-ratio 会塌成 0 高 */
  aspect-ratio: 1;
  border-radius: var(--r-full);
  object-fit: cover;
  border: 1px solid var(--accent-line);
  background: var(--surface-inset);
}
.quick-card-title {
  font-size: var(--t-sm);
  font-weight: var(--fw-semi);
  color: var(--ink);
  line-height: var(--lh-tight);
}
.quick-card-desc {
  flex: 1;
  font-size: var(--t-micro);
  line-height: var(--lh-tight);
  color: var(--muted);
}
.quick-card-cta {
  font-size: var(--t-micro);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  color: var(--accent);
}

/* 风格标签
   ★ 类名必须是 .tag-chip ★ —— app.js 的 renderTagChips() 渲染的就是
   「<span class="tag-chip" data-tag="...">」，并靠 document.querySelectorAll('.tag-chip')
   统计选中项。若只写共享层的 .chip（那是 <button class="chip"> 的组件），
   这 20 个标签会完全裸奔 —— 页面直接塌成一行文字。
   这里按 .chip 的观感重写一遍，但选择器锁定 .tag-chip。 */
.tag-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding: 0 var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  background: var(--surface);
  color: var(--ink-2);
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: background var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              transform var(--dur-1) var(--ease-out);
}
.tag-chip:hover { border-color: var(--line-strong); }
.tag-chip:active { transform: scale(.965); }
.tag-chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
/* 被键盘聚焦（site 层给加了 tabindex）时给可见焦点环 */
.tag-chip:focus-visible {
  outline: 2px solid var(--line-focus);
  outline-offset: 2px;
  background: var(--accent-soft);
  color: var(--accent);
}

/* ==========================================================================
   5. 生成中
   ========================================================================== */
.loading-wrap {
  position: relative;
  overflow: hidden;
  min-height: 70vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-9) var(--gutter);
  border-radius: var(--r-md);
}
.loading-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.loading-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 300px;
  text-align: center;
}
.loading-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  margin: 0 auto var(--s-5);
  border-radius: var(--r-full);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent);
}
.loading-icon svg { width: 30px; height: 30px; }
.loading-title {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: var(--fw-semi);
  letter-spacing: var(--ls-heading);
  color: var(--ink);
}
.loading-sub {
  min-height: 1.4em;
  margin-top: var(--s-2);
  font-size: var(--t-sm);
  color: var(--muted);
}
.loading-progress { margin-top: var(--s-6); }
.loading-label {
  margin-top: var(--s-2);
  font-size: var(--t-micro);
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

/* 加载仪表：环为主、细条为辅。
   环里孔色必须等于画布底色，否则会出现"环中一个异色圆盘"。 */
.loading-gauge {
  margin: var(--s-6) auto var(--s-4);
  --gauge-hole: var(--bg);
}
.loading-gauge__v {
  display: flex;
  align-items: baseline;
  gap: 1px;
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: var(--fw-semi);
  color: var(--accent);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.loading-gauge__unit { font-size: var(--t-xs); color: var(--muted); }

/* 细条降级为"辅助刻度"：压淡，别和环抢注意力 */
.loading-progress { opacity: .45; }
.loading-progress .progress__bar { background: var(--accent); }
.loading-inner .btn { margin-top: var(--s-7); }

/* ==========================================================================
   6. 播放器
   ========================================================================== */
/* 页面切换：业务脚本 showView() 只认 .active / .view（querySelectorAll('.view')），
   所以这里必须与它完全一致 —— 写成 .is-active 会导致两个页面同时渲染。
   用 flex 列布局把高度交给浏览器算，避免 dvh 硬算在「有降级提示条」时溢出。 */
.app-main { display: flex; flex-direction: column; }

.view { display: none; }
.view.active { display: block; animation: view-in var(--dur-3) var(--ease-out) both; }
@keyframes view-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

#viewPlayer.active {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.player-content {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0 var(--gutter);
}
.player-bar {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding-top: var(--s-2);
  flex: 0 0 auto;
}
.player-bar__label {
  flex: 1;
  text-align: center;
  font-size: var(--t-micro);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  color: var(--faint);
}
.player-bar__spacer { width: var(--tap-min); flex: 0 0 auto; }

/* 唱片舞台：占据剩余空间的中心 */
.disc-stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-3) 0;
}
.disc-wrap {
  position: relative;
  width: 208px;
  height: 208px;
  flex: 0 0 auto;
}
.disc-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 260px;
  height: 260px;
  transform: translate(-50%, -50%);
  border-radius: var(--r-full);
  background: radial-gradient(circle,
    color-mix(in srgb, var(--accent) 22%, transparent) 0%,
    transparent 70%);
  filter: blur(18px);
}
.disc {
  position: absolute;
  inset: 0;
  border-radius: var(--r-full);
  animation: disc-spin 24s linear infinite;
  animation-play-state: paused;
}
.disc.spinning { animation-play-state: running; }
@keyframes disc-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.disc-ring {
  position: absolute;
  inset: -5px;
  border-radius: var(--r-full);
  background: conic-gradient(from 0deg,
    var(--accent), var(--accent-line), var(--info), var(--accent));
  -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
  mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  padding: 2px;
  opacity: .75;
}
.cover-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: var(--r-full);
  object-fit: cover;
  background: var(--surface-inset);
  opacity: 0;
  transition: opacity var(--dur-4) var(--ease-out);
}
.cover-image.loaded { opacity: 1; }
/* 唱片中心孔：暗色下用底色的实心圆 + 描边，不靠阴影 */
.disc-hole {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: var(--r-full);
  background: var(--bg);
  border: 1px solid var(--accent-line);
}
.disc-fallback {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  color: var(--accent);
  opacity: .55;
  pointer-events: none;
}
.disc-fallback svg { width: 40px; height: 40px; }
.disc-fallback.hidden { display: none; }
.disc-loading {
  position: absolute;
  left: 50%;
  bottom: -30px;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: var(--t-micro);
  color: var(--muted);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
  pointer-events: none;
}
.disc-loading.show { opacity: 1; }
/* 频谱画环形铺在唱片外圈，尺寸由 JS 的 canvas 属性决定 */
.spectrum-canvas {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* 曲目信息 */
.song-info { flex: 0 0 auto; text-align: center; padding-bottom: var(--s-3); }
.song-title {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: var(--fw-semi);
  letter-spacing: var(--ls-heading);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.song-artist {
  margin-top: 2px;
  font-size: var(--t-xs);
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 播放进度：可拖可键盘（role=slider + 方向键） */
.seek { flex: 0 0 auto; padding-bottom: var(--s-2); }
.seek__track {
  position: relative;
  height: var(--tap-min);
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;
}
.seek__track::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 4px;
  border-radius: var(--r-full);
  background: var(--surface-inset);
  border: 1px solid var(--line);
}
.seek__fill {
  position: relative;
  height: 4px;
  width: 0%;
  border-radius: var(--r-full);
  background: var(--accent);
  transition: width 90ms linear;
}
.seek__fill::after {
  content: '';
  position: absolute;
  right: -6px;
  top: 50%;
  transform: translateY(-50%) scale(.6);
  width: 12px;
  height: 12px;
  border-radius: var(--r-full);
  background: var(--accent-hi);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.seek__track:hover .seek__fill::after,
.seek__track.dragging .seek__fill::after,
.seek__track:focus-visible .seek__fill::after {
  opacity: 1;
  transform: translateY(-50%) scale(1);
}
.seek__track:focus-visible { outline: none; }
.seek__track:focus-visible::before {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.seek__times {
  display: flex;
  justify-content: space-between;
  font-size: var(--t-micro);
  color: var(--faint);
}

/* 歌词 */
.lyrics-wrap {
  flex: 0 1 auto;
  min-height: 56px;
  max-height: 76px;
  overflow-y: auto;
  scrollbar-width: none;
  padding: var(--s-2) 0;
}
.lyrics-wrap::-webkit-scrollbar { display: none; }
.lyrics-wrap:focus-visible {
  outline: 2px solid var(--line-focus);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}
.lyric-line {
  padding: 3px var(--s-2);
  text-align: center;
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--faint);
  border-radius: var(--r-xs);
  cursor: pointer;
  transition: color var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out);
}
.lyric-line:hover { color: var(--ink-2); }
.lyric-line.active {
  color: var(--ink);
  font-weight: var(--fw-semi);
  background: var(--accent-soft);
}
.lyric-line.section {
  font-size: var(--t-micro);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  padding-top: var(--s-2);
}

/* 控制 */
.player-controls {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-2) 0;
}
.play-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 60px;
  height: 60px;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--on-accent);
  transition: background var(--dur-2) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.play-btn svg { width: 26px; height: 26px; margin-left: 3px; }
.play-btn svg.pause-icon { margin-left: 0; }
.play-btn:hover { background: var(--accent-hi); }
.play-btn:active { transform: scale(.94); }

/* 音量 */
.volume-row {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: 0 var(--s-5);
}
.volume-row__icon { flex: 0 0 auto; color: var(--faint); }
.volume-row__icon svg { width: 16px; height: 16px; }

.dl-btn { flex: 0 0 auto; margin: var(--s-4) 0 var(--s-5); }

/* 矮屏（横屏手机 / 小笔记本）：压缩唱片与歌词，保证控制条不被挤出视口 */
@media (max-height: 720px) {
  .disc-wrap { width: 152px; height: 152px; }
  .disc-glow { width: 196px; height: 196px; }
  .lyrics-wrap { max-height: 54px; }
  .play-btn { width: 52px; height: 52px; }
  .play-btn svg { width: 22px; height: 22px; }
  .disc-stage { padding: var(--s-2) 0; }
  .song-info { padding-bottom: var(--s-2); }
  .dl-btn { margin: var(--s-3) 0 var(--s-4); }
}

/* ==========================================================================
   7. 出错页
   ========================================================================== */
#viewError.active {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 70vh;
}
.error-state {
  text-align: center;
  padding: var(--s-8) var(--s-6);
  max-width: 320px;
}
.error-state__glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin: 0 auto var(--s-5);
  border-radius: var(--r-full);
  background: var(--danger-soft);
  color: var(--danger);
}
.error-state__glyph svg { width: 26px; height: 26px; }
.error-state__msg {
  margin-bottom: var(--s-6);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* ==========================================================================
   8. 图片兜底修补
   app-shell.js 的 markBroken() 在 <img> 触 error 时给**父元素**加
   .img-fallback（斜纹底 + data-fallback 文案）。多数时候这是对的，但本站有
   一处误判：

     app.js 在重置封面时执行 `$('coverArt').src = ''`。空 src 会被解析成
     当前文档地址，浏览器按图片加载当然失败 → error → 父容器 .disc 被套上
     .img-fallback。之后封面真的加载成功，那个占位斜纹与"封面暂缺"文案却
     留了下来，看起来像故障。（截图里唱片中央那个会转的"封面暂缺"就是它。）

   app.js 末尾的 UI 修补块会在图片真正 load 成功后摘掉这个类，这里再用
   :has() 兜一层，保证任何 data-uri 封面都不会被占位样式压住。
   ========================================================================== */
.img-fallback:has(img[src^="data:image/svg+xml"]) {
  background-color: transparent;
  background-image: none;
  min-height: 0;
}
.img-fallback:has(img[src^="data:image/svg+xml"])::after { content: none; }

/* 唱片内的图由 .cover-image 的 opacity 控制显隐，兜底层不要再压一层 */
.disc .img-fallback,
.disc-wrap .img-fallback { background: none; min-height: 0; }
.disc .img-fallback::after,
.disc-wrap .img-fallback::after { content: none; }

/* 收尾 z 序：封面在唱片环之上、中心孔之下（DOM 顺序已保证，这里只兜底） */
.disc-ring { z-index: 0; }
.cover-image { z-index: 1; }
.disc-hole { z-index: 2; }

/* ==========================================================================
   9. Toast
   注意：#toast 这个元素在本站**不会被业务脚本写入** —— app-shell.js 的
   window.toast 是 defer，执行晚于业务脚本的 function toast()，所以业务
   toast() 被它遮蔽了，所有提示（含 window.toast(msg, kind)）都走共享层的
   .toast-host（components.css 已定义样式）。
   这里仍然保留 #toast 的样式与元素：一是 DESIGN-SYSTEM 第 7 条要求保住
   getElementById 的目标，二是万一共享层未加载，业务 toast() 依然可用。
   ========================================================================== */
#toast {
  position: fixed;
  top: calc(var(--safe-t) + var(--s-5));
  left: 50%;
  transform: translate(-50%, -12px);
  z-index: var(--z-toast);
  max-width: min(88vw, calc(var(--app-max) - 32px));
  padding: var(--s-3) var(--s-5);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-full);
  background: var(--surface-2);
  color: var(--ink);
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
  text-align: center;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--dur-3) var(--ease-out),
              transform var(--dur-3) var(--ease-spring),
              visibility 0s linear var(--dur-3);
}
#toast.show {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
  transition: opacity var(--dur-3) var(--ease-out),
              transform var(--dur-3) var(--ease-spring),
              visibility 0s;
}
/* 演示数据提示条（offline.js 注入）会占据顶部，别让 toast 压住它 */
[data-offline='1'] #toast { top: calc(var(--safe-t) + var(--s-10)); }

/* ==========================================================================
   10. 声音：尊重系统的减弱动态偏好
   频谱与唱片是本站的核心动效，但仍须在 reduce 下停止旋转/闪烁。
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .stage__orb { filter: blur(40px); opacity: .4; }
  .stage__grain { display: none; }
  .disc { animation: none; }
  .disc-ring { background: var(--accent-line); }
  .view.active { animation: none; }
}

/* ==========================================================================
   11. 霓虹主视觉（原站招牌视觉的复刻）
   --------------------------------------------------------------------------
   原站 header 由三件东西组成，缺一不可：
     · #noteTrailCanvas —— app.js 的 initNoteTrail() 实时绘制的音符轨迹
     · .app-title       —— 44px 霓虹描边大标题，音符围绕它漂浮
     · .pulse-ring / .brand-logo —— 呼吸光环 + 浮动音符徽标
   重构时这三件被漏掉，导致该视觉整体消失。此处按原设计恢复，
   但颜色改走令牌（--accent / --accent-soft），不再写死紫色。
   ========================================================================== */
.hero {
  position: relative;
  text-align: center;
  padding: var(--s-7) var(--s-4) var(--s-6);
  overflow: hidden;
}
.hero__logo {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--s-5);
}
/* 呼吸光环 */
.hero__pulse {
  position: absolute;
  width: 72px; height: 72px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  animation: heroPulse 2.4s ease-out infinite;
}
@keyframes heroPulse {
  0%   { transform: scale(.8); opacity: .8; }
  100% { transform: scale(1.4); opacity: 0; }
}
/* 浮动徽标（原 .brand-logo：64px 圆角方块 + 上下漂浮） */
.hero__mark {
  position: relative;
  width: 64px; height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-lg);
  overflow: hidden;
  animation: heroFloat 4s ease-in-out infinite;
}
.hero__mark svg { width: 100%; height: 100%; }
@keyframes heroFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-8px) rotate(3deg); }
}

/* 霓虹描边大标题 —— 音符轨迹围绕它生成，字号不能改小 */
.hero__title {
  font-family: var(--font-display);
  font-size: 44px;
  font-weight: 900;
  letter-spacing: 6px;
  margin: 0 0 var(--s-2);
  color: transparent;
  -webkit-text-stroke: 1.5px color-mix(in srgb, var(--accent) 90%, transparent);
  text-shadow: 0 0 15px color-mix(in srgb, var(--accent) 40%, transparent);
  position: relative;
}
.hero__sub {
  margin: 0;
  font-size: var(--t-sm);
  letter-spacing: 1.5px;
  color: color-mix(in srgb, var(--accent) 60%, var(--muted));
}
/* 原站副标题是两行，第二行字号更小、更暗（对应原 .app-sub 的 13px/60% 透明度） */
.hero__sub2 {
  display: block;
  margin-top: 5px;
  font-size: var(--t-xs);
  letter-spacing: 1px;
  color: color-mix(in srgb, var(--accent) 38%, var(--muted));
}

/* 音符轨迹画布：铺满主视觉区，不拦截点击 */
.note-trail {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1;
}
.hero > *:not(.note-trail) { position: relative; z-index: 2; }

/* 播放页隐藏主视觉（原站 header 的既有行为）。
   app.js 只管 #mainHeader 的显隐，hero 是主视觉不是导航，得自己跟着藏。
   用 :has() 表达「当播放页激活时」—— 不依赖改业务 JS。
   不支持 :has() 的老浏览器上 hero 会继续显示，属可接受的降级，不会破版。 */
body:has(.view#viewPlayer.active) .hero { display: none; }

@media (prefers-reduced-motion: reduce) {
  .hero__mark, .hero__pulse { animation: none; }
  .hero__pulse { opacity: .35; }
}
