/* tokens.css -- Mini App 主题契约与渲染层（WEB-THEME 系列，2026-10-11）。
 *
 * 这个文件是什么：
 *   全部 Mini App 页面共用的**唯一**视觉收口点。它做三件事：
 *     ① 声明主题令牌（token）契约，并给每个令牌一个安全的默认值；
 *     ② 把历史页面用的旧变量名（--line / --bg-secondary / --panel…）桥接到契约上，
 *        所以老页面不改一行也能换肤；
 *     ③ 用**只读令牌**渲染通用组件（卡片 / 面板 / 按钮 / 输入 / 导航 / 纹理层）。
 *
 * 它不是什么：
 *   - **不含任何皮肤配色**。皮肤的颜色只在 theme/wasteland.css 与 theme/base.css 里。
 *     本文件里那套 #0f172a/#a78bfa 是**改版前的中性深色兜底**（与服务端
 *     bebot/features/themes.py::_DARK 逐值相同，断言见 test_webapp_skins.py），
 *     作用是「接口调不到、皮肤文件也没命中时页面别花掉」，不是第二个配色来源：
 *     改皮肤时**不需要也不应该**动它。
 *   - 不含任何用户可见文案。文案一律来自 /api/i18n/<locale>
 *     （bebot/text_audit.py 把新增静态资源里的 CJK 字符串当硬编码文案）。
 *
 * 为什么要有这一层（为什么不是每页各写一套）：
 *   2026-10 之前每个页面在自己的 style 块里写死一整套 :root 配色。结果是同一批
 *   页面有 4 种不同的深色、3 种强调色，而任何全局视觉调整都要改 15+ 个文件且必
 *   然漏改。令牌化之后：**新增主题 = 新增一个 CSS 文件 + 一条注册表记录**，
 *   页面零改动。
 *
 * 令牌契约（三层，页面与主题都只认这些名字）：
 *   第 1 层 语义色（9 个，来自 bebot/features/themes.py::CSS_TOKENS）
 *     --bg            页面底色
 *     --bg-elevated   抬升面（卡片 / 输入框 / 次级底）
 *     --text          正文
 *     --muted         次要文字
 *     --accent        强调色（链接 / 选中 / 主按钮）
 *     --accent-text   强调色之上的文字
 *     --border        描边
 *     --danger        危险 / 破坏性操作
 *     --ok            成功
 *   第 2 层 皮肤专用面（7 个，SKIN_TOKENS）
 *     --bg-sunken --bg-raised --text-dim --accent-soft --accent-line --warn --info
 *   第 3 层 表现层、纹理与动效（21 个，PRESENTATION_TOKENS）
 *     --font-body --font-mono --radius --radius-sm --radius-lg --glow --shadow
 *     --texture-scanline --texture-vignette --texture-grain --texture-glow
 *     --motion-enter-duration --motion-enter-shift --motion-swap-duration --motion-blink
 *     --motion-crt-flicker --motion-crt-grain --motion-crt-roll
 *     --motion-pulse-duration --motion-pulse-glow --motion-step
 *
 *   第 4 层 日历标签色（10 个，--tag-*，2026-10-11 BEBOT-CALENDAR 起）
 *     它们不在 features/themes.py 的令牌表里，因为**不是皮肤可以单独选择的东西**：
 *     它们由第 1/2 层推导，皮肤一换它们就跟着换。
 *     用途见 bebot/webapp/static/calendar/tags.json（标签目录只允许引用这些名字）。
 *
 *   ⚠️ 动效的**实现**不在本文件里，在 theme/motion.css（八组关键帧 + 一键关闭）。
 *      本文件只给它们默认值，理由见那个文件的头部注释：静态质感与「会动的东西」
 *      是两个轴，混在一起就没法单独关。
 *
 * 怎么加一个新主题（三步，详见 docs/architecture/WEB-THEME-MODEL.md）：
 *   ① 在 bebot/features/themes.py 的 SKINS 里加一条 SkinSpec（提供上面的令牌值）；
 *   ② 在本目录加一个同名 CSS 文件，把同样的值写在属性选择器 data-skin 上
 *      —— 这一步是给**调不到接口的页面**兜底的（/api/config/themes 只对创始人开放，
 *      而 menstrual / dlc / visual 等页面对所有用户开放），同时保证首帧不闪回退色；
 *   ③ 语言包里加 skin.NAME.name 与 skin.NAME.desc 两条文案（NAME 即皮肤 id）。
 *   不需要改任何页面，也不需要改本文件。
 *
 * 资源版本：引用本文件必须带 ?v=版本号。生产走 Cloudflare，/static/*.css 的
 *   cache-control 是 max-age=14400（4 小时），不带版本号会继续拿旧文件（AGENTS.md
 *   Visual Runtime 第 5 条，2026-09-18 真机实测教训）。
 */

