/* ==========================================================================
   Comic Style · 风格包语义类（手写 CSS，零构建依赖）
   --------------------------------------------------------------------------
   本文件是**纯手写 CSS**，不经过 Tailwind、不需要任何构建步骤，浏览器直接
   加载即可生效。它把风格包规则落成了可直接使用的语义类。

   为什么要单独一个文件（而不是塞进 comic-utilities.css）：
     工具层是从 Tailwind 产物一次性翻译来的工具类，规则顺序与原产物
     逐条对应、牵一发动全身；语义类混进去会互相牵连。
     分成两层后各管一件事：工具层承载 HTML 上的工具类，本层承载
     .comic-* 语义类。

   与工具层的关系：
     · HTML 上可以同时写 .comic-card 和工具类，互不冲突。
     · 优先级：两层都是未分层的普通声明，按加载顺序后者胜 —— 本文件在
       comic-utilities.css 之后加载，故语义类能覆盖工具层里的同名属性，
       不需要 !important。
     · 与迁移前一致：原 comic.css 同样是**未分层**声明，且同样在本文件之前
       加载，故迁移前后都是本层胜 —— 这次换实现没有改变任何级联结果。
       （本文件早先的注释写成「Tailwind 的 utilities 在 @layer 里、会赢过本层」，
        那是错的：CSS 规范里未分层声明优先于分层声明，而 comic.css 里
        实际一个 @layer 都没有。）

   边界（重要）：本层**只收敛视觉 token，不接管布局**。
     flex / grid / gap / p-4 / md: / mx-auto 一律留在 HTML 上。
     原因：
       1. 布局是结构属性。语义类塞布局会让组件无法灵活复用
          （同一个 .comic-card 在网格里和在侧栏里需要的 padding 不同）。
       2. 响应式断点数量大（md: 427 处），收进组件类会立刻失控。
     例外：卡片/按钮/输入框「本身就是个盒子」，自带 padding 属于
     风格包 token 的一部分，算视觉而非布局。

   命名：统一 .comic- 前缀 + 语义名，与项目自研机制类（char- / lab- /
         gm- / notify-）的命名习惯一致，一眼可辨来源。
   ========================================================================== */

/* ---------------------------------------------------------------
   1. 设计 token
   --------------------------------------------------------------- */
:root {
    /* 墨线 / 纸面（风格包主色） */
    --comic-ink: #1a1a1a;
    --comic-paper: #fffef0;
    --comic-white: #ffffff;

    /* 强调色（风格包指定的 5 色） */
    --comic-red: #ef4444;
    --comic-yellow: #facc15;
    --comic-blue: #3b82f6;
    --comic-green: #22c55e;

    /* 正文弱化色 */
    --comic-muted: #4a4a4a;

    /* 硬阴影 8 级刻度（对应风格包 Shadow Amplification） */
    --comic-shadow-1: 1px 1px 0px 0px rgba(26, 26, 26, 1);
    --comic-shadow-2: 2px 2px 0px 0px rgba(26, 26, 26, 1);
    --comic-shadow-3: 3px 3px 0px 0px rgba(26, 26, 26, 1);
    --comic-shadow-4: 4px 4px 0px 0px rgba(26, 26, 26, 1);
    --comic-shadow-5: 5px 5px 0px 0px rgba(26, 26, 26, 1);
    --comic-shadow-6: 6px 6px 0px 0px rgba(26, 26, 26, 1);
    --comic-shadow-8: 8px 8px 0px 0px rgba(26, 26, 26, 1);
    --comic-shadow-10: 10px 10px 0px 0px rgba(26, 26, 26, 1);

    /* 白色硬阴影（用于深色底上的浮起元素） */
    --comic-shadow-light-2: 2px 2px 0px 0px rgba(255, 255, 255, 1);
    --comic-shadow-light-4: 4px 4px 0px 0px rgba(255, 255, 255, 1);

    /* 墨线 / 圆角 / 动效时长 */
    --comic-border: 4px solid var(--comic-ink);
    --comic-radius: 2px;          /* 风格包禁用了 rounded-lg/xl，取最小圆角 */
    --comic-radius-bubble: 1.5rem;/* 对话气泡专用，风格包明确给出 */
    --comic-duration: 100ms;      /* 风格包：单次交互最长 100ms */
}

/* ---------------------------------------------------------------
   2. 动效速度分层
   ---------------------------------------------------------------
   风格包的「单次交互最长 100ms」针对的是**瞬时反馈**（按下、色变、
   缩放弹出）——那必须干脆。但「位移 + 阴影扩张」属于空间移动，
   100ms 会糊成一团，视觉上像是卡片在颤抖而不是被拿起来。

   故拆成两档：
     · 反馈档 100ms —— 底色 / 边框色 / 文字色 / transform 缩放
     · 位移档 300ms —— 盒子阴影 / translate

   实现方式：基础态只声明**位移档**，然后在「反馈档」那组属性上单独
   覆盖成 100ms（见下方 .comic-btn / .comic-card-interactive）。
   --------------------------------------------------------------- */
:root {
    --comic-duration-move: 300ms;
}

/* ---------------------------------------------------------------
   3. 按钮 · .comic-btn
   ---------------------------------------------------------------
   招牌动效 Heavy Ink：hover 下压 2px + 阴影缩到 2px；active 归零。
   hover/active 用**原生伪类**实现，不依赖 Tailwind 的变体前缀。
   --------------------------------------------------------------- */
/* ★ 2026-09-18 重新对齐 ★
   本类此前是「风格包标准刻度」（hover 下压 2px + 300ms 位移档），且**没有任何
   调用点**。现在把它对齐到 ComicTokens.BtnBase 的原写法，作为全站 82 个主按钮
   的落点。逐属性对照（左＝原 Tailwind 串，右＝本类）：
     inline-flex → display:inline-flex          gap-2        → gap:.5rem
     items-center → align-items:center          px-4 py-2    → padding:.5rem 1rem
     justify-center → justify-content:center    font-black   → font-weight:900
     border-4 → 4px  solid                      uppercase    → （已取消，见下）
     border-[#1a1a1a] → #1a1a1a                 tracking-wide→ letter-spacing:.025em
     rounded-sm → border-radius:.125rem         shadow-[4px…]→ --comic-shadow-4
     transition-all duration-100 → transition 的 property/duration/timing 全对齐
   ★ 强制大写已整体取消 ★
     上表里的 `uppercase → text-transform: uppercase` 一条**后来被删掉了** ——
     全站不再强制字母大写（按钮 / 徽章 / 导航 / 标题一律按原文大小写显示）。
     故本类的属性清单里没有 text-transform；tracking-wide 保留（字距与大小写无关）。
   ★ 两条必须显式写、否则静默回归的属性 ★
     · font-size / line-height：原写法靠 Tailwind preflight 的
       `button{font-size:100%;line-height:inherit}` 继承父级。本类不写就会退回
       UA 默认的 13.3333px —— 故显式 inherit，让本类**自足**。
     · font-family：同理，preflight 给的是 inherit。
   ★ 有意**不加**的两条（加了就是视觉变更，另行排期）★
     · cursor:pointer —— 原写法没有；<button> 是 default、<a href> 才是 pointer。
     · user-select:none —— 原写法没有。 */
.comic-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    border: 4px solid var(--comic-ink);
    border-radius: var(--comic-radius);
    padding: 0.5rem 1rem;
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    font-weight: 900;
    letter-spacing: 0.025em;
    box-shadow: var(--comic-shadow-4);
    transition-property: all;
    transition-duration: var(--comic-duration);
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.comic-btn:hover {
    transform: translate(2px, 2px);
    box-shadow: var(--comic-shadow-2);
}

.comic-btn:active {
    transform: translate(4px, 4px);
    box-shadow: none;
}

/* 尺寸变体 · 小按钮。★ 自足类，不依赖 .comic-btn ★
   对齐 ComicTokens.BtnSm 原写法（58 个调用点，管理后台主力按钮）：
     gap-1.5(.375rem) / px-3 py-1.5(.375rem .75rem) / text-[11px](.6875rem)
     shadow-3 → hover 缩到 shadow-1 + 平移 2px → active 平移 3px 且阴影归零
   ★ 为什么做成自足类而不是「.comic-btn + 尺寸覆盖」★
     两者的 gap / padding / 字号 / 阴影刻度 / active 位移幅度**全都不一样**，
     若叠在 .comic-btn 上要写 5 条覆盖，覆盖项多于相同项 —— 按本层既有判据
     （见 .comic-gm-collapse 的注释）就该给它一个名字。
   ★ 颜色变体（.comic-btn-red 等）只设 background-color + color，
     因此对本类同样生效，无需另建一套。 */
.comic-btn-sm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    border: 4px solid var(--comic-ink);
    border-radius: var(--comic-radius);
    padding: 0.375rem 0.75rem;
    font-family: inherit;
    font-size: 0.6875rem;        /* text-[11px]：任意值档，不带行高 */
    line-height: inherit;
    font-weight: 900;
    letter-spacing: 0.025em;
    box-shadow: var(--comic-shadow-3);
    transition-property: all;
    transition-duration: var(--comic-duration);
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.comic-btn-sm:hover {
    transform: translate(2px, 2px);
    box-shadow: var(--comic-shadow-1);
}

.comic-btn-sm:active {
    transform: translate(3px, 3px);
    box-shadow: none;
}

/* 禁用态：对齐原写法里的 `disabled:opacity-40`
   （Music/Index.cshtml 的 JS 按钮串、以及若干 <button disabled> 用） */
.comic-btn:disabled,
.comic-btn-sm:disabled {
    opacity: 0.4;
}

.comic-btn-lg {
    padding: 0.75rem 1.5rem;
    font-size: 1.125rem;
}

/* 颜色变体（底色 + 文字色；墨线与硬阴影保持不变）
   ★ 每一对都对齐本站原写法里出现过的「底色 + 文字色」配对 ★
     bg-[#fffef0] text-[#1a1a1a] → -paper
     bg-[#3b82f6] text-white     → -blue
     bg-[#ef4444] text-white     → -red
     bg-[#22c55e] text-white     → -green
     bg-[#facc15] text-[#1a1a1a] → -yellow
     bg-[#1a1a1a] text-white     → -ink
   ⚠ 待管理员裁决（未擅自改）⚠
     -green 是**绿底白字**，对比度 1.99:1，未达风格包要求的 WCAG AA。
     风格包的标准配对本应是绿底**墨字**（9.4:1）。此处按「先写后审、零回归」
     保留原值（全站 13 处），要不要改成墨字请管理员定夺 —— 那是视觉变更，
     按阶段二铁律① 不夹在替换里做。 */
.comic-btn-red { background-color: var(--comic-red); color: var(--comic-white); }
.comic-btn-yellow { background-color: var(--comic-yellow); color: var(--comic-ink); }
.comic-btn-blue { background-color: var(--comic-blue); color: var(--comic-white); }
.comic-btn-green { background-color: var(--comic-green); color: var(--comic-white); }
.comic-btn-ink { background-color: var(--comic-ink); color: var(--comic-white); }
.comic-btn-paper { background-color: var(--comic-paper); color: var(--comic-ink); }
/* 变暗的纸白底 + 弱化灰字（分页「已禁用」档在用，见 .comic-page-btn-disabled） */

/* 尺寸 / 排版修饰（对齐 ComicTokens.BtnBase 调用点上出现过的工具类）
   这些东西在原写法里是**逐调用点追加**的，取值高度固定，故收成单一职责的修饰类。
   判据同本层其它修饰类：一个类只改一件事，由调用点按需拼。 */
.comic-btn-block { width: 100%; }                    /* w-full */
.comic-btn-grow { flex: 1 1 0%; }                    /* flex-1 */
.comic-btn-pad-lg { padding: 0.75rem 1.5rem; }       /* px-6 py-3 */
.comic-btn-pad-y-lg { padding-top: 0.75rem; padding-bottom: 0.75rem; }  /* py-3（左右仍是 px-4） */
.comic-btn-text-xs { font-size: 0.75rem; line-height: 1rem; }      /* text-xs 自带行高 */
.comic-btn-text-sm { font-size: 0.875rem; line-height: 1.25rem; }   /* text-sm 自带行高 */
.comic-btn-text-base { font-size: 1rem; line-height: 1.5rem; }      /* text-base 自带行高 */
/* 响应式字号（小屏 text-base、≥768px 升 text-lg）。
   ★ 响应式断点一般留在 HTML 上（见文件头「边界」），但这里是**组件内部**的
   字号档，所以在类里直接用媒体查询收进来。 */
.comic-btn-text-fluid { font-size: 1rem; line-height: 1.5rem; }
@media (min-width: 768px) {
    .comic-btn-text-fluid { font-size: 1.125rem; line-height: 1.75rem; }
}

/* 反相按钮：深色文字块上的白底按钮（hover 时反转） */
.comic-btn-invert {
    background-color: var(--comic-white);
    color: var(--comic-ink);
    box-shadow: var(--comic-shadow-light-4);
}
.comic-btn-invert:hover { box-shadow: var(--comic-shadow-light-2); }

