/* 第四幕 · 报告屏（文档 3.4）+ 全局系统件（3.5）+ 移动端（3.6）+ 动效（3.7） */

/* ===================================================================
   报告屏：信息层级严格按「叙事 → 证据 → 数据 → 行动」
   判词是"再来一局"的燃料，不能让一张数据表盖在定格画面上就算完。
   =================================================================== */
.report-act { margin-bottom: 22px; }

.report-verdict-act {
  text-align: center;
  padding: 18px 0 8px;
}

.report-diviner {
  font-size: 34px;
  margin-bottom: 8px;
  opacity: 0.85;
}

.report-verdict {
  /* 判词是整个复盘屏的第一句话，字号压过下面所有数据。
     字体族见 tokens.css。 */
  font-size: 19px;
  line-height: 1.9;
  color: #f0d8e8;
  margin: 0 auto 8px;
  max-width: 520px;
  font-weight: 500;
}

.report-subtitle {
  font-size: 12px;
  color: var(--ink-faint);
  margin: 0;
}

/* 打字机光标：判词值得一个揭示过程 */
.report-verdict .caret {
  color: var(--accent-bright);
  animation: caret-blink 0.9s step-end infinite;
}

@keyframes caret-blink { 50% { opacity: 0; } }

/* ---- 判词舞台 ----
   占卜师的水晶球后面一团光。判词是这一屏的第一句话，也是"再来一局"
   的燃料——它需要一个舞台，而不是一段居中的文字。 */
.report-verdict-act {
  position: relative;
}
.report-verdict-act::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 0;
  width: 320px;
  height: 200px;
  margin-left: -160px;
  background: radial-gradient(ellipse at center, rgba(204, 79, 176, 0.16), transparent 68%);
  pointer-events: none;
}

.report-diviner {
  position: relative;
  animation: orb-float 5s ease-in-out infinite;
}
@keyframes orb-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}

/* ---- 名次：这一屏唯一的结论 ----
   原来它埋在一行文字里（"第 5 名 / 8 条鱼　最终体型…"）。
   名次是结论，其余全是解释——做成一个大数字，玩家扫一眼就知道成败。 */