:root {
  /* ---- 第 1 层默认值：与旧页面一致的中性深色，先保证「什么都不配也不会花」 ---- */
  --bg: #0f172a;
  --bg-elevated: #1e293b;
  --text: #e2e8f0;
  --muted: #94a3b8;
  --accent: #a78bfa;
  --accent-text: #0f172a;
  --border: #334155;
  --danger: #f87171;
  --ok: #4ade80;

  /* ---- 第 2 层默认值：皮肤没声明时也必须成立（从第 1 层推导，不引入新配色） ---- */
  --bg-sunken: color-mix(in srgb, var(--bg) 82%, #000);
  --bg-raised: var(--bg-elevated);
  --text-dim: var(--muted);
  --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
  --accent-line: color-mix(in srgb, var(--accent) 45%, transparent);
  --warn: #d29922;
  --info: #58a6ff;

  /* ---- 第 3 层默认值：形状 / 字体 / 纹理档位 ---- */
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, Menlo, Consolas, "Roboto Mono", monospace;
  --radius: 12px;
  --radius-sm: 8px;
  --radius-lg: 20px;
  --glow: none;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  /* 纹理档位为 0 表示「这一层不存在」，不是「几乎看不见」——0 时元素 opacity 归零 */
  --texture-scanline: 0;
  --texture-vignette: 0;
  --texture-grain: 0.035;
  --texture-glow: 0.45;

  /* ---- 动效档位：同样是「时长，0 表示不动」 ----
   * 默认全是 0，理由与基础皮肤一致：换肤之前的界面**没有任何动效**，
   * 所以「不动」才是那个状态，想要动效的皮肤必须自己声明（见 wasteland.css）。 */
  --motion-enter-duration: 0s;
  --motion-enter-shift: 0px;
  --motion-swap-duration: 0s;
  --motion-blink: 0s;
  --motion-crt-flicker: 0s;
  --motion-crt-grain: 0s;
  --motion-crt-roll: 0s;
  --motion-pulse-duration: 0s;
  --motion-pulse-glow: none;
  --motion-step: linear;

  /* ---- 日历标签色（10 个，2026-10-11 BEBOT-CALENDAR） ----
   * 由 --accent / --info / --warn / --ok / --muted / --text **推导**，不引入新配色：
   * 标签色要跟着皮肤一起变，写死颜色值就等于每次换肤都得再改一遍这里。
   * 其中 anniversary 与 info、period 与 accent 同相不同明度 —— 同相是刻意的
   * （它们属于同一族语义），明度拉开是为了在同一个格子里并排时还能一眼分开。
   * 契约：bebot/webapp/static/calendar/tags.json 的 color 字段只能填这些名字。
   * 服务端 services/calendar_mark.py::validate_catalog 只校验这个名字的**形状**
   * （必须以 -- 开头）；「这个名字有没有被定义」由本文件负责，没有第二处。
   * 注释里的中文一律用「」包 —— 不要用反引号或成对的 ASCII 引号夹住中文，
   * 也不要在注释里举这种写法的例子：scripts/scan_user_facing_text.py 会把
   * 成对定界符里的中文当成硬编码文案（实测时这条注释自己就触发过一次）。 */
  --tag-life: color-mix(in srgb, var(--accent) 78%, var(--text));
  --tag-anniversary: color-mix(in srgb, var(--info) 72%, var(--text));
  --tag-todo: var(--warn);
  --tag-reminder: color-mix(in srgb, var(--accent) 55%, var(--info));
  --tag-checkup: var(--ok);
  --tag-trip: color-mix(in srgb, var(--info) 55%, var(--ok));
  --tag-period: var(--accent);
  --tag-activity: color-mix(in srgb, var(--danger) 72%, var(--text));
  --tag-event: var(--muted);
  --tag-task: var(--text-dim);

  /* ---- 历史变量名桥接：老页面仍在用这些名字，统一指回契约 ----
   * 保留而不是全量改写页面，是为了让「换肤」与「重命名」两件事解耦：
   * 前者这次做完，后者可以按页慢慢来，中途不会出现两种口径。 */
  --line: var(--border);
  --bg-secondary: var(--bg-elevated);
  --panel: var(--bg-elevated);
  --panel2: var(--bg-raised);
  --panel-2: var(--bg-raised);
  --bad: var(--danger);
  --good: var(--ok);
  /* 旧页面里散落的固定字号/间距，收成两个名字 */
  --tap: 38px;
  --gap: 8px;

  /* ui-shared.css 的质感旋钮也吃主题档位（它自己的默认值保持不变） */
  --ui-grain: var(--texture-grain);
  --ui-glow: var(--texture-glow);
}

/* ------------------------------------------------------------------ 基础 -- */
/* 只设「主题应该决定的东西」。布局、间距、组件结构由各页自己负责。 */
html {
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
}

h1, h2, h3, h4 {
  color: var(--text);
  letter-spacing: 0.01em;
}

a {
  color: var(--accent);
}

code, kbd, samp, pre, .mono, .t-mono {
  font-family: var(--font-mono);
}

::selection {
  background: var(--accent-soft);
  color: var(--text);
}

/* 键盘可达性：焦点环用主题强调色，不用浏览器默认蓝（默认蓝在废土绿上很脏） */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ------------------------------------------------------ 纹理层（无外部请求） --
 * 扫描线 / 暗角画在 html 的伪元素上：全屏、固定、pointer-events:none。
 * 为什么不用 body::before（壳文件里那样做）：各页可能已经用了 body 伪元素，
 * 而 html 上的伪元素全仓库无人占用（实测 2026-10-11），不会互相覆盖。
 * 档位为 0 时 opacity 归零 —— 元素仍在，但不可见，也不吃掉任何点击。 */
html::before,
html::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 2147483000;
}

