/* ==========================================================================
   dialog.css —— 全站弹窗的唯一基类与 token
   ==========================================================================

   为什么放在项目根的 static/ 而不是某个 app：
     这套 token + 基类被 3 个模板跨 2 个 app 共用（app_picllery/index.html、
     app_users/profile.html、app_users/user_profile.html）。放进任一 app 都会让
     另一个 app 反向依赖它。根 static/ 从项目建立起就是预留的跨 app 共享目录
     （favicon、默认头像、社交图标都在这里），STATICFILES_DIRS 已含 BASE_DIR/static，
     无需改 settings。既有引用一律是 per-app 形式（app_picllery/css/index.css），
     本文件是第一个共享样式的使用者。
     依据 responsive-layout skill §9.1：引入新模式时在代码里写明为什么不用现有约定。

   形态：全终端统一**底部抽屉**，与主站评论面板同形。不存在形态切换的媒体查询 ——
     650px 是唯一的列宽来源，抽屉锚定在该列底部从下方升起。

   显隐：`[aria-hidden="false"]` 是唯一真相源。CSS 由它驱动动画与 visibility，
     所以 CSS 与 ARIA 不可能失步；`visibility:hidden` 同时解决「键盘 Tab 进隐藏弹窗」。
     只写正向选择器，**不写** `[aria-hidden="true"]{display:none}`，避免与既有的
     图片层/头像的 aria-hidden 用法冲突。

   前缀 `--dlg-` 按 responsive-layout skill §2.4 的新模块命名规则（2-6 字符、首字符有辨识度）。
   ========================================================================== */