/* ---------------------------------------------------------------
   3a-2. 深色底组件 · .comic-on-ink 组合类
   ---------------------------------------------------------------
   ★ 这是什么 ★
   本站有一类元素长在**深色底**上（页脚 `bg-[#1a1a1a]`、墨块卡片内部），
   它们的墨线与硬阴影必须是**白色**的 —— 墨色压墨色等于没有。
   这类元素全站约 6 处，形态高度一致：白墨线 + 白硬阴影 + 深色底。

   ★ 为什么收成组合类，而不是拆成一堆修饰类 ★
   最初考虑拆法（.comic-on-ink-hover-blue / -hover-yellow 之类的散件，
   甚至连阴影都想单独出个类），但拆完调用点反而更难读：读者要自己把
   「白边框 + 白阴影 + 深底」三件事拼起来才明白这是「深色底上的块」。
   而它们**永远同时出现、也只在深色底上出现**，是个不可分的整体，
   所以直接给它一个名字：.comic-on-ink。
   这也与 .comic-card-ink（深底卡片）的命名习惯一致。
   只有「hover 目标色」是真正会变的，才单独拆成 -hover-* 变体。

   ★ 与 --comic-shadow-light-* token 的关系 ★
   token 保留（它们是值），本类是用值的地方。
   将来要改深色底元素的阴影刻度，仍改 token，不必碰调用点。

   ★ hover 时阴影从 4px 缩到 2px ★
   与 .comic-btn 的 Heavy Ink 手感一致（按下时「被压进纸里」）。
   注意浅色底的元素 hover 是**位移 + 阴影缩**，这里只有阴影缩 ——
   因为原写法没有 translate，加位移就是视觉变更。

   ★ 为什么边框色要写成 `.comic-on-ink.comic-btn-h-11` 这样的复合选择器 ★
   「在深色底上」这件事的视觉后果就是**白边框 + 白阴影**，
   但尺寸类 .comic-btn-h-11 自带墨色边框（border: var(--comic-border)），
   而它在文件里排在**后面**（尺寸块在组合类块之后）—— 同特异性下后写的赢，
   于是白边框被墨边框覆盖，实测 border-top-color 编译成 rgb(26,26,26)。
   修法是提高本类的特异性，而不是调整文件顺序：
   顺序依赖是脆的（将来有人挪块就会再次静默坏掉），而复合选择器
   把「深色底 + 这个尺寸」这层关系**写进了选择器本身**，与位置无关。 */
.comic-on-ink {
    border-color: var(--comic-white);
    box-shadow: var(--comic-shadow-light-4);
}
.comic-on-ink.comic-btn-h-11 {
    border-color: var(--comic-white);
}

.comic-on-ink:hover {
    box-shadow: var(--comic-shadow-light-2);
}

/* 深色底 + hover 变蓝：GitHub 图标按钮 */
.comic-on-ink-hover-blue:hover { background-color: var(--comic-blue); }

/* 深色底 + hover 变黄（文字同时转墨色）：RSS 图标按钮 */
.comic-on-ink-hover-yellow:hover {
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
}

/* ---------------------------------------------------------------
   3b. 墨块弹出导航项 · .comic-nav-item
   ---------------------------------------------------------------
   ★ 这是什么 ★
   本站最高频的交互块之一，形态固定为一套「未选中 → 选中」的翻转：
     静止  透明边框 + 透明底 + 墨色文字
     hover 边框变墨色 + 底色上色 + 加 3px 硬阴影（墨块「长出来」）
     active 阴影归零 + 平移 3px（被按进去）
   全站出现 40+ 次（_Layout 顶部导航 5 处 + 管理下拉 7 处 + 移动端菜单
   + 登出/注册/登录按钮 + 各页面的次级导航）。

   ★ 为什么用「颜色变体」而不是一个通用类 ★
   hover 的目标色在各处不同：黄（导航）、蓝（下拉菜单）、红（管理）、
   墨黑（登出）。这些不是随手挑的 —— 红/蓝/黄是风格包的三个强调色，
   再翻转文字色以保证对比度。所以这里按「目标色」拆变体，
   每个变体内部是**一次完整的配对**（底色 + 文字色同时给出）。
   只给底色不给文字色会导致「黄底白字」这类对比度不足。

   ★ 为什么 hover 的位移在这里是 0 ★
   .comic-btn 是「按下时平移」；而导航项是「墨块从无到有长出来」——
   位移由阴影的产生与消失承担，元素本身不动。两套手感不同，别混用。

   ★ 时长 ★
   反馈类属性（底色/边框色/文字色/阴影）全部 100ms。导航项没有大位移，
   不需要 300ms 的位移档 —— 那会让菜单响应显得迟钝。 */
.comic-nav-item {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.5rem 0.625rem;
    border: 4px solid transparent;
    border-radius: var(--comic-radius);
    font-weight: 900;
    letter-spacing: 0.025em;
    font-size: 0.75rem;
    line-height: 1.3333;
    color: var(--comic-ink);
    background-color: transparent;
    cursor: pointer;
    user-select: none;
    box-shadow: none;
    transition: background-color var(--comic-duration),
                border-color var(--comic-duration),
                color var(--comic-duration),
                box-shadow var(--comic-duration),
                transform var(--comic-duration);
}

/* 各目标色变体：hover 是完整配对（底色 + 文字色 + 墨线 + 硬阴影） */
.comic-nav-item:hover {
    border-color: var(--comic-ink);
    box-shadow: var(--comic-shadow-3);
}

.comic-nav-item-yellow:hover { background-color: var(--comic-yellow); color: var(--comic-ink); }
.comic-nav-item-blue:hover { background-color: var(--comic-blue); color: var(--comic-white); }
.comic-nav-item-red:hover { background-color: var(--comic-red); color: var(--comic-white); }
.comic-nav-item-green:hover { background-color: var(--comic-green); color: var(--comic-ink); }
.comic-nav-item-ink:hover { background-color: var(--comic-ink); color: var(--comic-white); }

.comic-nav-item:active {
    box-shadow: none;
    transform: translate(3px, 3px);
}

/* 实底版本：静止就有墨线（以及由 -*-solid 给的底色）。
   ---------------------------------------------------------------
   ★ 为什么这里**不设** box-shadow ★
   最初这里写了 box-shadow: var(--comic-shadow-3)，想表达「实底 = 完整墨块」。
   实测发现这是错的 —— 本站「实底导航项」其实分两种：
     · 管理下拉菜单项   静止：墨线 + 纸白底 + **无阴影**
     · 移动端菜单 / 登录 静止：墨线 + 底色   + 3px 阴影
   一刀切加阴影会给下拉菜单项凭空多出一块阴影（验证报告里的 NAV_MENU / UM_ITEM）。
   故本类只管边框，阴影交给独立的 -shadow-3；要哪种组合由调用点说清楚。
   这样也更好读：「实底」说的是边框与底色，「带阴影」是另一件事。 */
.comic-nav-item-solid {
    border-color: var(--comic-ink);
}

/* 静止就带 3px 硬阴影（移动端菜单项、移动端登录、页脚项） */
.comic-nav-item-shadow-3 {
    box-shadow: var(--comic-shadow-3);
}

/* 实底 + 钉住底色：给「静止就是某色、且不需要 hover 换色」的项用。
   ★ 为什么必须单独一个类 ★
   .comic-nav-item-red:hover 会把底色改成红 —— 若元素静止已经是红底，
   这条 hover 规则就成了 no-op，视觉上「hover 没反应」。
   而 .comic-nav-item-solid 只补边框与阴影、不设底色，
   故静止底色必须由这个修饰类钉住。
   用法：.comic-nav-item .comic-nav-item-red .comic-nav-item-red-solid */
.comic-nav-item-red-solid {
    background-color: var(--comic-red);
    color: var(--comic-white);
}

.comic-nav-item-yellow-solid {
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
}

.comic-nav-item-ink-solid {
    background-color: var(--comic-ink);
    color: var(--comic-white);
}

.comic-nav-item-paper-solid {
    background-color: var(--comic-paper);
    color: var(--comic-ink);
}

/* 大号变体：移动端菜单 / 页脚（text-sm，间距更宽） */
.comic-nav-item-lg {
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    font-size: 0.875rem;
    line-height: 1.4286;
}

/* 中等号变体：管理下拉菜单（text-xs，带 gap-2 与 px-3） */
.comic-nav-item-md {
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
}

/* 左右内边距覆盖：px-3（12px）。
   ★ 为什么单独一个类 ★
   基础 .comic-nav-item 的左右内边距是 0.625rem（10px，对应 px-2.5），
   但顶部「登录」按钮原本写的是 px-3（12px）。差 2px 就是真回归
   （实测抓到：padding-left/right 12px → 10px）。
   这里不并进某个尺寸档，因为它是**单一属性覆盖**，
   语义上与「尺寸档」不同 —— 尺寸档改的是整套（gap+padding+字号）。 */
.comic-nav-item-px-3 {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
}

/* ---------------------------------------------------------------
   3d. hover 驱动 · 两种机制（.comic-hover-* 体系）
   ---------------------------------------------------------------
   ★ 背景：group-hover 特效的机制选择 ★
   本站 78 处交互特效靠 `group` + `group-hover:*` 实现，去 Tailwind 时这些
   工具类一并消失，因此把效果收进本层的 .comic-hover-* 体系。选机制时有个
   容易被忽略的硬约束：

     · `.group:hover .group-hover\:x` —— 由**祖先**的 hover 驱动后代。
       它的能力严格强于「按自身」：它能让一个**不可交互**的元素随
       兄弟/祖先的 hover 而变化。本站的网点层就是这种：
       网点层必须是 `pointer-events: none`（风格包硬性要求，
       否则它会吞掉父元素的点击），**它自己永远收不到 :hover**。
       所以网点浮现这类效果，改成「按自身 hover」是做不到的 ——
       不是风格问题，是物理上不可能。

     · `.x:hover` —— 由**自身**的 hover 驱动。DOM 更简单，但要改的是
       class 的归属（把它从祖先挪到后代身上）。

   ★ 两种机制都保留，按「能不能按自身」分发 ★
   所以本层提供**两套**，语义上明确分开：

     按自身： .comic-hover-scale-150 / -110 / -100
              .comic-hover-opacity-20
              用法：把这些类直接写在**要变形的那个元素**上。
              适用于元素本身可交互、或不需要交互也能 hover 的情况。

     按容器： .comic-hover-root           —— 写在容器上，声明「我是 hover 源」
              .comic-hover-child-*        —— 写在后代上，由容器的 hover 驱动
              用法：网点层这类 pointer-events:none 的元素必须走这条。

   ★ 关于 :focus-within（为什么按容器版比 .group 更强）★
   `.comic-hover-root` 用 `:is(:hover, :focus-within)`，比 `.group:hover`
   多接住一种情况：**键盘 Tab 进入容器内的链接时，容器也被视为激活**。
   风格包的无障碍要求里，键盘可达性是一等公民，而原 `group-hover` 的实现
   （`.group:hover`）对纯键盘用户完全不响应 —— 网点永不浮现。
   所以这里不是「顺手加的」，是把 group-hover 的一个既有无障碍缺口补上。
   增量影响：仅键盘聚焦时多出网点，鼠标路径与原来**逐像素一致**。

   ★ 时长沿用 100ms ★
   这些都是「瞬时反馈」（缩放弹出 / 不透明度），走反馈档，不走位移档。 */
.comic-hover-root:hover,
.comic-hover-root:focus-within {
    /* 只作「hover 源」标记，自身不设任何视觉 ——
       具体浮现什么由后代上的 .comic-hover-child-* 决定。
       这样同一个容器可以同时驱动多种后代效果。 */
}

/* 按自身：缩放弹出手感（风格包 Badge Pop） */
.comic-hover-scale-150,
.comic-hover-scale-110,
.comic-hover-scale-100 {
    transition: transform var(--comic-duration);
}

.comic-hover-scale-150:hover { transform: scale(1.5); }
.comic-hover-scale-110:hover { transform: scale(1.1); }
.comic-hover-scale-100:hover { transform: scale(1); }

/* 按自身：不透明度浮现 */
.comic-hover-opacity-20 {
    transition: opacity var(--comic-duration);
}
.comic-hover-opacity-20:hover { opacity: 0.2; }

/* 按容器：后代由容器 hover / 容器内焦点驱动。
   ★ 选择器为何写成 :is(:hover, :focus-within) 而不是 :hover ★
   见上方说明 —— 补上键盘可达性。 */
.comic-hover-root:is(:hover, :focus-within) .comic-hover-child-scale-150 { transform: scale(1.5); }
.comic-hover-root:is(:hover, :focus-within) .comic-hover-child-scale-110 { transform: scale(1.1); }
.comic-hover-root:is(:hover, :focus-within) .comic-hover-child-scale-100 { transform: scale(1); }
.comic-hover-root:is(:hover, :focus-within) .comic-hover-child-opacity-20 { opacity: 0.2; }

/* 被驱动的后代：过渡声明放在自己身上（与「按自身」版保持同一条时间线）。
   ★ 为什么不靠继承或写在容器上 ★
   transition 是逐元素的、不继承。写在容器上只会让容器自己过渡，
   网点的 opacity 变化依然是瞬变（没有淡入效果）。 */
.comic-hover-child-scale-150,
.comic-hover-child-scale-110,
.comic-hover-child-scale-100 {
    transition: transform var(--comic-duration);
}

.comic-hover-child-opacity-20 {
    transition: opacity var(--comic-duration);
}

/* ---------------------------------------------------------------
   3c. 按钮 · hover 上色变体
   ---------------------------------------------------------------
   与 .comic-nav-item 的区别：这里 .comic-btn 的「按下平移」手感保留，
   只是在 hover 时**同时**换色。用于「登录」「开始创作」这类主行动按钮。
   全站 20+ 处。 */
.comic-btn-hover-yellow:hover { background-color: var(--comic-yellow); color: var(--comic-ink); }
.comic-btn-hover-blue:hover { background-color: var(--comic-blue); color: var(--comic-white); }
.comic-btn-hover-red:hover { background-color: var(--comic-red); color: var(--comic-white); }
.comic-btn-hover-ink:hover { background-color: var(--comic-ink); color: var(--comic-white); }
.comic-btn-hover-paper:hover { background-color: var(--comic-paper); color: var(--comic-ink); }

/* ---------------------------------------------------------------
   4. 卡片 · .comic-card
   --------------------------------------------------------------- */
.comic-card {
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background: var(--comic-paper);
    box-shadow: var(--comic-shadow-4);
}

/* 尺寸变体：改变硬阴影刻度，营造层级感 */
.comic-card-sm { box-shadow: var(--comic-shadow-3); }
.comic-card-lg { box-shadow: var(--comic-shadow-6); }
.comic-card-xl { box-shadow: var(--comic-shadow-10); }

/* 配色变体 */
.comic-card-ink { background: var(--comic-ink); color: var(--comic-paper); }
.comic-card-white { background: var(--comic-white); }