.rank-block {
  --tier: var(--ink-dim);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-3);
  border-radius: var(--r-md);
  background:
    linear-gradient(90deg, color-mix(in srgb, var(--tier) 14%, transparent), transparent 62%),
    rgba(12, 22, 38, 0.7);
  border: 1px solid color-mix(in srgb, var(--tier) 26%, transparent);
  border-left: 3px solid var(--tier);
}
.rank-block.gold   { --tier: #f0cf7a; }
.rank-block.silver { --tier: #c6d2df; }
.rank-block.bronze { --tier: #cc9a6a; }
.rank-block.plain  { --tier: var(--ink-faint); }

.rank-num {
  font-size: 40px;
  font-weight: 700;
  line-height: 1;
  color: var(--tier);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 18px color-mix(in srgb, var(--tier) 45%, transparent);
}
.rank-meta { display: grid; gap: 2px; }
.rank-of { font-size: var(--fs-sm); color: var(--ink-dim); }
.rank-word { font-size: var(--fs-md); font-weight: 600; color: var(--ink); }

/* ---- 数据格 ----
   原来是一行用 `·` 隔开的文字。数字并排成格之后才好比较，
   而"比较"正是玩家看这一块的目的（这次比上次多杀了几条？）。 */
.rstat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
  gap: var(--sp-2);
}
.rstat {
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.035);
  display: grid;
  gap: 1px;
}
.rs-label { font-size: var(--fs-xs); color: var(--ink-dim); }
.rs-value {
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.rs-extra { font-size: var(--fs-xs); color: var(--ink-faint); }

.rset {
  margin-top: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  background: rgba(232, 184, 75, 0.1);
  border: 1px solid rgba(232, 184, 75, 0.28);
  color: var(--other);
  font-size: var(--fs-sm);
}

/* 复盘屏整体从下方浮上来。这一屏是"结算"，一个短促的上浮比直接出现
   更像"水面浮起一份报告"。 */
.report-panel:not([hidden]) .report-inner {
  animation: report-rise var(--t-slow) var(--ease-out);
}
@keyframes report-rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .report-panel:not([hidden]) .report-inner,
  .report-diviner { animation: none; }
}

/* ===================================================================
   Toast：右上角，同屏最多 2 条——刷屏的 toast 等于没有 toast
   =================================================================== */
.toast-stack {
  position: fixed;
  /* **贴内容列的右缘，不是视口的右缘。** 页面内容是 1320px 居中的一列，
     宽屏上视口右角离鱼缸有好几百像素——toast 弹在那儿会落到顶栏的
     "注册保存进度" 旁边，看着像是网站的通知，而不是这一局里发生的事。
     用同一个 1320 居中盒把它拉回内容列。 */
  /* 默认落点（标题/鱼谱/占卜这些没有鱼缸的屏）：内容列（1320 居中）的
     右上角，而不是视口右上角——宽屏上视口右角离内容有大半个屏幕。 */
  top: var(--toast-top, 64px);
  right: var(--toast-right, max(var(--sp-4), calc(50% - 660px + var(--sp-4))));
  z-index: 80;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  pointer-events: none;
}

/* 有鱼缸的屏（对战/复盘）：落在**缸内底栏上方、水平居中**。
   落点是量出来的，不是挑的——缸里四角都有人：顶栏满宽、排行榜占右侧
   178×223、底栏满宽 46px、播报在顶栏里右对齐。右上角看着最"标准"，
   但那正是排行榜的位置，贴过去就是用户已经报过的那个问题
   （播报被排行榜挡住）换个东西再犯一次。
   选底栏上方还有一层理由：那是唯一**高度稳定**的空地。播报会随内容从 0
   涨到 70 多像素，贴着它排 toast 就会跟着上下跳。 */
.toast-stack[data-anchor="tank"] {
  top: auto;
  /* 带兜底值：这两个变量由 JS 量完鱼缸才写入，而 data-anchor 属性是先设的
     ——中间那一瞬间变量还不存在。没有兜底的话 `bottom: var(--toast-bottom)`
     是无效声明会被整条丢弃，那一帧 toast 会跳回顶部。 */
  bottom: var(--toast-bottom, 72px);
  right: auto;
  left: var(--toast-left, 50%);
  transform: translateX(-50%);
  align-items: center;
}

.toast {
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  /* 和 HUD 同一套语言：毛玻璃 + 上缘内高光。toast 常常压在跑着的画布上，
     不做模糊的话背后的水会把字搅糊。 */
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.05), transparent 40%),
    rgba(10, 20, 32, 0.88);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(120, 170, 210, 0.14);
  border-left: 3px solid var(--glow);
  box-shadow: var(--lift-2);
  color: var(--ink);
  font-size: var(--fs-base);
  line-height: 1.5;
  max-width: 300px;
  animation: toast-in var(--t-base) var(--ease-out);
}