:root {
    /* ---- 形态与尺寸 ---- */
    --dlg-radius: 16px;          /* 抽屉上两角（下两角恒为 0） */
    --dlg-radius-sm: 12px;       /* 面板内嵌块 */
    --dlg-space-1: 4px;
    --dlg-space-2: 8px;
    --dlg-space-3: 12px;
    --dlg-space-4: 16px;
    --dlg-space-5: 20px;         /* P26 起才真有了消费者：下载面板 .dlg-body 的内边距。
                                     ⚠️ 这句注释原本写的是「唯一被用到的备用档（.download-sheet）」——
                                     那是**假的**：它当时零消费者，而它的邻居 --dlg-space-6 /
                                     --dlg-radius-pill 正是**因零消费者被删**。它靠这句不实的注释
                                     活到了 P26。**改 token 的消费者清单时，注释要跟着改。** */
    /* P7 删掉的两个备用档：--dlg-space-6(24px) 与 --dlg-radius-pill(999px)。
       它们在 P3–P5 的迁移里始终无人引用 —— 留着会让读者以为「已有一套 pill 按钮」，
       而实际没有任何元素是 pill。要用时再加，加一行比认一个空名字便宜。 */
    /* 48 = ✕ 32 + 上下各 8px。不是 44 —— 关闭按钮现在是头部的 flex 项，
       align-items:center 下它的 32px 恒撑高内容，44 的下限根本够不着。 */
    --dlg-head-h: 48px;
    --dlg-close-size: 32px;      /* hover 命中区用 ::before 撑到 44px */
    --dlg-gutter: var(--dlg-space-4);
    --dlg-sheet-h: 70%;          /* 固定高度内容型抽屉（评论） */
    --dlg-sheet-min-h: 280px;
    --dlg-sheet-max-h: 560px;
    --dlg-panel-max-h: 85%;      /* 内容自适应面板的高度上限（相对遮罩高） */
    /* P32：**列表型**面板的高度上限（信息 / 关注抽屉）。它们「扫一眼就走」，不该占满。
       85% 在手机上合适（667 高 ⇒ 567px，正是底部抽屉该有的样子），但在高视口上会被放大 ——
       实测 1080 高时 9 行通知就撑到 787px(73%)、21 行顶到上限 918px(85%)，盖住大半个窗口。
       560 不是新发明的数：它就是上面 --dlg-sheet-max-h（评论抽屉）一直在用的封顶。
       min() 让手机上仍然是 85%（667 高 ⇒ min(567,560)=560，只差 7px；568 高的手机完全不受影响）。
       ⚠️ **这是一处显式声明的耦合**：改 --dlg-sheet-max-h 会同时改变评论抽屉、信息面板、
          关注抽屉三者的封顶。要的就是这个 —— 它们表达的是同一个约束（抽屉不该超过这个高度）。
          哪天想拆开，把 560px 抄成字面量即可。 */
    --dlg-panel-max-h-list: min(85%, var(--dlg-sheet-max-h));
    /* P33：表单型面板的高度上限。760 不是新发明的数 —— 取自项目自己的响应式规范
       （.claude/skills/responsive-layout/skill.md 的 `max-height: min(78dvh, 760px)`），
       与上面 560 取自 --dlg-sheet-max-h 是同一个做法。
       ⚠️ 实测记一笔（P80h）：设置面板正文 **526px**（探针，431/1280 两档；面板自然高
          ≈ 61 头 + 526 + 71 脚 ≈ 658px **< 760**）⇒ **本档重新完全空转**（不夹任何高度）。
          曲线：P80f 把标签改成文档流时正文涨到 732px、这条曾临时真夹 ~100px；用户否掉入流（P80g）
          回到 632px；P80h 放弃分组（删组题 ×3 与两条章节头线）后降到 526px ⇒ 回到「不改变任何渲染」。
          它的价值是把「高度按内容类型分档」这条规则写死、并给未来封顶。
          ⚠️ 改这条数字前先跑探针量当前值 —— **别抄旧数**（696 / 632 / 732 都对应过不同形态）。 */
    --dlg-panel-max-h-form: min(85%, 760px);
    /* 键盘态的**可见高** = 布局视口高 − 被键盘遮住的那一段。
       dialog.js 在三个宿主页面统一写入这两个长度；桌面下 kb-inset 为 0。 */
    --dlg-vis-h: calc(var(--dlg-layout-h, 100vh) - var(--dlg-kb-inset, 0px));

    /* ---- 分享面板：海报的度量容器（P12 建、P13 改用途、P16 收宽度）----
       主站 #shareModal 与沉浸 #immersive-share-overlay 是两份平行实现，故这些值
       与下面的 .dlg-share-window / .dlg-share-primary / .dlg-share-qr 一起放在共用基类里。
       同一条规则写两遍就是 P8–P10 一直在收的那种漂移（responsive-layout skill §9.1）。

       ⚠️ P13 删掉了 `--dlg-share-window-h`（原为 112px 的裁剪窗高度）：
       窗口现在是**零高**的度量容器，高度不再是可调参数。
       按项目惯例（「要用时再加，加一行比认一个空名字便宜」）不保留空 token。 */
    --dlg-share-poster-max: 320px;   /* 与既有夹持值一致，smoke_p3 / smoke_p5 都钉着它 */
    /* P16 删掉了 `--dlg-share-window-w: 88%` —— 窗口宽度改为
       `min(100%, var(--dlg-share-poster-max))`，不再需要一个单独的百分比 token。
       按项目惯例（「要用时再加」）不留空 token。 */

    /* ---- 字号阶梯：5 档，收敛原先的 9 种 ---- */
    --dlg-font-title: 16px;
    --dlg-font-body: 14px;
    --dlg-font-label: 13px;
    --dlg-font-caption: 12px;
    --dlg-font-input: 16px;      /* 表单控件全宽统一（防 iOS 聚焦缩放），见文件末尾那条规则 */

    /* ---- 颜色 ---- */
    --dlg-surface: #ffffff;
    --dlg-text: #151515;
    --dlg-text-dim: #666666;
    --dlg-brand: #98e0ac;
    --dlg-on-brand: #173a22;
    --dlg-border: #e0e0e0;
    --dlg-hover: #f5f5f5;
    --dlg-pressed: #ebebeb;
    /* 危险色 = 一套两件：**亮红字**（文字/描边/图标）+ **干净浅粉底**（填充）。
       🔴 这两档都来回改过多次 —— 把序列记在这儿，别再「统一」回去：
         · 填充：#ff5252 实心（最初）→ #e33e3e（P21「全站一红」）→ #d24a4a（P22「过于的红」）
           → 实心整体退役、改软底 + 深红字（P23）→ **浅粉底换干净浅色 #ffe3e3、
             大按钮去描边**（P23 续②：用户「软粉底不是很好看」；旧值 #f7d9d9 偏灰、
             且描边比底色深一档 —— 两样叠起来正是「不好看」的来源）。
         · 红字：#ff5252 → #e33e3e → #d24a4a → 深红 #7a2b2b → **#e33e3e 亮红**
           （P23 续：用户看到实物后「深红色不好看，改回亮红色」）。
       ⚠️ 所以现在是**有意为之的组合**：干净浅粉 + 亮红字。它不满足 WCAG AA
          （亮红在白底 4.18:1、在浅粉上 3.45:1）—— 用户明确选了亮红，
          「过 AA」这笔账**挂起**，不要自动优化回深红。
       两件与全站绿主按钮的「浅底深字」同构（--dlg-brand + --dlg-on-brand），只是换了色相。
       ⚠️ 历史的三个实心红 token（--dlg-danger / -hover / -rgb）已**删除**，别复活：
         残留的 var() 会让整条声明静默失效且不报错（守卫见 DangerColourTokensTests）。 */
    --dlg-danger-ink: #e33e3e;          /* 危险红：文字 / 描边 / 图标 */
    --dlg-danger-ink-hover: #c83737;    /* 红字的 hover，深一档 */
    --dlg-danger-soft-bg: #ffe3e3;      /* 危险填充：干净浅粉 */
    --dlg-danger-soft-bg-hover: #ffd4d4;
    --dlg-danger-soft-border: #f5c2c2;  /* 只剩「未通过」角标用：它画在任意照片上，需要一道边 */
    --dlg-focus-ring: 0 0 0 3px rgba(152, 224, 172, 0.45);

    /* ---- 「关注」按钮的两个状态：**五处共用**（P71 起） ----
       P59：主站作者信息区（.author-follow-btn）/ 沉浸流（.immersive-follow-btn）/
       关注抽屉（.follow-action-btn）/ 个人页（.profile-follow-btn）此前**各写各的**，
       而且**三处是反的**（那三处「绿 = 已关注」）。用户 2026-09-30 拍板统一到
       「**绿 = 还没关注（CTA）**、中性白 = 已关注」—— 这也是 IG / XHS 的惯例，
       而个人页原本就是这一套。P71 新增第五颗：吸顶身份条（.mini-follow-btn）。
       ⇒ 六个数收敛到这里：**一处改、五处跟**（P42 的老 remedy）。
       顺带修掉两件事：个人页那个按钮**硬编码** `#98e0ac`/`#173a22`（违反「品牌绿只有一个来源」）；
       以及四处**状态载体不一致**（个人页用属性、另三处用 class；P71 的新颗两种载体都写）。
       ⚠️ 对比度：这两个状态在**色带 #eee** 上都过（绿底配深绿字 ≈ 8:1）；
         抽屉的地面是**白**、更高 ✓ ⇒ **一套值两处都成立**，别为抽屉另开一套 ✗。 */
    --dlg-follow-bg: var(--dlg-brand);          /* 未关注：实心品牌绿 */
    --dlg-follow-fg: var(--dlg-on-brand);       /* 未关注：字色（深绿） */
    --dlg-follow-border: var(--dlg-brand);      /* 保留 1px 边框 ⇒ 盒子尺寸不变（P51 的老规矩） */
    --dlg-follow-hover-bg: #44c67d;             /* 未关注 hover：与 `.pa-btn--primary:hover` 同一个绿 */
    --dlg-follow-done-bg: #ffffff;              /* 已关注：中性白（退成安静的那一态） */
    --dlg-follow-done-fg: #333333;
    --dlg-follow-done-border: #cfd8d3;

    /* ---- 遮罩：2 档底色 + 1 档模糊，收敛原先的 4 种透明度 + 2 种模糊 ---- */
    --dlg-scrim: rgba(0, 0, 0, 0.32);
    --dlg-scrim-plain: rgba(0, 0, 0, 0.20);   /* 下载面板：需透出底图看裁剪预览 */
    --dlg-scrim-strong: rgba(0, 0, 0, 0.55);  /* .crop-overlay 自身 */
    --dlg-scrim-blur: 4px;

    /* ---- 阴影：2 档，收敛原先的 8 种偏移（无卡片形态） ---- */
    --dlg-shadow-sheet: 0 -8px 24px rgba(0, 0, 0, 0.18);
    --dlg-shadow-pop: 0 8px 18px rgba(0, 0, 0, 0.12);

    /* ---- 动效：开/关对称，单一缓动 ---- */
    --dlg-dur: 0.22s;
    --dlg-dur-fast: 0.15s;
    --dlg-ease: cubic-bezier(0.32, 0.72, 0, 1);

    /* ---- z-index 刻度：原先的 13 个魔法数收敛为 8 档 ----
       必须保持原有的相对高低顺序（实测）：
         toast 99999 > 关注列表 10001 > .modal 10000 > 查看器 9999 > 沉浸 overlay 45/50（局部） */
    --dlg-z-content: 10;    /* 原 10：.navbar、.interaction-buttons、.author-comment、
                               .crop-overlay（两处，index.css / profile.css —— 它必须
                               留在遮罩之下，理由见各自的规则注释）

                               ⚠️ 原值里的 **11（.bottom-tools、.thumbnail-rail）没有并入**，
                               它们仍是字面量 11 —— 那不是漏做。两者同处一个层叠上下文，
                               且 .bottom-tools(11) 必须**压在** .navbar(10) 之上；若把它们
                               也改成这个值为 10 的 token，就会与 .navbar 打平，胜负改由
                               DOM 顺序决定，底部工具条的层级会静默翻转。要 token 化就得
                               另开一档（如 --dlg-z-raised），那属于改行为，P7 的定位是
                               「值不变、只换写法」，故留待需要时再做。 */
    --dlg-z-sticky: 20;     /* P70 复活（P10 曾删，当时的墓碑就写着「要用时再加」）：
                               个人页 `.tab-bar-row` 吸顶是全项目第一个 `position: sticky`。
                               低于查看器 900 / 遮罩 1000 —— 吸顶栏必须被这两者盖住；
                               高于页内一切内容层（profile.css 审计：页内非遮罩元素最大 10）。 */
    --dlg-z-viewer: 900;    /* 原 .immersive-viewer 的字面量 9999。
                               9999 → 900 是**revert 不是重排**：P8 之前 .modal(10000) 与
                               #followListModal(10001) 本来就压在查看器之上，本档把它们还原成
                               同一相对关系（scrim 1000 / follow 1003 仍高于查看器）。
                               查看器内部那批 1/2/10 是**局部值**（它自己带 z-index + transform，
                               自成层叠上下文），与本档不可比，故不受影响。
                               ⚠️ 唯一被翻转的是 .card-action-sheet（原字面量 1100 < 9999）——
                               它从网格卡片的长按触发，此时查看器必然已关闭，两者互斥，无实际影响。 */
    --dlg-z-scrim: 1000;    /* 原 .modal 10000 / .delete-modal 1000 / .comment-modal 28 / .share-modal 30 */
    /* P7 删掉 --dlg-z-crop: 1001。它是方案初稿给 .crop-overlay 定的档（「抬到遮罩之上」），
       实现时被证伪：该元素参与根层叠上下文，而 #downloadModal 自成层叠上下文，
       抬到 1000 以上会让整条遮罩盖住下载面板本身。实际做法是保持 10、改用更透的
       .dlg-scrim--plain 让预览透出来，于是这个档位从未被引用。 */
    --dlg-z-pop: 1002;      /* 面板内浮层：表情面板、评论内联删除确认 */
    --dlg-z-follow: 1003;   /* 原 .follow-list-modal 10001 —— 必须高于普通弹窗 */
    --dlg-z-toast: 1100;    /* 原 index 40 / profile 99999 —— 必须高于所有弹窗 */
}