/* 可交互卡片：Shadow Amplification —— hover 时阴影扩大配合上浮，
   即招牌的「卡片被拿起来」手感。此处不再依赖 Tailwind 的 hover: 前缀。

   ★ 基础阴影刻度：默认 8px，但提供 -4 变体 ★
   本站原本的高频卡片写法是「基础 4px + hover 10px」（Tailwind 内联任意值），
   共 96 处 shadow-[3px...]、73 处 shadow-[6px...]、46 处 shadow-[4px...]。
   如果统一用 8px，会是一次**全站视觉变更**而非零回归替换。

   所以拆两档，让迁移期可以精确对齐原值：
     · .comic-card-interactive     → 基础 8px，hover 16px（风格包标准扩张）
     · .comic-card-interactive-4   → 基础 4px，hover 10px（对齐本站原高频写法）
   阶段二逐页替换时，**优先用 -4 变体**（保持零回归），
   只有明确要提层级的位置才用默认 8px。

   注意：位移与阴影走 300ms（慢于按钮的 100ms）—— 它承载空间移动，
   100ms 会让「被拿起来」看起来像颤抖。 */
.comic-card-interactive {
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background: var(--comic-paper);
    box-shadow: var(--comic-shadow-8);
    transition: box-shadow var(--comic-duration-move),
                transform var(--comic-duration-move),
                background-color var(--comic-duration),
                border-color var(--comic-duration);
}

.comic-card-interactive:hover {
    box-shadow: 16px 16px 0px 0px rgba(26, 26, 26, 1);
    transform: translateY(-8px);
}

/* 4px 起步变体：对齐本站原有「4px → 10px + 上浮 8px」写法，零视觉回归 */
.comic-card-interactive-4 {
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background: var(--comic-paper);
    box-shadow: var(--comic-shadow-4);
    transition: box-shadow var(--comic-duration-move),
                transform var(--comic-duration-move),
                background-color var(--comic-duration),
                border-color var(--comic-duration);
}

.comic-card-interactive-4:hover {
    box-shadow: var(--comic-shadow-10);
    transform: translateY(-8px);
}

/* 6px 起步变体：对齐登录 / 注册 / 表单分镜的「基础 6px」写法。
   ★ 本变体不加 hover —— 调用方（表单面板）本身不需要被「拿起来」。
      需要 hover 放大时用 -6-hover（见下）。 */
.comic-card-6 {
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background: var(--comic-paper);
    box-shadow: var(--comic-shadow-6);
}

/* 6px 起步 + hover 扩张 → 10px + 上浮（首页卡片的写法，零回归对齐） */
.comic-card-interactive-6 {
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background: var(--comic-paper);
    box-shadow: var(--comic-shadow-6);
    transition: box-shadow var(--comic-duration-move),
                transform var(--comic-duration-move),
                background-color var(--comic-duration),
                border-color var(--comic-duration);
}

.comic-card-interactive-6:hover {
    box-shadow: var(--comic-shadow-10);
    transform: translateY(-8px);
}

/* ★ 原生过渡与 duration 类的冲突（踩坑留档）★
   调用点的 duration-100 只设置 transition-duration，在迁移期它会把上面
   -interactive-* 的「位移档 300ms」覆盖成 100ms —— 卡片被拿起时会显得
   像在颤抖。但本站 96 处卡片都写了 transition-all duration-100，
   直接改掉就是全站视觉变更。
   折中：保留调用点的 duration-100（零回归），在 -interactive-4/-6
   上把 transition 写全，使它自己就是完整声明；将来阶段二收尾时
   统一移除调用点的 duration-100，即可恢复 300ms 位移档。 */

/* ---------------------------------------------------------------
   5. 输入框 · .comic-input
   --------------------------------------------------------------- */
/* 对齐 ComicTokens.FieldBase 原写法（12 个调用点），并作为 InputBase / InputAuto
   的底座。逐属性对照：
     rounded-sm → border-radius:.125rem      font-bold → 700
     border-4 / border-[#1a1a1a] → 4px solid #1a1a1a
     bg-[#fffef0] → background-color: paper
     px-3 → padding: 0 .75rem  ★ 纵向是 0，不是 0.5rem ★
       —— 原写法的纵向内边距来自 Tailwind preflight 的 `padding:0`，
          本类此前写的是 0.5rem，直接套上去会让输入框凭空高 8px。
     text-sm → .875rem / 1.25rem（预设档自带行高，必须一起还原）
     placeholder:text-[#4a4a4a] → ::placeholder 颜色
   ★ 为什么**不**设 width:100% ★
     FieldBase 原写法没有 w-full（调用点自己按需追加），而 InputBase/InputAuto
     有。故宽度拆成独立的 .comic-input-full，由调用点决定 ——
     否则 textarea / 表单里的窄输入框会被撑满。
   ★ 自足性 ★ font-family / letter-spacing 显式 inherit：原写法靠 preflight
     给表单控件 `font-family:inherit; letter-spacing:inherit`。本类不写就会
     退回 UA 默认字体（肉眼可见的差异）。 */
.comic-input {
    border: 4px solid var(--comic-ink);
    border-radius: var(--comic-radius);
    background-color: var(--comic-paper);
    padding: 0 0.75rem;
    font-family: inherit;
    letter-spacing: inherit;
    font-weight: 700;
    font-size: 0.875rem;
    line-height: 1.25rem;
    color: var(--comic-ink);
}

.comic-input::placeholder { color: var(--comic-muted); }

/* 撑满一行（原写法的 w-full） */
.comic-input-full { width: 100%; }

/* 高度锁定变体：让输入框与同行按钮（border-4 + py-2）等高。
   本站原写法是 h-11（2.75rem），这里必须给**显式高度**而不是 padding ——
   显式高度会压掉 padding 对盒高的影响，才能与 h-11 逐像素等价。
   textarea 不要用这个变体，会被压成单行。 */
.comic-input-fixed {
    height: 2.75rem;
}

/* 聚焦态：对齐 `focus:shadow-[4px_4px…] focus:outline-none`
   ★ 工具层的 outline-none 不是 `outline:none`，而是「2px 透明描边 + offset 2px」——
     用 `outline:none` 在 computed 上是另一个值（0px），会报差异。如实还原。 */
.comic-input:focus {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: var(--comic-shadow-4);
}

/* 多行输入框：对齐 `min-h-[120px] resize-y py-2`（Article/View 与 Friends/Apply） */
.comic-textarea {
    min-height: 120px;
    resize: vertical;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
}

/* 文件选择框：对齐 `pt-2`（Profile 的头像上传）。
   ★ 为什么单独一个类而不是通用 padding 覆盖 ★
     type=file 的控件自带一个「选择文件」按钮，需要上方留白才不会贴边；
     这是该控件的形态需求，不是随手加的间距，故给它一个语义名。 */
.comic-input-file { padding-top: 0.5rem; }

/* ---------------------------------------------------------------
   6. 徽章 · .comic-badge
   ---------------------------------------------------------------
   小尺寸标签块。默认 inline-flex + 不折行，因为徽章内容都是短词
   （篇数、状态、NEW!），换行会破坏墨块形状。
   --------------------------------------------------------------- */
/* 对齐 ComicTokens.BadgeBase（19 个调用点）。逐属性对照：
     inline-flex / items-center / whitespace-nowrap / font-black(900) / text-[10px]
     px-2 py-0.5  → padding: .125rem .5rem   ✓ 逐值相同
     shrink-0     → flex-shrink: 0          ← 本次补上
   ★ 本次补 flex-shrink:0 的理由 ★
     BadgeBase 原写法带 shrink-0，而本类此前没有。徽章在 flex 行里本来就不该
     被压缩（内容都是「篇数 / 状态 / NEW!」这类短词，压了会挤成一团）。
     影响面：本类原有 3 个调用点（Tags 页）会多出这一条 computed 值，
     它们都不处在收缩压力下，视觉不变 —— 属白名单项，非回归。
   ★ 保留的两处「风格包合规」差异 ★
     letter-spacing .025em 与 line-height 1.6 是原写法没有的（原 text-[10px]
     是任意值档，不带行高）。这两项在 Tags 批次已作为「风格包合规补齐」放行，
     本次沿用同一政策覆盖 BadgeBase 的 19 处 —— 加了字距 0.25px、行高 16px，
     肉眼不可见。若管理员认为应严格零回归，删掉这两行即可。 */
.comic-badge {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    border: 4px solid var(--comic-ink);
    border-radius: var(--comic-radius);
    padding: 0.125rem 0.5rem;
    font-family: inherit;
    font-weight: 900;
    font-size: 0.625rem;
    line-height: 1.6;
    letter-spacing: 0.025em;
    white-space: nowrap;
}

.comic-badge-red { background-color: var(--comic-red); color: var(--comic-white); }
.comic-badge-yellow { background-color: var(--comic-yellow); color: var(--comic-ink); }
.comic-badge-blue { background-color: var(--comic-blue); color: var(--comic-white); }
/* ⚠ 待管理员裁决：绿底**白字**对比度 1.99:1，未达风格包要求的 WCAG AA。
   风格包的标准配对是绿底墨字（9.4:1）。此处按「零回归」保留原值（18 处：
   徽章 5 + 按钮 13）。要不要改墨字请管理员定夺 —— 属视觉变更，按铁律① 另行排期。 */
.comic-badge-green { background-color: var(--comic-green); color: var(--comic-white); }
.comic-badge-ink { background-color: var(--comic-ink); color: var(--comic-white); }
.comic-badge-paper { background-color: var(--comic-paper); color: var(--comic-ink); }

/* 方形徽章：给固定尺寸的图标块用（如右上角弹出的 GO! / NEW!）。
   ★ 为什么需要这个变体 ★
   默认 .comic-badge 自带 0.125rem/0.5rem 内边距，那是为「扁长文字块」设计的。
   但方形徽章自己已经用 h-9 w-9 钉死了宽高，再叠内边距会把内容挤变形
   （实测：padding 从 0 变 2px/8px，盒模型撑开，内容位移）。
   故 -square 变体把内边距归零，只保留边框/圆角/字重/居中。
   用法：.comic-badge .comic-badge-<色> .comic-badge-square + h-9 w-9 justify-center */
.comic-badge-square {
    padding: 0;
    justify-content: center;
}

/* ---------------------------------------------------------------
   6b. 标题层级 · .comic-section-title / .comic-label-sm
   ---------------------------------------------------------------
   这两种文字块在本站是「一个字面量重复几百次」的重灾区：
     · .comic-section-title —— 面板内小标题（蓝/红/黄竖条 + 图标 + 文字）
     · .comic-label-sm      —— 全大写、字距放宽的 10px 小标签
   ★ 为什么用 letter-spacing 而不是 tracking-* ★
   tracking-* 是工具层提供的类（迁移期它曾赢过本文件的未分层声明）。
   要让「删掉 tracking-* 后视觉不变」成立，必须在这里显式写出等价字距：
     tracking-wide     → 0.025em
     tracking-widest   → 0.1em
   字体大小同理（text-[10px] → 0.625rem）。
   故本类**故意**把这些值也声明一遍。 */
.comic-section-title {
    font-weight: 900;
    letter-spacing: 0.025em;
    font-size: 1.125rem;      /* text-lg */
    line-height: 1.5556;      /* text-lg 自带行高 1.75rem ÷ 18px */
    color: var(--comic-ink);
    margin: 0 0 1rem;
}

.comic-label-sm {
    font-weight: 900;
    letter-spacing: 0.1em;    /* tracking-widest */
    font-size: 0.625rem;      /* text-[10px] */
    color: var(--comic-muted);
}

/* 次要说明文字：11px 粗体灰 */
.comic-note-sm {
    font-weight: 700;
    font-size: 0.6875rem;     /* text-[11px] */
    color: var(--comic-muted);
}

/* 更小一档的说明文字：10px 粗体灰。
   ★ 为什么要和 -sm 分开 ★
   本站「小灰字」有 10px 与 11px 两档，都写成 text-[10px] / text-[11px]。
   两者差 1px，肉眼在密集区域几乎看不出，但 computed 值不同 ——
   套错就是静默回归。故各留一个类，强制调用点先想清楚要哪一档。 */
.comic-note-xs {
    font-weight: 700;
    font-size: 0.625rem;      /* text-[10px] */
    color: var(--comic-muted);
}

/* 固定尺寸的方形图标按钮 · 深色底版（h-11 w-11，页脚社交按钮）
   ★ 为什么尺寸要单独一个类 ★
   .comic-on-ink 只管边框与阴影（那是「在深色底上」这件事的视觉后果），
   尺寸是另一回事 —— 同一个深色底组合类也要能用在别的尺寸上。
   分开后「尺寸」与「环境」两个维度各自独立，组合数从 N×M 降到 N+M。

   ★ 踩坑（真实回归，由 _Layout 批次的逐属性 diff 抓出来）★
   本类初版只写了 width/height/justify-content + 颜色，**假设调用点会同时带
   .comic-btn 来提供盒模型**（display / align-items / border / 字号）。
   但页脚社交按钮的调用点是 `comic-on-ink comic-btn-h-11 comic-on-ink-hover-blue`
   —— **没有 .comic-btn**。结果实测 computed 值是：
       display: block（不是 inline-flex）
       border-width: 0（.comic-on-ink 只给 border-color，没给 border-width/style）
       align-items: normal（图标不居中）
   也就是说这个按钮**本来就是坏的**，且坏在「迁移前是好的、迁移后变坏」。
   教训：**组合类必须自足**。凡是「改成 xx 尺寸」这类命名（-h-11）的类，
   就要自带完整盒模型，不能把 display/border 寄希望于别的类同时出现。
   现在它与 .comic-btn 的对应关系是「同构但尺寸不同」，而非「叠加补丁」。 */
.comic-btn-h-11 {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;           /* w-11 */
    height: 2.75rem;          /* h-11 */
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-ink);
    color: var(--comic-white);
    font-size: 1.125rem;      /* text-lg：里面的图标字形大小 */
    line-height: 1.75rem;     /* text-lg 的配套行高，见 .comic-logo-mark 的说明 */
    /* Heavy Ink：与 .comic-btn 同一条时间线 */
    transition: box-shadow var(--comic-duration-move),
                transform var(--comic-duration-move),
                background-color var(--comic-duration),
                color var(--comic-duration);
}

.comic-btn-h-11:hover {
    transform: translate(2px, 2px);
}

.comic-btn-h-11:active {
    transform: translate(4px, 4px);
    box-shadow: none;
}