.toast-gold { border-left-color: var(--other); color: #f0d090; }
.toast-pink { border-left-color: var(--accent); color: #f0d8e8; }

.toast.leaving {
  opacity: 0;
  transform: translateX(10px);
  transition: opacity var(--t-slow) var(--ease-out), transform var(--t-slow) var(--ease-out);
}

@keyframes toast-in {
  from { opacity: 0; transform: translateX(16px); }
  to { opacity: 1; transform: translateX(0); }
}

/* ---- 弹窗 ----
   背景压暗 + 模糊，让弹窗真的"浮"在界面之上。原来只有一个半透黑底，
   弹窗读起来像贴在页面上的一张纸。 */
.modal-backdrop {
  backdrop-filter: blur(3px);
  animation: fade-in var(--t-fast) var(--ease-out);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.modal-card {
  max-width: 380px;
  margin: 0;
  box-shadow: var(--lift-3);
  animation: modal-rise var(--t-base) var(--ease-out);
}
@keyframes modal-rise {
  from { opacity: 0; transform: translateY(12px) scale(0.98); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .toast, .modal-card, .modal-backdrop { animation: none; }
}

/* 转场：全游戏只此一种，建立肌肉记忆 */
@keyframes screen-wipe {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

body.screen-transition .view {
  animation: screen-wipe var(--t-base) var(--ease-out);
}

/* 对战屏不吃转场：它承载一个正在跑的画布，任何 transform 都会让
   第一帧的镜头位置错一下（而那一帧正好是玩家最专注的时刻）。 */
body.screen-transition .view[data-view="arena"] { animation: none; }

@media (prefers-reduced-motion: reduce) {
  body.screen-transition .view { animation: none; }
}

/* ===================================================================
   移动端：单断点 768px，竖屏优先（文档 3.6）
   =================================================================== */
@media (max-width: 768px) {
  body { padding: 12px; }

  .lobby-grid { grid-template-columns: minmax(0, 1fr); }
  .divination-grid { grid-template-columns: minmax(0, 1fr); }

  /* ---- 大厅在窄屏下曾经是坏的 ----
     `.title-content` 没有宽度约束，里面的 hub-grid 按 minmax(174px,1fr)
     铺成了三列（522px），而舞台只有 375px 且 `overflow: hidden`——
     于是**六张卡里有四张被静默裁掉了**，玩家在手机上根本点不到。

     裁切不报错、不滚动、什么痕迹都不留，只有量一遍元素的 right 才看得出来。
     这是"只在桌面尺寸下看过"最典型的后果。 */
  .title-content {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    padding: var(--sp-5) var(--sp-3);
  }

  .hub-grid {
    /* 两列。一列太长（六张卡要滑很久），三列在 375px 下每张只剩 110px。 */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: 100%;
    gap: var(--sp-2);
  }

  .hub-card { padding: var(--sp-3); }
  .hub-desc { display: none; } /* 窄屏只留图标和名字，说明靠进去之后再看 */

  .title-tagline { font-size: var(--fs-base); }

  /* ---- 触控目标 ----
     手指的接触面约 44px。低于 36px 的按钮在手机上要瞄准，
     而这几个（面包屑、钱包、注册）恰恰是高频入口。 */
  .crumb-home,
  .wallet-chip,
  #register-cta,
  #logout-btn {
    min-height: 36px;
    padding-top: 7px;
    padding-bottom: 7px;
  }

  .screen-body { gap: var(--sp-3); }
  .card { padding: var(--sp-3); }

  /* 水晶球和词条卡收进底部抽屉，不跟聊天抢地方 */
  .divination-orb {
    position: sticky;
    bottom: 0;
    background: #101c30;
    border-top: 1px solid #22344f;
    padding-top: 10px;
    z-index: 10;
  }

  #world-canvas { width: 100%; height: auto; }

  .hud-leaderboard {
    top: 42px;
    right: 6px;
    width: 138px;
    font-size: 10.5px;
  }

  .hud-bottom {
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
  }

  .hud-right { justify-content: space-between; }
  .topbar { flex-wrap: wrap; gap: 8px; }
  .fish-card { width: min(260px, calc(100vw - 24px)); }

  /* 触屏没有 hover：词条卡改成点按翻转（JS 已支持） */
  .affix-card.revealed:hover .ac-inner { transform: none; }
  .affix-card.flipped .ac-inner { transform: rotateY(180deg); }
}

/* ===================================================================
   动效原则（3.7）：一切缓动模仿水——长尾 ease-out，禁用弹跳类缓动。
   尊重系统的"减少动态效果"设置。
   =================================================================== */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ===================================================================
   触控目标下限
   ===================================================================
   手指不是鼠标指针。桌面上 26px 高的标签页按钮点着很准，手机上就是要点
   两三次才中一次——而且**不会报错、不会留痕**，用户只会觉得"这个界面有
   点难用"，说不出哪难用。

   审计出来 13 个控件低于 44px（Apple HIG 的最小触控目标）：战报的三个筛选
   标签 105×26、鱼缸的场景下拉 282×26、装饰标签 326×28、几个 50×35 的小
   按钮。高度全都不够，宽度大多够——所以只兜 min-height，不动宽度。

   用 `pointer: coarse` 而不是宽度断点：**该放大的判据是"用什么点"，
   不是"屏幕多宽"**。平板横屏比很多笔记本还宽，但照样是手指。 */
/* 选择器**不按容器列举**。第一版写的是 `.view button, .hud button…`，
   于是弹层全漏了——登录框、投放卡、选鱼浮层都不在 `.view` 里面，实测登录框
   还有 5 个控件卡在 35~36px。**"哪些地方要够大"的答案是"所有地方"**，
   按容器列举就一定会漏掉下一个新容器。
   真要排除的话，用 .tap-exempt 显式声明，而不是靠没被列举到。 */
@media (pointer: coarse) {
  button:not(.tap-exempt),
  select:not(.tap-exempt),
  [role='button']:not(.tap-exempt),
  input:not([type='checkbox']):not([type='radio']):not(.tap-exempt) {
    min-height: 44px;
  }

  /* 勾选框本身也得够大——它旁边的文字虽然可点，但用户会去点那个方块。 */
  input[type='checkbox'],
  input[type='radio'] {
    width: 20px;
    height: 20px;
  }
}

/* ===================================================================
   减少动效：兜底规则
   ===================================================================
   这份样式里原本是**逐条列举**的——哪个选择器要关动画，就单独写一条
   `animation: none`。审计下来漏了一大片：feed-in（四处在用）、card-pulse、
   wallet-pop、reveal-pop，以及几乎所有 `transition`（只有 .ac-inner 被点名）。

   这和这个项目在静态托管那里已经总结过的道理是同一条：

     黑名单每加一个都要记得补一条，漏一条就是泄露；
     白名单漏一条只是 404，代价方向反过来。

   动效这里的代价是：漏一条，就是把一个**明确在系统设置里要求减少动效的人**
   晾在那儿——那类设置常常是因为前庭失调、偏头痛或晕动症，不是审美偏好。
   而且以后每加一个动画都要记得回来补，靠的是记性。

   所以改成兜底：默认全关，个别需要保留的再单独放行。
   不用 `animation: none` 而用极短时长——有些动画的**终态**才是正确的显示
   状态（比如淡入的元素起始 opacity 是 0），直接 none 会把它们卡在起始帧。 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ===================================================================
   输入框的占位文字
   ===================================================================
   全站五个 placeholder 之前**一个都没上过样式**——用的是浏览器默认的
   `#757575`，在这里的深色输入框上对比度只有 3.74~3.78，全部低于 AA 的 4.5。

   而它们承载的是真信息，不是可有可无的提示：
     "比如：胆小怕事，只想混口饭吃…"  —— 教你怎么描述一条鱼的范例
     "密码（至少 6 位）"              —— **校验规则写在这里面**
     "输入鱼码" / "用户名" / "别人会看到"

   取 --ink-faint（实测两种输入框底色上 4.54 / 4.59，刚好过线）而不是更亮的
   --ink-dim：placeholder **要能读，但不能读得像已经填好的值**。
   ink-faint 与正文的亮度比是 3.39，ink-dim 只有 2.17——后者会让空输入框
   看起来像是已经有内容了。 */
::placeholder {
  color: var(--ink-faint);
  opacity: 1; /* Firefox 默认给 placeholder 再压一道 opacity，会把对比度打回去 */
}