/* ==========================================================================
   遮罩
   ========================================================================== */

/* 默认：填满**调用方提供的定位祖先**。调用方必须保证祖先是定位元素。
   这个默认值是实测倒推的 —— 12 个弹窗里有 6 个（index 三弹窗 + 3 个沉浸 overlay）
   的现有写法本来就是 absolute;inset:0，用默认值后它们的定位一字不改。 */
.dlg-scrim {
    box-sizing: border-box;
    position: absolute;
    inset: 0;
    z-index: var(--dlg-z-scrim);
    background: var(--dlg-scrim);
    /* 遮罩是面板的定位祖先，同时也是它的裁剪盒。
       关闭态的面板停在 translateY(100%)，即整块落在遮罩下沿之外 —— 没有这行，
       它会一路把文档撑高：实测桌面端 html.scrollHeight 从 800 变 1360。
       🔴 P70：这条 overflow 从「保险」升为**承重** —— 个人页改为文档流滚动后，
       `.wrapper` 不再兜底裁剪（它的 overflow 已随 sticky 需求删除，见 profile.css 文件头），
       关着的抽屉若不被这一行裁掉，会在文档里凭空多出一屏可滚区，且不报任何错。 */
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--dlg-dur) var(--dlg-ease),
                visibility 0s linear var(--dlg-dur);
}

.dlg-scrim[aria-hidden="false"] {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity var(--dlg-dur) var(--dlg-ease);
}