/* 弹出徽章 · .comic-award-badge
   ---------------------------------------------------------------
   从 scale(0) 弹出的小方块徽章（FAB 上的 NEW!、卡片上的 GO!）。
   ★ 为什么需要它而不是复用 .comic-badge-square ★
   .comic-badge-square 是「扁长文字块去掉内边距」，它的字体是 10px、
   但不带 transform 也无法从 0 弹出。
   这里的是「先藏起来、hover 时弹出来」的**动效**徽章，
   自带 scale(0) 起始态与尺寸。两者语义不同，不合并。 */
.comic-award-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;           /* h-9 */
    height: 2.25rem;          /* w-9 */
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
    font-size: 0.625rem;      /* text-[10px] */
    font-weight: 900;
    white-space: nowrap;
    transform: scale(0);
    transition: transform var(--comic-duration);
}

/* ---------------------------------------------------------------
   7. 标题 · .comic-heading
   ---------------------------------------------------------------
   ★ 关于 line-height ★
   最初这里写了 line-height: 1.2，验证时发现它会造成真实回归：
   工具层的 text-xl 自带 line-height: 1.75rem（28px），而 1.2 在
   20px 字号下算出来是 24px —— 标题视觉上会变挤。

   但也不能直接删掉。风格包要求标题「紧凑」，而工具层在**有**
   text-size 类时行高由该类提供、无 text-size 类时才用 inherit。
   直接删掉 line-height 会让「没写 text-xl 的标题」继承正文行高，同样不对。

   结论：默认不设 line-height（交给工具层的 text-* 决定，
   保住零回归），需要紧凑时用 -tight 变体显式指定。
   --------------------------------------------------------------- */
.comic-heading {
    font-weight: 900;
    letter-spacing: 0.025em;
    color: var(--comic-ink);
    margin: 0;
}

/* 紧凑行高：用于大字标题块（字号 ≥ 3xl 时默认行高会显得散） */
.comic-heading-tight {
    line-height: 1.1;
}

/* ---------------------------------------------------------------
   7c. 站名 · .comic-logo
   ---------------------------------------------------------------
   导航栏左上角的 M 方块 + 字标。方块 hover 时**倾斜 + 放大**
   （风格包 Badge Pop 的变体），这是全站视觉识别度最高的一处动效。

   ★ 为什么分成 -mark（方块）与 -text（字标）两个类 ★
   两者可以出现在不同容器里（页脚也有一个 M 方块，但它不倾斜，
   字号也不同），硬绑成一个 .comic-logo 会让页脚那处没法复用。
   拆开后：方块效果收进 -mark，字标排版收进 -text。

   ★ 方块尺寸为何用 display:inline-flex + 显式宽高 ★
   原写法是 `inline-flex h-9 w-9 items-center justify-center`。
   这里把 h-9/w-9（2.25rem）与居中一起收进来，目的是让调用点只剩
   「背景色 + 文字色」这类真正变化的部分。 */
.comic-logo-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;              /* h-9 */
    height: 2.25rem;             /* w-9 */
    flex-shrink: 0;
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    font-weight: 900;
    font-size: 1.125rem;         /* text-lg */
    /* line-height：这里**要**写。它与「语义类不要设 line-height」那条规则不冲突 ——
       那条针对的是**外层仍会写 text-* 的排版类**（如 .comic-heading），
       设了会覆盖 text-* 自带的行高。而本类已经把 text-lg **整个折进来**了
       （调用点不再写 text-lg），由本类如实还原它的 1.75rem 行高才是忠实的。
       不写会退回 normal（27px），固定尺寸方块里的字形垂直位置差 0.5px ——
       肉眼不可见，但那是真实的规格漂移，没有理由留着。 */
    line-height: 1.75rem;
    color: var(--comic-white);
    background-color: var(--comic-red);
    box-shadow: var(--comic-shadow-3);
    /* 动效：反馈档 —— 旋转与缩放都是瞬时反馈，不是空间位移 */
    transition: transform var(--comic-duration);
}

/* hover：歪一下 + 放大。负旋转而非正旋转 —— 向右倾更像「跳出来打招呼」 */
.comic-logo-mark:hover {
    transform: rotate(-6deg) scale(1.1);
}

/* 字标：大字标（导航栏 text-lg md:text-2xl）。
   响应式字号留在调用点 —— 断点属阶段三，此处只收不随断点变的视觉。
   注意 line-height 不设：交给 text-* 工具类（见「关于 line-height」的说明）。 */
.comic-logo-text {
    font-weight: 900;
    letter-spacing: 0.025em;     /* tracking-wide */
    color: var(--comic-ink);
    white-space: nowrap;
}

/* 字标里被染红的半截（MiG**Fantasy**） */
.comic-logo-accent { color: var(--comic-red); }

/* ---------------------------------------------------------------
   7e. 图标字号 · .comic-icon-*
   ---------------------------------------------------------------
   图标字号在本站是高频 token（text-lg / text-xl / text-base / text-xs …）。
   它们与「文字字号」的区别：图标不需要行高参与排版，且永远配在
   Font Awesome 的 <i> 上，语义清楚。
   收成 .comic-icon-* 后，调用点只剩尺寸档，且将来换图标体系
   （如换 SVG sprite）时字号仍能复用。 */
.comic-icon-lg { font-size: 1.125rem; }   /* text-lg */
.comic-icon-xl { font-size: 1.25rem; }    /* text-xl */
.comic-icon-base { font-size: 1rem; }     /* text-base */
.comic-icon-sm { font-size: 0.875rem; }   /* text-sm */
.comic-icon-xs { font-size: 0.75rem; }    /* text-xs */

/* 相对字号（图标跟随父级字号的 0.9em / 0.7em 档） */
.comic-icon-em-9 { font-size: 0.9em; }
.comic-icon-em-7 { font-size: 0.7em; }

/* ---------------------------------------------------------------
   7f. 布局契约的固定写法 · .comic-fixed-fill / .comic-absolute-fill
   ---------------------------------------------------------------
   ★ 为什么这两个「布局类」会出现在只收视觉 token 的本层 ★
   本层的边界是「不收 flex/grid/gap/p-4 这类**可复用**的布局原子」——
   因为同一个原子在不同上下文里取值不同。
   但 `fixed inset-0` 与 `absolute inset-0` 不是原子，是**一个固定的
   语义**：「铺满最近的定位祖先」。全站出现 10+ 次，取值永远相同，
   没有任何上下文会改它的值。把它们收成一个类，既减少 token，
   也让「这层是覆盖层」在调用点一眼可读。
   故这是边界内的例外，不是边界破了 —— 判据是「取值会不会变」。 */
.comic-fixed-fill {
    position: fixed;
    inset: 0;
}

.comic-absolute-fill {
    position: absolute;
    inset: 0;
}

/* ---------------------------------------------------------------
   7g. 网点层不透明度档 · .opacity-paper-6 / .opacity-ink-14
   ---------------------------------------------------------------
   本站网点层用两个不透明度档，语义上分别是：
     · 6%  —— 全页底纹（大面积，必须极淡，否则干扰阅读）
     · 14% —— 深色底上的网点（深底会吃掉对比度，故调高）
   用语义名而不是数字名，是为了让「为什么是 6%」写在名字里，
   将来调档能一眼找全（而不是全站搜 opacity-[0.06]）。 */
.opacity-paper-6 { opacity: 0.06; }
.opacity-ink-14 { opacity: 0.14; }

/* ---------------------------------------------------------------
   7h. 汉堡按钮 · .comic-nav-toggle
   ---------------------------------------------------------------
   移动端导航开关：黄底方块 + 墨线 + 3px 阴影，hover 下压 3px 且阴影归零。
   ★ 为什么幅度是 3px 而不是 .comic-btn 的 2px ★
   它属于**导航体系**（.comic-nav-item 的 active 也是平移 3px），
   与正文按钮的 2px 是两套手感。混用会让导航区的按压反馈不一致。 */
.comic-nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;               /* h-10 */
    height: 2.5rem;              /* w-10 */
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
    box-shadow: var(--comic-shadow-3);
    cursor: pointer;
    transition: box-shadow var(--comic-duration),
                transform var(--comic-duration),
                background-color var(--comic-duration),
                color var(--comic-duration);
}

.comic-nav-toggle:hover {
    transform: translate(3px, 3px);
    box-shadow: none;
}

/* 按下时只补一条「阴影归零」。
   ⚠ 这里**不要**再写 transform。
   原写法是 `hover:translate-x-[3px] hover:translate-y-[3px] active:shadow-none`
   —— 按下的瞬间元素仍处于 :hover，位移由 hover 规则**继承**而来，
   写在这里是冗余。
   冗余本身无害，但会让「单强 :active 而不强 :hover」的逐属性验证
   报出一个假的位移差异 —— 验证器没法模拟「同时 hover + active」。
   忠实还原能让验证结果只反映真实差异。 */
.comic-nav-toggle:active {
    box-shadow: none;
}

/* ---------------------------------------------------------------
   7h-2. 移动端展开菜单容器 · .comic-nav-mobile
   ---------------------------------------------------------------
   这是固定导航（position: fixed）里向下展开的那块菜单，它有两个硬约束：

     1. 高度必须封在「视口 − 导航条自身」以内。
        展开后的条目最多 6（导航）+ 3（我的）+ 7（管理后台）+ 1（登出）= 17 个，
        任何手机上都会超过一屏；而 fixed 元素的溢出部分既看不见也点不到
        —— 这正是「菜单显示不全」的直接成因。
     2. 溢出部分要能滚出来，滚到尽头不要连带整页一起滚
        （overscroll-behavior: contain，否则手指划到底会把背后的正文一起滚）。

   ★ 为什么是 68px ★
   = 导航条最小高度 64px（min-h-[64px]）+ 底部 4px 墨线。
   与 <main> 上补偿固定导航的 pt-[68px] 是同一个数 —— 两处若不一致，
   菜单底部会露一条缝，或者反过来多盖住下面一行。
   ★ 为什么 vh 与 dvh 各写一条 ★
   移动端浏览器地址栏收放时，100vh 取的是「最高水位」，菜单会有一截缩到
   地址栏后面被挡住；dvh 跟随真实可视高度。先写 vh 兜底（老浏览器不认 dvh
   时保留这一条），再写 dvh 覆盖。 */
.comic-nav-mobile {
    max-height: calc(100vh - 68px);
    max-height: calc(100dvh - 68px);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* 移动端菜单里的分节标题：把「导航 / 我的 / 管理后台 / 账号」几段分开。
   十几个墨块平铺在一起，没有分节就分不清哪一块属于谁。
   左内边距 = 菜单项的 0.75rem 内边距 + 4px 透明墨线，
   这样标题文字与菜单项文字落在同一条竖线上（差的就是那 4px 边框）。 */
.comic-nav-mobile-label {
    padding-left: calc(0.75rem + 4px);
    font-size: 0.6875rem;        /* 11px，比菜单项低一档 */
    font-weight: 900;
    letter-spacing: 0.12em;
    color: var(--comic-muted);
}

/* ---------------------------------------------------------------
   7i. 页脚 M 方块 · .comic-footer-mark
   ---------------------------------------------------------------
   ★ 为什么不复用 .comic-logo-mark ★
   两者形态相近但**三处不同**：
     1. 边框是**白色**（深底上墨色边框不可见）
     2. 没有 hover 动效（页脚是静态区，不需要吸引点击）
     3. 没有硬阴影（页脚整块是深色，阴影无处可投）
   一刀切复用会把「白边框 + 无动效」这两条差异偷偷改掉。 */
.comic-footer-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;              /* h-9 */
    height: 2.25rem;             /* w-9 */
    border: 4px solid var(--comic-white);
    border-radius: var(--comic-radius);
    background-color: var(--comic-red);
    font-weight: 900;
    font-size: 1.125rem;         /* text-lg */
    /* line-height：这里**要**写。它与「语义类不要设 line-height」那条规则不冲突 ——
       那条针对的是**外层仍会写 text-* 的排版类**（如 .comic-heading），
       设了会覆盖 text-* 自带的行高。而本类已经把 text-lg **整个折进来**了
       （调用点不再写 text-lg），由本类如实还原它的 1.75rem 行高才是忠实的。
       不写会退回 normal（27px），固定尺寸方块里的字形垂直位置差 0.5px ——
       肉眼不可见，但那是真实的规格漂移，没有理由留着。 */
    line-height: 1.75rem;
    color: var(--comic-white);
}

/* ---------------------------------------------------------------
   7d. 反色页脚 · .comic-footer
   ---------------------------------------------------------------
   深底白字墨块。用 bg #1a1a1a + text #ffffff 保证 WCAG AA 对比度。

   ★ 为什么单独一个类而不是复用 .comic-card-ink ★
   页脚的深底**没有圆角**（它是通栏的，圆角在通栏上不可见且会在
   左右两端露出纸色缺口），而 .comic-card-ink 带 2px 圆角。
   两者语义也不同：一个是「深色卡片」，一个是「反色区块」。 */
.comic-footer {
    background-color: var(--comic-ink);
    color: var(--comic-white);
    border-top: 4px solid var(--comic-ink);
}

/* 页脚内的小标题（导航 / 找到我）：比正文大一档的墨块字 */
.comic-footer-heading {
    font-weight: 900;
    letter-spacing: 0.025em;
    color: var(--comic-white);
}

/* 页脚正文/链接文字：粗体小白字 */
.comic-footer-text {
    font-weight: 700;
    color: var(--comic-white);
}

/* 页脚导航链接：hover 转黄。
   ★ 为什么用 .comic-footer-text 而不是 <a> 上的 hover ★
   这些链接的文字色继承自 .comic-footer，若只写 :hover 换色，
   静止态就依赖继承 —— 继承会让「文字色」变成两个真相来源。
   显式写出来才能在验证器里比出稳定值。 */
.comic-footer-link {
    color: var(--comic-white);
    transition: color var(--comic-duration);
}

.comic-footer-link:hover { color: var(--comic-yellow); }

/* 页脚链接前的小黄方块（hover 时从 2px 弹到 3px） */
/* 页脚导航的小黄方块。
   ⚠ hover 放大**必须由祖先驱动**，不能用自身的 :hover ——
   原写法是 `group-hover:scale-150`（挂在 <li> 里的 <a.group> 上），
   即「鼠标进整条链接就放大」，而不是「指到 8px 的小方块才放大」。
   改成自身 :hover 会让 8px 的命中区变得极小，是实打实的手感回归。
   故这里只保留静止视觉，放大由 .comic-hover-child-scale-150 承担，
   驱动的祖先在调用点上是 .comic-hover-root。 */