/* 扫描线：每 3px 一条 1px 暗线，尺度与 POS 剧情终端一致 */
html::after {
  opacity: var(--texture-scanline);
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 0 2px,
    rgba(0, 0, 0, 0.22) 2px 3px
  );
}

/* 暗角：中心透明、边缘压暗，让内容看起来像长在显像管上 */
html::before {
  opacity: var(--texture-vignette);
  background: radial-gradient(
    ellipse at center,
    rgba(0, 0, 0, 0) 55%,
    rgba(0, 0, 0, 0.55) 100%
  );
}

@media print {
  html::before,
  html::after {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  html::before,
  html::after {
    /* 静态纹理对前庭无害，但打印/录屏时用户可能只想要干净的版面 */
    opacity: calc(var(--texture-scanline) * 0.5);
  }
}

/* ------------------------------------------------------------ 组件：面板 -- */
/* .t-panel 是通用的「终端面板」：硬边、细描边、可选角标。
 * 用它包住内容块，页面就自动获得主题的形状与描边，不必各自写卡片样式。 */
.t-panel {
  position: relative;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  box-shadow: var(--shadow);
  color: var(--text);
}

.t-panel + .t-panel {
  margin-top: 10px;
}

/* 角标：四个直角上的短线段。用 ::before/::after 只能画两个，所以每条边
 * 只用一条线段交叉示意 —— 更多角会让移动端小块显得脏。 */
.t-panel.is-marked::before,
.t-panel.is-marked::after {
  content: "";
  position: absolute;
  width: 10px;
  height: 10px;
  border: 1px solid var(--accent-line, var(--accent));
  pointer-events: none;
}

.t-panel.is-marked::before {
  top: -1px;
  left: -1px;
  border-right: 0;
  border-bottom: 0;
}

.t-panel.is-marked::after {
  right: -1px;
  bottom: -1px;
  border-left: 0;
  border-top: 0;
}

/* 面板标题：左侧一道强调色竖条，代替「大写英文标题」那种会引入文案的写法 */
.t-panel-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.t-panel-title::before {
  content: "";
  flex: none;
  width: 3px;
  height: 14px;
  border-radius: 1px;
  background: var(--accent);
  box-shadow: var(--glow);
}

/* 危险警示带：斜纹条，用作破坏性操作区块的分隔（纯渐变，零请求） */
.t-tape {
  height: 6px;
  border-radius: 2px;
  background: repeating-linear-gradient(
    45deg,
    var(--warn) 0 6px,
    color-mix(in srgb, var(--warn) 25%, transparent) 6px 12px
  );
  opacity: 0.7;
}

/* ------------------------------------------------------------ 组件：按钮 -- */
/* 只兜底没有自带样式的按钮；页面写了 .btn / 自身类名时仍以页面为准。 */
.t-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: var(--ui-tap, 38px);
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}