@supports (backdrop-filter: blur(4px)) {
    .dlg-scrim {
        backdrop-filter: blur(var(--dlg-scrim-blur));
        -webkit-backdrop-filter: blur(var(--dlg-scrim-blur));
    }
}

/* --plain（下载面板：要透出底图看裁剪预览）必须写在**上面两个块之后**：
   它与 .dlg-scrim 特异性相同，靠源码顺序取胜，写在 @supports 之前会被里面的
   blur 盖掉。
   底色更不能放进 @supports —— 这个变体的全部意义就是「更透」，一旦 @supports
   不成立就退回默认的 0.32，比默认还暗，正好反了。而 backdrop-filter: none
   在不支持它的旧浏览器上只是被忽略，无害。 */
.dlg-scrim--plain {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: var(--dlg-scrim-plain);
}

/* 仅 profile 的平级弹窗：无定位祖先可用，改为自成 650px 居中列。
   必须在 .dlg-scrim **之后**声明 —— 两者特异性相同，靠源码顺序决定。
   为什么这些不能用 absolute：① .wrapper 没有 position，补 relative 会改变
   .edit-profile-btn / .tab-indicator / .card-like-btn 等一批绝对定位后代的参照物；
   ② #followListModal 在 .wrapper 之外，absolute 根本够不着。
   为什么沉浸 overlay 绝不能用 fixed：.immersive-viewer 带 transform，
   是 fixed 的包含块，套上 fixed 后 height 会退化成绝对 100vh，
   地址栏收缩时底部被查看器的 overflow:hidden 裁掉 —— 现有代码写 absolute
   是刻意且正确的。 */
.dlg-scrim--fixed {
    position: fixed;
    inset: auto;
    /* P33：遮罩是**调暗层**，按**布局视口**铺满。
       真机那条缝（抽屉与输入法之间露出背后页面的图片卡片）的成因是：
       这里原本用的是视觉视口高（calc(var(--vh)*100)），而真机上它可能比
       「键盘上方实际可见的高度」小一截 —— 短掉的那段没被压暗，就露出了页面。
       P32 曾试图用 `top` 的位移去补，方向错了（短的是高度、不是位移）。
       ⚠️ 不用 100vh：那是**大**视口（含地址栏收起后那段），移动端会把面板算到屏幕外。
       ⚠️ 不用 height:100%：它在 position:fixed 上解析成哪个视口，无法在本地确证。
       两个长度全部由 dialog.js 写入，这里只消费（兜底 100% 与改动前等价）。 */
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: min(100vw, 650px);
    max-width: 100%;
    height: var(--dlg-layout-h, 100%);
}

/* 键盘态：面板从布局视口底部上抬被键盘遮住的高度，底边落在可见视口底部。
   这条同时覆盖主站 absolute 抽屉与个人页 fixed 抽屉，避免两边各自猜 viewport。
   max-height 按可见高度夹住，防止键盘打开后面板顶部被 overflow:hidden 裁掉。 */
.dlg-scrim--fixed > .dlg-panel {
    bottom: var(--dlg-kb-inset, 0px);
    max-height: min(var(--dlg-panel-max-h), var(--dlg-vis-h));
}

/* 主站的 .dlg-scrim 是 absolute，父级 .wrapper 在旧式 resizes-visual 浏览器里
   默认只跟随 visualViewport。keyboard-active 时 index.js 会把 --vh 临时切到布局高，
   因此这里与 fixed 抽屉使用同一个 bottom 偏移即可，不会被父级裁掉。 */
html.dlg-keyboard-active .dlg-scrim > .dlg-panel {
    bottom: var(--dlg-kb-inset, 0px);
    max-height: min(var(--dlg-panel-max-h), var(--dlg-vis-h));
    /* 键盘已经覆盖手势区，不能再把 safe-area 作为面板内容空间重复计算。 */
    padding-bottom: 0;
}

/* 评论抽屉带固定高度时，键盘态优先保证输入区和底部操作可见。 */
html.dlg-keyboard-active .dlg-panel--fill {
    height: auto;
    min-height: 0;
    max-height: min(92%, var(--dlg-vis-h));
}

/* ==========================================================================
   抽屉面板
   ========================================================================== */

.dlg-panel {
    box-sizing: border-box;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    flex-direction: column;
    background: var(--dlg-surface);
    border-radius: var(--dlg-radius) var(--dlg-radius) 0 0;
    box-shadow: var(--dlg-shadow-sheet);
    max-height: var(--dlg-panel-max-h);
    /* 基线安全区内边距放在**面板**上而不是 .dlg-foot 上：
       让没有 .dlg-foot 的面板（删除确认 / 卡片操作单等）也不会内容贴到屏幕底边。
       因为放在这里，.dlg-foot 内部**不可**再加 env()，否则叠加成双倍留白。 */
    padding-bottom: env(safe-area-inset-bottom);
    transform: translateY(100%);
    overflow: hidden;
    transition: transform var(--dlg-dur) var(--dlg-ease);
}

.dlg-scrim[aria-hidden="false"] .dlg-panel {
    transform: translateY(0);
}

/* 面板本体是 tabindex="-1" 的读屏落地目标，打开时由 dialog.js 程序化聚焦（见其
   initialFocus 兜底那段的注释）。Chromium 会给它匹配 :focus-visible 并在 **border box
   之外**画 UA 焦点环；而面板宽度恰好等于遮罩列、且贴底，左/右/下三边整个落在
   .dlg-scrim 的 overflow:hidden 之外，只在**上沿**留下一圈「1px 白 + 2px 近黑」的黑边，
   沿 16px 圆角走 —— 实测就是这个可见的 bug。
   面板本身不是可交互控件（tabindex="-1"，不可 Tab 到达），焦点环由面板**内部的控件**
   各自承担（.dlg-close 用 --dlg-focus-ring），故关掉本体的环。
   ⚠️ 不要用 box-shadow 替代 —— 它同样画在 border box 外，会被同一把剪刀裁成
      「上沿一道绿边」，只是把黑边换成绿边。
   ⚠️ 也不要因此删掉 dialog.js 里的 panel.focus()：这里只消除**绘制**，不改变**焦点**
      （面板仍是 activeElement，读屏与 Tab trap 的行为一字不变）。 */