.comic-footer-dot {
    display: inline-block;
    width: 0.5rem;               /* w-2 */
    height: 0.5rem;              /* h-2 */
    background-color: var(--comic-yellow);
}

/* 深色底上的版权行等弱化文字：略小一档的白字 */
.comic-footer-note {
    font-weight: 700;
    color: var(--comic-white);
}

/* 带墨块的标题（背后一块黄色底色 + 硬阴影） */
.comic-heading-block {
    display: inline-block;
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background: var(--comic-yellow);
    box-shadow: var(--comic-shadow-6);
    padding: 0.75rem 1.5rem;
}

/* ---------------------------------------------------------------
   7b. 页面大标题 · .comic-page-title
   ---------------------------------------------------------------
   全站页面顶部的「墨块标题」：红底 + 白字 + 墨线 + 6px 硬阴影，
   可选叠加网点（ink-text-pop）。
   本站出现 10+ 次，且逐字节同构，是继按钮之后重复度最高的视觉块。

   ★ 为什么必须覆盖 box-shadow / padding / font-size ★
   本层在 comic-utilities.css 之后加载，两者同为未分层声明，等特异性下本层胜。
   但如果这里不声明这几个值，调用点一旦删掉 shadow-[6px...] / px-6 py-3，
   这些样式就彻底没了。所以本类**故意**把这几个关键值声明一遍，再删掉调用点的
   对应工具类，使「删掉工具类后视觉不变」成为可验证的等价替换，同时避免
   同一个值留下两处真相。 */
.comic-page-title {
    display: inline-block;
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-red);
    box-shadow: var(--comic-shadow-6);
    padding: 0.75rem 1.5rem;
    font-weight: 900;
    letter-spacing: 0.025em;
    color: var(--comic-white);
    margin: 0;
}

/* ---------------------------------------------------------------
   8. 分隔线 · .comic-divider
   --------------------------------------------------------------- */
.comic-divider {
    border: 0;
    border-top: 4px solid var(--comic-ink);
    margin: 1rem 0;
}

/* ---------------------------------------------------------------
   9. 特效 · 网点 / 对话气泡
   --------------------------------------------------------------- */
/* 半调网点：漫画印刷质感。必须叠在绝对定位层上，且 pointer-events-none，
   否则会吞掉父元素的 hover / click（风格包硬性要求）。
   ★ 与项目自研的 .ink-halftone（comic-utilities.css 里）是同一效果的两套实现：
     这里的是纯手写版，用于新页面；.ink-halftone 是旧版，迁移完成后可删其一。
     现阶段两者并存互不影响（同一套 radial-gradient 参数）。 */
.comic-halftone {
    background-image: radial-gradient(circle, var(--comic-ink) 1px, transparent 1px);
    background-size: 4px 4px;
    pointer-events: none;
}

/* ---- 9a. 网点图层组合类 -------------------------------------------------
   ★ 为什么需要组合类 ★
   全站有 80+ 处「网点叠层」，写法高度重复但**散落在 30 个页面里**：
       <span class="comic-halftone absolute inset-0 opacity-10">
       <span class="comic-halftone absolute inset-0 opacity-20">
       <span class="comic-halftone absolute inset-0 opacity-0
                    group-hover:opacity-20 transition-opacity duration-100">
   分别是 31 / 10 / 39 处。这三组是**不可分的整体**（网点 + 铺满 + 透明度），
   与 .comic-on-ink 同理，故各给一个名字，而不是留给读者在 12 个工具类里
   自己拼出「这是一个会浮现的网点层」。

   ★ 一个网点层 = .comic-halftone + 一个档位变体 ★
       .comic-halftone           仅纹理（radial-gradient + pointer-events:none）
       .comic-halftone-fill      纹理 + 铺满，**透明度留给调用点**
       .comic-halftone-static    纹理 + 铺满 + 10% 静态
       .comic-halftone-strong    纹理 + 铺满 + 20% 静态
       .comic-halftone-pop       纹理 + 铺满 + 悬停浮现（Halftone Reveal）
   前三类无状态，后两类有状态。五个档位互斥，不要同时写两个。

   ★ 为什么 single 一个 -fill（只有布局、不带透明度）★
   还有 14 处用的是**细粒度透明度**（[0.06] / [0.07] / [0.08] / [0.12] /
   [0.14]），各有各的用途、不能合并成一个档。给它们再造 5 个
   .comic-halftone-xxx 只会把档位数从 3 涨到 8，反而更难挑；故这里只收敛
   「铺满」这一件纯布局的事，浓度仍由调用点的 opacity-[…] 决定 ——
   读者一眼看出「这是铺满的网点，浓度在此」。

   ★ .comic-halftone-pop 的驱动方与既有约定一致 ★
   本层永远 pointer-events:none（见 .comic-halftone），自身收不到 :hover，
   所以必须由**父元素**驱动。本站既有的两种父写法都支持：
       .comic-hover-root  —— 推荐，见第 6 节（含键盘 :focus-within）
       .group             —— 旧写法，仅 :hover，纯键盘用户看不到网点
   这里两个选择器都写，是为了让**遗留调用点不必先改父类**也能用本品。

   ★ 为什么 opacity:0 能压过 .opacity-0 工具类 ★
   两者特异性相同（单类），但本文件在 comic-utilities.css **之后**加载 ——
   未分层声明里后写的胜。故调用点只写本类即可，不会有两处抢同一属性。

   ★ 为什么 z-index 不进本类（重要，别"顺手"加回来）★
   z-[1] / z-[2] 两个工具类是**带变量**的实现（z-index: var(--tw-z-index)），
   换成本类里的静态值会与它们形成两份同属性声明，谁赢取决于书写顺序 ——
   正是文件头警告的"顺序依赖是脆的"。故 z-index 一律留在 HTML 上。
   ------------------------------------------------------------------------- */
.comic-halftone-fill {
    position: absolute;
    inset: 0;
}

.comic-halftone-static {
    position: absolute;
    inset: 0;
    opacity: 0.1;
}

.comic-halftone-strong {
    position: absolute;
    inset: 0;
    opacity: 0.2;
}

.comic-halftone-pop {
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity var(--comic-duration);
}

/* 父元素驱动：容器 hover 或容器内获得焦点。
   ★ 为什么不是 `.comic-halftone-pop:hover` ★
   本层 pointer-events:none，自身永远不触发 :hover —— 那是一条死规则。 */
.comic-hover-root:is(:hover, :focus-within) .comic-halftone-pop,
.group:hover .comic-halftone-pop {
    opacity: 0.2;
}

/* 对话气泡：圆角 + 墨线 + 下方三角尾巴 */
.comic-bubble-wrap {
    position: relative;
    border: var(--comic-border);
    border-radius: var(--comic-radius-bubble);
    background: var(--comic-white);
}

.comic-bubble-wrap::after {
    content: '';
    position: absolute;
    bottom: -22px;
    left: 34px;
    width: 0;
    height: 0;
    border-left: 14px solid transparent;
    border-right: 14px solid transparent;
    border-top: 22px solid var(--comic-ink);
}

/* ---------------------------------------------------------------
   9b. 浮动音乐播放器 · .comic-gm-*
   ---------------------------------------------------------------
   ★ 为什么播放器要单独一套 ★
   它和普通按钮的区别在**尺寸种类多**：h-14/h-12/h-11/h-10/h-9/h-8
   六档方形按钮，且每档的阴影刻度不同（4px / 3px）。
   硬收成一个 .comic-btn 会让调用点又写回一堆尺寸类，等于没收。

   故按「语义 + 尺寸」两段命名：
     .comic-gm-fab        —— 主浮动按钮（h-14，蓝底，hover 转黄）
     .comic-gm-btn        —— 控制条按钮（h-10 基准，hover 转蓝）
     .comic-gm-play       —— 播放键（h-12，红底，hover 转黄）
   尺寸差异用 .comic-gm-h-9 / -h-8 覆盖 —— 因为 h-8/h-9/h-10 在本站
   只出现在这一处，单独建类不如直接给个显式的档位。

   ★ 状态类不在这里 ★
   播放器的运行状态（.playing / .mode-active / .show-badge / .collapsed）
   由 global-music.js 切换，且**已经手写在 comic-utilities.css 里**，
   不依赖 Tailwind。本层不重复定义，避免出现两套真相。

   ★ 禁用态 ★
   .disabled:opacity-40 是视觉反馈，属本层；用 :disabled 伪类表达，
   比 disabled: 变体更贴语义（且 JS 无需改）。 */
.comic-gm-fab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3.5rem;               /* h-14 */
    height: 3.5rem;              /* w-14 */
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-blue);
    color: var(--comic-white);
    box-shadow: var(--comic-shadow-4);
    cursor: pointer;
    transition: box-shadow var(--comic-duration-move),
                transform var(--comic-duration-move),
                background-color var(--comic-duration),
                color var(--comic-duration);
}

/* Heavy Ink：hover 下压 2px、阴影缩到 2px；active 归零 + 下压 4px */
.comic-gm-fab:hover {
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
    transform: translate(2px, 2px);
    box-shadow: var(--comic-shadow-2);
}

.comic-gm-fab:active {
    transform: translate(4px, 4px);
    box-shadow: none;
}

/* 控制条按钮：纸白底 + 墨线 + 3px 阴影，hover 转蓝（文字转白）。
   ★ active 只缩阴影、不平移 —— 与原写法一致（原写法无 active:translate）。 */
.comic-gm-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;               /* h-10 */
    height: 2.5rem;              /* w-10 */
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-paper);
    color: var(--comic-ink);
    box-shadow: var(--comic-shadow-3);
    cursor: pointer;
    transition: box-shadow var(--comic-duration),
                transform var(--comic-duration),
                background-color var(--comic-duration),
                color var(--comic-duration);
}

.comic-gm-btn:hover {
    background-color: var(--comic-blue);
    color: var(--comic-white);
}

.comic-gm-btn:active {
    transform: translate(3px, 3px);
    box-shadow: none;
}

.comic-gm-btn:disabled { opacity: 0.4; }

/* 播放键（h-12，红底，hover 转黄 + 下压 2px）——主行动按钮手感 */
/* 播放键：h-12 红底方块，hover 转黄（文字转墨色）+ 阴影收窄，active 下压 4px。
   ⚠ 本类必须自足：调用点是 `gm-btn play-btn relative comic-gm-play`，
   而 .gm-btn **不提供盒模型**（它只是状态选择器的挂载点，见 comic-utilities.css）。
   早期版本只写了尺寸与颜色，实测 computed 为 display:block / border-width:0 —— 坏样式。
   同类教训见 .comic-btn-h-11 的注释。 */
.comic-gm-play {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;                 /* h-12 */
    height: 3rem;                /* w-12 */
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-red);
    color: var(--comic-white);
    box-shadow: var(--comic-shadow-4);
    cursor: pointer;
    transition: box-shadow var(--comic-duration-move),
                transform var(--comic-duration-move),
                background-color var(--comic-duration),
                color var(--comic-duration);
}

.comic-gm-play:hover {
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
    transform: translate(2px, 2px);
    box-shadow: var(--comic-shadow-2);
}

.comic-gm-play:active {
    transform: translate(4px, 4px);
    box-shadow: none;
}

.comic-gm-play:disabled { opacity: 0.4; }

/* 收起按钮：h-9 纸白底方块，hover 转黄、active 下压 3px。
   ★ 为什么不写成 .comic-gm-btn .comic-gm-h-9 + hover 覆盖 ★
   三处差异（尺寸 h-9、hover 色黄、active 幅度 3px）叠加下来，
   调用点要写三个类才表达清楚，反而比一个语义类更难读。
   判据：当一个组合的**差异项多于相同项**时，就该给它一个名字。
   （早先确实写过 .comic-gm-h-9 / -h-8 两个「只改尺寸」的类，最后没有任何调用点 ——
   尺寸直接内联进各自的语义类更简单、也保证自足，故已删除。
   审计脚本 audit-selfcontained.js 会盯着这类「只有尺寸、没有盒模型」的类。） */
.comic-gm-collapse {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;              /* h-9 */
    height: 2.25rem;             /* w-9 */
    flex-shrink: 0;
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-paper);
    color: var(--comic-ink);
    box-shadow: var(--comic-shadow-3);
    cursor: pointer;
    transition: box-shadow var(--comic-duration),
                transform var(--comic-duration),
                background-color var(--comic-duration),
                color var(--comic-duration);
}

.comic-gm-collapse:hover { background-color: var(--comic-yellow); }

.comic-gm-collapse:active {
    transform: translate(3px, 3px);
    box-shadow: none;
}

/* 静音按钮：h-8 纸白底方块，hover 转黄、active 下压 3px。
   与 -collapse 只差尺寸（h-8 vs h-9），但保持独立命名 ——
   两者在面板里承担不同职责，将来任一处要单独调尺寸时不必牵动另一处。

   ⚠ 这里**刻意不写 flex-shrink: 0**：调用点（_Layout.cshtml:553）是
   `gm-volume-btn comic-gm-volume`，原写法没有 shrink-0，而同为 flex 子项的
   音量滑块带 flex-1。补上 shrink-0 是「防止按钮被挤压」的改进，属视觉变更 ——
   按阶段二铁律①「优先对齐原值」，改进要单独排期，不能夹在替换里做。
   （对照：.comic-gm-collapse 的调用点写了 shrink-0，那边才如实落地。同类元素
   判决相反不是疏漏，是逐处核对了调用点。） */
.comic-gm-volume {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;                 /* h-8 */
    height: 2rem;                /* w-8 */
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-paper);
    color: var(--comic-ink);
    box-shadow: var(--comic-shadow-3);
    cursor: pointer;
    transition: box-shadow var(--comic-duration),
                transform var(--comic-duration),
                background-color var(--comic-duration),
                color var(--comic-duration);
}

.comic-gm-volume:hover { background-color: var(--comic-yellow); }

.comic-gm-volume:active {
    transform: translate(3px, 3px);
    box-shadow: none;
}