.t-btn:hover:not(:disabled) {
  border-color: var(--accent-line, var(--accent));
}

.t-btn:active:not(:disabled) {
  background: var(--accent-soft);
}

.t-btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.t-btn.is-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-text);
  box-shadow: var(--glow);
}

.t-btn.is-ghost {
  background: transparent;
}

.t-btn.is-danger {
  border-color: var(--danger);
  color: var(--danger);
}

/* ------------------------------------------------------------ 组件：徽标 -- */
.t-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 11px;
  line-height: 1.7;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.t-badge.is-ok { border-color: var(--ok); color: var(--ok); }
.t-badge.is-warn { border-color: var(--warn); color: var(--warn); }
.t-badge.is-danger { border-color: var(--danger); color: var(--danger); }
.t-badge.is-info { border-color: var(--info); color: var(--info); }

/* -------------------------------------------------- 组件：输入 / 下拉 / 行 -- */
/* 老页面用裸 input / select；这里只补「跟主题走」的部分（描边、圆角、底色）。 */
input[type="text"],
input[type="number"],
input[type="date"],
input[type="search"],
input[type="password"],
textarea,
select {
  border-color: var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  color: var(--text);
  font-family: inherit;
}

input::placeholder,
textarea::placeholder {
  color: var(--muted);
}

/* 表格/列表分隔线统一走主题描边，避免页面各自写死灰色 */
hr {
  border: 0;
  border-top: 1px solid var(--border);
}

/* ------------------------------------------------- 组件：主题与音效开关 -- */
/* theme.js 挂上来的控制条。放在导航条里，尺寸与 ui-nav 的胶囊一致。 */
.t-switchbar {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin: 6px 0;
}

.t-switch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}

.t-switch:hover {
  border-color: var(--accent-line, var(--accent));
  color: var(--text);
}

.t-switch.is-on {
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: var(--glow);
}

/* 开关上的指示点：开 = 实心强调色，关 = 空心描边。不用 emoji 做状态，
 * 因为 emoji 渲染在 Android/iOS 差异很大，在废土绿上尤其脏。 */
.t-switch-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1px solid currentColor;
  background: transparent;
}

.t-switch.is-on .t-switch-dot {
  background: currentColor;
}

/* ------------------------------------------------------- 组件：启动序列 -- */
/* POS 剧情终端的开机自检画面。只在声明了 data-boot 的页面挂载（入口页），
 * 每会话一次、可点击跳过、prefers-reduced-motion 下不出现。
 * 它是纯装饰层：不承载任何必须读到的信息，所以即使被跳过也不丢内容。 */
.t-boot {
  position: fixed;
  inset: 0;
  z-index: 2147483001;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 24px;
  background: var(--bg-sunken, var(--bg));
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.6;
  cursor: pointer;
  overflow: hidden;
}

.t-boot-line {
  opacity: 0;
  animation: t-boot-line 0.28s ease-out forwards;
}

.t-boot.is-done {
  animation: t-boot-out 0.24s ease-in forwards;
  pointer-events: none;
}

/* 自检进度条：宽度走 transition，**时机取 `--motion-step`** ——
 * 废土皮肤是 steps(6)，于是它一格一格跳；基础皮肤是 linear 且关闭动效，
 * 于是它直接跳到终点（同一个函数、两种观感，这就是令牌化的用处）。
 * 时长写死 .6s 是刻意的：它是这一屏的节奏，不属于皮肤的表现层。 */
.t-boot-bar {
  width: min(280px, 60vw);
  height: 8px;
  margin-top: 10px;
  border: 1px solid var(--accent-line, var(--accent));
  background: transparent;
}

.t-boot-bar i {
  display: block;
  width: 0;
  height: 100%;
  background: var(--accent);
  transition: width 0.6s var(--motion-step);
}

/* 光标跟在自检条下面，沿用动效层的闪烁（见 motion.css 的 .t-cursor）。 */
.t-boot .t-cursor::after {
  animation-duration: var(--motion-blink);
}

@keyframes t-boot-line {
  from { opacity: 0; transform: translateY(3px); }
  to { opacity: 1; transform: none; }
}

@keyframes t-boot-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .t-boot { display: none; }
}