.dlg-panel:focus,
.dlg-panel:focus-visible {
    outline: none;
}

/* 固定高度的内容型抽屉（评论）；键盘态由上方的 keyboard-active 规则降为自适应。 */
.dlg-panel--fill {
    height: var(--dlg-sheet-h);
    /* 短视口下 280px 的死 min-height 会把输入框顶出视口，故夹住比例上限；键盘态
       另由 html.dlg-keyboard-active 规则切换为按可见高度自适应。 */
    min-height: min(var(--dlg-sheet-min-h), 45%);
    max-height: var(--dlg-sheet-max-h);
}

/* 面板内的结构件 */

.dlg-head {
    box-sizing: border-box;      /* 不加这行 min-height 量的是**内容**高度 —— 实测主站评论头部因此是 52px 而不是 44px */
    flex: 0 0 auto;
    min-height: var(--dlg-head-h);
    display: flex;
    align-items: center;
    gap: var(--dlg-space-2);
    /* 上下对称。原来这里是 `8px 16px 0` + 一条 padding-right 算式：当时 .dlg-close 是绝对
       定位、脱离文档流，头部必须自己让出它的宽度（否则标题被压在 × 下面）。现在 ✕ 是头部的
       flex 项、自己占位（见下方 .dlg-head > .dlg-close），那条算式被手抄的 5 份
       （52 / 52 / 40 / 48 / 68px）随之全部消失。 */
    padding: var(--dlg-space-2) var(--dlg-gutter);
}