/* 播放器面板：纸白底墨线面板 + 6px 硬阴影 + 左下方原点（展开动画的锚点） */
.comic-gm-panel {
    background-color: var(--comic-paper);
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    box-shadow: var(--comic-shadow-6);
    transform-origin: bottom left;
    transition: all var(--comic-duration);
}

/* 面板内的封面/图标块（蓝底 + 墨线 + 裁切） */
.comic-gm-iconwrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;                 /* h-12 */
    height: 3rem;                /* w-12 */
    flex-shrink: 0;
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-blue);
    color: var(--comic-white);
    overflow: hidden;
}

/* 有封面时隐藏兜底音符图标。
   ★ 为什么必须有这条 ★ 图标 <i> 与封面 <img> 是同一个 inline-flex 容器里
   的两个子项，容器按 justify-content: center 整组居中；而封面是 w-full
   （占满内容宽），叠加图标宽度后整行必然溢出，居中会把溢出量分到两侧 ——
   封面于是被图标宽度的一半向右挤。把图标移出布局（display: none）后，
   封面成为唯一子项，自然严丝合缝地居中铺满。
   （旧 wwwroot/css/global-music.css 里的 .gm-icon-wrap.has-cover i
   规则在样式合并进本文件时丢失，这里补回，并改用 .comic-gm-* 命名。） */
.comic-gm-iconwrap.has-cover > i { display: none; }

/* 进度条轨道：纸白底 + 墨线 + 裁切（填充块在里面） */
.comic-gm-track {
    background-color: var(--comic-paper);
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    overflow: hidden;
}

/* 进度条填充：红块 */
.comic-gm-fill { background-color: var(--comic-red); }

/* 状态条：黄底墨线的提示块 */
.comic-gm-status {
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
}

/* 音量滑块：纸白底 + 墨线 + 裁切（把手样式在 comic-utilities.css 的
   ::-webkit-slider-thumb，属于「原生控件机制」，不搬到这里） */
.comic-gm-slider {
    -webkit-appearance: none;
    appearance: none;
    background-color: var(--comic-paper);
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    cursor: pointer;
}

/* 播放列表顶部分隔线 */
.comic-gm-playlist {
    border-top: 4px solid var(--comic-ink);
}

/* 小标签文字：10px / 900 / 大写 / 字距 0.025em / 弱化灰（Now Playing） */
.comic-gm-label {
    font-size: 0.625rem;         /* text-[10px] */
    font-weight: 900;
    letter-spacing: 0.025em;
    color: var(--comic-muted);
}

/* 字距更宽的小标签（tracking-widest，用于 Now Playing） */
.comic-gm-label-wide { letter-spacing: 0.1em; }

/* 时间戳：10px / 900 / 等宽数字 */
.comic-gm-time {
    font-size: 0.625rem;         /* text-[10px] */
    font-weight: 900;
    font-variant-numeric: tabular-nums;
}

/* 曲名 / 艺人
   ★ 行高为什么必须有 ★
   工具层的**预设字号档**自带行高（text-xs = 12px/16px，text-sm = 14px/20px），
   而**任意值档**不带（text-[10px] 只有字号）。所以折叠预设档时必须把行高一起带回来，
   否则 computed 会从 16px 变成 normal 的 18px —— 差 2px，行内元素会挤。
   判据：折叠的档名若是 xs/sm/base/lg/xl/2xl... 就补行高；方括号任意值不补。 */
.comic-gm-track-name {
    font-weight: 900;
    font-size: 0.875rem;         /* text-sm */
    line-height: 1.25rem;        /* text-sm 配套 */
}
.comic-gm-track-artist {
    font-weight: 700;
    font-size: 0.75rem;          /* text-xs */
    line-height: 1rem;           /* text-xs 配套 */
    color: var(--comic-muted);
}

/* 播放列表条目上的模式角标（黄底墨框小块，9px） */
.comic-gm-modebadge {
    font-size: 0.5625rem;        /* text-[9px] */
    font-weight: 900;
    line-height: 1;
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
    border: 2px solid var(--comic-ink);
}

/* ---- 9b-2. 歌单切换器（全站曲库 / DJ 歌单） ----
   由 global-music.js 动态生成条目，容器常驻在面板顶部。
   一条横向滚动条：歌单数量不可预期（每个 DJ 一份），面板只有 330px 宽，
   换行会把播放器撑高，所以固定单行 + overflow-x 横滚。
   ★ 为什么单独写 [hidden] ★ 本类自带 display:flex，会盖掉 HTML 的 hidden 属性
   （hidden 只提供 display:none，优先级更低）。没有显式规则时"没有歌单就整块隐藏"
   会静默失效，面板上永远挂着一条空的分隔线。 */
.comic-gm-pltabs {
    display: flex;
    gap: 6px;
    margin-bottom: 10px;
    padding-bottom: 8px;
    overflow-x: auto;
    border-bottom: 4px solid var(--comic-ink);
    scrollbar-width: thin;
}
.comic-gm-pltabs[hidden] { display: none; }

/* 单个歌单按钮：默认纸白，hover 转黄，选中态蓝底白字（与列表项选中同色系） */
.comic-gm-pltab {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    max-width: 150px;
    padding: 3px 8px;
    background-color: var(--comic-paper);
    border: 3px solid var(--comic-ink);
    border-radius: var(--comic-radius);
    color: var(--comic-ink);
    font-size: 0.6875rem;        /* 11px */
    font-weight: 900;
    line-height: 1.1;
    white-space: nowrap;
    cursor: pointer;
    transition: all var(--comic-duration);
}
.comic-gm-pltab:hover { background-color: var(--comic-yellow); }
.comic-gm-pltab.is-active {
    background-color: var(--comic-blue);
    color: var(--comic-white);
}
.comic-gm-pltab-name {
    overflow: hidden;
    text-overflow: ellipsis;
}
/* 私密歌单上的小锁 / 歌单曲目数，弱化显示的小字 */
.comic-gm-pltab-meta {
    font-size: 0.5625rem;        /* 9px */
    opacity: 0.75;
}

/* ---- 9b-3. 音乐入口（面板底部） ----
   歌单广场（所有人）/ 音乐管理（DJ 与管理员）。放在面板最下方：这些是低频动作，
   不该挤占播放控制的位置，但要对"正在听歌的人"随手可达。 */
.comic-gm-manage {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 10px;
    padding-top: 8px;
    border-top: 4px solid var(--comic-ink);
}

/* 墨线小按钮（同 .comic-gm-pltab 家族，但带硬边投影强调"可点） */
.comic-gm-managelink {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px;
    background-color: var(--comic-paper);
    border: 3px solid var(--comic-ink);
    border-radius: var(--comic-radius);
    box-shadow: 2px 2px 0 0 var(--comic-ink);
    color: var(--comic-ink);
    font-size: 0.6875rem;        /* 11px */
    font-weight: 900;
    line-height: 1.1;
    text-decoration: none;
    transition: all var(--comic-duration);
}
.comic-gm-managelink:hover {
    background-color: var(--comic-yellow);
    transform: translate(-1px, -1px);
    box-shadow: 3px 3px 0 0 var(--comic-ink);
}
.comic-gm-managelink:active {
    transform: translate(0, 0);
    box-shadow: 2px 2px 0 0 var(--comic-ink);
}

/* ---------------------------------------------------------------
   10. 无障碍兜底 · prefers-reduced-motion
   ---------------------------------------------------------------
   风格包硬性要求：降级 ≠ 消失。用户要求减弱动效时，只取消**位移与循环**，
   保留阴影、边框、配色 —— 视觉身份不因此丢失。
   --------------------------------------------------------------- */
/* ==========================================================================
   11. ComicTokens 批次 · 容器 / 表格 / 分页 / 表单标签
   --------------------------------------------------------------------------
   背景：Pages/Shared/ComicTokens.cs 的 13 个常量此前存的是 **Tailwind 工具类串**，
   被 389 个调用点引用（形态如 `class="@ComicTokens.BtnBase bg-[#3b82f6] text-white"`）。
   本节把那些串的对应用法逐个收成语义类，使 ComicTokens 里不再有一个 Tailwind 类。

   本节所有取值都来自原工具类串在 Tailwind v3.4.17 下的 computed 值，
   逐条写在注释里便于将来核对。断点（md:）只在「组件自身的固定形态」上收进来，
   可复用的布局原子（flex / p-4 / gap-4）仍留在调用点，属阶段三。
   ========================================================================== */

/* ---- 11a. 面板内边距 ---- */

/* 标准内边距面板：对齐 `ComicTokens.Panel + p-4 md:p-6`
   ★ 断点为什么收进来 ★ 「面板的标准内边距」是这一个组件的固定形态，
     不是可复用的布局原子，所以 md: 落在类里是对的。 */
.comic-card-padded { padding: 1rem; }
@media (min-width: 768px) {
    .comic-card-padded { padding: 1.5rem; }
}

/* 宽松内边距：对齐 `ComicTokens.PanelPadded + p-5`（Nonaction/Chaos 的非行动页面板）
   ★ 为什么小屏 1.25rem、大屏 1.5rem ★ 原写法里 p-5 覆盖了 PanelPadded 的 p-4，
     但 md:p-6 在媒体查询里、按 CSS 顺序后写者赢，所以 ≥768px 仍是 1.5rem。 */
.comic-card-padded-loose { padding: 1.25rem; }
@media (min-width: 768px) {
    .comic-card-padded-loose { padding: 1.5rem; }
}

/* 撑满所在网格行高：对齐 `h-full`（Statistics 的四张卡）
   ★ 为什么不引入通用布局原子层 ★ 这是「卡片填满栅格单元」这一个语义，
     取值永远相同，收成类比引入 .comic-l-h-full 这类原子更可读。 */
.comic-card-stretch { height: 100%; }

/* 可横向滚动的表格容器：对齐 `ComicTokens.Panel + overflow-x-auto`（2 处） */
.comic-table-wrap { overflow-x: auto; }

/* ---- 11a-2. 页面分节外壳 · .comic-section ---- */

/* 对齐 `relative px-4 md:px-8 lg:px-12 py-10 md:py-16`（26 处，横跨 19 个页面）。
   ★ 为什么这是纯布局原子却能收 ★
   本文件开头的边界写的是「布局不接管」，理由是布局原子需要灵活复用。
   但这一串不是原子 —— 它是**整站每一页最外层分节的固定形态**：
   水平内边距随断点三档（16/32/48px）、垂直内边距两档（40/64px），
   19 个页面逐字节相同。换句话说，它的「可复用」恰恰体现为「处处相同」。
   留着它的代价是 19 个文件的读者只看到 5 个 padding 工具类，
   读不出「这是什么」。

   ★ 为什么连 position:relative 一起收进来 ★
   26 处里有 10 处自己多写了一个 `relative`，另外 16 处没写。
   这 16 处里若有人想叠一个网点层，忘记加 relative 就会让网点相对
   **更外层**定位 —— 一个看不见的坑。统一成「页面分节 = 定位上下文」后，
   这个坑不存在了，而 `position: relative` 无偏移时对布局是惰性的。
   调用点原来的 `relative` 随之删掉（重复声明无害，但没有信息量）。

   ★ 为什么值写死而不用变量 ★ 与 .comic-card-padded 同政策：
     断点属于这个组件的固定形态，写进媒体查询里最直观。
   ★ 特异性提示 ★ 调用点仍可能写 bg-* / border-b-4（hero 区块），
     本文件在 comic-utilities.css 之后加载，故同名属性由本节胜出 ——
     这里只声明 padding 与 position，不会去动背景与边框。 */
.comic-section {
    position: relative;
    padding: 2.5rem 1rem;          /* py-10 px-4 */
}

@media (min-width: 768px) {
    .comic-section { padding: 4rem 2rem; }      /* md:py-16 md:px-8 */
}

@media (min-width: 1024px) {
    .comic-section { padding: 4rem 3rem; }      /* lg:px-12 */
}

/* 分节外壳 · 加大纵向留白：对齐 `px-4 md:px-8 lg:px-12 py-10 md:py-20`（5 处）
   ★ 为什么是独立类而非修饰类 ★ 它与 .comic-section 只在 **≥768px 的纵向值**
     上不同（80px vs 64px），横向完全一致。写成 .comic-section-xl 让它自足，
     调用点不会出现「先写 A 再写 B 去覆盖一个属性」的隐式依赖 —— 那种写法
     一旦文件顺序变动就会静默失效（正是本文件反复提醒的坑）。 */
.comic-section-xl {
    position: relative;
    padding: 2.5rem 1rem;
}

@media (min-width: 768px) {
    .comic-section-xl { padding: 5rem 2rem; }   /* md:py-20 */
}

@media (min-width: 1024px) {
    .comic-section-xl { padding: 5rem 3rem; }
}

/* 分节外壳 · 账户/表单页紧凑档：对齐 `px-4 md:px-8 py-10 md:py-16`（11 处）
   ★ 与 .comic-section 的差别只有「没有 lg:px-12」★
   ≥1024px 时横向停在 32px，而不是 48px。账户页的表单列宽本身有 max-w 约束，
   再加大屏内边距会把它挤窄，故原写法就没加 lg 档 —— 这里照实保留。 */
.comic-section-account {
    position: relative;
    padding: 2.5rem 1rem;
}

@media (min-width: 768px) {
    .comic-section-account { padding: 4rem 2rem; }
}

/* ---- 11b. 弹窗 ---- */

/* 弹窗内容盒：对齐 ComicTokens.ModalCard（10 处，全在 Admin/*）
   `w-full max-h-[90vh] overflow-y-auto` + 纸白底 + 墨线 + 10px 硬阴影
   ★ 宽度不在这里 ★ 调用点分 max-w-md / max-w-3xl / 无 三档，拆成两个宽度修饰。
   ★ max-h 用 vh 而不是百分比 ★ 原写法就是 90vh；百分比会按父元素算，语义不同。 */
.comic-modal-card {
    width: 100%;
    max-height: 90vh;
    overflow-y: auto;
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-paper);
    box-shadow: var(--comic-shadow-10);
}

.comic-modal-card-sm { max-width: 28rem; }   /* max-w-md  */
.comic-modal-card-lg { max-width: 48rem; }   /* max-w-3xl */

/* ---- 11c. 概览统计卡 ---- */

/* 对齐 `ComicTokens.Panel + p-4 text-center transition-all duration-100
   hover:-translate-y-2 hover:shadow-[8px_8px…]`（Admin/UserManagement 的 4 张概览卡）
   ★ hover 走的是 4px → 8px + 上浮 8px，与 .comic-card-interactive-4（→10px）不同，
     故不合并。卡内文字的 group-hover 变色由页面自己负责，本类只管底色。 */
.comic-stat-card {
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background-color: var(--comic-paper);
    box-shadow: var(--comic-shadow-4);
    padding: 1rem;
    text-align: center;
    transition-property: all;
    transition-duration: var(--comic-duration);
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.comic-stat-card:hover {
    box-shadow: 8px 8px 0px 0px rgba(26, 26, 26, 1);
    transform: translateY(-8px);
}

/* hover 泛色：4 张卡各不同，故拆修饰类（只改底色） */
.comic-stat-card-hover-yellow:hover { background-color: var(--comic-yellow); }
.comic-stat-card-hover-green:hover { background-color: var(--comic-green); }
.comic-stat-card-hover-ink:hover { background-color: var(--comic-ink); }

/* 横排统计卡：对齐 `ComicTokens.PanelPadded + flex items-center gap-4 …`
   （Statistics.cshtml:41）—— 基础阴影同为 4px、hover 同为 10px + 上浮，
   故交互部分直接复用 .comic-card-interactive-4，本类只负责横向排布。 */
.comic-stat-row {
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* 4px → 8px + 上浮 的交互卡：对齐 `PanelPadded + … hover:shadow-[8px_8px…]
   hover:-translate-y-2`（Nonaction/Chaos 的 panelHover）。
   ★ 与 -interactive-4 只差 hover 的目标阴影刻度，故并列成一个独立档。 */
.comic-card-interactive-4-8 {
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background: var(--comic-paper);
    box-shadow: var(--comic-shadow-4);
    transition: box-shadow var(--comic-duration-move),
                transform var(--comic-duration-move),
                background-color var(--comic-duration),
                border-color var(--comic-duration);
}

.comic-card-interactive-4-8:hover {
    box-shadow: 8px 8px 0px 0px rgba(26, 26, 26, 1);
    transform: translateY(-8px);
}

/* ---- 11d. 表格 ---- */

/* 表头单元格：对齐 ComicTokens.ThBase（13 处，Admin/UserManagement） */
.comic-th {
    padding: 0.5rem 0.75rem;
    text-align: left;
    font-weight: 900;
    letter-spacing: 0.1em;        /* tracking-widest */
    font-size: 0.625rem;          /* text-[10px]：任意值档，不带行高 */
    color: var(--comic-ink);
}

/* 表格单元格：对齐 ComicTokens.TdBase（14 处） */
.comic-td {
    padding: 0.5rem 0.75rem;
    font-weight: 700;
    font-size: 0.75rem;           /* text-xs */
    line-height: 1rem;            /* text-xs 自带行高 */
    color: var(--comic-ink);
}

/* 空状态单元格：对齐 `TdBase + text-center text-[#4a4a4a] py-6`（1 处） */
.comic-td-empty {
    text-align: center;
    color: var(--comic-muted);
    padding-top: 1.5rem;
    padding-bottom: 1.5rem;
}

/* ---- 11e. 分页按钮 ---- */

/* 对齐 ComicTokens.PageBtn（10 处，Messages/Index + Notifications/Index）。
   基础态就是「纸白底 + 墨字」（4 个调用点如此，故直接内置）。
   ★ hover 只平移、不换色 ★ 原写法就是这样；「换色」是另一件事，不顺手加。 */
.comic-page-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 4px solid var(--comic-ink);
    border-radius: var(--comic-radius);
    padding: 0.25rem 0.75rem;     /* px-3 py-1 */
    background-color: var(--comic-paper);
    color: var(--comic-ink);
    font-family: inherit;
    font-size: 0.6875rem;         /* text-[11px] */
    line-height: inherit;
    font-weight: 900;
    box-shadow: var(--comic-shadow-2);
    transition-property: all;
    transition-duration: var(--comic-duration);
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.comic-page-btn:hover {
    transform: translate(2px, 2px);
    box-shadow: none;
}

/* 当前页：对齐三元里的 `bg-[#3b82f6] text-white` */
.comic-page-btn-current {
    background-color: var(--comic-blue);
    color: var(--comic-white);
}

/* 不可用档（首/末页的箭头）：
   对齐 `bg-[#fffef0] text-[#4a4a4a] pointer-events-none opacity-40` */
.comic-page-btn-disabled {
    color: var(--comic-muted);
    pointer-events: none;
    opacity: 0.4;
}

/* ---- 11f. 表单标签 / 字段间距 ---- */

/* 表单标签：对齐 ComicTokens.LabelBase（74 处，全站最高频的常量）
   原串 = `block font-black tracking-widest text-[10px] text-[#1a1a1a] mb-2`
   （原串里的 `uppercase` 已随「取消强制大写」一并移除。）
   ★ 为什么不复用 .comic-label-sm ★ 三处不同：display(block)、颜色(墨色 vs 弱化灰)、
     下边距。差异项与相同项数量相当，按本层判据（见 .comic-gm-collapse 注释）给名字。
   ★ mb-2 为什么收进来 ★ 它是「标签与控件之间的固定间距」，74 处取值完全一致，
     属表单组件的内部结构，不是可复用的布局原子。 */
.comic-form-label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 900;
    letter-spacing: 0.1em;        /* tracking-widest */
    font-size: 0.625rem;          /* text-[10px] */
    color: var(--comic-ink);
}

/* 字段之间的纵向间距：对齐 `mb-4`（Article/View 的评论框，1 处）
   ★ 为什么不引入通用 .comic-l-mb-* 布局原子层 ★ 只有这一处需要，
     引入一层命名体系来解决一个点不划算；将来阶段三处理全站布局时再统一。 */
.comic-form-field-spaced { margin-bottom: 1rem; }

/* 无圆角徽章：对齐 Music/Index.cshtml 的三个 JS 串（badgeOff / badgeInfo / badgeWarn）
   与 BadgeBase 只差两点：**没有 rounded-sm**（圆角 0）与 gap-1（0.25rem）。
   这三个串由 JS 在搜索结果里拼 HTML，改不到 class 属性，只能给个类。 */
.comic-badge-flat {
    gap: 0.25rem;
    border-radius: 0;
}

/* 徽章与上方内容之间的小间距：对齐 `mt-1`（Admin/UserManagement 表内的一处徽章） */
.comic-badge-top-gap { margin-top: 0.25rem; }

/* 小按钮的「不可用」档：对齐 `bg-[#fffef0] text-[#4a4a4a] pointer-events-none opacity-40`
   （ArticleList 分页里的上一页/下一页禁用态，用在小按钮上） */
.comic-btn-sm-disabled {
    color: var(--comic-muted);
    pointer-events: none;
    opacity: 0.4;
}

/* ---- 11h. 实验页工具条按钮（Nonaction/Chaos 与 Nonaction/ParticleLife）----
   ★ 为什么单独一套，不复用 .comic-btn-sm ★
   与 .comic-btn-sm 只差一点：**gap 是 0.5rem（gap-2）而不是 0.375rem（gap-1.5）**。
   两个实验页的按钮都是「图标 + 文字」，图标与文字之间需要更宽一点才不挤；
   这属于该处形态的一部分，改成 -sm 的 0.375rem 就是 2px 的真实回归。
   （ParticleLife 的 px 是 0.625rem，Chaos 的是 0.75rem，故后者加 -wide 修饰。） */
.comic-lab-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    border: 4px solid var(--comic-ink);
    border-radius: var(--comic-radius);
    padding: 0.375rem 0.625rem;
    background-color: var(--comic-paper);
    color: var(--comic-ink);
    font-family: inherit;
    font-size: 0.6875rem;         /* text-[11px] */
    line-height: inherit;
    font-weight: 900;
    letter-spacing: 0.025em;
    box-shadow: var(--comic-shadow-3);
    transition-property: all;
    transition-duration: var(--comic-duration);
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.comic-lab-btn:hover {
    transform: translate(2px, 2px);
    box-shadow: var(--comic-shadow-1);
}

.comic-lab-btn:active {
    transform: translate(3px, 3px);
    box-shadow: none;
}

.comic-lab-btn-wide { padding-left: 0.75rem; padding-right: 0.75rem; }  /* px-3 */

/* 较矮的多行输入框：对齐 CharacterEdit 的 `inputBase + min-h-[96px] resize-y py-2 leading-relaxed`
   ★ 为什么不复用 .comic-textarea ★ 那个的最小高度是 120px（评论框），这里是 96px；
     且这里额外带 leading-relaxed（行高 1.4219rem）。差了就是回归。 */
.comic-textarea-short {
    min-height: 6rem;
    resize: vertical;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    line-height: 1.4219rem;       /* leading-relaxed：1.625 × 0.875rem */
}

/* ---- 11g. 补充 reduced-motion 覆盖（见第 10 节的说明）---- */
@media (prefers-reduced-motion: reduce) {
    .comic-stat-card,
    .comic-card-interactive-4-8,
    .comic-page-btn {
        transition-duration: 0.01ms;
    }

    .comic-stat-card:hover,
    .comic-card-interactive-4-8:hover,
    .comic-page-btn:hover {
        transform: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .comic-btn,
    .comic-card-interactive,
    .comic-card-interactive-4,
    .comic-card-interactive-6 {
        transition-duration: 0.01ms;
    }

    .comic-btn:hover,
    .comic-btn:active {
        transform: none;
    }

    .comic-card-interactive:hover {
        transform: none;
        box-shadow: var(--comic-shadow-8);
    }

    .comic-card-interactive-4:hover,
    .comic-card-interactive-6:hover {
        transform: none;
    }

    .comic-card-interactive-4:hover { box-shadow: var(--comic-shadow-4); }
    .comic-card-interactive-6:hover { box-shadow: var(--comic-shadow-6); }
}

/* ---------------------------------------------------------------
   12. PWA · 安装引导 / 更新提示 / 离线标记
   ---------------------------------------------------------------
   调用点只有一处（_Layout 的页脚"找到我"列），但仍单独成一节：
   这一小块里挤了**四个互斥的可见状态**（原生安装按钮 / iOS 分步指引 /
   更新提示 / 离线标记），而它们各自只在特定平台上出现。视觉收进本层，
   页脚那一段 HTML 剩下的就只有结构。

   ★ 全部长在墨色页脚上 ★ 故一律用白墨线 + 白硬阴影（--comic-shadow-light-*），
   与 .comic-on-ink 同一套环境 token；墨色压墨色等于没有边框。

   ★ 显隐开关：hidden 属性 + data-pwa-block ★
   规则见本节末尾，**凡由 JS 切 hidden 的元素都必须带 data-pwa-block**。
   --------------------------------------------------------------- */

/* ---- 12a. 安装 / 刷新按钮（图标 + 文字，宽度自适应）----
   ★ 为什么不复用 .comic-btn-h-11 ★
   那个类是**固定方形**（2.75rem × 2.75rem），只放得下一枚图标；
   本按钮是「图标 + 文字」。
   也刻意不套 .comic-btn：它的底色是纸白，而这里在墨色页脚上，
   色序要反过来（透明/墨底 + 白字）。 */
.comic-pwa-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    border: 4px solid var(--comic-white);
    border-radius: var(--comic-radius);
    padding: 0.375rem 0.75rem;
    background-color: transparent;
    color: var(--comic-white);
    font-family: inherit;
    font-size: 0.75rem;
    font-weight: 900;
    letter-spacing: 0.025em;
    box-shadow: var(--comic-shadow-light-4);
    cursor: pointer;
    /* 与 .comic-btn 同一条时间线：色变走反馈档，位移/阴影走位移档 */
    transition: background-color var(--comic-duration),
                color var(--comic-duration),
                box-shadow var(--comic-duration-move),
                transform var(--comic-duration-move);
}

/* hover 转黄 = "这是推荐动作"，与页脚 RSS 按钮同一套语义 */
.comic-pwa-btn:hover {
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
    transform: translate(2px, 2px);
    box-shadow: var(--comic-shadow-light-2);
}

.comic-pwa-btn:active {
    transform: translate(4px, 4px);
    box-shadow: none;
}

/* 更新提示条里的反色变体：提示条本身是黄底，按钮必须是墨底才分得出层次。
   ★ 必须写在 .comic-pwa-btn:hover 之后 ★ 同特异性下后写的胜，
   否则 hover 时会被上面的黄底规则盖掉，黑按钮悬停变黄 = 看不见了。 */
.comic-pwa-btn-ink {
    border-color: var(--comic-ink);
    background-color: var(--comic-ink);
    color: var(--comic-white);
    box-shadow: var(--comic-shadow-4);
}

.comic-pwa-btn-ink:hover {
    background-color: var(--comic-white);
    color: var(--comic-ink);
    box-shadow: var(--comic-shadow-2);
}

/* ---- 12b. 「不再提示」按钮 ---- */
/* 方形 ✕（Chrome 系那行用） */
.comic-pwa-dismiss {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border: 4px solid var(--comic-white);
    border-radius: var(--comic-radius);
    background-color: transparent;
    color: var(--comic-white);
    font-family: inherit;
    font-size: 0.75rem;
    font-weight: 900;
    cursor: pointer;
    box-shadow: var(--comic-shadow-light-2);
    transition: background-color var(--comic-duration),
                border-color var(--comic-duration),
                color var(--comic-duration);
}

/* 关闭是"负向"动作，用红底区分于安装按钮的黄（正向） */
.comic-pwa-dismiss:hover {
    background-color: var(--comic-red);
    border-color: var(--comic-red);
    color: var(--comic-white);
}

/* 文字版（iOS 指引的"知道了"）：与 ✕ 同形态，宽度交给内容 */
.comic-pwa-dismiss-wide {
    width: auto;
    height: auto;
    padding: 0.25rem 0.625rem;
}

/* ---- 12c. iOS 分步指引 ---- */
.comic-pwa-steps {
    display: grid;
    gap: 0.25rem;
    margin: 0;
    padding: 0;
    list-style: none;            /* 序号由 .comic-pwa-step-num 自己画, 不用列表标记 */
    font-weight: 700;
    font-size: 0.6875rem;
    color: var(--comic-white);
}

.comic-pwa-steps > li {
    display: flex;
    align-items: center;
    gap: 0.375rem;
}

/* 步骤序号：黄底墨字小方块（页脚墨底上唯一的高对比锚点，用来串起步骤） */
.comic-pwa-step-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 1.125rem;
    height: 1.125rem;
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
    font-size: 0.625rem;
    font-weight: 900;
}