.dlg-title {
    margin: 0;
    font-size: var(--dlg-font-title);
    font-weight: 600;
    color: var(--dlg-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;   /* flex 项默认 min-width:auto，不置 0 就不会收缩、也就出不来省略号 */
}

/* 标题是头部的**直接**子元素时才吃掉剩余宽度。在 .dlg-heading 里时不能 ——
   那是个纵向 flex，flex:1 会让标题在副标题之外还多占一截高度。 */
.dlg-head > .dlg-title {
    flex: 1 1 auto;
}

/* 标题里的计数（如「评论 12」）。全项目**只有评论抽屉**用它 —— 下载/分享标题刻意不带：
   那两个抽屉恒为 3 / 6 个动作、列表长度是常数，能填进标题的数字只剩「这张图被下载过
   几次」，而那是**面板外面**的图片热度，且**已经显示在行动栏上**了。
   评论抽屉不同：下面是一个长度可变的列表，标题里的数字说的是「这里面有多少条」。

   · **保持 inline**：它嵌在 .dlg-title 的 `overflow:hidden / text-overflow:ellipsis /
     white-space:nowrap` 上下文里，与标题共享同一套省略号机制。改成 flex/block 会拆掉
     省略号，并在 .dlg-head 这个 flex 行里凭空多出一个纵向盒子。
   · 特异性 (0,2,0)：dialog.css 先于 index.css / profile.css 加载，同为 (0,1,0) 时
     页面 CSS 必胜。抬到与 `.dlg-head > .dlg-title` 同档即可，不必用 !important。
   · **font-weight 必须显式写**：字重会继承，不写就仍是标题的 600，而 13px 与 16px 的
     差距不足以断开「评论12」的连读。400 也正是 .dlg-subtitle 的实际档位（它不设字重，
     继承 .dlg-heading 的 400）。
   · **字号用 token、不用 em**：`0.85em` 在 16px 父级下算出 13.6px，会当场触发
     smoke_p8 / p9 / p10 / p12 四条「字号白名单 {12,13,14,16,24}px」断言。 */
.dlg-title .dlg-title-count {
    font-size: var(--dlg-font-label);
    font-weight: 400;
    color: var(--dlg-text-dim);
}

/* 带副标题/摘要的头部（消息 / 设置 / 注销 / 通知）：把标题与副标题捆成一个整体，
   由这个整体去吃剩余宽度，✕ 依旧自己占位 */
.dlg-heading {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.dlg-subtitle {
    margin: var(--dlg-space-1) 0 0;
    font-size: var(--dlg-font-label);
    line-height: 1.5;
    color: var(--dlg-text-dim);
}

.dlg-body {
    flex: 1 1 auto;
    min-height: 0;                 /* flex 子项默认 min-height:auto，不置 0 则内部滚动失效 */
    overflow-y: auto;
    padding: var(--dlg-space-2) var(--dlg-gutter);
    touch-action: pan-y;           /* responsive-layout skill §一：滚动容器必须设 */
    -webkit-overflow-scrolling: touch;
}

.dlg-foot {
    flex: 0 0 auto;
    padding: var(--dlg-space-3) var(--dlg-gutter);
    /* 注意：safe-area 已由 .dlg-panel 承担，此处不可再加 env() */
}

/* 面板内浮层（表情面板、评论内联删除确认）—— 不是遮罩，不参与 dialog.js 的 registry */
.dlg-pop {
    border-radius: var(--dlg-radius-sm);
    background: var(--dlg-surface);
    box-shadow: var(--dlg-shadow-pop);
    border: 1px solid var(--dlg-border);
    z-index: var(--dlg-z-pop);
}

/* 关闭按钮：4 套既有实现里 0 个有 hover/focus 态，这里统一补齐 */
.dlg-close {
    position: absolute;
    top: var(--dlg-space-2);
    right: var(--dlg-space-3);
    width: var(--dlg-close-size);
    height: var(--dlg-close-size);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    border-radius: var(--dlg-radius-sm);
    font-size: 24px;
    line-height: 1;
    color: var(--dlg-text-dim);
    cursor: pointer;
    z-index: var(--dlg-z-pop);
}

/* 把 32px 的视觉尺寸撑出 44px 的命中区（触屏可点性），不占布局 */
.dlg-close::before {
    content: '';
    position: absolute;
    inset: calc((44px - var(--dlg-close-size)) / -2);
}

/* ✕ 搬进头部后由「绝对定位 + 手抄预留宽度」改为「头部的 flex 项」—— 纵向错位、5 处手抄算式、
   死掉的 68px、跟着内容滚走 这四类问题都由这一处改动一并消灭，且不再需要任何魔数：
   标题多长都挤不动 ✕（标题自己收缩出省略号），✕ 的纵向中心交给 align-items:center 算，
   与头部高度 / 字号 / 断点全部解耦。

   ⚠️ 必须限定成 .dlg-head 的子选择器，**不能去改上面 .dlg-close 的基础规则**：
      还有 8 个面板的 ✕ 是面板的直接子元素（P9 / P10 才迁移）。基础规则一旦改成 flex 项，
      它们会立刻把 ✕ 渲染成面板左上角的一个 32×32 方块、标题被顶下去一行 —— 一个肉眼可见的
      中间态。限定作用域后「搬进头部的走 flex、没搬的保持原样」，三个阶段各自可独立上线。 */
.dlg-head > .dlg-close {
    position: relative;   /* 只为让 ::before 那个 44px 命中区有定位祖先（原先靠 absolute 顺带提供） */
    flex: 0 0 auto;
    top: auto;            /* 必须撤销：不撤的话 relative 仍会被基础规则的 top/right 推走 8px、12px */
    right: auto;
    z-index: auto;        /* ✕ 已在文档流里、不与任何元素重叠，不需要再压图层 */
}

@media (hover: hover) {
    .dlg-close:hover {
        color: var(--dlg-text);
        background: var(--dlg-hover);
    }
}

.dlg-close:active {
    background: var(--dlg-pressed);
}

.dlg-close:focus-visible {
    outline: none;
    box-shadow: var(--dlg-focus-ring);
}

/* ==========================================================================
   分享面板：主操作卡 + 平台行 + 通栏链接行（P13）
   ==========================================================================
   P12 把海报压成 112px 的「紧凑预览条」，用户看后报「只显示一个窄条，很不美观」。
   实测确认了原因：**112px 里只有 48–54% 是照片，其余全是海报自己的标题/分隔线/meta** ——
   照片只剩 54–60px 的一条，而 290×54 这种 5.4:1 的比例，任何照片切进去都像图坏了。
   根因是我把取舍做反了：按「别把文字切在半行上」定尺寸 → 文字占了大半、照片成了碎片。
   预览要展示的是**照片**。

   P13 重新设计整条链（用户：「不要受限于我原有的设计，结合行业主流做法」）：

     ① 主操作卡（本文件）—— 一次点击，**按设备能力降级**真分享：
        `canShare({files})` → 把海报 PNG 当载荷调起系统面板（手机上真·发微信，带图）
        → 只有 `share` → 发链接
        → 都没有 → 保存海报 + 复制链接
        **同一个按钮，做它在这台设备上唯一能做成的事。**
     ② 平台行 —— 只留真实可用的（微博 / QQ / 保存海报）。删掉「微信」（它其实只是
        复制链接 + 提示去微信粘贴，是个假入口）与「链接」（与底部通栏行完全重复）。
     ③ 通栏链接行 —— 兜底，P12 已有。

   ⚠️ 海报子树仍然**一个字不改**，只是从「被裁成 112px」变成「被裁成 0 高」：
      downloadPosterCanvas() / downloadImmersivePoster() 不是截图，而是手写 Canvas 2D 重绘，
      把海报子树的 rect 与 computed style 逐项读走当绘图参数。这 110 行在全部冒烟里零覆盖，
      改坏了没有任何断言会响。新缩略图**不复用海报**，而是一张普通的 <img>（方图，
      object-fit:cover）—— 既拿得到正方形，又完全绕开这套度量契约。 */

/* 海报的**零高容器**：唯一职责是让海报留在 DOM 里、且**布局盒完整**。
   ⚠️ 绝不能改成 display:none —— 那会让海报子树变成零 rect，
   downloadPosterCanvas() 会算出一张 0×0 的画布**且不报错**。
   height:0 + overflow:hidden 只裁绘制，布局盒原样保留（这条在 P12 已被实测证实）。
   ⚠️ 主站的导出时机在 `closeShareModal()` **之后**（index.js 的海报按钮），
   所以「面板关着时海报高仍 > 200」是必须成立的前提，冒烟里有一条专测它。 */
.dlg-share-window {
    position: relative;
    /* P16：宽度从 `min(88%, 320px)` 改成 **`min(100%, 320px)`**。
       原来看似只是 4% 的差别，但它决定**导出海报的分辨率**：
       88% 在 320 视口下只给 253px、375 下给 302px，而 ≥400px 视口恒为 320。
       改成 100% 之后：视口 ≥400 ⇒ 窗口恒为 320 ⇒ **导出完全一致**；
       更窄的手机仍是 body 内容宽（288–368），**同形状同版式、只是分辨率更小**。
       ⚠️ 为什么不用「固定 320px」一刀切：窗口固定 320 而 body 只有 288 时会横向溢出，
       被 `.dlg-body` 的 `overflow-y:auto`（另一轴按规范计算成 auto）吞成**内部横向滚动条**，
       在面板底部露出一条 —— 正是 P14 记录过的那种静默失败形态。要根治得让窗口脱离文档流
       （`position:absolute`），而 `.dlg-body` 加 `position:relative` 会影响**全部 13 个面板**
       的绝对定位后代，属高风险的连带改动，收益不抵。 */
    width: min(100%, var(--dlg-share-poster-max));
    margin: 0 auto;
    height: 0;
    overflow: hidden;
    flex: 0 0 auto;   /* .dlg-body 是 column flex：不写这行，溢出时会被压成负高 */
    /* ── P27：抵消它在 `.dlg-body` 的 flex gap 里多占的那一份 ──────────────────
       它是 body 的**第一个** flex 子项：`height: 0` 但**仍参与 flex 布局**，
       所以它后面那一份 `gap: 8px` 照样存在，把主操作卡往下推 8px。
       P12 加 `gap` 的理由是「还原海报与图标网格之间的 8px」—— 可**海报现在在零高
       容器里、根本看不见**，那份间距的作用已经消失，只剩位移。

       ⚠️⚠️ **这一行必须放在上面那条 `margin: 0 auto` 之后。** 那是**简写**，
          会把 margin-top/bottom 一起置 0 —— 写在它前面会被静默重置，
          表现只是「顶留白没变」，不报错不崩溃。
          （同型陷阱见 P14：`gap: 6px 4px` 曾把基础档的 `column-gap: 0` 悄悄改成 4px。）

       ⚠️ **用 margin、不能**把它移出文档流（`position:absolute`）—— 它的**布局盒必须
          留在流内**，`downloadPosterCanvas()` 要读这棵子树里每个元素的 rect。
       ⚠️ 负 margin 只影响**下一个 item 的起点**，不改它自己的位置与 border box
          ⇒ 窗口与海报子树的**所有 rect 逐字节不变** ⇒ 导出零影响。 */
    margin-bottom: calc(-1 * var(--dlg-space-2));
}

.dlg-share-window > .share-poster,
.dlg-share-window > .immersive-share-poster {
    width: 100%;
    max-width: var(--dlg-share-poster-max);
    /* 两侧统一到 border-box。主站原本是 content-box：边框盒 = 内容 + 垫片（>650px 垫 16、
       ≤650px 垫 12），同一块屏幕上两端口宽 336 vs 320、可见地不一致。
       统一后两端的宽度都由本窗口唯一决定。⚠️ 这会改变主站**导出**的海报宽度（约 −5%）
       —— 是刻意的、且方向是「向沉浸端对齐」，见方案里的逐档对照表。
       **这四条在 P13 一个都不能删**：窗口虽然零高，宽度仍决定导出的画布宽度。 */
    box-sizing: border-box;
    margin: 0;
}

/* ---- ① 主操作卡 ---- */
.dlg-share-primary {
    /* 是 <button>：必须自带 font-size —— 否则漏出 Chromium 给 <button> 的 UA 默认
       13.3333px，落在字号白名单之外（这是本项目第 5 次抓到同一个泄漏）。 */
    font-size: var(--dlg-font-body);
    font-family: inherit;
    display: flex;
    align-items: center;
    gap: var(--dlg-space-3);
    box-sizing: border-box;
    /* 与平台行同一条 max-width：桌面端两行等宽，读起来是一栏而不是散开的几块 */
    width: 100%;
    max-width: 400px;
    margin: 0 auto;
    padding: var(--dlg-space-3);
    background: var(--dlg-hover);
    border: none;
    border-radius: var(--dlg-radius-sm);
    cursor: pointer;
    text-align: left;
    -webkit-tap-highlight-color: transparent;
}

.dlg-share-primary-thumb {
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
    border-radius: var(--dlg-radius-sm);
    object-fit: cover;
    object-position: center center;
    background: var(--dlg-surface);
    display: block;
}

.dlg-share-primary-text {
    flex: 1 1 auto;
    /* min-width:0 必须有 —— 否则 320px 下长文案会把卡片撑破而不是出省略号 */
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.dlg-share-primary-title {
    font-size: var(--dlg-font-body);
    font-weight: 500;
    color: var(--dlg-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dlg-share-primary-sub {
    font-size: var(--dlg-font-caption);
    color: var(--dlg-text-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (hover: hover) {
    .dlg-share-primary:hover {
        background: var(--dlg-pressed);
    }
}

/* P33/P36：这个高度分支现在只服务横屏。
   三个抽屉页面显式声明了 `interactive-widget=resizes-content`，旧浏览器/旧 WebView
   则由 dialog.js 的 `visualViewport` 回退写入 `html.dlg-keyboard-active`；因此键盘态
   不再依赖这里的布局高度媒体查询。下面这一档保留给真正的短屏横屏场景。 */

/* 高度压缩（横屏）：面板上限 = 视口 × 85%，≈300px 视口下只剩 255px，
   而标准态面板 264px —— 缺 9px。**只隐藏副文案没用**：卡片高度由 56px 缩略图撑着、
   不是文字，所以必须连缩略图一起收。收后卡片 60px、面板 244px，余 11px。
   只随高度变、不随宽度变（P8–P10 立的规矩：面板内不出现宽度断点的密度漂移）。 */
@media (max-height: 480px) {
    .dlg-share-primary {
        padding: var(--dlg-space-2) var(--dlg-space-3);
    }

    .dlg-share-primary-thumb {
        width: 44px;
        height: 44px;
    }

    .dlg-share-primary-sub {
        display: none;
    }
}

/* ---- ③ 微信扫码的二维码块（P14）----
   ⚠️ **放在正常流里**（图标行下方），不用绝对定位。三个理由都是响应式上的：
     ① 绝对定位的浮层要自己算「窄屏不横向溢出」+「短屏不顶出面板上沿」两套边界；
        正常流天然不会溢出 —— 超出就由 .dlg-body 自己滚（它已带 touch-action: pan-y）；
     ② 短屏/横屏下它把后面的内容往下推，用户滚动即可看到，不会遮住图标行；
     ③ 不新增滚动容器，就少一处要配 touch-action 的地方（skill §一 硬性要求）。
   `.dlg-pop` 只提供圆角/白底/阴影/边框，**不含任何定位声明**，所以当一张流内卡片用正好。 */
.dlg-share-qr {
    margin-top: var(--dlg-space-2);
    padding: var(--dlg-space-3);
    text-align: center;
    flex: 0 0 auto;
}

.dlg-share-qr[hidden] {
    display: none;
}

.dlg-share-qr-img {
    display: block;
    width: 100%;
    /* ⚠️ max-width 是**必须的**，不是保险：/entries/<id>/qrcode/ 返回的 PNG
       尺寸**不固定** —— 它是 (二维码模块数 + 2×边框) × box_size(8)，随 URL 长度变化，
       且接口**不接受 `?size=` 参数**。深链算下来约 200–280px，
       而 320px 视口下面板内容宽只有 288px —— 不封顶就会把面板撑破。
       180 ≤ 320 档的 288 − 2×12(padding) = 264 ✓ */
    max-width: 180px;
    height: auto;          /* 宽高比靠它保持，不写死高 */
    margin: 0 auto;
    border-radius: var(--dlg-radius-sm);
}

.dlg-share-qr-tip {
    margin: var(--dlg-space-2) 0 0;
    font-size: var(--dlg-font-caption);
    color: var(--dlg-text-dim);
}

/* ==========================================================================
   高度压缩 / 动效偏好 / 表单字号
   （responsive-layout skill §2.2 的高度断点与 §一 的硬性要求）
   ========================================================================== */

@media (max-height: 480px) {
    /* 横屏：固定高度抽屉改为自适应，优先保证内容与输入框可见。 */
    .dlg-panel--fill {
        height: auto;
        min-height: 0;
        max-height: 92%;
    }

    .dlg-panel input,
    .dlg-panel textarea {
        max-height: 30%;
    }

    /* 键盘弹起 / 极短视口下防止浮层溢出可视区。原先主站 .comment-emoji-panel 与沉浸版
       .immersive-comment-emoji-panel 各写了一份**逐值相同**的规则，P10 起由 .dlg-pop 承担
       （两个 emoji 浮层都已挂这个类）。

       ⚠️ 必须是 `.dlg-panel .dlg-pop`（0,2,0）而不是 `.dlg-pop`（0,1,0）—— 这是本轮
       第三次遇到同一个陷阱的不同形态：**基础规则写在 dialog.css、而 dialog.css 在页面 CSS
       之前加载**，同特异性下永远是页面规则赢。两个浮层的定位都写在页面 CSS 里且各自带
       `bottom: 36px`，用单类选择器写的覆盖会**静默失效一半**（实测：max-height 与
       overflow-y 生效了、bottom 仍是 36px，因为页面规则没声明后两者）。两者都在面板内，
       故加一层 `.dlg-panel` 前缀即可，与 `.dlg-scrim .dlg-panel input` 同一手法。 */
    .dlg-panel .dlg-pop {
        bottom: 4px;
        max-height: 140px;
        overflow-y: auto;
    }
}

@media (prefers-reduced-motion: reduce) {
    .dlg-scrim,
    .dlg-panel,
    .dlg-close {
        transition-duration: 0.01ms !important;
    }

    /* 仍然贴底，只是不再从下方滑入 —— 不能退化成居中 */
    .dlg-panel {
        transform: none !important;
    }
}

/* 🔴 P60：**`<button>` 不继承 `font-family`** —— UA 的 `font: 400 13.3333px Arial` 简写直接
   定了它，祖先上写什么都没用。实测（探针 round20 遍历三个面的每个 `<button>`）：
   **主站 34/38、访客页 34/44、本人页 92/103** 的按钮都渲染在 **Arial** 上 ✗
   —— 中文因为回落到同一个中文字体而看不出来（P57 实测过），**拉丁字母与数字会露馅**
   （如分享面板的「QQ」、卡片角上的赞数、通知行的文本）。
   ⇒ 在这里兜一层：**一行修掉整族**。之所以一直没做，是因为项目三次都只修了当时被看到的那一个
      （P56 顶栏标签、P57 退出按钮、P60 关注按钮 —— 这个坑在 `profile.css` 里被记过三次）。
   ⚠️ **只声明 family 一项**：字号与字重各有各的档，不能在这里一起重置
      （若用 `font: inherit` 会连 line-height 一起改 ⇒ 有改变盒子高度的风险）。
   ⚠️ **特异性 (0,0,1) 是最低的一档，而且本表最先加载** ⇒ 任何更具体的规则照常赢 ✓
      （这正是它能当「兜底」的原因；下面那条 iOS 字号规则抬高特异性是同一个道理的反面教材）。
   ⚠️ 与下面那条「面板内控件恒 16px」**不冲突**：那条讲的是**字号**（防 iOS 聚焦放大，
      而 `<button>` 不触发它，所以它故意不含 button）；本条只管**字体**。
   ⚠️ **这一行是承重的**：删掉它，上面那 160 个按钮立刻回到 Arial。
      守卫见 `FollowButtonStyleContractTests.test_button_family_reset_is_present`。 */
button {
    font-family: inherit;
}

/* 表单控件字号：全宽统一 16px（防 iOS 聚焦自动放大，同时是字号阶梯的输入档）。
   ⚠️ 必须用 .dlg-scrim 把特异性抬到 0,2,1 —— 各面板自己的 `X input{…}` 是 0,1,1，而页面 CSS
      在 dialog.css **之后**加载，同特异性下由源码顺序决出胜负。
      这正是这条规则此前挂在 @media (max-width:650px) 里却**从未生效**的原因：它被
      .comment-editor textarea(14px) / .edit-profile-content input(0.95em) 等 6 条逐条压掉，
      所谓「≤650px 防 iOS 聚焦缩放」一直是个空壳。抬高特异性后它才真的赢，
      各面板里那几条互相打架的控件字号随之删除，token 成为唯一真相源。 */
.dlg-scrim .dlg-panel input,
.dlg-scrim .dlg-panel textarea,
.dlg-scrim .dlg-panel select {
    font-size: var(--dlg-font-input);
}