/* ---- 12d. 更新提示条 ---- */
/* 黄底墨字：页脚整块是墨色，黄块是这里唯一能"跳出来"的底色 */
.comic-pwa-notice {
    display: block;
    border: 4px solid var(--comic-white);
    border-radius: var(--comic-radius);
    background-color: var(--comic-yellow);
    color: var(--comic-ink);
    padding: 0.5rem 0.75rem;
}

.comic-pwa-notice-text {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    margin: 0 0 0.5rem;
    font-size: 0.75rem;
    font-weight: 900;
    color: var(--comic-ink);
}

/* ---- 12e. 离线标记 ---- */
/* 黄字 + 断线图标：状态提示，不抢眼但看得见（对比度在墨底上满足 AA） */
.comic-pwa-offline {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    margin: 0;
    font-size: 0.6875rem;
    font-weight: 700;
    color: var(--comic-yellow);
}

/* ---- 12f. ★ hidden 属性的显隐契约（本节所有块都依赖它）★ ----
   UA 样式表里的 `[hidden] { display: none }` 会被**任何**作者样式里的
   display 声明盖掉 —— 作者样式恒胜 UA 样式，与特异性无关。
   而本节的块既要 display（flex / block / grid）又要能整块藏起来，
   于是 hidden 会静默失效：表现为「点了关闭还留在页面上」「更新提示关不掉」。
   用属性选择器 + 类选择器组成复合选择器（特异性 0,2,0 > 单类 0,1,0）
   才能稳定压过它们各自的 display。

   ★ 为什么不直接全局写 [hidden] { display: none } ★
   那会波及站内既有的 hidden 用法（其他地方可能有元素同时写着
   hidden 与 display 工具类，目前的实际表现是"显示着"）。修它们是**另一件事**，
   不该顺手塞进这次改动里 —— 那是一次没有回归测试兜底的全局视觉变更。
   故这里只约束带 data-pwa-block 的元素。 */
[data-pwa-block][hidden] { display: none; }

/* 动效偏好兜底：与第 10 节对 .comic-btn 的处理一致，去掉位移只留色变 */
@media (prefers-reduced-motion: reduce) {
    .comic-pwa-btn,
    .comic-pwa-dismiss {
        transition-duration: 0.01ms;
    }

    .comic-pwa-btn:hover,
    .comic-pwa-btn:active {
        transform: none;
    }
}

/* ==========================================================================
   13. 漫画技法层 · 爆炸线 / 速度线 / 反相网点 / 直角气泡 / 倾角 / 滚动入场
   --------------------------------------------------------------------------
   本节来自参考实现（oc-showcase-comic）的技法清单，用于把「墨线 + 硬阴影 + 纸张色」
   这套 token 的表达力往上抬一档。为什么放进 stylekit 而不是某个页面的 css：
   这些都是**与页面无关的装饰手法**，任何页面叠一层都成立，且没有 HTML 结构要求
   （除了各自那一个必须绝对定位的铺满层）。

   ★ 与参考实现的唯一差别：颜色全部走本站 token ★
   参考用的是 #ff3333 / #ffcc00 / #3366ff，本站是 --comic-red/yellow/blue。
   照抄 hex 会让同一套技法在两个项目里落地成两种配色 —— 技法该学，配色该守自己的。
   ========================================================================== */

/* ---- 13a. 放射爆炸线：漫画封面背景（从中心向外发散的细条纹）----
   铺满层：这个类自带 position/inset/pointer-events，调用点直接挂在容器里即可。
   ★ pointer-events:none 是硬要求 ★ 它铺满且盖在内容之上（DOM 顺序在后），
   不关掉指针事件就会吞掉整块区域的 hover 与点击。 */
.comic-radiate,
.comic-radiate-light,
.comic-speedlines,
.comic-halftone-light {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.comic-radiate {
    background-image: repeating-conic-gradient(
        from 0deg at 50% 50%,
        rgba(26, 26, 26, 0.06) 0deg 1.6deg,
        transparent 1.6deg 8deg
    );
}

/* 深色分镜上的爆炸线：墨线在墨底上等于没有，换白线 */
.comic-radiate-light {
    background-image: repeating-conic-gradient(
        from 0deg at 50% 50%,
        rgba(255, 255, 255, 0.07) 0deg 1.6deg,
        transparent 1.6deg 8deg
    );
}

/* ---- 13b. 速度线：斜向细纹，给"画面在动"的暗示 ---- */
.comic-speedlines {
    background-image: repeating-linear-gradient(
        -52deg,
        transparent 0 20px,
        rgba(26, 26, 26, 0.055) 20px 22px
    );
}

/* ---- 13c. 反相网点（白点）----
   ★ 为什么必须有反相版本 ★
   .comic-halftone 用的是 --comic-ink 墨点 —— 铺在 #1a1a1a 的深色分镜上
   和背景同色，视觉上**等于没铺**。深色区要的是白点。
   ★ 为什么这个类不拆 -fill ★
   第 9a 节把"铺满"单独拆成 .comic-halftone-fill 是因为墨点网点有 80+ 处调用、
   浓度档位各异；白点网点目前只有深色分镜一个用法，拆开只会多一个必须成对书写的类。
   真出现第二种用法时再拆，不预先抽象。 */
.comic-halftone-light {
    background-image: radial-gradient(circle, var(--comic-white) 1.1px, transparent 1.1px);
    background-size: 5px 5px;
    opacity: 0.09;
}

/* ---- 13d. 直角对话气泡 ----
   ★ 与第 9 节的 .comic-bubble-wrap 的区别（两个都留着，别合并）★
   .comic-bubble-wrap 是圆角 + 等腰三角尾巴，气质偏"对话框"；
   本类是**直角方框 + 旋转 45° 的方块**做尖角，是漫画分镜里的硬派画法。
   两者是同一种东西的两种画风，合并会丢掉其中一种表达。
   尾巴用旋转方块而不是三角形，是为了让尾巴继承方框的墨线，
   而不是靠 border 拼一个和方框对不齐的三角。 */
.comic-bubble {
    position: relative;
}

.comic-bubble::after {
    content: '';
    position: absolute;
    left: 2.6rem;
    bottom: -13px;
    width: 22px;
    height: 22px;
    background: var(--comic-white);
    border-right: var(--comic-border);
    border-bottom: var(--comic-border);
    transform: rotate(45deg);
}

/* 尾巴换到右侧（用在右侧栏 / 反向排版的段落） */
.comic-bubble--right::after {
    left: auto;
    right: 2.6rem;
}

/* 尾巴底色跟随气泡底色：气泡不是白纸时用这个变体，否则尾巴会露出白方块 */
.comic-bubble--paper::after {
    background: var(--comic-paper);
}

/* ---- 13e. 贴纸微倾角 ----
   四个角度而不是一个：同一屏里出现两三张贴纸时，同一个角度会读成"排版歪了"
   而不是"随手贴的"。四个值互质、都不超过 2.5°，眼睛看得出来但不影响阅读。

   ★ 使用禁忌：别把 .comic-tilt-* 与 .comic-reveal / .comic-sect-no 写在同一元素上 ★
   三者都在写 transform，同元素叠加时后写的那个胜，倾角会静默消失。
   参考实现的写法是**分层**：外层 .comic-reveal 管入场位移，
   内层的贴纸自己带倾角 —— 照这个结构写就不会打架。 */
.comic-tilt-a { transform: rotate(-2.5deg); }
.comic-tilt-b { transform: rotate(1.8deg); }
.comic-tilt-c { transform: rotate(-1.2deg); }
.comic-tilt-d { transform: rotate(2.4deg); }

/* ---- 13f. 编号章节徽章 ----
   几何 + 倾角 + 墨线 + 硬阴影；**配色留给调用点**（bg-* / text-* 工具类），
   因为同一页里编号是轮换配色的，写死在本类里反而要再造四个变体。
   深色分镜上用 --on-ink：墨线换白线、阴影换白影，否则整个徽章糊在墨底里。 */
.comic-sect-no {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 3rem;
    height: 3rem;
    transform: rotate(-6deg);
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    box-shadow: var(--comic-shadow-4);
    font-size: 1.25rem;
    font-weight: 900;
    line-height: 1;
    letter-spacing: 0.02em;
}

.comic-sect-no--on-ink {
    border-color: var(--comic-white);
    box-shadow: var(--comic-shadow-light-4);
}

/* 章节标题行：徽章 + 标题成组，两者间距与基线靠这个类统一 */
.comic-sect-head {
    display: flex;
    align-items: center;
    gap: 1rem;
}

@media (min-width: 768px) {
    .comic-sect-no {
        width: 3.5rem;
        height: 3.5rem;
        font-size: 1.5rem;
    }
}

/* ---- 13g. 滚动入场 ----
   ★ 为什么整块藏在 @media (scripting: enabled) 里（重要，别拆出来）★
   .comic-reveal 的初始态是 opacity:0。若把它无条件写在全局，
   一旦浏览器禁用了脚本，IntersectionObserver 永远不跑，
   内容就**永久不可见** —— 页面看起来是空的，且没有任何报错。
   包进 scripting 媒体查询后：无脚本环境压根不匹配这条规则，内容直接可见（fail-open）。
   ★ 残留风险（已知并接受）★ "脚本已启用、但 comic-effects.js 加载失败"时仍会停在隐藏态。
   该文件是同源静态资源、且进 SW 预缓存清单，此风险低于收益；
   若要彻底消除，只能把初始隐藏改由 JS 添加类 —— 那会带来首屏闪一下的代价。 */
@media (scripting: enabled) {
    .comic-reveal {
        opacity: 0;
        transform: translateY(34px) rotate(-0.6deg);
        transition: opacity 0.42s ease-out, transform 0.42s ease-out;
        /* --d 由调用点给：同一屏里的元素依次入场（style="--d:.12s"） */
        transition-delay: var(--d, 0s);
    }

    .comic-reveal.is-in {
        opacity: 1;
        transform: none;
    }
}

/* ---- 13h. 阅读进度条 ----
   ★ z-index 为什么是 1050（高于导航的 1030）★
   导航是 fixed top-0 z-[1030]，进度条要**压在导航上沿**才看得见；
   写在 CSS 里而不是用 z-[1050] 工具类，是因为这类 arbitrary value 若没被
   翻译进 comic-utilities.css 会**静默失效**（本站最危险的陷阱）。

   ★★ 但 z-index 只是必要条件，不是充分条件 ★★
   _Layout 把 @RenderBody() 包在 <div class="relative z-[1]"> 里，那是一个
   层叠上下文。fixed 元素若写在页面标记里，就被关在该上下文内部 ——
   这个 1050 只在"内部"成立，整块对外仍只是 z-index 1，会被 z-1030 的导航整个盖住。
   所以调用点必须把它渲染在包裹层之外（@section Overlays）。
   样式上把 z-index 调多大都救不回来，这是结构问题而不是数值问题。

   ★ 高度 5px 而非 6px ★ 导航本身有 4px 下边框，5px 的条刚好把上沿压住、
   又不至于盖掉导航内容。 */
.comic-progress {
    position: fixed;
    inset: 0 0 auto 0;
    height: 5px;
    z-index: 1050;
    pointer-events: none;
}

.comic-progress-bar {
    display: block;
    height: 100%;
    width: 100%;
    transform: scaleX(0);
    transform-origin: 0 50%;
    background: var(--comic-red);
}

/* ---- 13i. 返回顶部 ----
   ★ 为什么右下方留出 5.5rem 的底距 ★
   左下角是全站音乐播放器（left:18px bottom:18px，z-index 1080）。
   按钮放右下不会和它抢位置，但底部仍留出一段，避免小屏上两者视觉挤在一起。

   ★★ 与 13h 同一条结构性约束（真被踩到过）★★
   页脚是 `relative z-[1]`、且在 DOM 里排在正文之后。按钮若渲染在页面的
   .relative.z-[1] 包裹层内部，滚到底时页脚正好盖住右下角 ——
   按钮**看得见却点不动**，不报任何错，返回顶部功能等于没有。
   必须渲染在包裹层之外（@section Overlays）。 */
.comic-to-top {
    position: fixed;
    right: 1.1rem;
    bottom: 5.5rem;
    z-index: 1070;
    display: none;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border: var(--comic-border);
    border-radius: var(--comic-radius);
    background: var(--comic-yellow);
    color: var(--comic-ink);
    box-shadow: var(--comic-shadow-4);
    cursor: pointer;
    transition: transform var(--comic-duration), box-shadow var(--comic-duration);
}

.comic-to-top.is-visible {
    display: flex;
}

.comic-to-top:hover {
    transform: translate(-2px, -2px);
    box-shadow: var(--comic-shadow-8);
}

/* ---- 13j. 本节动效兜底 ----
   与第 10 节同一原则：减动效偏好下**去掉位移与渐变，保留可见性**。
   ★ .comic-reveal 必须显式落回 opacity:1 ★ 它的 0 是初始态，
   只把 transition 时长压到 0 的话，元素仍然要靠 is-in 才能显示；
   万一 JS 因任何原因没跑到，减动效用户反而看到一片空白。
   这里直接把终态写死，等于给这条链路加一道与 JS 无关的保险。 */
@media (prefers-reduced-motion: reduce) {
    .comic-reveal {
        opacity: 1;
        transform: none;
        transition: none;
    }

    .comic-progress-bar,
    .comic-to-top {
        transition: none;
    }

    .comic-to-top:hover {
        transform: none;
        box-shadow: var(--comic-shadow-4);
    }
}
/* ★ .comic-tilt-* 刻意不进这个兜底 ★
   它是**静态装饰**（一贴上就不动的 2.5° 倾角），不是动画。
   减动效偏好针对的是"会动的位移/缩放"，把倾角也抹平属于误伤：
   用户会觉得"怎么贴纸都摆正了"，而不是"动效被关掉了"。 */
