/* 海博社 X 主题 tokens：颜色、字号、间距、圆角只在本段定义 */
:root {
    --light-bg: #FFFFFF;
    --light-bg-elev: #FFFFFF;
    --light-ink: #0F1419;
    --light-ink-2: #536471;
    --light-line: #EFF3F4;
    --light-field: #EFF3F4;
    --light-hover: #F7F9F9;
    --light-page: #FFFFFF;
    --light-header: rgba(255, 255, 255, 0.88);
    --light-nav-hover: rgba(15, 20, 25, 0.07);
    --light-like: #F91880;
    --dark-bg: #000000;
    --dark-bg-elev: #16181C;
    --dark-ink: #E7E9EA;
    --dark-ink-2: #71767B;
    --dark-line: #2F3336;
    --dark-field: #202327;
    --dark-hover: #181818;
    --dark-page: #000000;
    --dark-header: rgba(0, 0, 0, 0.84);
    --dark-nav-hover: rgba(231, 233, 234, 0.10);
    --dark-like: #F91880;
    --blue: #1D9BF0;
    --blue-dark: #1A8CD8;
    --blue-soft-light: #E8F5FE;
    --blue-soft-dark: #031018;
    /* 174-F：海博日历搜索关键字高亮——卡面要求"浅黄底"，跟全站已有的
       --blue-soft（.hx-search-results mark 用的那份，全文搜索页专用）是
       两种不同场景各自的颜色，不复用同一个变量；写法照抄 --blue-soft 这套
       "light/dark 两份原始值 + 一个跟主题走的别名"三段式，保证深色模式下
       自动换成暗色版本，不用另外写 [data-theme="dark"] 覆盖规则。 */
    --yellow-soft-light: #FFF3C4;
    --yellow-soft-dark: #332B05;
    --brand-light: #CB0202;
    --brand-dark: #CB0202;
    --brand-soft-light: #FAE6E6;
    --brand-soft-dark: #140000;
    --white: #FFFFFF;
    --transparent: transparent;
    --scrim: rgba(0, 0, 0, 0.40);
    --media-overlay: rgba(0, 0, 0, 0.55);
    --shadow-light: 0 0 14px rgba(101, 119, 134, 0.20), 0 0 3px 1px rgba(101, 119, 134, 0.14);
    --shadow-dark: 0 0 14px rgba(255, 255, 255, 0.14), 0 0 3px 1px rgba(255, 255, 255, 0.10);
    --bg: var(--light-bg);
    --bg-elev: var(--light-bg-elev);
    --ink: var(--light-ink);
    --ink-2: var(--light-ink-2);
    --line: var(--light-line);
    --field: var(--light-field);
    --hover: var(--light-hover);
    --page: var(--light-page);
    --header-bg: var(--light-header);
    --nav-hover: var(--light-nav-hover);
    --blue-soft: var(--blue-soft-light);
    --yellow-soft: var(--yellow-soft-light);
    --brand: var(--brand-light);
    --brand-soft: var(--brand-soft-light);
    --like: var(--light-like);
    --shadow: var(--shadow-light);
    --font-scale: 1;
    --font-body: calc(16px * var(--font-scale));
    --font-meta: calc(13px * var(--font-scale));
    --font-small: calc(12px * var(--font-scale));
    --font-button: calc(14px * var(--font-scale));
    --font-tab: calc(17px * var(--font-scale));
    --font-nav: calc(19px * var(--font-scale));
    --font-heading: calc(20px * var(--font-scale));
    --font-article: calc(17px * var(--font-scale));
    --font-title: calc(27px * var(--font-scale));
    --font-display: calc(32px * var(--font-scale));
    /* 60-F (3)：字体方案——Inter 打头（自托管，见 typography/sans.hbs 的
       @font-face，拉丁子集 unicode-range，全站唯一自托管的网络字体），往后依次
       苹方、微软雅黑、Noto Sans SC / CJK SC、Noto Sans JP 接住各平台中日文
       glyph，最后兜底通用 sans-serif；全站不出现任何衬线体候选。 */
    --font-stack: "Inter", "PingFang SC", "Microsoft YaHei", "Noto Sans SC", "Noto Sans CJK SC", "Noto Sans JP", sans-serif;
    --font-serif-stack: Georgia, "Times New Roman", "Songti SC", serif;
    --font-mono-stack: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
    --composer-font-small: 13px;
    --composer-font-normal: 16px;
    --composer-font-large: 18px;
    --composer-font-heading: 22px;
    --line-height-body: 1.68;
    --font-weight-body: 400;
    --font-weight-heading: 600;
    --space-1: 2px;
    --space-2: 4px;
    --space-3: 6px;
    --space-4: 8px;
    --space-5: 10px;
    --space-6: 12px;
    --space-7: 14px;
    --space-8: 16px;
    --space-9: 18px;
    --space-10: 20px;
    --space-12: 24px;
    --space-14: 28px;
    --space-16: 32px;
    --space-20: 40px;
    --space-24: 48px;
    --space-28: 56px;
    --space-30: 60px;
    --space-32: 64px;
    --space-40: 80px;
    --radius-small: 4px;
    --radius-medium: 8px;
    --radius-logo: 10px;
    --radius-large: 12px;
    --radius-card: 16px;
    --radius-round: 999px;
    --avatar-small: 32px;
    --avatar: 40px;
    --avatar-large: 48px;
    --brand-mark-mobile: 36px;
    --icon-small: 18px;
    --icon: 22px;
    --icon-large: 26px;
    --button-height: 44px;
    --compose-size: 52px;
    --mobile-bar-height: 68px; /* 30-F：53→60→68px，logo 从 32px 加到 40px 再加到 48px（--avatar-large），两端各留跟原来差不多的余量（48+20）。全站高度计算（栏目页/搜索页/账户页三处 track、bookmarks-header、article-bar 等）都是吃这个变量算的，改这一处即可级联生效，不用逐处硬编码 */
    --feed-tab-height: 44px; /* 44-F：栏目条（.hx-feed-tab）原来跟顶栏共用
       --mobile-bar-height（68px），照 X 的比例改窄——第一轮定到 52px，第二轮再收到
       44px（正好跟 --button-height 相等）。44-F 第三轮曾经把桌面端单独退回
       --mobile-bar-height（晖哥当时说桌面不动），46-F 又明确要求两端一致改回来，
       现在 .hx-feed-tab 的基础规则（桌面+手机通用，见 line ~274）直接吃这个变量，
       不再按断点拆成两条规则。原来几处假设"顶栏和栏目条一样高"顺手写的 calc()
       （滚动隐藏的 translateY、账户页 track 高度）都在手机端媒体查询范围内，本来
       就只服务手机端，不受这次桌面端统一影响。 */
    --mobile-dock-height: 56px;
    --center-width: 600px;
    --left-width: 275px;
    --left-compact-width: 88px;
    --right-width: 350px;
    --modal-width: 430px;
    --preview-phone-width: 375px;
    --content-line: 36rem;
    --action-left-grow: 4;
    --action-spacer-grow: 1;
    --border-width: 1px;
    --focus-width: 2px;
    --tab-line-width: 4px;
    --motion-fast: 120ms;
    --motion-medium: 220ms;
}
/* tokens 结束 */

@media (prefers-color-scheme: dark) {
    :root[data-theme="system"] {
        --bg: var(--dark-bg); --bg-elev: var(--dark-bg-elev); --ink: var(--dark-ink); --ink-2: var(--dark-ink-2);
        --line: var(--dark-line); --field: var(--dark-field); --hover: var(--dark-hover); --page: var(--dark-page);
        --header-bg: var(--dark-header); --nav-hover: var(--dark-nav-hover); --blue-soft: var(--blue-soft-dark);
        --brand: var(--brand-dark); --brand-soft: var(--brand-soft-dark); --like: var(--dark-like); --shadow: var(--shadow-dark);
        --yellow-soft: var(--yellow-soft-dark);
    }
}
:root[data-theme="dark"] {
    --bg: var(--dark-bg); --bg-elev: var(--dark-bg-elev); --ink: var(--dark-ink); --ink-2: var(--dark-ink-2);
    --line: var(--dark-line); --field: var(--dark-field); --hover: var(--dark-hover); --page: var(--dark-page);
    --header-bg: var(--dark-header); --nav-hover: var(--dark-nav-hover); --blue-soft: var(--blue-soft-dark);
    --brand: var(--brand-dark); --brand-soft: var(--brand-soft-dark); --like: var(--dark-like); --shadow: var(--shadow-dark);
    --yellow-soft: var(--yellow-soft-dark);
}
:root[data-font-size="small"] { --font-scale: 0.93; }
:root[data-font-size="large"] { --font-scale: 1.13; }

*, *::before, *::after { box-sizing: border-box; }
html { color-scheme: light dark; }
body {
    margin: 0;
    background: var(--page);
    color: var(--ink);
    font-family: var(--font-stack);
    font-size: var(--font-body);
    line-height: var(--line-height-body);
    font-weight: var(--font-weight-body);
    letter-spacing: 0;
    -webkit-font-smoothing: antialiased;
}
/* 60-F (3)：标题统一 600——只定这一条全局基线，已有更具体选择器设过更重
   字号（文章正文 H1 800、书签页 H1 800，都是早前几张卡刻意调过的显示层级）
   靠选择器优先级继续生效，不在这里覆盖。 */
h1, h2, h3, h4, h5, h6 { font-weight: var(--font-weight-heading); }
body.is-drawer-open { overflow: hidden; }
a { color: inherit; text-decoration: none; }
button, input, textarea, select { color: inherit; font: inherit; }
button { padding: 0; border: 0; background: var(--transparent); cursor: pointer; }
img, picture, video, canvas, svg { display: block; max-width: 100%; height: auto; }
figure { margin: 0; }
p, h1, h2, h3, h4, ul, ol { margin-top: 0; }
:focus-visible { outline: var(--focus-width) solid var(--blue); outline-offset: var(--space-1); }
/* 46-F 第三轮：改成 !important——这条重置本来就该是"不管别的规则写了什么
   display，hidden 属性说了算"，之前没加 !important 靠"源码顺序压过去"这种
   隐性约定，同优先级选择器谁写得晚谁赢，这个坑在 28/30/35/46-F 踩了四次
   （新加一条 display:xxx 忘了补 [hidden] 覆盖）。改 !important 之后这条规则
   赢面无条件成立，不用再逐处补覆盖——下面全文件那些 `.xxx[hidden] { display:
   none; }` 逐处覆盖规则现在是冗余的（留着不会错，!important 已经赢了，删不
   删是另一回事，这次没删，只加这一条）。 */
[hidden] { display: none !important; }
.hx-skip-link { position: fixed; z-index: 200; top: var(--space-4); left: var(--space-4); padding: var(--space-4) var(--space-8); border-radius: var(--radius-round); background: var(--ink); color: var(--bg); transform: translateY(-200%); }
.hx-skip-link:focus { transform: none; }

.hx-shell {
    display: grid;
    grid-template-columns: var(--left-width) var(--center-width) var(--right-width);
    justify-content: center;
    align-items: start;
    min-height: 100vh;
    margin: 0 auto;
    background: var(--bg);
}
.hx-shell[data-left-rail="closed"] { grid-template-columns: 0 var(--center-width) var(--right-width); }
.hx-shell[data-right-rail="closed"] { grid-template-columns: var(--left-width) var(--center-width) 0; }
.hx-shell[data-left-rail="closed"][data-right-rail="closed"] { grid-template-columns: 0 var(--center-width) 0; }
.hx-shell[data-left-rail="closed"] .hx-left-rail,
.hx-shell[data-right-rail="closed"] .hx-right-rail { display: none; }
.hx-center { grid-column: 2; width: var(--center-width); min-height: 100vh; }
.hx-main { min-height: 100vh; }

.hx-left-rail { grid-column: 1; position: sticky; top: 0; display: flex; flex-direction: column; height: 100vh; padding: 0 var(--space-6) var(--space-10) 0; overflow-y: auto; scrollbar-width: none; }
.hx-left-rail::-webkit-scrollbar { display: none; }
.hx-rail-tools { display: flex; justify-content: flex-end; padding: var(--space-4) var(--space-2) 0; }
.hx-resizer { position: absolute; z-index: 20; top: 0; bottom: 0; width: var(--space-5); cursor: col-resize; }
.hx-resizer::after { position: absolute; top: 0; bottom: 0; left: var(--space-2); width: var(--border-width); background: var(--transparent); content: ""; }
.hx-resizer:hover::after { background: var(--brand); }
.hx-resizer-left { right: 0; }
.hx-resizer-right { left: calc(-1 * var(--space-5)); }
body.is-resizing { cursor: col-resize; user-select: none; }
.hx-icon-button { display: grid; width: var(--avatar-small); height: var(--avatar-small); place-items: center; border-radius: 50%; color: var(--ink-2); }
.hx-icon-button:hover { color: var(--ink); background: var(--nav-hover); }
.hx-icon-button svg { width: var(--icon-small); }
/* 2026-11-05：标语「看懂机会·跟上交易」取消（手机端/网页端都去掉）——
   .hx-brand-slogan 这条规则连同它的渲染分支（brand.hbs）一起删掉；
   .hx-brand 的 padding-top 原来专门给 slogan 腾出来的那段
   calc(var(--font-meta)*1.25 + var(--space-2))（172-F 加的）改回跟
   padding-bottom 一样的 var(--space-4)，恢复没有 slogan 时的间距；
   overflow 保留 visible 不动——161-F 当时改成 visible 只是为了不裁掉
   slogan，现在改回 hidden 没有实际收益，也没有已知副作用，不值得为了
   "复原到字面一致"去动它，留着无害。 */
.hx-brand { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-4) var(--space-6); overflow: visible; color: var(--ink); font-weight: 700; white-space: nowrap; }
.hx-brand-row { display: flex; align-items: center; gap: var(--space-4); }
.hx-brand-text { font-size: var(--font-meta); }
.hx-brand-mark-wrap { position: relative; flex: none; }
.hx-brand-mark { display: grid; flex: none; width: var(--avatar); height: var(--avatar); overflow: hidden; place-items: center; border-radius: var(--radius-round); background: var(--brand); color: var(--white); }
.hx-brand-mark svg { width: auto; height: 60%; }
.hx-nav-list { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1); margin-top: var(--space-1); }
.hx-nav-item, .hx-nav-list .hx-account-nav-item { display: flex; align-items: center; gap: var(--space-8); padding: var(--space-6); border-radius: var(--radius-round); font-size: var(--font-nav); transition: background var(--motion-fast); }
.hx-nav-item:hover, .hx-nav-list .hx-account-nav-item:hover { background: var(--nav-hover); }
.hx-nav-item[aria-current="page"], .hx-nav-list .hx-account-nav-item[aria-current="page"] { font-weight: 700; }
.hx-nav-item svg, .hx-nav-list .hx-account-nav-item svg { width: var(--icon-large); height: var(--icon-large); }
.hx-compose { display: grid; width: 90%; height: var(--compose-size); margin-top: var(--space-8); place-items: center; border-radius: var(--radius-round); background: var(--brand); color: var(--white); font-size: var(--font-tab); font-weight: 700; }
.hx-compose[hidden] { display: none; }
.hx-compose:hover, .hx-primary-button:hover { background: var(--brand); }
.hx-compose svg { display: none; width: var(--icon); }
.hx-account { display: flex; align-items: center; gap: var(--space-6); margin-top: auto; padding: var(--space-6); border-radius: var(--radius-round); }
.hx-account:hover { background: var(--nav-hover); }
.hx-account-avatar { display: grid; flex: none; width: var(--avatar); height: var(--avatar); overflow: hidden; place-items: center; border-radius: var(--radius-logo); background: var(--field); color: var(--ink-2); }
.hx-account-avatar img { width: 100%; height: 100%; object-fit: cover; }
.hx-account-copy { min-width: 0; }
.hx-account-copy strong, .hx-account-copy small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hx-account-copy strong { font-weight: var(--font-weight-heading); }
.hx-account-copy small { color: var(--ink-2); font-size: var(--font-meta); }
.hx-google-signin { display: flex; align-items: center; justify-content: center; margin: var(--space-2) var(--space-6) 0; padding: var(--space-4); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; text-align: center; }
.hx-google-signin:hover { background: var(--hover); color: var(--ink); }
.hx-google-signin:disabled { opacity: .5; cursor: not-allowed; }
.hx-google-signin:disabled:hover { background: var(--transparent); color: var(--ink-2); }
.hx-login-options { display: flex; flex-direction: column; gap: var(--space-4); }
.hx-login-options .hx-google-signin { margin: 0; }
/* 18-F 手机号登录：跟邮箱那一项一样就地展开，不新开页面，复用 .hx-search-input-wrap
   的输入框样式（圆角描边+聚焦变色），保持视觉一致 */
.hx-sms-login { display: flex; flex-direction: column; gap: var(--space-6); margin-top: var(--space-4); }
.hx-sms-field { display: flex; flex-direction: column; gap: var(--space-3); font-size: var(--font-meta); color: var(--ink-2); }
.hx-sms-input-wrap { display: flex; height: var(--button-height); padding: 0 var(--space-8); align-items: center; border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); }
.hx-sms-input-wrap:focus-within { border-color: var(--brand); background: var(--bg); }
.hx-sms-input-wrap input { width: 100%; border: 0; outline: 0; background: var(--transparent); color: var(--ink); font-size: var(--font-body); }
.hx-sms-input-wrap input::placeholder { color: var(--ink-2); }
.hx-sms-status { margin: 0; color: var(--ink-2); font-size: var(--font-meta); }
.hx-sms-status.is-error { color: var(--brand); }
.hx-sms-back { color: var(--ink-2); font-size: var(--font-meta); text-align: center; }
.hx-sms-back:hover { color: var(--ink); }

.hx-right-rail { grid-column: 3; position: relative; display: flex; flex: none; flex-direction: column; gap: var(--space-8); min-width: 0; padding: 0 0 var(--space-20) var(--space-16); }
.hx-right-tools { justify-content: flex-start; padding-left: 0; }
.hx-search-box { position: sticky; z-index: 10; top: var(--space-3); display: flex; align-items: center; gap: var(--space-6); width: 100%; height: var(--button-height); padding: 0 var(--space-8); border: var(--border-width) solid var(--transparent); border-radius: var(--radius-round); background: var(--field); color: var(--ink-2); }
.hx-search-box[hidden] { display: none; } /* 31-F：这个框现在要在搜索页用 hidden 隐藏，跟 [hidden] 全局重置同优先级源码顺序会输，28-F 也踩过同一个坑，这里提前补上 */
.hx-search-box:hover { border-color: var(--blue); background: var(--bg); color: var(--blue); }
.hx-search-box button { flex: none; color: inherit; }
.hx-search-box svg { width: var(--icon-small); }
.hx-search-box input { min-width: 0; flex: 1; border: 0; outline: 0; background: var(--transparent); }
.hx-search-box input::placeholder { color: var(--ink-2); }
.hx-side-card, .hx-subscribe-card { overflow: hidden; border: var(--border-width) solid var(--line); border-radius: var(--radius-card); transition: box-shadow var(--motion-fast); }
.hx-side-card:hover, .hx-subscribe-card:hover { box-shadow: var(--shadow); }
.hx-side-card h2, .hx-subscribe-card h2 { margin: 0; padding: var(--space-6) var(--space-8); font-size: var(--font-heading); font-weight: 800; line-height: 1.3; }
.hx-side-placeholder { margin: 0; padding: var(--space-6) var(--space-8) var(--space-8); color: var(--ink-2); }
/* 两列网格，不是竖排单列列表——每格一个专题，行高跟 .hx-side-row 一致方便
   点按，超长标题单行省略号（5 个专题名称目前都不长，省略号只是兜底）。
   2026-10-28：卡片不再有标题，网格是卡片的第一个子元素。 */
.hx-topics-grid { display: grid; grid-template-columns: 1fr 1fr; }
.hx-topics-grid-link { display: block; overflow: hidden; padding: var(--space-6) var(--space-8); color: var(--ink); font-size: var(--font-tab); white-space: nowrap; text-overflow: ellipsis; }
.hx-topics-grid-link:hover { background: var(--hover); }
/* 2026-10-28：卡片底部「登录免费查看」——跟 .hx-product-card small（「点击
   开通，了解更多」「已开通」那一行）同一套字号字重，不是新定义一套蓝色
   链接样式。 */
.hx-topics-login-hint { margin: 0; padding: var(--space-2) var(--space-8) var(--space-6); color: var(--blue); font-size: var(--font-meta); font-weight: 600; }
.hx-side-row { display: block; padding: var(--space-6) var(--space-8); }
.hx-side-row[hidden] { display: none; } /* 28-F：「清空全部」按钮现在要用 hidden 切态，.hx-side-row 自己定了 display，跟全局 [hidden] 同优先级源码顺序会输，这里补一条压住 */
.hx-side-row:hover { background: var(--hover); }
.hx-side-row small, .hx-side-row strong { display: block; }
.hx-side-row small { color: var(--ink-2); font-size: var(--font-meta); }
.hx-side-row strong { margin-top: var(--space-1); }
/* 65-F (7)：右栏产品卡三行——产品名/一句话介绍/CTA，跟上面两行版（价格在前、
   名字在后）的 .hx-side-row small/strong 顺序相反，得单独定一套间距，不能指望
   上面那条通用规则（strong margin-top 是给"先 small 后 strong"的旧顺序留的，
   直接沿用会让现在排第一行的产品名平白多一段顶部间距）。 */
.hx-product-card strong { display: block; margin-top: 0; }
.hx-product-card span { display: block; margin-top: var(--space-1); color: var(--ink-2); font-size: var(--font-meta); line-height: 1.4; }
.hx-product-card small { display: block; margin-top: var(--space-2); color: var(--blue); font-size: var(--font-meta); font-weight: 600; }
.hx-subscribe-card { padding: var(--space-8); }
.hx-subscribe-card h2 { padding: var(--space-3) 0 var(--space-2); font-size: var(--font-nav); }
.hx-subscribe-card p { margin: 0 0 var(--space-6); color: var(--ink-2); font-size: var(--font-button); line-height: 1.45; }
[data-product-card-tagline],
[data-product-intro-tagline],
[data-paywall-tagline] { white-space: pre-line; }
/* 右栏产品卡介绍保留真实换行，最多展示三行；产品页介绍和付费墙不截断。 */
.hx-product-card [data-product-card-tagline] { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 3; }
.hx-subscribe-card strong { display: block; margin-bottom: var(--space-6); }
.hx-subscribe-label { color: var(--brand); font-size: var(--font-meta); font-weight: 700; }
.hx-primary-button { display: grid; min-height: var(--button-height); padding: var(--space-4) var(--space-8); place-items: center; border-radius: var(--radius-round); background: var(--brand); color: var(--white); font-weight: 700; }
/* 63-F (5)：订阅产品页「即将开放」按钮——disabled 属性本身已经挡住点击/键盘
   激活，这里只补视觉，之前只有 [data-composer-panel] 范围内有这条規則，产品
   介绍卡不在那个范围内，需要一条不限定容器的通用规则。 */
.hx-primary-button:disabled { opacity: .5; cursor: not-allowed; }
/* 149-F：loadProductCategories() 给配置里查不到（等价于"隐藏"）的产品 tab
   置 hidden——.hx-feed-tab 自己那条基础规则是 display:flex（作者样式表，
   优先级压过浏览器 UA 样式表默认的 [hidden]{display:none}），不额外补一条
   才会真的不显示，照抄 .hx-feed-tab-arrow[hidden] 已经在用的同一个手法，
   只限定在 .hx-product-tabs 范围内，不影响首页栏目/日历分类/搜索排序等
   其它同样用 .hx-feed-tab 的地方。 */
.hx-product-tabs .hx-feed-tab[hidden] { display: none; }
/* 56-F：产品页顶部——.hx-subscribe-card 原来只活在右栏那个 flex 列（gap 由
   .hx-right-rail 自己的 gap 撑开），搬进 .hx-main 正文流之后要自己补外边距，
   不然贴着上面的产品 tab 和下面的文章列表。已开通态是个不可点的静态徽章，
   照 .hx-primary-button 的形状但换成中性配色，跟旁边还能点的「开通」区分开。 */
.hx-product-intro { margin: var(--space-8); }
.hx-subscribe-embedded-feeds { min-width: 0; margin-top: var(--space-8); }
.hx-subscribe-feed-panel[hidden] { display: none; }
/* .hx-primary-button 是 display:grid + place-items:center，只吃得下一个孩子——
   这个徽章图标+文字两个孩子，照 .hx-mobile-subscribe.is-subscribed（原来手机
   顶栏订阅按钮那份，见 navigation.hbs 56-F 注释，已删）的路数改回 inline-flex。 */
.hx-product-subscribed { display: inline-flex; align-items: center; gap: var(--space-2); background: var(--field); color: var(--ink-2); }
.hx-product-subscribed svg { width: var(--icon-small); }
/* 73-F (B)：产品介绍卡标题行——产品名左边，管理员才看得到的「编辑」按钮靠右。 */
.hx-product-intro-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
/* 73-F (B)：产品编辑弹窗表单——跟日历录入表单（.hx-calendar-entry-row 那组）同一套
   胶囊输入框视觉语言，直接照抄尺寸变量，不新起一套。 */
.hx-product-edit-form { display: flex; flex-direction: column; gap: var(--space-6); margin-top: var(--space-6); }
.hx-product-edit-input { width: 100%; height: var(--button-height); padding: 0 var(--space-8); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); color: var(--ink); font-size: var(--font-body); }
.hx-product-edit-description { display: grid; gap: var(--space-2); }
.hx-product-edit-textarea { height: auto; padding: var(--space-6) var(--space-8); border-radius: var(--radius-card); line-height: 1.45; resize: vertical; }
.hx-product-edit-count { justify-self: end; color: var(--ink-2); font-size: var(--font-small); }
.hx-product-edit-input:focus { outline: 0; border-color: var(--brand); background: var(--bg); }
.hx-product-edit-plan-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-6); padding: var(--space-6) 0; border-top: var(--border-width) solid var(--line); }
.hx-product-edit-plan-row strong { flex: 1 1 100%; }
.hx-product-edit-input-narrow { width: auto; flex: 1 1 120px; }
.hx-product-edit-active-toggle { display: flex; align-items: center; gap: var(--space-2); font-size: var(--font-meta); color: var(--ink-2); }
.hx-footer { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-6); padding: 0 var(--space-2) var(--space-20); color: var(--ink-2); font-size: var(--font-meta); line-height: 1.7; }
.hx-footer a:hover { text-decoration: underline; }
.hx-footer span { width: 100%; }
.hx-site-account { display: flex; align-items: center; gap: var(--space-6); padding: var(--space-6) var(--space-8); }
.hx-site-account:hover { background: var(--hover); }
.hx-site-account img { flex: none; width: var(--avatar); height: var(--avatar); border-radius: var(--radius-logo); object-fit: cover; }
.hx-site-account span { min-width: 0; flex: 1; }
.hx-site-account strong, .hx-site-account small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hx-site-account small { color: var(--ink-2); font-size: var(--font-meta); }
.hx-site-account b { flex: none; padding: var(--space-3) var(--space-8); border-radius: var(--radius-round); background: var(--ink); color: var(--bg); font-size: var(--font-button); }

/* 61-F (3)：「+」按钮用 --space-24（48px）比栏目条自己的 --feed-tab-height
   （44px）高，默认 align-items 是 stretch——.hx-feed-tab/.hx-feed-tab-custom
   有自己的固定 height，stretch 对它们不生效，会退化成 flex-start（顶部对齐），
   而 .hx-feed-tab-add-wrap 没设 height，stretch 照样把它撑满整条（现在被「+」
   拉高的）49px，两边基准不一样，垂直中心天然对不齐（实测偏差 2px）。加
   align-items:center 让两边都按各自的高度在同一条线上居中，「+」和栏目文字的
   中心就重合了。.hx-feed-tabs 这个类还被筛选栏/账户页标签等复用（36-F），
   那些场景里所有子项高度本来就一致，stretch 和 center 结果一样，不会有副作用。 */
/* 63-F (14)：桌面端原来 overflow:visible——栏目多起来（加了几个读者分类）会
   直接顶到右栏底下或者换行，不好看也点不到。改成 overflow-x:auto + 隐藏原生
   滚动条，配 JS 现插的两个箭头按钮（ensureFeedTabArrows）控制滚动，不用鼠标
   拖滚动条那套。手机端本来就是 overflow-x:auto 横滑查看（700px 媒体查询那条，
   见下方），跟这条基础规则改成一样的值之后，两端现在共用同一条 overflow 规则，
   媒体查询里原来重复的那句可以留着也不冲突（scrollbar-width 那条还只在移动端
   生效，桌面这里单独补一份，箭头本来就是给还没到手机断点的场景用的，两条
   规则各管各的宽度范围，没有互相覆盖）。 */
.hx-feed-tabs { position: sticky; z-index: 30; top: 0; display: flex; align-items: center; overflow-x: auto; scrollbar-width: none; border-bottom: var(--border-width) solid var(--line); background: var(--header-bg); backdrop-filter: blur(var(--space-6)); }
.hx-feed-tabs::-webkit-scrollbar { display: none; }
/* 日历与明日走势共用的页面级切换栏，桌面与手机保持同一套 Tab 形态。 */
.hx-insight-tabs { display: flex; position: sticky; z-index: 31; top: 0; border-bottom: var(--border-width) solid var(--line); background: var(--header-bg); backdrop-filter: blur(var(--space-6)); }
.hx-insight-tab { display: grid; flex: 1 1 0; min-height: var(--feed-tab-height); place-items: center; padding: 0 var(--space-6); color: var(--ink-2); font-size: var(--font-tab); white-space: nowrap; }
.hx-insight-tab.is-active { color: var(--ink); font-weight: 700; box-shadow: inset 0 calc(-1 * var(--border-width) * 2) var(--brand); }
/* 桌面端仅保留频道 Tab，日历与走势通过左侧一级菜单切换。 */
@media (min-width: 701px) { .hx-insight-tabs { display: none; } }
/* 频道栏三枚固定 Tab 居中，基础 .hx-feed-tabs 仍保留给其它标签栏使用。 */
/* card-feed-tabs-scroll-stuck：置顶专题之后这条栏目条可能超过 3 格，上面
   437 行那条旧注释"只有3个短标签用不到横向滚动"已经不成立——真机反馈（小米
   MIUI 浏览器壳子包的 Chrome 135 Mobile WebView，Android 16）划到底之后
   滑不回最左边，touch-action 实测在这台设备上是 auto（没有被覆盖），
   defaultPrevented 全程 false（没有监听器拦）、代码层面排查不到"谁拦住了"，
   照安卓 WebView 壳子类浏览器已知的 overflow-x:auto 横向触摸滚动不稳定这个
   老问题处理：显式声明 touch-action: pan-x（不依赖继承下来的 auto，部分
   WebView 内核对继承值的处理不如显式声明可靠）+ -webkit-overflow-scrolling:
   touch（日历笔记那次类似滚动问题验证过这条对 Android/iOS 有效）。 */
.hx-feed-tabs-channels { justify-content: center; touch-action: pan-x; -webkit-overflow-scrolling: touch; }
.hx-feed-tabs-channels > .hx-feed-tab[data-channel-key="discovery"] { flex: 0 0 calc(4 * var(--font-tab) + 2 * var(--space-10)); justify-content: center; }
/* 桌面端专用的滚动箭头——JS（ensureFeedTabArrows）判断真的溢出才摘 hidden，
   700px 以下断点强制不显示（手机端用原生横滑，不需要箭头，见下方媒体查询）。
   position:sticky 插在 .hx-feed-tabs 这个 flex 容器的第一个/最后一个孩子位置，
   跟着可视区域左右两边贴住、不随中间栏目一起滚走——这是 sticky 在横向滚动
   容器里的标准用法，不需要额外一层包裹容器，也不会打乱 .hx-feed-tab 这批
   既有子元素之间的先后关系（createSwipeTrack 的 tabSelector 只认
   .hx-feed-tab，这两个按钮用的是 .hx-feed-tab-arrow，不会被误当成第 4/5 个
   虚拟面板，没有碰手势 JS 依赖的那份选择器）。 */
.hx-feed-tab-arrow { position: sticky; z-index: 20; display: grid; flex: none; width: var(--space-24); height: var(--space-24); place-items: center; border-radius: var(--radius-round); background: var(--bg-elev); color: var(--ink); box-shadow: var(--shadow); }
.hx-feed-tab-arrow[hidden] { display: none; }
.hx-feed-tab-arrow-left { left: var(--space-2); }
/* 2026-11-03（推特网页版还原，二轮修正）：右箭头跟"+"号要挨在一起、箭头在左
   "+"在右——之前右偏移量写反了（箭头贴着真正的右边缘，"+"反而在它左边，
   视觉上变成"+号在左、箭头在右"，跟董哥要的顺序正好相反）。这里把两个的
   right 偏移对调：箭头让位到"+"宽度+间距之外，"+"接管贴边缘的位置，中间
   留的 --space-2（4px）间距不变，只是归属换了边——两者都还是
   position:sticky、都还是 .hx-feed-tabs 的直接子元素，没有挪出容器，只是
   视觉顺序调整。 */
.hx-feed-tab-arrow-right { right: calc(var(--space-24) + var(--space-2) * 2); margin-left: auto; }
.hx-feed-tab-arrow svg { width: var(--icon-small); fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.hx-feed-tab-arrow-right svg { transform: scaleX(-1); }
/* 21-F：颜色用 color-mix 按 --mix（0%~100%）在未选中/选中两色之间插值，JS 只管算百分比，
   不用手动解析/混合 rgb，深色模式下 --ink/--ink-2 换了值这里也跟着对。 */
/* 46-F：44-F 第三轮曾经把桌面端高度退回 --mobile-bar-height（晖哥当时定的是桌面
   不动），这轮明确要求两端一致改回 --feed-tab-height（44px）——不用再按断点拆
   两条规则，下面 700px 媒体查询里原来那条覆盖（改到一样的值）删掉了，改这一处
   两端跟着变。gap 是给「海博机会」那个 <a> 里文字和 chevron 图标之间留的间距
   （4px），对只有一个文字子节点的其它 tab 没有实际影响（没有第二个 flex item，
   gap 无处可加）。 */
.hx-feed-tab { position: relative; display: flex; flex: 0 0 auto; align-items: center; gap: var(--space-2); height: var(--feed-tab-height); padding: 0 var(--space-10); border-bottom: var(--tab-line-width) solid var(--transparent); --mix: 0%; color: color-mix(in srgb, var(--ink) var(--mix), var(--ink-2)); font-size: var(--font-tab); font-weight: 400; }
.hx-feed-tab:hover { background: var(--hover); }
.hx-feed-tab.is-active { font-weight: 700; }
/* 下划线：JS 用 transform/width 实时对着 track 的 scrollLeft 跟手位移，替代原来
   is-active 那道固定的 border-bottom-color，能在两个栏目之间平滑过渡宽度 */
.hx-feed-tab-underline { position: absolute; left: 0; bottom: 0; height: var(--tab-line-width); background: var(--brand); pointer-events: none; }
/* 60-F (9)：读者自己加的分类——视觉上抄 .hx-feed-tab 的形状（高度/内边距/字号/
   悬停色），但故意不共用这个类名（见 feed-tabs.hbs/hyboox-x.js 60-F 注释：
   避免被 createSwipeTrack 默认 tabSelector '.hx-feed-tab' 捞进横滑下划线那套
   逻辑）。 */
/* card-topics-sidebar：栏目条「+」读者自定义分类，桌面端（≥1081px）已经
   改成右栏 .hx-topics-card（见那边 CSS），这里恢复的是手机网页端（右栏
   整个隐藏，没有常驻卡片可看）专用的版本——2026-10-28 董哥要求首页在
   手机宽度上保留这份入口，标签页/交易日历不受影响（那两处 HBS 没传
   mobilePlus，节点本身不会渲染，这里的 CSS 不用额外排除）。
   63-F (13)：长按拖动排序——跟 .hx-event-draggable（50-F 日历那份）同一套处理，
   -webkit-touch-callout/user-select 关掉手机浏览器自己那套"长按链接弹出复制/
   新标签页"系统菜单，不关的话会跟这里自己的长按语义打架；.is-dragging 是真正
   进入拖动态才加的（wireCustomChannelDragReorder，hyboox-x.js），之前保持
   touch-action 默认值，手机端横向 overflow-x:auto 的原生滚动不受影响。 */
.hx-feed-tab-custom { position: relative; display: flex; flex: 0 0 auto; align-items: center; height: var(--feed-tab-height); -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; cursor: grab; }
.hx-feed-tab-custom.is-dragging { touch-action: none; z-index: 5; opacity: .6; box-shadow: var(--shadow); cursor: grabbing; }
/* 64-F (7)：-webkit-user-drag:none 是 draggable="false"（hyboox-x.js
   buildCustomChannelTab）在 CSS 层的同款兜底，双重关掉原生链接拖拽，别让它
   抢在长按排序自己那套 pointermove 逻辑前面。 */
/* 2026-11-03（推特网页版还原）：顶栏"×"删除按钮删掉之后，右边不用再给它
   留位置，padding 两侧对称改回跟左边一样的 --space-10；补 gap 给以后接入的
   专题图标（跟 .hx-feed-tab 对齐同一套 icon+文字布局）用，图标还没接的分类
   这个 gap 不影响单独文字的间距（flex 只有一个子元素时 gap 不生效）。 */
/* card-feed-tabs-scroll-stuck：真机卡住那次 tgt= 落的就是这个 <a> 本身——
   -webkit-user-drag:none 父级 .hx-feed-tab-custom 已经有 -webkit-touch-callout，
   但部分安卓 WebView 壳子浏览器对继承值处理不稳定，这里在触摸真正命中的
   元素上显式补一份 -webkit-touch-callout: none，排除链接自身默认的长按/拖拽
   行为跟顶栏横向滚动抢手势这个可能性，跟 touch-action 那条一样是追加低风险
   CSS，不改 JS。 */
.hx-feed-tab-custom-link { display: flex; align-items: center; gap: var(--space-2); height: 100%; padding: 0 var(--space-10); color: var(--ink-2); font-size: var(--font-tab); font-weight: 400; white-space: nowrap; -webkit-user-drag: none; -webkit-touch-callout: none; }
/* 63-F (12)：读者自己加的分类当前正好在这一页时高亮——不跟 .hx-feed-tab.is-active
   那套 color-mix 动画（服务于横滑切栏目下划线跟手过渡，读者分类不接那套 track，
   用不上），直接换色换字重，够用。 */
.hx-feed-tab-custom-link.is-active { color: var(--ink); font-weight: 700; }
.hx-feed-tab-custom:hover { background: var(--hover); }
/* 2026-11-04：顶栏专题图标（.hx-feed-tab-icon 那套规则）整个去掉——JS 侧
   buildCustomChannelTab 已经不再创建这个 span，没有对应 DOM 了。编辑层列表
   那份图标（.hx-plus-editor-row-icon）在下面单独删，见该处注释。 */
/* 2026-10-28：这个按钮最早只在手机网页端（<1081px）显示——≥1081px 那个
   宽度右栏 .hx-topics-card 已经露出来了，当时判断不需要这份入口跟它重复。
   2026-11-03：按钮不再自带包一层 .hx-feed-tab-add-wrap（下拉面板改成全局
   编辑层 .hx-plus-editor，见下面该类自己的注释），display 切换直接挂在
   按钮本身上，原来 -wrap/-panel/-list/-empty 这几条跟着面板 DOM 一起删的
   规则一并删掉，不留死代码。
   2026-11-03 又补：桌面端也要接入"时间线"编辑层（董哥拍板），改成不分宽度
   常驻显示——右栏 .hx-topics-card 保持不变、不建立任何关联，两个入口各自
   独立（候选专题卡片只能看/关注候选，这个"+"打开的编辑层能管理已置顶+
   排序，功能不重复）。原来 <1080px 才显示的媒体查询删掉，不再需要。
   2026-11-03 再补（推特网页版还原）：栏目太多横向溢出时，.hx-feed-tab-arrow-right
   是 position:sticky 贴在可视区域右边（见上面该类自己的规则），这个「+」按钮
   原来是普通flex子项、跟着内容一起被滚走——溢出+鼠标悬停触发箭头出现那一刻，
   箭头会贴到视口右边，这个按钮如果恰好也卡在那附近，两者会叠在一起。改成
   「+」自己也 sticky 贴右边，不管箭头出不出现，两者并排不重叠；给个不透明
   背景，不然滚动中的栏目文字会从它身后透出来。
   2026-11-03 三补：跟箭头的左右顺序原来写反了（见上面 .hx-feed-tab-arrow-right
   同名注释）——这里改成贴最右边缘（right: --space-2，箭头本来用的这个值），
   箭头让位到它左边；原来左右各留 --space-6 外边距，现在只在左边留（右边
   贴边缘的空间已经靠 right 偏移量本身控制，不用margin重复占位，两个按钮之间
   不会凭空多出一截margin造成的缝隙，看起来像两个各自独立悬浮的东西——这也是
   董哥反馈"+号跑到容器外面"视觉上的真正原因，不是DOM结构的问题，DOM上
   「+」一直都在 .hx-feed-tabs 内部，没挪出去过。 */
.hx-feed-heading { padding: var(--space-8); border-bottom: var(--border-width) solid var(--line); }
.hx-feed-heading h1 { margin: 0; font-size: var(--font-heading); }
.hx-feed-heading p { margin: var(--space-2) 0 0; color: var(--ink-2); }

.hx-post-card { display: flex; gap: var(--space-6); padding: var(--space-6) var(--space-8) var(--space-2); border-bottom: var(--border-width) solid var(--line); transition: background var(--motion-fast); }
.hx-post-card:hover { background: var(--hover); }
.hx-avatar { display: grid; flex: none; width: var(--avatar); height: var(--avatar); overflow: hidden; place-items: center; border-radius: var(--radius-round); background: var(--field); color: var(--ink-2); }
.hx-avatar img { width: 100%; height: 100%; object-fit: cover; }
.hx-avatar svg { width: var(--icon); }
.hx-post-body { min-width: 0; flex: 1; }
.hx-post-byline, .hx-author-name { display: flex; align-items: center; gap: var(--space-2); min-width: 0; line-height: 1.35; }
.hx-post-author, .hx-author-name a { overflow: hidden; font-weight: var(--font-weight-heading); text-overflow: ellipsis; white-space: nowrap; }
/* 60-F (5)：置顶标记——跟 .hx-post-date 一起挤在 byline 里，.hx-post-more 的
   margin-left:auto 还是把「…」推到最右边，不受这条影响。 */
.hx-pinned-badge { flex: none; padding: var(--space-1) var(--space-4); border-radius: var(--radius-round); background: var(--brand-soft); color: var(--brand); font-size: var(--font-small); font-weight: var(--font-weight-heading); white-space: nowrap; }
.hx-post-more { display: grid; width: var(--avatar-small); height: var(--avatar-small); margin: calc(-1 * var(--space-3)) calc(-1 * var(--space-3)) calc(-1 * var(--space-3)) auto; flex: none; place-items: center; border-radius: var(--radius-round); color: var(--ink-2); }
.hx-post-more:hover { background: var(--blue-soft); color: var(--blue); }
.hx-post-more svg { width: var(--icon-small); }
.hx-handle, .hx-post-date { overflow: hidden; color: var(--ink-2); text-overflow: ellipsis; white-space: nowrap; }
.hx-verified { flex: none; width: var(--space-8); height: var(--space-8); color: var(--blue); }
.hx-verified .is-check { stroke: var(--white); stroke-width: var(--focus-width); }
.hx-post-link { display: block; }
.hx-visually-hidden { position: absolute; width: var(--border-width); height: var(--border-width); padding: 0; margin: calc(-1 * var(--border-width)); overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
/* 71-F (A)：卡片标题从图片叠字条挪到文字第一行——字号/行高不新起，字号复用
   字体方案里正文变量 --hb-fs-body（hyboox-type.css），行高、字体栈跟紧接着的
   .hx-post-text 保持一致（line-height 沿用同一个 1.47，font-family 显式 inherit
   顶掉 Ghost 默认给 h1~h6 挂的标题字体，跟正文同一个字体栈），只加粗一项
   区别于正文。 */
.hx-post-title { margin: var(--space-1) 0 0; overflow-wrap: break-word; font-family: inherit; font-size: var(--hb-fs-body); font-weight: var(--hb-fw-bold); line-height: 1.47; }
/* 65-F (6)：white-space:pre-line 从 .is-expanded 挪成 .hx-post-text 自己的基础
   规则——{{excerpt}} 吐出来的文本本来就用 \n\n 分段（真机拉 Content API
   比对过 excerpt/plaintext 两个字段，值一样，见 post-card-excerpt.hbs 65-F (6)
   注释），只是原来只在 .is-expanded 补这条 white-space:pre-line，收起状态用的
   还是默认 white-space:normal，会把这些换行折叠掉、拼成一整段——这才是董哥
   反馈"挤成一大段"的根因，不用动 helper，挪一下这条规则的作用范围就够了。 */
/* 243-F (3)：标题到正文第一段的间距要跟正文内部段落间距（applyRichFeedExcerpt
   里真正的 <p> 之间补的 \n\n，靠 white-space:pre-line 撑出一整行空白）看起来
   一致——那个空白行的高度就是本元素自己的 line-height(1.47) × 字号，所以这里
   直接用 1.47em（相对本元素字号算，不是固定 px），不再用原来那个几乎看不出来的
   var(--space-1)（2px）。换算成固定 px 会在 --font-scale 调整字号时跟正文内部
   空白行的实际高度脱节，用 em 才能两边始终对得上。 */
.hx-post-text { display: -webkit-box; margin: 1.47em 0 0; overflow: hidden; font-weight: 400; white-space: pre-line; -webkit-box-orient: vertical; -webkit-line-clamp: 6; line-height: 1.47; }
.hx-post-locked { display: flex; align-items: center; gap: var(--space-3); font-weight: 700; -webkit-line-clamp: 1; white-space: normal; }
.hx-post-locked svg { flex: 0 0 auto; width: var(--icon-small); color: var(--ink-2); }
.hx-show-more, .hx-show-less { display: inline-block; margin-top: var(--space-1); color: var(--blue); cursor: pointer; touch-action: manipulation; }
.hx-show-more:hover, .hx-show-less:hover { text-decoration: underline; }
.hx-show-more[hidden], .hx-show-less[hidden] { display: none; } /* 46-F：跟本文件其它常驻元素同一个坑，这条自己定了 display:inline-block，得补 */
/* 46-F：点「显示更多」不再跳转去文章页——去掉 4 行的 line-clamp。
   47-F：展开的内容换成了 hyboox-x.js 里拉回来的整篇正文纯文本（GET
   /api/posts/{id}/plaintext），用 .textContent 写进去、不是 .innerHTML——纯文本
   不含 HTML 标签，Ghost Content API 返的 plaintext 字段用 \n 分段，白名单
   white-space:pre-line 现在挪到上面 .hx-post-text 基础规则里了（65-F (6)），
   这里只需要去掉 line-clamp、恢复完整高度。 */
.hx-post-text.is-expanded { -webkit-line-clamp: unset; overflow: visible; }
.hx-media-card { position: relative; aspect-ratio: 16 / 9; margin-top: var(--space-6); overflow: hidden; border: var(--border-width) solid var(--line); border-radius: var(--radius-card); background: var(--field); }
.hx-media-card img { position: relative; z-index: 0; width: 100%; height: 100%; object-fit: cover; }
.hx-media-overlay { position: absolute; z-index: 1; right: auto; bottom: var(--space-4); left: var(--space-4); display: -webkit-box; max-width: calc(100% - var(--space-8)); padding: var(--space-3) var(--space-5); overflow: hidden; border-radius: var(--radius-medium); background: var(--media-overlay); color: var(--white); font-weight: 600; text-align: left; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.hx-media-source { display: block; margin-top: var(--space-2); color: var(--ink-2); font-size: var(--font-meta); }
.hx-post-actions { position: relative; display: flex; align-items: center; min-height: var(--avatar); margin-top: var(--space-2); }
.hx-action-group { display: flex; align-items: center; }
.hx-action-group-left { max-width: var(--right-width); flex: var(--action-left-grow); }
.hx-action-group-left > * { min-width: 0; flex: 1 1 0; }
.hx-action-group-left .hx-action { width: 100%; }
.hx-action-spacer { min-width: 0; flex: var(--action-spacer-grow); }
.hx-action-group-right { flex: none; }
.hx-action { display: flex; width: auto; min-width: var(--avatar-small); height: var(--avatar-small); padding: 0 var(--space-3); align-items: center; justify-content: center; gap: var(--space-2); border-radius: var(--radius-round); color: var(--ink-2); }
.hx-action:hover { background: var(--blue-soft); color: var(--blue); }
.hx-action svg { width: var(--icon-small); }
.hx-action-count { font-size: var(--font-meta); line-height: 1; }
.hx-like.is-liked { color: var(--like); }
.hx-like.is-liked:hover { background: var(--nav-hover); color: var(--like); }
.hx-like.is-liked svg path { fill: currentColor; stroke: none; }
.hx-view { cursor: default; }
.hx-view:hover { background: var(--transparent); color: var(--ink-2); }
.hx-bookmark.is-bookmarked { color: var(--brand); }
.hx-bookmark.is-bookmarked:hover { background: var(--nav-hover); color: var(--brand); }
.hx-bookmark.is-bookmarked svg path { fill: currentColor; stroke: none; }
.hx-quote-menu { position: relative; }
.hx-quote-menu summary { list-style: none; }
.hx-quote-menu summary::-webkit-details-marker { display: none; }
.hx-quote-menu > div { position: absolute; z-index: 45; top: calc(100% + var(--space-2)); left: 0; width: calc(var(--right-width) - var(--space-40)); padding: var(--space-3) 0; border-radius: var(--radius-card); background: var(--bg-elev); box-shadow: var(--shadow); }
.hx-quote-menu > div a, .hx-quote-menu > div button { display: block; width: 100%; padding: var(--space-6) var(--space-8); text-align: left; }
.hx-quote-menu > div a:hover, .hx-quote-menu > div button:hover { background: var(--hover); }
.hx-empty { display: block; margin: 0; padding: var(--space-14) var(--space-8); color: var(--ink-2); text-align: center; }
.hx-pagination .pagination { display: flex; align-items: center; justify-content: space-between; padding: var(--space-8); border-bottom: var(--border-width) solid var(--line); }
.hx-pagination .page-number { color: var(--ink-2); font-size: var(--font-meta); }
.hx-pagination a { color: var(--blue); font-weight: 700; }
.hx-feed-status { margin: 0; padding: var(--space-14) var(--space-8); color: var(--ink-2); text-align: center; }
.hx-feed-status[data-state="error"] { color: var(--brand); cursor: pointer; }

.hx-search-main { display: grid; height: 100vh; overflow: hidden; grid-template-rows: auto minmax(0, 1fr); }
.hx-search-scroll { min-height: 0; overflow-y: auto; }
.hx-search-status { margin: 0; padding: var(--space-8); border-bottom: var(--border-width) solid var(--line); color: var(--ink-2); }
.hx-search-status a { color: var(--blue); font-weight: 700; }
.hx-search-form { padding: var(--space-6) var(--space-8); border-bottom: var(--border-width) solid var(--line); background: var(--header-bg); backdrop-filter: blur(var(--space-6)); }
.hx-search-input-wrap { display: flex; height: var(--button-height); padding: 0 var(--space-8); align-items: center; gap: var(--space-6); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); }
.hx-search-input-wrap:focus-within { border-color: var(--brand); background: var(--bg); }
.hx-search-input-wrap svg { width: var(--icon-small); color: var(--ink-2); }
.hx-search-input-wrap:focus-within svg { color: var(--brand); }
.hx-search-input-wrap input { min-width: 0; flex: 1; border: 0; outline: 0; background: var(--transparent); }
.hx-search-input-wrap input::placeholder { color: var(--ink-2); }
.hx-search-results mark { padding: 0; border-radius: var(--radius-small); background: var(--blue-soft); color: var(--ink); }

.hx-bookmarks-header { position: sticky; z-index: 25; top: 0; display: flex; height: var(--mobile-bar-height); padding: 0 var(--space-8); align-items: center; justify-content: space-between; gap: var(--space-6); border-bottom: var(--border-width) solid var(--line); background: var(--header-bg); backdrop-filter: blur(var(--space-6)); }
.hx-bookmarks-header h1 { margin: 0; font-size: var(--font-nav); font-weight: 800; }
/* 193-F：机会页「关注」按钮——跟 .hx-watchlist-header 同一个道理，.hx-primary-button
   默认不定宽，这里收窄成贴着内容宽的小胶囊，不要撑满整行。 */
.hx-bookmarks-header .hx-primary-button { flex: none; width: auto; padding: 0 var(--space-8); }
/* 机会标签页专用顶栏：标题相对整条顶栏居中，左右控件保留独立点击区。 */
.hx-opp-tag-header h1 { position: absolute; left: 50%; width: max-content; max-width: calc(100% - var(--button-height) - var(--button-height) - var(--space-32) - var(--space-32) - var(--space-4) - var(--space-4)); overflow: hidden; transform: translateX(-50%); text-overflow: ellipsis; white-space: nowrap; }
.hx-opp-tag-back { display: grid; flex: none; width: var(--button-height); height: var(--button-height); place-items: center; border-radius: var(--radius-round); color: var(--ink); }
.hx-opp-tag-back:hover { background: var(--nav-hover); }
.hx-opp-tag-back svg { width: var(--icon); }
.hx-opp-follow-button.is-following { background: var(--field); color: var(--ink); }
/* 40-F：账户页的 [data-account-tabs] 也是 .hx-feed-tabs，基础规则（line 262）
   给它的 sticky top 是 0——跟上面 .hx-bookmarks-header 自己也是 sticky top:0 会在
   桌面端滚动时完全叠在一起（无头 Chrome 实测量过两者 getBoundingClientRect 重合）。
   手机端有专门的媒体查询把 .hx-feed-tabs 顶到 var(--mobile-bar-height)，这里给账户页
   一条不分断点都生效的同值覆盖，两端都对，也不影响手机端原有规则（同一个值）。 */
.hx-account-main .hx-feed-tabs { top: var(--mobile-bar-height); }
.hx-bookmarks-status { margin: 0; padding: var(--space-14) var(--space-8); border-bottom: var(--border-width) solid var(--line); color: var(--ink-2); text-align: center; }
.hx-bookmarks-status:empty { display: none; }
.hx-bookmarks-status a, .hx-bookmarks-status button { color: var(--blue); font-weight: 700; }
/* 174-F：海博日历搜索框——.hx-bookmarks-header 是 bookmarks/comments 等好几个
   页面共用的基础规则（display:flex 单行、固定 height:var(--mobile-bar-height)），
   不能直接改那条规则本身（会牵连其它页面），这里用 page-calendar.hbs 里另外
   加在这个 <header> 自己身上的 .hx-calendar-header 标记类限定，只对海博日历
   这一页生效：allow wrap 换行、把固定高度松成 min-height，装得下两行。
   真机测出来的坑：一开始想用 .hx-calendar-main .hx-bookmarks-header 这种
   祖先限定写法，手机端量出来完全没生效——initCalendarHeaderGroup()（57-F）
   在手机宽度下会把这个 <header> 从 .hx-calendar-main 里搬到页面级共用的
   .hx-mobile-header-group 容器（配合顶栏滚动收起），祖先选择器因为 DOM
   位置变了直接不认了；改成挂在元素自己身上的类，不管它被 JS 搬到哪个父级
   下面都还认得出来。
   .hx-calendar-search 用现成的 .hx-search-input-wrap 胶囊样式（跟 /search/
   页同一份，见该文件 549 行），只是加了个 margin-left:auto 把它推到标题右边；
   电脑端一行放得下（flex-wrap:wrap 不会触发换行），手机端断点把它强制变成
   100% 宽度撑满一行，自然换到标题下面单独一行。 */
.hx-calendar-header { flex-wrap: wrap; height: auto; min-height: var(--mobile-bar-height); padding-top: var(--space-4); padding-bottom: var(--space-4); row-gap: var(--space-4); }
.hx-calendar-search { flex: 0 1 260px; min-width: 0; margin-left: auto; }
.hx-calendar-search-input { font-size: var(--font-meta); color: var(--ink); }
/* 海博日历页桌面端把栏目条、标题搜索、筛选条作为连续的三层吸顶区；
   手机端这三个节点会被 initCalendarHeaderGroup() 搬进统一的 mobile header，
   由现有容器吸顶，因此这里只作用于仍在日历主内容下的桌面节点。 */
.hx-calendar-main > .hx-feed-tabs:not(.hx-calendar-filters) { top: 0; z-index: 32; }
.hx-calendar-main > .hx-calendar-header { top: var(--feed-tab-height); z-index: 31; }
.hx-calendar-main > .hx-calendar-filters { top: calc(var(--feed-tab-height) + var(--mobile-bar-height)); z-index: 30; }
/* 174-F：清空按钮复用 .hx-icon-button（32px 圆形，跟抽屉关闭按钮等全站一致的
   小图标按钮同一个尺寸），44px 高的胶囊装得下，右边负 margin 把它往里收一点，
   跟 .hx-event-more 那处（36-F）同一个"负 margin 收紧视觉间距"的手法。 */
.hx-calendar-search-clear { flex: none; margin-right: calc(-1 * var(--space-3)); }
@media (max-width: 700px) {
    .hx-calendar-search { flex-basis: 100%; margin-left: 0; }
}
/* 174-F：搜索结果关键字高亮——卡面要求"浅黄底"，跟 .hx-search-results mark
   （全站文章搜索页用的浅蓝底）是两种场景，颜色不一样，各自一条规则，不合并。
   桌面表格/手机卡片两条搜索结果渲染路径最终都是同一份 .hx-event-table-date/
   .hx-event-title/[data-group-date]/[data-event-schedule-text] 元素结构，
   mark 元素本身样式不用分桌面/手机各写一份。 */
.hx-calendar-main mark { padding: 0; border-radius: var(--radius-small); background: var(--yellow-soft); color: var(--ink); }
/* 35-F：海博日历录入框，只有管理员看得到（服务端默认 hidden，JS 探测通过才露出）。
   行内 date + market 用 flex-wrap，窄屏（320/360）装不下并排时自动换行堆叠，
   不硬挤/不溢出——两端都要能用，不单独写一份桌面/手机分支。 */
/* 45-F：表单本身不再直接带 hidden 属性——可见性交给外层 .hx-appearance[hidden]
   那个弹窗壳子管（发布按钮弹窗，见 page-calendar.hbs），这条表单自己不用再补
   [hidden] 覆盖了（原来的 28/30/31-F 那个坑是"表单自己常驻页面、自己带 hidden"
   时才会踩，现在结构变了，没有对应的 attribute 会打到这个元素上）。 */
.hx-calendar-entry { display: flex; flex-direction: column; gap: var(--space-6); padding: 0; border-bottom: 0; }
/* 46-F 补：交易事件那三行（粒度/日期取值/地区）包进了 data-calendar-entry-trading-fields
   这个 wrapper 之后，不再是 .hx-calendar-entry 的直接子项——父级那条 gap:6 只管得到
   wrapper 跟它的兄弟（常规事件的文本行、事件、备注）之间的间距，wrapper 内部这三行
   自己没有 flex/gap，会挤在一起。这里给 wrapper 补上跟外层一样的纵向 flex + gap，
   两层套娃但视觉间距保持一致。 */
.hx-calendar-entry [data-calendar-entry-trading-fields] { display: flex; flex-direction: column; gap: var(--space-6); }
.hx-calendar-entry [data-calendar-entry-trading-fields][hidden] { display: none; } /* 又是这个坑——上面这条选择器比全局 [hidden] 更精确、源码顺序也更靠后，自己定了 display:flex 不补这条压不住，本文件里反复踩过同一个坑，这次是我自己写新 CSS 时又踩了一遍 */
.hx-calendar-entry-row { display: flex; flex-wrap: wrap; gap: var(--space-6); }
.hx-calendar-entry-row[hidden] { display: none; } /* 35-F 补：粒度切换时藏起 month/quarter 那两行取值控件，跟上面 .hx-calendar-entry[hidden] 同一个坑——.hx-calendar-entry-row 自己定了 display:flex，不补这条 [hidden] 压不住，写这版时直接照抄上次踩过的教训一并补上，没有再靠截图踩一遍 */
/* 46-F：常规事件的「文本」输入框（.hx-calendar-entry-schedule-text）补进这一组——
   之前漏了，没样式的裸 input 只有浏览器默认细边框、没有灰底、也没占满宽度，跟
   「事件」「备注」两个框（下面 width:100% 那条）不是同一套视觉语言。 */
.hx-calendar-entry-date, .hx-calendar-entry-year, .hx-calendar-entry-quarter-select, .hx-calendar-entry-month-period-select, .hx-calendar-entry-schedule-text { height: var(--button-height); padding: 0 var(--space-8); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); color: var(--ink); font-size: var(--font-body); }
.hx-calendar-entry-date:focus, .hx-calendar-entry-year:focus, .hx-calendar-entry-quarter-select:focus, .hx-calendar-entry-month-period-select:focus, .hx-calendar-entry-schedule-text:focus { outline: 0; border-color: var(--brand); background: var(--bg); }
/* 41-F：事件内容改单行 input 为多行 textarea，支持换行——不能再跟上面那组单行控件
   共用 height:var(--button-height) + border-radius:var(--radius-round)（胶囊形状
   套在多行框上会很怪），照抄 .hx-composer-input（发帖框）那套 min-height + 圆角
   矩形的多行文本框样式。resize:vertical 让用户需要时自己拉高，不锁死行数。
   233-F：备注框从这组单行胶囊控件挪过来，跟「事件」标题框共用这条规则——
   尺寸/多行/工具条都要对齐，不能再是单独一行矮胶囊。 */
.hx-calendar-entry-title, .hx-calendar-entry-note { min-height: calc(var(--space-40) * 1.4); padding: var(--space-6) var(--space-8); border: var(--border-width) solid var(--line); border-radius: var(--radius-medium); background: var(--field); color: var(--ink); font-size: var(--font-body); resize: vertical; }
.hx-calendar-entry-title:focus, .hx-calendar-entry-note:focus { outline: 0; border-color: var(--brand); background: var(--bg); }
.hx-calendar-entry-title-count { margin: calc(var(--space-4) * -1) 0 0; }
/* date/precision/market/year/quarter-select 都是 .hx-calendar-entry-row（行内 flex）
   的直接子项，flex-basis 在这里控制的是横向宽度，按视觉留几个 px 的换行断点；
   title/note/schedule-text 是 .hx-calendar-entry 本身（纵向 flex，schedule-text
   这个是它套的 .hx-calendar-entry-row 那层，但视觉上也是要占满一行）的直接/间接
   子项，同一个 flex 简写在纵向容器里 flex-basis 控制的是高度——上一版两组元素
   混用过一条 flex 规则，把 title/note 撑成椭圆（无头 Chrome 截图揪出来的），这次
   两组从一开始就分开写，不共用。46-F：kind 那个字段已经不在这个表单里了（顶层
   type 选择器顶替了），从这条选择器列表里删掉，不留死代码。 */
.hx-calendar-entry-precision, .hx-calendar-entry-market { flex: 1 1 180px; }
.hx-calendar-entry-date { flex: 1 1 160px; }
.hx-calendar-entry-year { flex: 1 1 100px; }
.hx-calendar-entry-quarter-select, .hx-calendar-entry-month-period-select { flex: 1 1 140px; }
.hx-calendar-entry-title, .hx-calendar-entry-note, .hx-calendar-entry-schedule-text { width: 100%; }
.hx-calendar-entry-visibility { display: flex; align-items: center; gap: var(--space-2); color: var(--ink-2); font-size: var(--font-meta); cursor: pointer; }
/* 36-F：筛选栏改用 .hx-feed-tabs/.hx-feed-tab（跟首页/搜索页/账户页同一套滑动
   tab 条样式），原来这条 .hx-segmented 风格的胶囊规则整个不再需要——.hx-feed-tabs
   自己已经有 display:flex/sticky/border-bottom，.hx-feed-tab 自己也有 padding，
   留着这条只会跟新样式重复/打架（重复 border-bottom、多余 gap），直接删掉。 */
/* 69-F (D)：海博日历事件文字放大——原来日期分组表头/桌面表格都是 --font-meta
   （13px），事件正文（.hx-event-title）没设字号、吃 body 默认 16px，三处大小不
   统一还都偏小。卡面要求按 hyboox-type.css 的字号梯度（meta 13/16 → body 15/20 →
   read 17/24 → …）往上取两档，不自造字号——从 meta 起跳两档正好落在 read 这一
   档（17px/24px），日期列和事件正文统一到这个尺寸，行高跟着换成对应的 24px，
   不是各自单独调。 */
/* 2026-10-28：日期分组表头颜色从次要文字色 --ink-2 改成主文字色 --ink——
   董哥反馈太浅看不清；用主题色 token 不是写死黑色，深色模式下 --ink 也会
   跟着换成浅色，不会在深色模式下变成"黑底黑字"看不见。 */
.hx-calendar-date { margin: 0; padding: var(--space-6) var(--space-8); border-top: var(--border-width) solid var(--line); background: var(--field); color: var(--ink); font-size: 17px; line-height: 24px; font-weight: 700; }
.hx-event-row { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-6) var(--space-8); border-top: var(--border-width) solid var(--line); }
/* 38-F：只剩事件内容 + 管理员操作两个子项——市场/时间/类型/备注不再渲染，
   对应的 .hx-event-meta/.hx-event-time/.hx-event-market/.hx-event-kind/
   .hx-event-body/.hx-event-note 规则整个删掉，不留死代码。
   56-F：常规事件卡片（.hx-event-regular-row，event-regular-row.hbs）不再
   在这份基础规则上横向并排——外层 .hx-event-regular-row 本身不带样式，纯粹是
   给 data-event-row 找一个语义外壳，视觉全部来自内部直接复用的 .hx-calendar-date
   + .hx-event-row 这两份现成规则，46-F 那版横排样式（.hx-event-regular-row
   自己的 flex-direction/.hx-event-schedule-text 等）整个删掉，不留死代码。 */
/* 63-F (10)：事件文字默认正常字重，加粗要靠事件文本里显式写 **...** 才出现
   （渲染成 <strong>，字重由 <strong> 元素自己的默认粗体表现，不用另外定义）。 */
/* 2026-10-28：事件内容支持换行——textarea 录入的 \n 一直都原样存到数据库
   （前端 trim()/后端 title.strip() 都只掐头去尾，不碰中间的换行符），
   display:block 的默认 white-space:normal 才是真正把换行渲染成空格的
   地方，加 pre-line 保留 \n 的同时仍然正常折行/合并多余空格，不用改
   renderCalendarMarkupText() 插 <br>（那样还要额外处理 XSS 转义，纯 CSS
   方案更简单，createTextNode 建的文本节点本来就是安全的）。 */
/* 233-F：事件文字默认色改蓝色——{color:xxx} 标记过的事件走的是行内 style
   （renderCalendarMarkupText 里 span.style.color），优先级天然高于这条
   class 规则，标记颜色不受影响；没用过标记的事件才会落到这条默认蓝色。
   2026-11-02（董哥拍板确认，跟右栏 --font-tab 对齐，17px，不是 14px）：
   这个值 2026-11-02 当天被改动/改回了三次（14→17→14→17），起因是两个
   互不知情的 Claude Code 会话同时在改这份未提交的线上主题文件，各自
   以为对方是"未授权改动"在互相撤销——不是代码逻辑有分歧，是并发编辑
   冲突。17px 是最终确认值，改这行之前先确认没有别的会话/进程也在盯着
   这份文件（`ps aux | grep claude`，查 cwd 是不是指向这个主题目录），
   不要凭直觉又改回 14。 */
.hx-event-title { display: block; font-weight: 400; font-size: 17px; line-height: 24px; white-space: pre-line; color: var(--blue); }
/* 64-F (4)：日历录入表单「事件」文本框的加粗/斜体/插入链接改成跟发帖弹窗
   共用同一份浮动工具条（.hx-composer-toolbar/.hx-composer-link-popover，见
   composer.hbs），63-F 那份专属的静态工具条样式不再需要，删掉。 */
a.hx-event-title[href] { color: var(--blue); }
/* 50-F (2/F)：长按拖动排序——只有管理员、且当前这一行确实在拖动范围内
   （wireCalendarDragReorder 只给这些行加这个 class，见 hyboox-x.js）才会带
   .hx-event-draggable。-webkit-touch-callout/user-select 关掉手机浏览器自己
   那套"长按链接弹出复制/新标签页"系统菜单——不关的话会跟我们自己的长按语义
   打架；.is-dragging 是真正进入拖动态之后加的，touch-action:none 交出触屏
   手势控制权给 JS 自己算的实时挪动，不然浏览器还会顺手把这当成滚动处理。 */
.hx-event-draggable { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; cursor: grab; }
.hx-event-draggable.is-dragging { touch-action: none; position: relative; z-index: 5; opacity: .6; box-shadow: var(--shadow); cursor: grabbing; }
/* 桌面鼠标允许选择管理员日历行文字；手机仍保留长按排序所需的禁选规则。 */
@media (hover: hover) and (pointer: fine) {
    .hx-event-draggable { -webkit-user-select: text; user-select: text; }
}
/* 36-F：每条事件右边的管理员操作，只有管理员看得到（服务端默认 hidden）。
   45-F：编辑/删除从每行自己的 ✓/✗ + 内联二次确认，改成一个「…」触发按钮
   + 全页面共用一份菜单（.hx-event-menu，见下面和 page-calendar.hbs），不用
   .hx-primary-button 那种大胶囊（一屏可能同时列很多条事件，胶囊太重）。 */
.hx-event-admin-actions { display: flex; align-items: center; margin-top: var(--space-2); }
.hx-event-admin-actions[hidden] { display: none; } /* 跟 .hx-calendar-entry 同一类坑，这条自己定了 display:flex，得补 */
.hx-event-more { display: grid; width: var(--avatar-small); height: var(--avatar-small); margin: calc(-1 * var(--space-3)); place-items: center; border: 0; border-radius: var(--radius-round); background: none; color: var(--ink-2); cursor: pointer; }
.hx-event-more:hover { background: var(--hover); color: var(--ink); }
.hx-event-more svg { width: var(--icon-small); }
/* 45-F：编辑/删除共用菜单——手机端从底部滑上来（照 X 的底部弹出面板），桌面端是
   紧贴触发按钮的小下拉。跟账户抽屉同一套"常驻 DOM、用 transform + .is-open 切
   开合"的技巧（而不是 [hidden] 那种直接跳变）——底部面板需要滑动动画，display
   跳变没法过渡。遮罩 .hx-drawer-scrim（这份是 data-event-menu-scrim，复用同一个
   类名的样式但开关状态各管各的）只在手机端才会被 JS 翻出来，桌面端下拉不需要
   整页压暗。 */
/* 桌面端默认形态：小下拉，位置是 JS 按触发的「…」按钮 getBoundingClientRect()
   现算的 top/left（跟 44-F 栏目条那个下拉菜单同一个技巧），这里只管开合过渡——
   透明度 + 轻微位移，不用手机端那种整面板滑动（下拉本来就小，滑起来没必要）。 */
.hx-event-menu { position: fixed; z-index: 95; min-width: 9.5rem; border-radius: var(--radius-card); pointer-events: none; opacity: 0; transform: translateY(-4px); background: var(--bg-elev); box-shadow: var(--shadow); transition: transform var(--motion-medium), opacity var(--motion-medium); }
.hx-event-menu.is-open { pointer-events: auto; opacity: 1; transform: none; }
/* 45-F 第二轮：桌面端发布按钮不再是独立的 position:fixed 元素——晖哥要求"独占
   一行、不压账号信息"，改成挪进 navigation.hbs 的 .hx-left-rail 正常文档流里，
   跟「发帖」按钮共用 .hx-compose 那份样式（--brand 长条胶囊/90% 宽/1300px 压窄
   断点自动收成图标-only，都是现成的，不用再定义一遍），.hx-calendar-publish-desktop
   这个类现在只是个语义标记，不带任何样式。手机端形态（右下角悬浮圆按钮）没变，
   还是 position:fixed，见 700px 媒体查询里那条。 */
.hx-calendar-publish-mobile { display: none; }
.hx-event-menu [data-event-menu-actions], .hx-event-menu [data-event-menu-confirm] { padding: var(--space-3) 0; }
.hx-event-menu [data-event-menu-confirm] { padding: var(--space-6) var(--space-8) var(--space-8); }
.hx-event-menu [data-event-menu-confirm][hidden] { display: none; }
.hx-event-menu p { margin: 0 0 var(--space-6); color: var(--ink-2); font-size: var(--font-meta); }
.hx-event-menu button { display: block; width: 100%; padding: var(--space-6) var(--space-8); border: 0; background: none; text-align: left; font-size: var(--font-body); color: var(--ink); cursor: pointer; }
.hx-event-menu [data-event-menu-confirm] button { width: auto; display: inline-block; padding: var(--space-4) var(--space-8); border-radius: var(--radius-round); margin-right: var(--space-4); font-size: var(--font-meta); font-weight: 700; }
.hx-event-menu [data-event-menu-delete-yes] { background: var(--brand); color: var(--white); }
.hx-event-menu [data-event-menu-delete-no] { background: var(--field); color: var(--ink-2); }
.hx-event-menu button:not([data-event-menu-delete-yes]):not([data-event-menu-delete-no]):hover { background: var(--hover); }
.hx-event-menu .is-danger { color: var(--brand); }
/* 213-F：「复制」结果的轻提示——固定在视口底部居中，没有遮罩、不挡交互，
   约1.5秒后由 JS 重新设回 hidden 自动消失。深色/浅色模式都用 ink/bg 反色，
   跟页面当前主题保持对比度，不需要单独的深色覆盖规则。 */
.hx-copy-toast { position: fixed; z-index: 150; left: 50%; bottom: calc(var(--space-24) + env(safe-area-inset-bottom, 0)); max-width: calc(100vw - var(--space-16)); padding: var(--space-4) var(--space-10); border-radius: var(--radius-round); background: var(--ink); color: var(--bg); font-size: var(--font-meta); font-weight: 700; text-align: center; box-shadow: var(--shadow); transform: translateX(-50%); pointer-events: none; }
.hx-copy-toast[hidden] { display: none; }
@media (max-width: 700px) {
    .hx-copy-toast { bottom: calc(var(--mobile-dock-height) + var(--space-10) + env(safe-area-inset-bottom, 0)); }
}
/* 37-F：「显示历史事件」开关，只有管理员看得到（服务端默认 hidden，JS 探测通过
   才露出）——跟 .hx-calendar-entry 同一个坑，这条自己定了 display:flex，得补
   [hidden] 覆盖。 */
.hx-calendar-history-toggle { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-4) var(--space-8); border-bottom: var(--border-width) solid var(--line); color: var(--ink-2); font-size: var(--font-meta); cursor: pointer; }
.hx-calendar-history-toggle[hidden] { display: none; }
/* 37-F：桌面端表格视图，手机端不用——390 宽装不下表格，手机端继续用上面按日期
   分组的卡片（.hx-calendar-list/.hx-event-row），这份表格样式只在
   initializeCalendar() 判定为桌面（没有 track）时才会被翻出来。
   38-F：表格也改成按日期分组（日期列用 rowspan 撑满整组），只剩日期+事件两列
   实际显示内容，跟卡片版一样市场/备注/类型不再渲染。 */
/* 46-F：桌面端表格最后一条事件底部留白，跟手机端 .hx-calendar-list 那条
   padding-bottom:33dvh（42-F 加的，怕最后一条贴底栏 dock）保持一致的量——
   桌面端没有底栏遮挡的问题，但晖哥要求视觉上两端一致，数值直接照抄。
   50-F (3)：46-F 当时用的是 padding-bottom，一直没生效——`border-collapse:
   collapse` 的表格，浏览器会整个忽略 <table> 元素自己的 padding（computed
   style 量得到 297px，但 table.offsetHeight 跟"表头+表体实际内容高度"分毫不差，
   这 297px 从来没被算进布局），只有「常规」筛选胶囊因为条目多、页面本来就要
   滚动，才会让最后一条贴底这件事露出来给真机测出来——「全部/A股/美股」内容矮，
   页面根本没滚到底，这个坑一直没人看见。border-collapse 不能动（影响单元格
   边框），改用 margin-bottom：外边距不吃 border-collapse 那条规则，效果一样是
   撑高文档、把最后一条推离视口底边，四个筛选胶囊共用同一个 <table> 元素，这一
   处改完全部生效，不用分别处理。 */
.hx-calendar-table { width: 100%; border-collapse: collapse; margin-bottom: 33dvh; }
.hx-calendar-table[hidden] { display: none; }
.hx-calendar-table th, .hx-calendar-table td { padding: var(--space-4) var(--space-6); border-top: var(--border-width) solid var(--line); text-align: left; font-size: var(--font-meta); vertical-align: top; }
.hx-calendar-table thead th { border-top: 0; border-bottom: var(--border-width) solid var(--line); color: var(--ink-2); font-weight: 700; }
/* 174-F：日期列/操作列宽度钉住——JS（measureCalendarTableColumnWidths()/
   pinCalendarTableColumnWidths()，hyboox-x.js）页面加载时量一次固定参照
   （"当年12月31日"这种最长的常见日期），四个筛选胶囊（含"全部"）和搜索
   结果永远用同一份宽度，table-layout 永远是 fixed，不再有"全部"unconstrained
   自己量一次这个特殊分支（174-F 真机反馈修正：旧版"全部"现量哪天数据凑巧
   偏窄，切到其它胶囊就折行，见 hyboox-x.js 174-F 注释）。事件内容列
   （colgroup 里中间那个不带 class 的 <col>）没有单独一条宽度规则，
   table-layout:fixed 下唯一没显式定宽的列会自动吃掉剩下的空间，不用另外算。
   真机测出来的坑：table-layout:fixed 只认 <colgroup><col> 或者 thead 那一行
   的宽度，不认 tbody 单元格上的 class——原来直接把这两条规则写在
   .hx-event-table-date/.hx-event-admin-actions（tbody 专属 class）上，css
   变量设了也不生效，三列退回等分；改成挂在 page-calendar.hbs 新加的
   <colgroup><col> 上（class 名 hx-calendar-col-date/hx-calendar-col-actions），
   fixed 布局下 colgroup 的显式宽度优先级最高，thead/tbody 内容宽度都不会
   再影响它。 */
/* 日期列实测仍不够宽——"2026年10月 · 月底"这类月度+月中/月底、"10月26日—29日"
   这类同月区间会在钉住的参照宽度（calendarWidestDateSample() 量的"月/日 · 周几"）
   下换成两行，额外加约两个汉字的宽度（32px）把这几种常见格式都兜进单行，不改
   JS 侧的测量逻辑，只在这条宽度规则上加一个 calc 偏移。 */
.hx-calendar-col-date { width: calc(var(--calendar-date-col-width, 0px) + 32px); }
.hx-calendar-col-actions { width: var(--calendar-actions-col-width, auto); }
/* 174-F：原来 nowrap 是给未被约束宽度的日期列防止意外换行——现在四个筛选
   胶囊永远是 fixed 布局钉宽（见上面），只有「常规」的自由文本 schedule_text
   偶尔会比钉住的宽度长，改成 normal 才能真的换行，不会被裁切/溢出；
   全部/中国/美国的常见日期串本来就在钉住的宽度以内（这版参照按最长的常见
   日期量出来的，见 hyboox-x.js calendarWidestDateSample()），实际视觉上
   不会换行。 */
.hx-event-table-date { font-weight: 700; white-space: normal; }
/* 69-F (D)：桌面表格日期列/事件列跟卡片版同一个目标尺寸（read 档 17px/24px）——
   `.hx-calendar-table th, .hx-calendar-table td` 那条通用规则选择器权重是
   (0,1,1)，比单独一个类选择器高，不加 `.hx-calendar-table` 前缀提权重的话这两条
   会被通用规则盖掉，表头「日期」不在这两个选择器里，维持原来的 --font-meta 不变。
   233-F：「事件文字统一字号14」只改事件列——日期列跟卡片版 .hx-calendar-date
   保持一致继续用 17px，两条规则从这里拆开，不再共用一条。 */
.hx-calendar-table .hx-event-table-date { font-size: 17px; line-height: 24px; }
.hx-calendar-table .hx-event-title { font-size: 17px; line-height: 24px; }
/* .hx-event-admin-actions 的 margin-top 是给卡片版（挂在标题/备注下面）留的呼吸
   空间，表格版本身就在单元格 padding 里，不需要再叠一层，覆盖掉即可 */
.hx-calendar-table .hx-event-admin-actions { margin-top: 0; }
.hx-comment-body { display: block; overflow: visible; -webkit-line-clamp: unset; }
.hx-admin-manage-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-6); padding: var(--space-6) var(--space-8); border-bottom: var(--border-width) solid var(--line); }
.hx-admin-manage-info { min-width: 0; }
.hx-admin-manage-info strong { display: block; }
.hx-admin-manage-info .hx-handle { max-width: none; }
.hx-admin-manage-row .hx-primary-button { flex: none; width: auto; padding: 0 var(--space-8); }
.hx-admin-manage-row .hx-primary-button.is-remove { background: var(--field); color: var(--ink); }
.hx-admin-vip-list { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-4); }
.hx-admin-vip-item { font-size: var(--font-meta); color: var(--ink-2); }

/* 137-F：栏目管理页——数值沿用本文件已有 token（--space-*/--line/--field/
   --ink*/--radius-*），没有出现第二处色值，深色模式靠 :root[data-theme]
   那两条已有规则自动换算，这里不用再补一份 .is-dark 之类的覆盖（跟
   miniprogram 的 wxss 不是同一套机制，这边纯 CSS 变量级联就够）。
   布局照卡面第 4 条"左边拖拽手柄、中间名称和目标、右边操作"：
   .hx-channel-manage-row 是一行 flex，中间 .hx-channel-manage-info 用
   flex:1 吃掉剩余空间，两侧固定宽度。 */
.hx-channel-manage-group { margin-bottom: var(--space-10); }
.hx-channel-manage-group:last-child { margin-bottom: 0; }
.hx-channel-manage-group-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-6); margin-bottom: var(--space-4); }
.hx-channel-manage-group-head h3 { margin: 0; font-size: var(--font-body); font-weight: 700; }
.hx-channel-manage-add { width: auto; min-height: auto; padding: var(--space-3) var(--space-6); font-size: var(--font-meta); }
.hx-channel-manage-action-status { padding: var(--space-4) 0; }
.hx-channel-manage-list { display: flex; flex-direction: column; gap: var(--space-3); }
.hx-channel-manage-row { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-4) var(--space-6); border: var(--border-width) solid var(--line); border-radius: var(--radius-medium); background: var(--bg-elev); }
/* 138-F (1)：拖拽中这一行要跟手实时移动（JS 用 transform:translateY 逐帧
   跟着指针走，见 hyboox-x.js wireChannelDragHandle 头部注释），position:
   relative 让 z-index 生效，拖动时盖在其它行上面，阴影给一点"提起来"的
   层次感，不再只靠透明度变化表达"正在拖拽"这一件事。 */
.hx-channel-manage-row.is-dragging { position: relative; z-index: 5; box-shadow: var(--shadow); cursor: grabbing; }
.hx-channel-drag-handle { flex: none; display: grid; width: var(--avatar-small); height: var(--avatar-small); place-items: center; border-radius: var(--radius-medium); color: var(--ink-2); cursor: grab; touch-action: none; }
.hx-channel-drag-handle:hover { background: var(--hover); }
.hx-channel-drag-handle svg { width: var(--icon-small); }
.hx-channel-manage-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.hx-channel-manage-info strong { font-size: var(--font-body); }
.hx-channel-manage-info .hx-handle { color: var(--ink-2); font-size: var(--font-meta); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hx-channel-manage-info .hx-handle:empty { display: none; }
.hx-channel-visible-toggle { flex: none; display: flex; align-items: center; gap: var(--space-2); color: var(--ink-2); font-size: var(--font-meta); white-space: nowrap; }
.hx-channel-manage-row .hx-icon-button { flex: none; }

/* 编辑弹窗表单——壳子是全站通用的 .hx-appearance（见 802-809 行那组规则），
   这里只补表单内部字段的排布和输入框数值，输入框数值照抄
   .hx-calendar-entry-date 等一批已有表单控件（547-548 行）同一套
   height/padding/border/radius/背景，不新起一套输入框视觉语言。 */
.hx-channel-edit-form { display: flex; flex-direction: column; gap: var(--space-6); }
.hx-channel-edit-field { display: flex; flex-direction: column; gap: var(--space-2); }
.hx-channel-edit-field label { color: var(--ink-2); font-size: var(--font-meta); }
.hx-channel-edit-input { height: var(--button-height); padding: 0 var(--space-8); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); color: var(--ink); font-size: var(--font-body); }
.hx-channel-edit-input:focus { outline: 0; border-color: var(--brand); background: var(--bg); }
select.hx-channel-edit-input { flex: 1; }
.hx-channel-manage-target-select-row { display: flex; gap: var(--space-4); align-items: center; }

@media (max-width: 640px) {
    /* 卡面第 4 条"电脑端和手机端都要能用"——窄屏下显示开关的文字先藏起来
       （checkbox 本身保留，触控目标够大），操作区不会被挤得太窄导致
       误触，跟 .hx-left-rail 收窄时先藏文字、图标常驻的既有取舍一致
       （966 行那组 @media (max-width: 1300px) regole 同一个思路）。 */
    .hx-channel-visible-toggle span { display: none; }
    .hx-channel-manage-row { gap: var(--space-3); padding: var(--space-3) var(--space-4); }
    .hx-channel-manage-info strong { font-size: var(--font-meta); }
}
.hx-watchlist-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-6); padding: var(--space-6) var(--space-8); border-bottom: var(--border-width) solid var(--line); }
.hx-watchlist-header .hx-primary-button { flex: none; width: auto; padding: 0 var(--space-8); }
.hx-watchlist-button.is-added { background: var(--field); color: var(--ink); }
.hx-account-section { padding: var(--space-6) var(--space-8); border-bottom: var(--border-width) solid var(--line); }
.hx-account-section h2 { margin: 0 0 var(--space-6); font-size: var(--font-heading); }
.hx-watchlist-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-6); padding: var(--space-4) 0; border-top: var(--border-width) solid var(--line); }
.hx-watchlist-row:first-child { border-top: 0; }
.hx-watchlist-row .hx-primary-button { flex: none; width: auto; padding: 0 var(--space-8); }
.hx-account-profile-row { display: flex; align-items: center; gap: var(--space-8); margin-bottom: var(--space-8); }
.hx-account-profile-info { min-width: 0; }
.hx-account-field { display: flex; gap: var(--space-4); margin: var(--space-2) 0 0; color: var(--ink-2); font-size: var(--font-meta); }
.hx-account-field small { flex: none; color: var(--ink-2); }
.hx-account-field a { color: var(--blue); font-weight: 700; }
/* 63-F (1)：订阅与账单页——每条记录一张卡，跟 .hx-watchlist-row 同一层级但内容
   更多行，改用 border-radius 卡片而不是列表行分隔线（.hx-account-section 本来
   就有 padding，卡片之间靠 margin 隔开）。徽章配色照抄 .hx-pinned-badge 那套
   brand 配色（品牌红本来就是全站的"需要注意"色，未开专门的警示色 token，不
   新增一套）。 */
.hx-billing-list { display: flex; flex-direction: column; gap: var(--space-6); }
.hx-billing-row { padding: var(--space-6); border: var(--border-width) solid var(--line); border-radius: var(--radius-card); }
.hx-billing-row-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
.hx-billing-badge { flex: none; padding: var(--space-1) var(--space-4); border-radius: var(--radius-round); background: var(--brand-soft); color: var(--brand); font-size: var(--font-small); font-weight: var(--font-weight-heading); white-space: nowrap; }
.hx-billing-badge[hidden] { display: none; }
.hx-billing-refund-button { width: auto; margin: var(--space-4) 0 0; min-height: 36px; padding: 0 var(--space-6); font-size: var(--font-small); }
/* 24-F：头像更换的选图+确认按钮一排，选图是 label 包 file input（点哪都能唤起选择器），
   确认按钮平时隐藏，选完图 JS 才显出来 */
.hx-avatar-actions { display: flex; align-items: center; gap: var(--space-6); margin-top: var(--space-2); }
.hx-avatar-actions .hx-primary-button { flex: none; width: auto; padding: 0 var(--space-8); }
.hx-avatar-pick { display: inline-flex; align-items: center; justify-content: center; height: var(--button-height); padding: 0 var(--space-8); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; cursor: pointer; }
.hx-avatar-pick:hover { background: var(--hover); color: var(--ink); }
.hx-article-bar { position: sticky; z-index: 25; top: 0; display: flex; align-items: center; justify-content: space-between; height: var(--mobile-bar-height); padding: 0 var(--space-8); border-bottom: var(--border-width) solid var(--line); background: var(--header-bg); backdrop-filter: blur(var(--space-6)); font-size: var(--font-nav); font-weight: 800; }
.hx-article-bar > a { display: flex; height: 100%; align-items: center; gap: var(--space-10); }
.hx-article-bar svg { width: var(--icon); }
.hx-article-bar-actions { display: flex; align-items: center; gap: var(--space-4); }
.hx-article-bar-actions svg { width: var(--icon-small); }
.hx-article { padding: var(--space-8) var(--space-8) 0; }
.hx-author-row { display: flex; align-items: center; gap: var(--space-6); }
.hx-author-copy { min-width: 0; }
.hx-author-meta { display: flex; min-width: 0; align-items: center; gap: var(--space-2); line-height: 1.35; }
.hx-author-meta .hx-handle { max-width: none; }
/* 188-F：机会库一行一个机会 tag——结构/间距照抄 .hx-side-row（块级、同样的内边距、
   hover 底色），名称+简介在左边纵向堆叠，文章数固定在右边，不随内容换行挤动。 */
.hx-opportunity-row { display: flex; align-items: center; gap: var(--space-6); padding: var(--space-6) var(--space-8); }
.hx-opportunity-row:hover { background: var(--hover); }
.hx-opportunity-copy { flex: 1; min-width: 0; }
.hx-opportunity-name { display: block; font-weight: var(--font-weight-heading); }
.hx-opportunity-description { display: block; margin-top: var(--space-1); overflow: hidden; color: var(--ink-2); font-size: var(--font-meta); text-overflow: ellipsis; white-space: nowrap; }
.hx-opportunity-description[hidden] { display: none; }
.hx-opportunity-count { flex: none; color: var(--ink-2); font-size: var(--font-meta); }

/* 196-F：机会库分组浏览视图——搜索页「机会库」格查询词为空时的默认内容，
   三类（199-F 起展示名改为 当下/热门/备选，接口分类值仍是 trade/hot/
   reserve）各一个灰色背景标题条（跟 .hx-calendar-date 同一个
   "background: var(--field)" 做法），tag 列表 grid 两列、按后端给的 seq
   顺序逐行从左到右填充，两列数量天然均衡（差至多 1 个）。completed=true
   显示黑色（var(--ink)），否则灰色（var(--ink-2)），靠 .is-completed 这个
   class 切换，JS 渲染时按 item.completed 打上去。管理员编辑面板
   （.hx-opp-library-more/.hx-opp-library-edit-panel）只在 isPostAdmin 为真时
   才会被 JS 挂上，访客看到的是不带这些控件的纯展示行——右栏「机会库」卡片
   复用同一套 .hx-opp-library-row（不分组、不建 grid 两列，容器自己是单列
   块级），右栏那边也只会拿到 isAdmin=false 的行。拖动手柄
   （.hx-opp-library-handle）203-F 起备选类目也会出现，但只在这一条 tag
   当天没有更新（today_new===0）时才加，被置顶的那些没有手柄、也不会被当成
   拖动落点的候选（见 hyboox-x.js oppLibraryItemDraggable()/
   computeReserveOrderAfterDrag() 203-F 注释）——提交时不用 DOM 顺序，改用
   202-B 新给的 sort_order 字段重建真正的手动顺序，只移动被拖的那一个 tag，
   199-F 当时因为接口还没有这个字段撤回的那一版在这次重做。 */
.hx-opp-library-group { margin-bottom: var(--space-8); }
.hx-opp-library-group-heading { margin: 0; padding: var(--space-6) var(--space-8); background: var(--field); color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; }
.hx-opp-library-group-list { display: grid; grid-template-columns: 1fr 1fr; }
.hx-opp-library-row, .hx-opp-library-item { display: flex; align-items: center; gap: var(--space-3); min-width: 0; padding: var(--space-6) var(--space-8); border-bottom: var(--border-width) solid var(--line); }
.hx-opp-library-item { flex-direction: column; align-items: stretch; padding: 0; }
.hx-opp-library-item > .hx-opp-library-row { border-bottom: none; }
.hx-opp-library-item.is-dragging { opacity: 0.6; }
.hx-opp-library-row:hover { background: var(--hover); }
.hx-opp-library-seq { flex: none; color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; }
.hx-opp-library-name { flex: 1; min-width: 0; overflow: hidden; color: var(--ink-2); font-weight: var(--font-weight-heading); text-overflow: ellipsis; white-space: nowrap; }
.hx-opp-library-row.is-completed .hx-opp-library-name { color: var(--ink); }
.hx-opp-library-card .hx-opp-library-name, [data-search-results] .hx-opp-library-name { color: var(--ink); }
.hx-opp-library-count { flex: none; color: var(--ink-2); font-size: var(--font-meta); }
.hx-opp-library-handle { flex: none; color: var(--ink-2); cursor: grab; touch-action: none; }
.hx-opp-library-more { flex: none; color: var(--ink-2); }
.hx-opp-library-edit-panel { padding: var(--space-6) var(--space-8); border-bottom: var(--border-width) solid var(--line); background: var(--field); }
.hx-opp-library-edit-panel[hidden] { display: none; }
.hx-opp-library-edit-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.hx-opp-library-edit-actions button, .hx-opp-library-seq-form button, .hx-opp-library-delete-confirm button { height: var(--button-height); padding: 0 var(--space-6); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--bg); color: var(--ink); font-size: var(--font-meta); }
.hx-opp-library-seq-form, .hx-opp-library-delete-confirm { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin-top: var(--space-4); }
.hx-opp-library-seq-form[hidden], .hx-opp-library-delete-confirm[hidden] { display: none; }
.hx-opp-library-seq-form input { width: calc(var(--space-40) * 1.6); height: var(--button-height); padding: 0 var(--space-4); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--bg); color: var(--ink); }
.hx-opp-library-delete-confirm p { flex-basis: 100%; margin: 0; color: var(--ink-2); font-size: var(--font-meta); }
.hx-opp-library-edit-status { margin: var(--space-4) 0 0; color: var(--brand); font-size: var(--font-meta); }
.hx-opp-library-edit-status[hidden] { display: none; }
/* 196-F：右栏「机会库」卡片——行复用 .hx-opp-library-row，容器本身不需要
   grid 两列（右栏窄，单列更合适），「查看更多」照抄 rightRail 其它卡片
   链接行的既有排版（居中、蓝色、加粗）。 */
.hx-opp-library-card-list { display: flex; flex-direction: column; }
.hx-opp-library-card-more { display: block; padding: var(--space-6) var(--space-8); color: var(--blue); font-size: var(--font-meta); font-weight: 700; text-align: center; }
.hx-opp-library-card-more:hover { background: var(--hover); }

@media (max-width: 700px) {
    /* 断点跟 CHANNEL_TRACK_BREAKPOINT（hyboox-x.js 判断手机/桌面端的同一个
       700px）保持一致——手机端屏幕窄，两列会把名字挤成省略号堆在一起，改回
       单列，跟桌面端唯一的差异只是这一条，其它样式（灰色标题条、序号、
       completed 配色、管理员控件）完全共用。 */
    .hx-opp-library-group-list { grid-template-columns: 1fr; }
}

/* 192-F：机会曲线——机会页「#名称」下方、管理页「机会曲线」预览区共用这一份
   结构/样式（JS 的 renderOpportunityCurveBlock 两处都往同一套 data-opp-*
   选择器里填）。图是纯 SVG（viewBox 自适应，不量宽度/不监听 resize），颜色
   全部走 var(--token)，深色模式不用另外补一套覆盖规则。 */
.hx-opp-curve { max-width: 100%; padding: var(--space-8); border-bottom: var(--border-width) solid var(--line); overflow-x: hidden; }
.hx-opp-curve[hidden] { display: none; }
/* 193-F：游客关注后的提示行——没有浮层/toast 组件，就是普通一行文字+链接，
   位置在「关注」按钮所在的 header 和机会曲线区块之间。 */
.hx-opp-follow-guest-note { margin: 0; padding: var(--space-4) var(--space-8); border-bottom: var(--border-width) solid var(--line); color: var(--ink-2); font-size: var(--font-meta); }
.hx-opp-follow-guest-note[hidden] { display: none; }
.hx-opp-follow-guest-note a { color: var(--blue); font-weight: 700; }
.hx-opp-rating { display: flex; align-items: baseline; gap: var(--space-6); margin-bottom: var(--space-6); }
.hx-opp-rating[hidden] { display: none; }
.hx-opp-rating-badge { padding: var(--space-1) var(--space-6); border-radius: var(--radius-round); background: var(--field); color: var(--ink); font-weight: 700; font-size: var(--font-meta); }
.hx-opp-rating-date { color: var(--ink-2); font-size: var(--font-meta); }
.hx-opp-chart-meta { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-4) var(--space-8); min-width: 0; }
.hx-opp-axis-label { flex: none; color: var(--ink); font-size: var(--font-meta); }
.hx-opp-axis-note, .hx-opp-structure-note { margin: var(--space-3) 0 0; color: var(--ink-2); font-size: var(--font-small); line-height: 1.5; }
.hx-opp-axis-note[hidden], .hx-opp-structure-note[hidden] { display: none; }
.hx-opp-legend { display: flex; flex: 1 1 auto; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-3) var(--space-6); min-width: 0; color: var(--ink-2); font-size: var(--font-small); }
.hx-opp-legend-item { display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
.hx-opp-legend-swatch { position: relative; display: inline-block; flex: none; width: var(--space-10); height: var(--space-6); color: var(--ink); }
.hx-opp-legend-actual::before, .hx-opp-legend-forecast::before { position: absolute; top: 50%; right: 0; left: 0; border-top: calc(var(--border-width) * 2) solid currentColor; content: ""; }
.hx-opp-legend-forecast::before { border-top-style: dashed; }
.hx-opp-legend-actual::after, .hx-opp-legend-forecast::after, .hx-opp-legend-overall::after, .hx-opp-legend-server::after { position: absolute; top: 50%; left: 50%; width: var(--space-3); height: var(--space-3); border: var(--border-width) solid currentColor; background: currentColor; content: ""; transform: translate(-50%, -50%); }
.hx-opp-legend-actual::after, .hx-opp-legend-forecast::after, .hx-opp-legend-overall::after { border-radius: var(--radius-round); }
.hx-opp-legend-forecast::after { background: var(--bg); }
.hx-opp-legend-server { color: var(--brand); }
.hx-opp-legend-server::after { transform: translate(-50%, -50%) rotate(45deg); }
.hx-opp-chart { width: 100%; }
.hx-opp-svg { display: block; width: 100%; height: auto; }
.hx-opp-stage-svg { display: block; width: 100%; max-width: 100%; height: auto; overflow: hidden; }
.hx-opp-stage-name { fill: var(--ink); font-size: 11px; font-weight: 700; }
.hx-opp-stage-band { fill: var(--brand-soft); }
.hx-opp-stage-band-baseline { fill: var(--field); }
.hx-opp-stage-band-emerge { fill: var(--yellow-soft); }
.hx-opp-stage-band-peak { fill: var(--yellow-soft); }
.hx-opp-stage-band-narrow { fill: var(--blue-soft); }
.hx-opp-stage-band-fade { fill: var(--field); }
.hx-opp-stage-band-pending { fill: var(--field); stroke: var(--line); stroke-width: var(--border-width); stroke-dasharray: 4 3; }
.hx-opp-stage-gap rect { fill: var(--bg); stroke: var(--line); stroke-width: var(--border-width); stroke-dasharray: 3 2; }
.hx-opp-stage-gap text { fill: var(--ink-2); font-weight: 700; }
.hx-opp-stage-pending-text, .hx-opp-stage-quarter { fill: var(--ink-2); font-size: 10px; }
.hx-opp-stage-forecast { fill: var(--blue-soft); stroke: var(--blue); stroke-width: var(--border-width); stroke-dasharray: 4 3; }
.hx-opp-stage-forecast-label { fill: var(--ink); font-size: 10px; font-weight: 700; }
.hx-opp-stage-now line { stroke: var(--brand); stroke-width: 1.5; stroke-dasharray: 3 3; }
.hx-opp-stage-now text { fill: var(--brand); font-size: 10px; font-weight: 700; }
.hx-opp-point-detail { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-6); min-width: 0; margin-top: var(--space-4); padding: var(--space-4) var(--space-6); border: var(--border-width) solid var(--line); border-radius: var(--radius-medium); background: var(--field); color: var(--ink-2); font-size: var(--font-small); line-height: 1.5; }
.hx-opp-point-detail[hidden] { display: none; }
.hx-opp-point-detail strong { flex-basis: 100%; color: var(--ink); }
.hx-opp-point-detail a, .hx-opp-point-detail span { min-width: 0; overflow-wrap: anywhere; }
.hx-opp-point-detail a { color: var(--blue); font-weight: 700; }
.hx-opp-conclusion { margin: var(--space-6) 0 0; color: var(--ink-2); font-size: var(--font-meta); line-height: 1.6; }
.hx-opp-read { margin: var(--space-6) 0 0; color: var(--ink-2); font-size: var(--font-meta); line-height: 1.6; }
.hx-opp-timeline-list { display: flex; flex-direction: column; gap: var(--space-3); margin: var(--space-6) 0 0; padding: 0; list-style: none; }
.hx-opp-timeline-item { display: flex; flex-wrap: wrap; gap: 0 var(--space-4); font-size: var(--font-meta); }
.hx-opp-timeline-item-no { flex: none; color: var(--ink-2); }
.hx-opp-timeline-item-date { flex: none; color: var(--ink-2); }
.hx-opp-timeline-item-text { color: var(--ink); }
.hx-opp-timeline-item-source { flex-basis: 100%; color: var(--ink-2); }
.hx-opp-sources { margin: var(--space-6) 0 0; color: var(--ink-2); font-size: var(--font-small); }
.hx-opp-sources[hidden] { display: none; }

/* SVG 内部元素——全部用 CSS 控制颜色（不在 JS 里写死 fill/stroke），深色模式
   随 var(--token) 自动切换。 */
.hx-opp-band { fill: var(--yellow-soft); }
.hx-opp-band-b { fill: var(--blue-soft); }
.hx-opp-band-c { fill: var(--brand-soft); }
.hx-opp-band-name { fill: var(--ink); font-size: 11px; font-weight: 700; }
.hx-opp-band-range { fill: var(--ink-2); font-size: 10px; }
.hx-opp-band-leader { stroke: var(--ink-2); stroke-width: 0.8; }
.hx-opp-cap-line { stroke: var(--ink-2); stroke-width: 1; stroke-dasharray: 4 3; }
.hx-opp-cap-label { fill: var(--ink-2); font-size: 10px; }
.hx-opp-y-tick, .hx-opp-x-tick { fill: var(--ink-2); font-size: 10px; }
.hx-opp-grid-line { stroke: var(--line); stroke-width: 1; }
.hx-opp-now line { stroke: var(--brand); stroke-width: 1.5; stroke-dasharray: 3 3; }
.hx-opp-now text { fill: var(--brand); font-size: 10px; font-weight: 700; }
.hx-opp-measured-line { fill: none; stroke: currentColor; stroke-width: 1.6; }
.hx-opp-measured-line-dashed { stroke-dasharray: 5 4; }
.hx-opp-series-overall { color: var(--ink); }
.hx-opp-series-server { color: var(--brand); }
.hx-opp-measured-dot { fill: currentColor; stroke: currentColor; stroke-width: 1.5; cursor: pointer; }
.hx-opp-measured-dot.hx-opp-point-forecast { fill: var(--bg); }
.hx-opp-measured-dot:focus { outline: none; stroke-width: 2.6; }
.hx-opp-pullback { fill: var(--brand); font-size: 10px; font-weight: 700; }
.hx-opp-projection { fill: none; stroke-width: 1.4; stroke-dasharray: 5 4; }
.hx-opp-projection-current { stroke: var(--ink); }
.hx-opp-projection-other { stroke: var(--ink-2); }
.hx-opp-shift-dot { fill: var(--ink); }
.hx-opp-arrow line, .hx-opp-arrow polygon { stroke: var(--ink); }
.hx-opp-arrow line { stroke-width: 1.2; }
.hx-opp-arrow polygon { fill: var(--ink); }
.hx-opp-shift-label { fill: var(--ink); font-size: 10px; font-weight: 700; }
.hx-opp-subrow-base { stroke: var(--line); stroke-width: 1; }
.hx-opp-subrow-actual { fill: var(--ink); }
.hx-opp-subrow-forecast { fill: url(#hx-opp-hatch); stroke: var(--ink-2); stroke-width: 1; }
.hx-opp-hatch-line { stroke: var(--ink-2); stroke-width: 2; }
.hx-opp-subrow-label { fill: var(--ink); font-size: 10px; font-weight: 700; }
.hx-opp-subrow-period { fill: var(--ink-2); font-size: 10px; }
.hx-opp-peak-range { fill: var(--brand-soft); }
.hx-opp-peak-label { fill: var(--ink-2); font-size: 10px; }
.hx-opp-baseline { stroke: var(--ink-2); stroke-width: 1; stroke-dasharray: 4 3; }
.hx-opp-scenario-path { fill: none; stroke-width: 1.4; }
.hx-opp-supply-current .hx-opp-scenario-path { stroke: var(--ink); stroke-dasharray: 5 4; }
.hx-opp-supply-other .hx-opp-scenario-path { stroke: var(--ink-2); stroke-dasharray: 5 4; }
.hx-opp-demand-other .hx-opp-scenario-path { stroke: var(--ink-2); stroke-dasharray: 3 3; }
.hx-opp-demand-current .hx-opp-scenario-path { stroke: #D64545; stroke-width: 1; stroke-dasharray: 2 2; }
.hx-opp-demand-triggered .hx-opp-scenario-path { stroke-width: 2.2; }
.hx-opp-scenario-label { fill: #D64545; font-size: 10px; font-weight: 700; }
.hx-opp-timeline-axis { stroke: var(--line); stroke-width: 1; }
.hx-opp-tl-dot { fill: var(--ink); stroke: var(--ink); stroke-width: 1.4; }
.hx-opp-tl-dot.hx-opp-tl-expected { fill: var(--bg); }
.hx-opp-tl-dot.hx-opp-cat-demand { fill: #E3912B; stroke: #E3912B; }
.hx-opp-tl-dot.hx-opp-cat-demand.hx-opp-tl-expected { fill: var(--bg); }
.hx-opp-tl-dot.hx-opp-cat-supply { fill: #3C9A5C; stroke: #3C9A5C; }
.hx-opp-tl-dot.hx-opp-cat-supply.hx-opp-tl-expected { fill: var(--bg); }
.hx-opp-tl-no { fill: var(--ink-2); font-size: 10px; }
.hx-opp-illustrative { fill: var(--ink-2); font-size: 11px; font-style: italic; }

/* 210-F：驼峰曲线示意状态（measured 为空）专用元素——baseline/peak_range/
   supply_lines/demand_lines 没有来源时的示意占位形状，颜色沿用既有的
   资金需求/新增供给/价格合同三色（跟 .hx-opp-tl-dot.hx-opp-cat-* 同一套色值），
   不新引入变量。 */
.hx-opp-hump-schematic { fill: none; stroke: var(--ink-2); stroke-width: 2.2; opacity: .75; }
.hx-opp-hump-stage { fill: var(--ink-2); font-size: 10px; }
.hx-opp-hump-watermark { fill: var(--ink); font-size: 34px; fill-opacity: .12; }
.hx-opp-baseline-label { fill: var(--ink-2); font-size: 10px; }
.hx-opp-supply-node-dot { fill: none; stroke: #3C9A5C; stroke-width: 2; }
.hx-opp-supply-node-label { fill: #3C9A5C; font-size: 10px; }
.hx-opp-supply-schematic-path { fill: none; stroke: var(--ink); stroke-width: 2; stroke-dasharray: 6 4; }
.hx-opp-demand-schematic-path { fill: none; stroke: #D64545; stroke-width: 1.3; stroke-dasharray: 4 3; }
.hx-opp-hump-pending-note { fill: var(--ink-2); font-size: 10px; }

@media (max-width: 699px) {
    .hx-opp-chart-meta { align-items: flex-start; flex-direction: column; }
    .hx-opp-legend { justify-content: flex-start; width: 100%; }
    .hx-opp-point-detail { flex-direction: column; }
}

/* 212-F：对照原型截图返修——驼峰曲线文字不该继承任何描边/粗体，水印压在
   曲线下层、用 fill-opacity 而不是整体 opacity（stroke:none 时两者对文字
   视觉上等价，但卡面明确要 fill-opacity，便于以后给水印加描边也不会被
   这条带偏）。用 svg 根节点上的 hx-opp-svg-hump 类限定作用域——只有
   oppBuildChart() 给 type=hump 画的 <svg> 才会带这个类，S 曲线的 <svg> 是
   hx-opp-svg-s，选择器完全选不中，S 曲线文字样式不受这里任何一条规则
   影响。(0,1,1) 的选择器特异度稳赢下面所有单类名规则（含共用的
   .hx-opp-y-tick/.hx-opp-x-tick/.hx-opp-now/.hx-opp-pullback/.hx-opp-tl-no
   等），但这些共用类本来就没设过 stroke，这里只是显式补一道防线。 */
.hx-opp-svg-hump text { stroke: none; paint-order: normal; font-weight: 400; }
.hx-opp-svg-hump text.hx-opp-hump-watermark { font-weight: 500; }
.hx-opp-svg-hump .hx-opp-now text { font-weight: 500; }

/* 管理页「机会曲线」——下拉/版本号一行，JSON 文本框照抄 .hx-calendar-entry-title
   这份既有 textarea 样式（边框/圆角/焦点态都是同一套 token），预览区复用
   上面 .hx-opp-curve 那整套。 */
.hx-opp-curve-admin-controls { display: flex; align-items: center; gap: var(--space-6); margin-bottom: var(--space-6); }
.hx-opp-curve-admin-controls .hx-composer-select { flex: 1 1 auto; }
.hx-opp-curve-admin-version { flex: none; color: var(--ink-2); font-size: var(--font-meta); }
.hx-opp-curve-admin-json { width: 100%; min-height: calc(var(--space-40) * 3); padding: var(--space-6) var(--space-8); border: var(--border-width) solid var(--line); border-radius: var(--radius-medium); background: var(--field); color: var(--ink); font-size: var(--font-meta); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; resize: vertical; }
.hx-opp-curve-admin-json:focus { outline: 0; border-color: var(--brand); background: var(--bg); }
.hx-opp-curve-admin-actions { display: flex; gap: var(--space-6); margin-top: var(--space-6); }
.hx-opp-curve-admin-actions .hx-primary-button { width: auto; padding: 0 var(--space-10); }
[data-opp-curve-admin-preview-block] { padding: 0; border-bottom: 0; margin-top: var(--space-8); }

.hx-article-header h1 { margin: var(--space-7) 0 var(--space-4); font-size: var(--font-title); font-weight: 800; line-height: 1.3; }
.hx-article-deck { margin: 0 0 var(--space-4); color: var(--ink-2); font-size: var(--font-article); }
.hx-article-meta { display: flex; flex-wrap: wrap; gap: var(--space-2); padding-bottom: var(--space-6); border-bottom: var(--border-width) solid var(--line); color: var(--ink-2); font-size: var(--font-meta); }
.hx-article-meta a:hover { color: var(--blue); }
.hx-article-header .gh-article-image { margin: var(--space-8) 0 0; overflow: hidden; border-radius: var(--radius-card); }
.hx-article-header .gh-article-image img { width: 100%; }
.hx-article-header figcaption { padding-top: var(--space-2); color: var(--ink-2); font-size: var(--font-meta); }
.hx-article-content { font-size: var(--font-article); line-height: 1.75; overflow-wrap: anywhere; }
.hx-article-content > * { max-width: 100%; }
.hx-article-content p, .hx-article-content ul, .hx-article-content ol { margin: var(--space-8) 0; }
.hx-article-content h2 { margin: var(--space-14) 0 var(--space-4); font-size: var(--font-heading); line-height: 1.35; }
.hx-article-content h3 { margin: var(--space-12) 0 var(--space-4); font-size: var(--font-article); line-height: 1.4; }
.hx-article-content a { color: var(--blue); text-decoration: underline; text-underline-offset: var(--space-1); }
.hx-article-content blockquote { margin: var(--space-10) 0; padding-left: var(--space-8); border-left: var(--tab-line-width) solid var(--brand); color: var(--ink-2); }
.hx-article-content blockquote.hx-callout { padding: var(--space-6) var(--space-8); border-radius: var(--radius-medium); background: var(--field); color: var(--ink); }
.hx-article-content hr { margin: var(--space-10) 0; border: 0; border-top: var(--border-width) solid var(--line); }
.hx-article-content pre, .hx-article-content .kg-code-card { overflow-x: auto; padding: var(--space-8); border-radius: var(--radius-medium); background: var(--field); }
.hx-article-content code { font-size: var(--font-button); }
.hx-article-content table { display: block; max-width: 100%; overflow-x: auto; border-collapse: collapse; }
.hx-article-content th, .hx-article-content td { padding: var(--space-4) var(--space-6); border: var(--border-width) solid var(--line); }
.hx-article-content .kg-image-card, .hx-article-content .kg-gallery-card, .hx-article-content .kg-embed-card, .hx-article-content .kg-bookmark-card, .hx-article-content .kg-callout-card { margin: var(--space-10) 0; }
.hx-article-content .kg-image { width: 100%; border-radius: var(--radius-medium); }
.hx-article-content figcaption { margin-top: var(--space-3); color: var(--ink-2); font-size: var(--font-meta); text-align: center; }
.hx-article-content .kg-button { display: inline-flex; min-height: var(--button-height); padding: var(--space-4) var(--space-8); align-items: center; border-radius: var(--radius-round); background: var(--brand); color: var(--white); font-weight: 700; text-decoration: none; }
.hx-article-content a.hx-button { display: inline-flex; min-height: var(--button-height); padding: var(--space-4) var(--space-8); align-items: center; border-radius: var(--radius-round); background: var(--brand); color: var(--white); font-weight: 700; text-decoration: none; }
/* 点击放大：正文图片加一个放大镜光标提示可点——灯箱本身（.hx-lightbox）的样式见下面 */
.hx-article-content img { cursor: zoom-in; }
.hx-lightbox { position: fixed; z-index: 140; inset: 0; display: grid; place-items: center; padding: var(--space-8); background: rgba(0, 0, 0, .9); opacity: 0; visibility: hidden; transition: opacity var(--motion-medium), visibility var(--motion-medium); }
.hx-lightbox.is-open { opacity: 1; visibility: visible; }
.hx-lightbox-img { max-width: calc(100vw - (var(--space-8) * 2)); max-height: calc(100vh - (var(--space-8) * 2)); width: auto; height: auto; object-fit: contain; cursor: default; box-shadow: var(--shadow); }
.hx-lightbox-close { position: absolute; top: var(--space-8); right: var(--space-8); display: grid; width: var(--space-20); height: var(--space-20); place-items: center; border: 0; border-radius: var(--radius-round); background: rgba(255, 255, 255, .12); color: var(--white); font-size: var(--font-heading); line-height: 1; cursor: pointer; }
.hx-lightbox-close:hover { background: rgba(255, 255, 255, .2); }
.hx-article-fade { position: relative; }
.hx-article-fade::after { content: ""; position: absolute; right: 0; bottom: 0; left: 0; height: 8em; background: linear-gradient(to bottom, transparent, var(--bg)); pointer-events: none; }
.hx-paywall-note { margin-top: var(--space-10); padding: var(--space-9) var(--space-8); border: var(--border-width) solid var(--line); border-radius: var(--radius-card); background: var(--hover); text-align: center; }
.hx-paywall-icon { display: grid; width: var(--avatar-small); height: var(--avatar-small); margin: 0 auto var(--space-4); place-items: center; border-radius: 50%; background: var(--brand); color: var(--white); }
.hx-paywall-icon svg { width: var(--icon-small); }
.hx-paywall-note h2 { margin: 0 0 var(--space-2); font-size: var(--font-heading); }
.hx-paywall-note p { margin: 0 0 var(--space-6); color: var(--ink-2); font-size: var(--font-meta); }
.hx-paywall-note strong { display: block; margin-bottom: var(--space-6); }
/* 付费墙开通/登录按钮居中并适度加长；窄屏最多占满组件内容宽度。 */
.hx-paywall-note [data-open-pay], .hx-paywall-note [data-open-login] { width: min(100%, calc(var(--space-40) * 3)); margin-right: auto; margin-left: auto; }
.hx-comments { margin: var(--space-8) calc(-1 * var(--space-8)) 0; padding: var(--space-8); border-top: var(--border-width) solid var(--line); }
.hx-comments h2 { font-size: var(--font-heading); }
/* 220-B：游客评论区登录按钮——居中显示，宽度按内容走（不撑满整行），375
   窄屏下同样居中、不会横向溢出。 */
.hx-comments-login-button { width: auto; min-width: calc(var(--space-40) * 2); margin: var(--space-6) auto 0; padding-left: var(--space-16); padding-right: var(--space-16); }

.hx-mobile-bar, .hx-mobile-dock { display: none; }
/* 57-F：顶栏 .hx-mobile-bar 和栏目条 .hx-feed-tabs 收起/出现要是同一个动作，不能
   分成两条各自的 transform——桌面端这层壳只是占位，不生成盒子，不影响 .hx-left-rail/
   .hx-rail-reopen 这些兄弟节点的布局；手机端的实际定位/收起效果见 700px 媒体查询里
   .hx-mobile-header-group 那条。 */
.hx-mobile-header-group { display: contents; }
.hx-rail-reopen { position: fixed; z-index: 70; top: var(--space-6); display: none; align-items: center; gap: var(--space-3); padding: var(--space-4) var(--space-6); border-radius: var(--radius-round); background: var(--bg-elev); box-shadow: var(--shadow); color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; }
.hx-rail-reopen svg { width: var(--icon-small); }
.hx-rail-reopen-left { left: var(--space-6); }
.hx-rail-reopen-right { right: var(--space-6); }
.hx-shell[data-left-rail="closed"] .hx-rail-reopen-left,
.hx-shell[data-right-rail="closed"] .hx-rail-reopen-right { display: flex; }

/* 129-F：宽度从铺满屏幕的 100% 改成 X 风格的 85%——右侧留出的 15% 露出
   .hx-drawer-scrim 遮罩（上面这条本来就是 inset:0 全屏遮罩、
   background:var(--scrim)，已经是 rgba(0,0,0,0.40)，宽度改小后自动露出
   变暗的页面，遮罩本身不用改）；点遮罩关闭是已有逻辑
   （hyboox-x.js `[data-close-drawer]` 的 click 监听），没有改。
   transform:translateX(-110%) 是相对抽屉自身宽度的百分比，宽度改了也还是
   完全移出屏幕，打开/关闭的过渡动画不用跟着调整数值。小程序 118-xcx 已经
   照同一个做法把宽度改成 85%，这里让网站手机端跟上。 */
.hx-drawer-scrim { position: fixed; z-index: 90; inset: 0; background: var(--scrim); }
.hx-account-drawer { position: fixed; z-index: 100; top: 0; bottom: 0; left: 0; width: 85%; padding: var(--space-24) 0 var(--space-10); overflow-y: auto; background: var(--bg-elev); box-shadow: var(--shadow); transform: translateX(-110%); transition: transform var(--motion-medium); }
.hx-account-drawer.is-open { transform: none; }
.hx-account-drawer.is-dragging { transition: none; }
.hx-drawer-close { position: absolute; top: var(--space-6); right: var(--space-6); }
.hx-drawer-profile { display: flex; flex-direction: column; padding: 0 var(--space-8) var(--space-6); border-bottom: var(--border-width) solid var(--line); }
.hx-drawer-profile strong { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-4); font-weight: var(--font-weight-heading); }
.hx-drawer-profile > span:last-child { color: var(--ink-2); }
.hx-drawer-profile .hx-primary-button { display: inline-flex; align-self: flex-start; min-height: 0; padding: var(--space-3) var(--space-10); background: var(--brand); }
.hx-drawer-avatar { display: grid; width: var(--avatar-large); height: var(--avatar-large); overflow: hidden; place-items: center; border-radius: var(--radius-round); background: var(--field); color: var(--ink-2); font-weight: 700; }
.hx-drawer-avatar img { width: 100%; height: 100%; object-fit: cover; }
.hx-drawer-avatar svg { width: var(--icon-large); }
.hx-drawer-links { padding: var(--space-3) 0; }
.hx-drawer-links [data-drawer-item] { display: flex; align-items: center; gap: var(--space-8); width: 100%; padding: var(--space-7) var(--space-8); font-size: var(--font-nav); font-weight: 700; text-align: left; }
.hx-drawer-links [data-drawer-item]:hover { background: var(--hover); }
.hx-drawer-links [hidden] { display: none; }
.hx-drawer-links svg { width: var(--icon); }
.hx-drawer-separator { height: var(--border-width); margin: var(--space-3) var(--space-8); background: var(--line); }
.hx-account-nav-separator { display: none; }
.hx-drawer-links .hx-account-nav-separator { display: block; height: var(--border-width); margin: var(--space-3) var(--space-8); background: var(--line); }
.hx-drawer-plan { margin: var(--space-6) var(--space-8); padding: var(--space-8); border: var(--border-width) solid var(--line); border-radius: var(--radius-card); }
.hx-drawer-plan > span { color: var(--brand); font-size: var(--font-meta); font-weight: 700; }
.hx-drawer-plan h2 { margin: var(--space-2) 0 var(--space-4); font-size: var(--font-heading); }
.hx-drawer-plan p { margin: 0 0 var(--space-6); color: var(--ink-2); font-size: var(--font-meta); }
.hx-appearance { position: fixed; z-index: 120; inset: 0; display: grid; padding: var(--space-8); place-items: center; background: var(--scrim); }
.hx-appearance[hidden] { display: none; }
.hx-appearance > section { position: relative; width: min(100%, var(--modal-width)); padding: var(--space-10); border-radius: var(--radius-card); background: var(--bg-elev); box-shadow: var(--shadow); }
.hx-appearance h2 { margin-bottom: var(--space-8); }
.hx-appearance h3 { margin: var(--space-8) 0 var(--space-3); color: var(--ink-2); font-size: var(--font-meta); }
/* 2026-11-03：首页"+"编辑层，仿推特「管理时间线」——壳子是 .hx-appearance
   原样（桌面端居中卡片），内容区自己撑满卡片高度、分两块各自可滚动，不是
   像发帖/日历录入那样一整块一起滚。手机宽度（<700px）另有整页铺满的
   覆盖规则，见下面 700px 媒体查询区块（跟 .hx-composer-modal 同一个
   断点/同一套"padding:0 + 子元素100vh+border-radius:0"写法）。 */
.hx-plus-editor > section { display: flex; flex-direction: column; max-height: calc(100dvh - (var(--space-8) * 2)); padding: 0; overflow: hidden; }
.hx-plus-editor-header { display: flex; flex: none; align-items: center; justify-content: space-between; gap: var(--space-4); padding: var(--space-6) var(--space-8); border-bottom: var(--border-width) solid var(--line); }
.hx-plus-editor-header h2 { margin: 0; font-size: var(--font-tab); }
/* 2026-11-04：董哥反馈 X 关闭按钮颜色太浅看不清。第一轮只改了 color（--ink-2
   换成 --ink）没解决，真机截图还是浅灰——用 getComputedStyle 查了才发现真正
   原因不是颜色，是 fill：这个弹窗的壳子是全站共用的 .hx-appearance，而
   assets/css/hyboox-x.css 里有一条 `.hx-appearance svg { fill: none; stroke:
   currentColor; ... }`（账户抽屉/日历录入等一批线框风格图标用的），对所有
   .hx-appearance 内部的 svg 一视同仁。icons/close.hbs 这个 ✕ 图标不是线框图标，
   是单个实心 <path>（fill="currentColor"，没有 stroke），被这条规则一压，
   fill 变 none、只剩 1.9px 描边勾勒轮廓——不管 color 调多深，视觉上都只是一圈
   极细的灰边，这才是"看不清"的真正原因，不是对比度不够。这里单独把这颗
   ✕ 的 fill/stroke 改回实心图标该有的样子，不动 .hx-appearance svg 那条通用
   规则（线框图标们还要靠它）。 */
.hx-plus-editor-close { color: var(--ink); }
/* .hx-appearance svg 那条通用线框规则跟这条选择器权重一样（都是"一个类+一个
   元素"），源码顺序它排在后面会赢，颜色/描边压不过去——这里用
   .hx-plus-editor-header 再绕一层把权重提高一级，不靠 !important、也不用去动
   排在后面那条通用规则本身（其它线框图标还在用它）。 */
.hx-plus-editor-header .hx-plus-editor-close svg { fill: currentColor; stroke: none; }
.hx-plus-editor-body { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.hx-plus-editor-section { padding: var(--space-6) 0; border-bottom: var(--border-width) solid var(--line); }
.hx-plus-editor-section:last-child { border-bottom: 0; }
.hx-plus-editor-section h3 { margin: 0; padding: 0 var(--space-8) var(--space-4); color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; }
.hx-plus-editor-list { margin: 0; padding: 0; list-style: none; }
.hx-plus-editor-row { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-6) var(--space-8); }
.hx-plus-editor-row-name { flex: 1 1 auto; overflow: hidden; color: var(--ink); font-size: var(--font-tab); text-overflow: ellipsis; white-space: nowrap; }
/* 2026-11-03（推特网页版还原，二轮修正）：删除按钮原来是实心40px圆底白字，
   跟"+"按钮改款（细线描边、透明底、28px）不是同一套语言，董哥反馈"尺寸太大"
   ——改成跟"+"按钮同一套视觉规格（同样 --space-14=28px、同样细线描边
   透明底），只换红色，两个按钮现在是视觉上成对的控件，不是一大一小。
   2026-11-04：原来靠 .is-editing 门控、跟拖拽手柄互斥显示，董哥这次反馈
   改掉——去掉"编辑/完成"这个状态之后，删除按钮常驻显示，不再需要先点
   "编辑"。 */
.hx-plus-editor-row-remove { flex: none; display: grid; place-items: center; width: var(--space-14); height: var(--space-14); border-radius: var(--radius-round); border: 1.5px solid var(--plus-editor-remove); background: transparent; color: var(--plus-editor-remove); font-size: var(--font-tab); font-weight: 700; line-height: 1; }
.hx-plus-editor-row-remove:hover { background: color-mix(in srgb, var(--plus-editor-remove) 10%, transparent); }
.hx-plus-editor-row-add { flex: none; display: none; width: var(--space-14); height: var(--space-14); border-radius: var(--radius-round); border: 1.5px solid var(--plus-editor-add); background: transparent; color: var(--plus-editor-add); font-size: var(--font-tab); font-weight: 700; line-height: 1; }
.hx-plus-editor-row-add { display: grid; place-items: center; }
.hx-plus-editor-row-add:hover { background: color-mix(in srgb, var(--plus-editor-add) 10%, transparent); }
/* 2026-11-03（推特网页版还原）：「已置顶」纵向拖拽排序手柄，桌面鼠标专用，
   常驻可拖。.is-dragging 是真正进入拖动态才加的（wirePinnedDragReorder()，
   hyboox-x.js），跟日历事件列表/顶栏自定义标签那两处拖拽排序用的是同一套
   视觉反馈（轻微半透明+投影+抓手光标变成抓取态），风格统一。
   2026-11-04：手柄和红色删除按钮这次改成常驻同时显示（见 buildRow() 里
   DOM 顺序本来就是删除按钮在前、手柄在后，布局上删除按钮在左——不用再靠
   .is-editing 互斥，这条门控规则跟"编辑/完成"状态一起去掉了）。 */
.hx-plus-editor-row-handle { flex: none; display: grid; width: var(--space-20); height: var(--space-20); place-items: center; border-radius: var(--radius-medium); color: var(--ink-2); cursor: grab; touch-action: none; }
.hx-plus-editor-row-handle:hover { background: var(--hover); color: var(--ink); }
.hx-plus-editor-row-handle svg { width: var(--icon-small); fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.hx-plus-editor-row.is-dragging { position: relative; z-index: 5; opacity: .6; box-shadow: var(--shadow); background: var(--bg-elev); cursor: grabbing; }
.hx-plus-editor-empty { margin: 0; padding: var(--space-4) var(--space-8) var(--space-6); color: var(--ink-2); font-size: var(--font-meta); }
/* 2026-11-04：编辑层列表每一行的图标去掉——JS 侧 buildRow 已经不再调用
   buildRowIcon（整个函数删掉了），这里的规则没有对应 DOM 了，见顶栏那份
   图标删除点（上面 .hx-feed-tab-custom-link 附近）的同一条说明。 */
/* 164-F：「收藏到桌面」提示框——外壳视觉复用 .hx-appearance 那套 section
   样式（宽度/圆角/背景/阴影完全一致），但开合机制不一样：这里默认
   opacity:0+visibility:hidden+pointer-events:none，.is-open 才反过来，
   不用 [hidden]（那是 display:none）。原因见 shortcut-hint.hbs 顶部
   注释——144-F/144-xcx 踩过"opacity 隐藏但 pointer-events 没关掉"的坑，
   这次直接用安全写法，不留同样的风险。 */
.hx-shortcut-hint { position: fixed; z-index: 120; inset: 0; display: grid; padding: var(--space-8); place-items: center; background: var(--scrim); opacity: 0; visibility: hidden; pointer-events: none; transition: opacity var(--motion-medium), visibility var(--motion-medium); }
.hx-shortcut-hint.is-open { opacity: 1; visibility: visible; pointer-events: auto; }
.hx-shortcut-hint > section { position: relative; width: min(100%, var(--modal-width)); padding: var(--space-10); border-radius: var(--radius-card); background: var(--bg-elev); box-shadow: var(--shadow); }
.hx-shortcut-hint h2 { margin-bottom: var(--space-8); }
.hx-shortcut-hint-steps { margin: 0 0 var(--space-8); padding-left: var(--space-8); color: var(--ink); font-size: var(--font-body); line-height: 1.6; }
.hx-shortcut-hint-steps li { margin-bottom: var(--space-3); }
.hx-shortcut-hint-steps li:last-child { margin-bottom: 0; }
.hx-segmented { display: flex; gap: var(--space-3); }
.hx-segmented button { flex: 1; padding: var(--space-4); border-radius: var(--radius-round); background: var(--field); color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; text-align: center; }
.hx-segmented button[aria-pressed="true"] { background: var(--blue-soft); color: var(--blue); }
/* 49-F (2/3)：composer.hbs 照 X 重做——头像 + 三个下拉（栏目/权限/作者）、
   摘要、正文、图片缩略图、定时提示行、底栏，都是新结构，样式统一收在这一段。 */
/* 49-F 真机验收补：桌面弹窗照 X 加宽到 600px（.hx-composer-modal 前缀把
   specificity 提到比通用的 .hx-appearance > section 高，压住共用的 430px
   --modal-width，不改那个变量——login/pay/appearance 几个弹窗都吃它，不能
   跟着一起变宽），三个下拉改成不换行、等分宽度，一行放下不再挤成两行。
   手机端全屏时的横向滚动规则在下面 700px 媒体查询里维持不变。 */
.hx-composer-modal .hx-composer-dialog { width: min(100%, 680px); }
/* 日历「发布事件」弹窗尺寸对齐发帖/文章编辑窗口——复用 composer 弹窗同一套
   宽度/高度公式（680px 宽、max-height 卡 100dvh 可视高度），不是另起一套
   数字；跟上面 .hx-composer-modal 同一个套路，新开 .hx-calendar-entry-modal
   把 specificity 提过 .hx-appearance > section 共用的 430px，不改
   --modal-width 本身（login/pay/appearance 几个窄弹窗还要吃这个变量）。
   原来没有 max-height/overflow，矮视口下表单被撑到底部不可见也滚不到；这次
   顺带补上，宽高都对齐的同时修了这个潜在问题。 */
.hx-calendar-entry-modal .hx-calendar-entry-dialog { width: min(100%, 680px); max-height: calc(100vh - (var(--space-8) * 2)); max-height: calc(100dvh - (var(--space-8) * 2)); overflow-y: auto; }
.hx-composer-modal .hx-secondary-button { min-height: var(--button-height); padding: 0 var(--space-8); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--bg-elev); color: var(--ink); font-size: var(--font-button); font-weight: 700; }
.hx-composer-modal .hx-secondary-button:hover { background: var(--hover); }
.hx-composer-actions { display: flex; justify-content: flex-end; gap: var(--space-4); }
.hx-composer-actions .hx-primary-button { width: auto; }
/* 220-F：预览外壳只负责弹层、切换器和宽度模拟；帖子/文章/付费墙内容本身
   继续使用站内现成的 .hx-post-card、.hx-article-content、.hx-paywall-note。 */
.hx-composer-modal .hx-composer-preview-dialog { display: flex; flex-direction: column; width: min(100%, calc(var(--center-width) + (var(--space-40) * 2))); max-height: calc(100dvh - (var(--space-8) * 2)); padding: 0; overflow: hidden; }
.hx-composer-preview-dialog[hidden] { display: none; }
.hx-composer-preview-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-6); padding: var(--space-8); border-bottom: var(--border-width) solid var(--line); }
.hx-composer-preview-head h2 { flex: 1; margin: 0; }
.hx-composer-preview-switches { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.hx-composer-preview-switch { display: flex; padding: var(--space-1); border-radius: var(--radius-round); background: var(--field); }
.hx-composer-preview-switch button { padding: var(--space-4) var(--space-6); border-radius: var(--radius-round); color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; }
.hx-composer-preview-switch button[aria-pressed="true"] { background: var(--bg-elev); color: var(--ink); box-shadow: var(--shadow); }
.hx-composer-preview-warning { margin: 0; padding: var(--space-5) var(--space-8); background: var(--brand-soft); color: var(--brand); font-size: var(--font-meta); font-weight: 700; text-align: center; }
.hx-composer-preview-warning[hidden] { display: none; }
.hx-composer-preview-scroll { flex: 1; min-height: 0; padding: var(--space-8); overflow: auto; background: var(--page); }
.hx-composer-preview-stage { width: min(100%, var(--center-width)); margin: 0 auto; border: var(--border-width) solid var(--line); background: var(--bg); }
.hx-composer-preview-stage[data-preview-width="phone"] { width: min(100%, var(--preview-phone-width)); }
.hx-composer-preview-stage [data-composer-preview-pane][hidden] { display: none; }
.hx-composer-preview-card { border-bottom: 0; }
.hx-composer-preview-taxonomy, .hx-composer-preview-opportunities { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-3); font-size: var(--font-meta); }
.hx-composer-preview-taxonomy a, .hx-composer-preview-opportunities a { color: var(--blue); }
.hx-composer-preview-opportunities:empty { display: none; }
.hx-composer-preview-stage .hx-article { min-width: 0; }
.hx-composer-preview-stage .hx-paywall-note { margin-right: var(--space-8); margin-left: var(--space-8); }
.hx-composer-preview-note { margin: 0; padding: var(--space-4) var(--space-8); border-top: var(--border-width) solid var(--line); color: var(--ink-2); font-size: var(--font-small); text-align: center; }
.hx-composer-preview-footer { display: flex; justify-content: flex-end; gap: var(--space-4); padding: var(--space-6) var(--space-8); border-top: var(--border-width) solid var(--line); }
/* 真机验收补：正文写长会把整个弹窗撑到视口外，底栏和发布按钮就看不见了。
   弹窗本身改成 flex 列，高度封顶在视口内；表单也是一列 flex，只有
   .hx-composer-scroll 吃 flex:1+overflow-y:auto 去滚。
   踩了一个 flex 的坑：光靠"没给 flex:1 就不参与滚动"不够——摘要输入框
   显式 height:44px，但 flex 子项的自动最小高度（min-height:auto）默认是按
   内容算的，撑爆时它可以一路缩到单行文字的高度（实测缩到 17px），显式
   height 不是下限、min-height 才是。底栏/发布按钮没踩这个坑是因为凑巧
   自身内容的最小高度本来就跟设定高度差不多（发布按钮还额外用的是
   min-height，这个天然就是下限）。统一给表单里"不该滚"的子项
   flex-shrink:0，只让标题/摘要/正文这个合并区域自己收缩，底部操作条保持在
   正文之后。100dvh 兜底 100vh 前面那行，支持的浏览器会覆盖成"实际可见
   视口"，为移动端弹出键盘那条埋个头（见下面 227-F 真机复测那条，已验证）。
   232-F：标题/摘要和正文编辑区一度被拆成两个各自独立 flex:1+overflow-y:auto
   的容器（.hx-composer-scroll 和已删除的 .hx-composer-editor-region），从
   标题滚到正文会先触底再切到第二条滚动条，体验是断的。现在只保留
   .hx-composer-scroll 一个滚动容器，正文编辑区（.hx-composer-editor-shell）
   挪进来当它的直接子节点，回到这条注释最初描述的"只有一个滚动区"。 */
.hx-composer-dialog { position: relative; display: flex; flex-direction: column; max-height: calc(100vh - (var(--space-8) * 2)); max-height: calc(100dvh - (var(--space-8) * 2)); }
.hx-composer-dialog[hidden] { display: none; }
[data-composer-form] { display: flex; flex-direction: column; flex: 1; min-height: 0; }
[data-composer-form] > * { flex-shrink: 0; }
.hx-composer-scroll { flex: 1; min-height: 0; overflow-y: auto; }
.hx-composer-toolbar-host { flex: none; min-width: 0; }
.hx-composer-head { display: flex; align-items: center; gap: var(--space-6); margin-bottom: var(--space-6); }
.hx-composer-avatar { display: grid; flex: none; width: var(--avatar); height: var(--avatar); overflow: hidden; place-items: center; border-radius: var(--radius-round); background: var(--field); color: var(--ink-2); }
.hx-composer-avatar img { width: 100%; height: 100%; object-fit: cover; }
.hx-composer-avatar svg { width: var(--icon); }
.hx-composer-selects { display: flex; flex-wrap: nowrap; gap: var(--space-3); min-width: 0; }
.hx-composer-select { flex: 1 1 0; min-width: 0; height: var(--button-height); padding: 0 var(--space-6); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); color: var(--ink); font-size: var(--font-meta); font-weight: 700; }
.hx-composer-select:focus-visible { outline: 0; border-color: var(--brand); background: var(--bg); }
.hx-composer-tiers { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-6); padding: var(--space-5); border: var(--border-width) solid var(--line); border-radius: var(--radius-medium); }
.hx-composer-tiers label { display: flex; align-items: center; gap: var(--space-2); font-size: var(--font-meta); }
/* 196-F：机会 tag 搜索多选——选中的 chip 一排，下面是搜索框+结果下拉（下拉
   相对搜索框定位，不用跟链接弹层那样算 position:fixed 坐标，正文区本来就是
   纵向滚动容器，不会被祖先裁掉）。 */
.hx-composer-taxonomy-row { display: flex; align-items: flex-end; gap: var(--space-3); min-width: 0; }
.hx-composer-taxonomy-row .hx-composer-opportunity { flex: 1 1 auto; min-width: 0; width: auto; }
.hx-composer-opportunity { width: 60%; max-width: 100%; margin-top: var(--space-6); margin-bottom: var(--space-6); }
.hx-composer-tweet-select { flex: 0 0 auto; margin-bottom: var(--space-6); }
.hx-composer-opportunity-chips { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.hx-composer-opportunity-chips:not(:empty) { margin-bottom: var(--space-4); }
.hx-composer-opportunity-chip { display: inline-flex; align-items: center; gap: var(--space-2); height: calc(var(--button-height) * 0.7); padding: 0 var(--space-3) 0 var(--space-6); border-radius: var(--radius-round); background: var(--field); color: var(--ink); font-size: var(--font-meta); font-weight: 700; }
.hx-composer-opportunity-chip button { display: grid; width: var(--icon-small); height: var(--icon-small); place-items: center; border-radius: var(--radius-round); color: var(--ink-2); }
.hx-composer-opportunity-chip button:hover { background: var(--hover); color: var(--ink); }
.hx-composer-opportunity-search { position: relative; }
.hx-composer-opportunity-input { width: 100%; height: var(--button-height); padding: 0 calc(var(--avatar-small) + var(--space-2)) 0 var(--space-6); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); color: var(--ink); font-size: var(--font-meta); }
.hx-composer-opportunity-input:focus-visible { outline: 0; border-color: var(--brand); background: var(--bg); }
/* 220-A：下拉箭头——复用全站统一的 32px 圆形图标按钮样式，贴在搜索框右边
   竖直居中；输入框右内边距（上面那条）腾出来的空间刚好够它不挡住文字。 */
.hx-composer-opportunity-toggle { position: absolute; top: 50%; right: var(--space-2); transform: translateY(-50%); }
.hx-composer-opportunity-toggle svg { transition: transform var(--motion-medium); }
.hx-composer-opportunity-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
.hx-composer-opportunity-results { position: absolute; z-index: 5; top: calc(100% + var(--space-2)); left: 0; right: 0; max-height: calc(var(--space-40) * 3); overflow-y: auto; border: var(--border-width) solid var(--line); border-radius: var(--radius-medium); background: var(--bg-elev); box-shadow: var(--shadow); }
.hx-composer-opportunity-results[hidden] { display: none; }
.hx-composer-opportunity-result { display: block; width: 100%; padding: var(--space-5) var(--space-6); text-align: left; font-size: var(--font-meta); }
.hx-composer-opportunity-result:hover { background: var(--hover); }
.hx-composer-opportunity-result-empty { padding: var(--space-5) var(--space-6); color: var(--ink-2); font-size: var(--font-meta); }
/* 51-F：标题输入框在三个下拉下面、摘要上面——跟摘要共用同一份基础样式，
   摘要额外带 margin-top 隔开两者（标题前面已经有 .hx-composer-head/
   .hx-composer-tiers 自己的 margin-bottom，不用再加）。 */
.hx-composer-title, .hx-composer-excerpt { width: 100%; height: var(--button-height); padding: 0 var(--space-6); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); font-size: var(--font-meta); }
.hx-composer-title:focus-visible, .hx-composer-excerpt:focus-visible { outline: 0; border-color: var(--brand); background: var(--bg); }
/* 220-D：标题/摘要是原子 <input>，没法只给其中一段文字加下划线——镜像层
   （见下面 .hx-composer-highlight）垫在底下负责画命中词的波浪线，input 自己
   的背景改透明露出镜像层，文字/光标/选区完全还是 input 原生的，不受影响。
   焦点态也强制透明（覆盖上面 focus-visible 那条背景规则，边框色不受影响）
   ——不然输入时一聚焦背景变回不透明，正在改的词下划线就被挡住看不见了，
   代价是焦点态原本"背景变亮"这个细节效果没有了。 */
.hx-composer-field { position: relative; }
.hx-composer-field > input { position: relative; z-index: 1; background: transparent; }
.hx-composer-field > input:focus-visible { background: transparent; }
.hx-composer-field + .hx-composer-field { margin-top: var(--space-6); }
.hx-composer-highlight { position: absolute; z-index: 0; inset: 0; display: flex; align-items: center; height: var(--button-height); padding: 0 var(--space-6); overflow: hidden; white-space: pre; border: var(--border-width) solid transparent; border-radius: var(--radius-round); background: var(--field); font-size: var(--font-meta); color: transparent; pointer-events: none; }
.hx-composer-highlight mark { background: transparent; color: transparent; text-decoration-line: underline; text-decoration-style: wavy; text-decoration-color: var(--brand); text-decoration-skip-ink: none; }
.hx-composer-editor-shell { margin-top: var(--space-6); overflow: hidden; border: var(--border-width) solid var(--line); border-radius: var(--radius-medium); background: var(--field); }
.hx-composer-editor-shell:focus-within { border-color: var(--brand); }
.hx-composer-input { width: 100%; min-height: calc(var(--space-40) * 2); margin-top: 0; padding: var(--space-6); border: 0; border-radius: 0; background: transparent; overflow-wrap: anywhere; }
.hx-composer-input:focus-visible { outline: 0; border-color: transparent; }
.hx-composer-input:empty::before { content: attr(data-placeholder); color: var(--ink-2); }
.hx-composer-input b, .hx-composer-input strong { font-weight: 700; }
.hx-composer-input i, .hx-composer-input em { font-style: italic; }
.hx-composer-input pre { max-width: 100%; overflow-x: auto; padding: var(--space-6); border-radius: var(--radius-medium); background: var(--bg); white-space: pre-wrap; }
.hx-composer-input blockquote.hx-callout { padding: var(--space-6) var(--space-8); border-left: var(--tab-line-width) solid var(--brand); border-radius: var(--radius-medium); background: var(--bg); }
.hx-composer-input a.hx-button { display: inline-flex; min-height: var(--button-height); padding: var(--space-4) var(--space-8); align-items: center; border-radius: var(--radius-round); background: var(--brand); color: var(--white); font-weight: 700; text-decoration: none; }
/* 220-D：正文命中敏感词——红色波浪下划线，文字颜色/字重原样不变（不是新增
   视觉强调，只是加一条装饰线），background 显式设透明防止 <mark> 默认的
   黄底被浏览器 UA 样式带出来。 */
.hx-composer-input mark.hx-sensitive-hit { background: transparent; color: inherit; text-decoration-line: underline; text-decoration-style: wavy; text-decoration-color: var(--brand); text-decoration-skip-ink: none; }
/* 220-E：空白瘦身——原来是单层 flex-wrap，靠插入组 flex:1 1 100% 的百分比
   强制自己换行；工具条 position:fixed 没有显式宽度（靠内容撑开形状），
   百分比 flex-basis 在这种"宽度不确定"的祖先上解析时，Chromium 会把整条
   工具条撑到 max-width 上限而不是内容实际宽度，表现为左右两块大片空白。
   改成显式按行堆叠（每一排包一层 .hx-composer-toolbar-row），不再用任何
   百分比 flex-basis 强制换行，宽度就只取决于真实按钮内容。 */
.hx-composer-toolbar { position: fixed; z-index: 130; display: flex; flex-direction: column; max-width: calc(100vw - var(--space-16)); align-items: center; gap: var(--space-1); padding: var(--space-2); border-radius: var(--radius-medium); background: var(--ink); box-shadow: var(--shadow); }
.hx-composer-toolbar.is-composer-fixed { position: static; z-index: auto; width: 100%; max-width: none; box-sizing: border-box; flex-direction: column; align-items: stretch; gap: 0; margin-top: 0; overflow: visible; border-radius: 0; background: var(--field); box-shadow: none; color: var(--ink); }
.hx-composer-toolbar.is-composer-fixed > .hx-composer-toolbar-row { flex: 0 0 auto; flex-wrap: nowrap; justify-content: flex-start; max-width: none; gap: var(--space-2); padding: var(--space-1) 0; overflow-x: auto; }
.hx-composer-toolbar.is-composer-fixed > .hx-composer-toolbar-row > * { flex: 0 0 auto; }
.hx-composer-toolbar.is-composer-fixed button { width: calc(var(--space-12) + var(--space-4)); height: calc(var(--space-12) + var(--space-4)); color: var(--ink-2); font-size: var(--font-meta); }
.hx-composer-toolbar.is-composer-fixed button:hover { background: var(--hover); color: var(--ink); }
.hx-composer-toolbar.is-composer-fixed .hx-composer-toolbar-insert-group { display: flex; flex: 1 1 auto; align-items: center; gap: var(--space-1); padding-top: 0; border-top: 0; }
.hx-composer-toolbar.is-composer-fixed .hx-composer-toolbar-insert-group[hidden] { display: none; }
.hx-composer-toolbar.is-composer-fixed .hx-composer-toolbar-insert-row { display: flex; flex-wrap: wrap; justify-content: flex-start; gap: var(--space-2); }
.hx-composer-toolbar.is-composer-fixed .hx-composer-toolbar-insert-group button { width: auto; min-width: calc(var(--space-24) + var(--space-8)); padding: 0 var(--space-4); border-radius: var(--radius-small); }
.hx-composer-toolbar-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--space-1); max-width: 100%; }
.hx-composer-toolbar button { display: grid; width: var(--space-24); height: var(--space-24); place-items: center; border-radius: var(--radius-round); color: var(--bg); font-size: var(--font-button); }
.hx-composer-toolbar button:hover { background: var(--nav-hover); }
/* 220-C2：选区工具条的插入组固定另起一组；按钮使用文字所需宽度，整组最大
   不超过工具条。220-E：insert-group 自己也改成列方向（按钮行 + 提示条
   两个子项各占一行），避免"付费分隔已存在"提示条显示时，同样的百分比
   换行 bug 在这一层重演（之前 flex:1 1 100% 的提示条会把 insert-group
   自己也撑宽）。 */
.hx-composer-toolbar-insert-group { display: flex; flex-direction: column; align-items: center; gap: var(--space-1); max-width: 100%; padding-top: var(--space-2); border-top: var(--border-width) solid var(--nav-hover); }
.hx-composer-toolbar-insert-group[hidden] { display: none; }
.hx-composer-toolbar .hx-composer-toolbar-insert-group button { width: auto; padding: 0 var(--space-3); white-space: nowrap; }
.hx-composer-toolbar .hx-composer-toolbar-insert-group button:disabled { color: var(--ink-2); cursor: not-allowed; }
.hx-composer-insert-notice { padding: var(--space-2) var(--space-3); color: var(--bg); font-size: var(--font-meta); text-align: center; }
.hx-composer-paywall-notice { display: block; padding: var(--space-2) var(--space-3); color: var(--bg); font-size: var(--font-meta); text-align: center; }
.hx-composer-paywall-notice[hidden] { display: none; }
/* 203-F：格式刷武装状态——选中源文字点了格式刷之后，按钮亮起来提示"正等着
   选目标文字"，选完目标套用完样式会自动摘掉这个 class，不需要再点一次关闭。 */
.hx-composer-toolbar button[data-format="paintbrush"].is-armed { background: var(--nav-hover); color: var(--brand); }
/* 203-F：段落样式下拉——跟工具条其它按钮一样高、嵌在同一条深色胶囊里，边框去掉、
   背景透明继承胶囊颜色，只在 hover 时跟按钮一样提示可交互。 */
.hx-composer-toolbar-style { height: calc(var(--space-12) + var(--space-4)); max-width: none; padding: 0 var(--space-3); border: 0; border-radius: var(--radius-small); background: transparent; color: var(--bg); font-size: var(--font-meta); }
.hx-composer-toolbar.is-composer-fixed .hx-composer-toolbar-style { color: var(--ink-2); }
.hx-composer-toolbar.is-composer-fixed .hx-composer-toolbar-style:hover { background: var(--hover); color: var(--ink); }
.hx-composer-toolbar.is-composer-fixed .hx-composer-toolbar-style option { color: var(--ink); }
.hx-composer-color-control { display: inline-flex; align-items: center; height: calc(var(--space-12) + var(--space-4)); color: var(--ink-2); }
.hx-composer-color-glyph { font-weight: 700; text-decoration: underline; text-decoration-color: var(--brand); }
.hx-composer-highlight-glyph { color: var(--ink-2); font-size: var(--font-small); }
.hx-composer-toolbar.is-composer-fixed .hx-composer-color-control { flex: none; }
.hx-composer-toolbar.is-composer-fixed [data-format-style] { width: 76px; }
.hx-composer-toolbar.is-composer-fixed [data-format-font] { width: 76px; }
.hx-composer-toolbar.is-composer-fixed [data-format-size] { width: 60px; }
.hx-composer-toolbar.is-composer-fixed [data-format-color], .hx-composer-toolbar.is-composer-fixed [data-format-highlight] { width: calc(var(--space-20) + var(--space-16)); }
.hx-composer-toolbar-style:hover { background: var(--nav-hover); }
.hx-composer-toolbar-style option { color: var(--ink); }
/* 65-F (4)：插入链接图标原来只靠 .hx-appearance svg（父级 .hx-composer-modal
   带这个类）兜底 fill/stroke，工具条第一次被 ensureSharedToolbarDetached()
   挪成 document.body 直接子节点（59-F/64-F，日历事件框选中文字就会触发）之后
   脱离 .hx-appearance，兜底规则不再命中，图标变回浏览器默认的黑色实心，跟
   加粗/斜体（纯文字，不受这条规则影响）不一致。这里把 fill/stroke 直接定死
   在 .hx-composer-toolbar svg 自己身上，不管工具条当前挂在 DOM 哪个位置
   （原来的 .hx-composer-modal 下，还是挪出去之后的 document.body 下）图标
   都长一个样，跟发帖弹窗里没挪动过的表现一致。 */
.hx-composer-toolbar svg { width: var(--icon-small); height: var(--icon-small); fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
/* 59-F (7)：插入链接小弹层，浮在工具条下面；跟 .hx-composer-toolbar 一样是
   position:fixed，openComposerLinkPopover() 会把 top/left 定位到工具条正下方。 */
.hx-composer-link-popover, .hx-composer-button-popover { position: fixed; z-index: 131; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); width: min(280px, calc(100vw - var(--space-16))); padding: var(--space-4); border-radius: var(--radius-medium); background: var(--bg-elev); box-shadow: var(--shadow); }
.hx-composer-link-popover input, .hx-composer-button-popover input { flex: 1 1 100%; min-width: 0; height: var(--button-height); padding: 0 var(--space-4); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); color: var(--ink); font-size: var(--font-meta); }
.hx-composer-link-popover input:focus-visible, .hx-composer-button-popover input:focus-visible { outline: 0; border-color: var(--brand); }
.hx-composer-link-popover button, .hx-composer-button-popover button { padding: var(--space-3) var(--space-6); border-radius: var(--radius-round); font-size: var(--font-meta); font-weight: 700; }
.hx-composer-link-popover [data-composer-link-confirm], .hx-composer-button-popover [data-composer-button-confirm] { background: var(--brand); color: var(--white); }
.hx-composer-link-popover [data-composer-link-cancel], .hx-composer-button-popover [data-composer-button-cancel] { color: var(--ink-2); }
.hx-composer-link-error { flex: 1 1 100%; margin: 0; color: var(--brand); font-size: var(--font-tab); }
.hx-composer-divider-menu { position: fixed; z-index: 130; padding: var(--space-2); border-radius: var(--radius-medium); background: var(--ink); box-shadow: var(--shadow); }
.hx-composer-divider-menu button { display: block; padding: var(--space-3) var(--space-6); color: var(--bg); font-size: var(--font-meta); white-space: nowrap; text-align: left; }
.hx-composer-divider-menu button:hover { background: var(--nav-hover); }
.hx-composer-divider-menu button:disabled { color: var(--ink-2); cursor: not-allowed; }
.hx-composer-insert-trigger { position: fixed; z-index: 129; display: grid; width: var(--space-24); height: var(--space-24); place-items: center; border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--bg-elev); color: var(--ink-2); font-size: var(--font-button); box-shadow: var(--shadow); }
.hx-composer-divider { margin: var(--space-6) 0; border: 0; border-top: var(--border-width) dashed var(--ink-2); }
.hx-composer-rule { margin: var(--space-6) 0; border: 0; border-top: var(--border-width) solid var(--line); }
.hx-composer-images { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-3); margin-top: var(--space-6); }
.hx-composer-image-item { position: relative; aspect-ratio: 1; overflow: hidden; border-radius: var(--radius-medium); background: var(--field); }
.hx-composer-image-item img { width: 100%; height: 100%; object-fit: cover; }
.hx-composer-image-remove { position: absolute; top: var(--space-2); right: var(--space-2); display: grid; width: var(--space-20); height: var(--space-20); place-items: center; border-radius: var(--radius-round); background: rgba(0, 0, 0, .6); color: var(--white); font-size: var(--font-tab); line-height: 1; }
.hx-composer-schedule-hint { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-4) 0 0; color: var(--ink-2); font-size: var(--font-meta); }
.hx-composer-schedule-hint button { color: var(--brand); font-weight: 700; }
.hx-composer-error { margin: var(--space-3) 0 0; color: var(--brand); font-size: var(--font-meta); }
.hx-composer-bar { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-6); padding-top: var(--space-4); border-top: var(--border-width) solid var(--line); }
.hx-composer-bar-button { display: grid; width: var(--space-24); height: var(--space-24); place-items: center; border-radius: var(--radius-round); color: var(--brand); }
.hx-composer-bar-button:hover { background: var(--brand-soft); }
.hx-composer-bar-button svg { width: var(--icon); }
.hx-composer-bar-button:disabled { color: var(--ink-2); cursor: not-allowed; }
.hx-composer-bar-button:disabled:hover { background: var(--transparent); }
.hx-composer-bar-button[data-composer-schedule-button] { display: inline-flex; width: auto; gap: var(--space-2); padding: 0 var(--space-4); }
.hx-composer-schedule-input { height: var(--button-height); padding: 0 var(--space-4); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); color: var(--ink); }
.hx-composer-schedule-input:focus-visible { outline: 0; border-color: var(--brand); }
.hx-composer-emoji-panel { position: fixed; z-index: 130; display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--space-2); width: min(320px, calc(100vw - var(--space-16))); max-height: 220px; padding: var(--space-4); overflow-y: auto; border-radius: var(--radius-medium); background: var(--bg-elev); box-shadow: var(--shadow); }
.hx-composer-emoji-panel button { display: grid; height: var(--space-24); place-items: center; border-radius: var(--radius-small); font-size: var(--font-tab); }
.hx-composer-emoji-panel button:hover { background: var(--hover); }
[data-composer-panel] .hx-primary-button { width: 100%; margin-top: var(--space-6); }
/* 59-F (2)：草稿入口——跟 .hx-drawer-close 一样 position:absolute 卡在弹窗
   顶部右侧，紧挨在 ✕ 左边（.hx-drawer-close 是 right: var(--space-6)、
   宽度 var(--avatar-small)，这里在它左边留一个按钮宽度 + 一个间距）。 */
.hx-composer-drafts-button { position: absolute; top: var(--space-6); right: calc(var(--space-6) + var(--avatar-small) + var(--space-3)); padding: 0 var(--space-4); height: var(--avatar-small); border-radius: var(--radius-round); color: var(--brand); font-size: var(--font-meta); font-weight: 700; }
.hx-composer-drafts-button:hover { background: var(--brand-soft); }
.hx-composer-popout-button { position: absolute; top: var(--space-6); right: calc(var(--space-6) + var(--avatar-small) + var(--space-3)); display: none; }
@media (hover: hover) and (pointer: fine) {
    .hx-composer-popout-button { display: grid; }
    .hx-composer-drafts-button { right: calc(var(--space-6) + (var(--avatar-small) * 2) + (var(--space-3) * 2)); }
}
/* 227-F：顶栏——关闭/弹出/草稿/标题挪进 .hx-composer-topbar-start 这个 flex
   子组，预览/发布的落脚点 .hx-composer-topbar-actions 挪进
   .hx-composer-topbar-end；这几个按钮原来是各自 position:absolute 手算
   right 偏移（上面几条规则，给其它共用 .hx-drawer-close 等类名的弹窗用，
   不能删），这里在 .hx-composer-topbar 范围内整体改回 flex 普通流，靠
   gap/space-between 自己排，不用再算第三、第四个按钮该偏移多少像素——
   弹窗类名共用的是同一批 class，缩窄选择器到 .hx-composer-topbar 前缀，
   不影响其它仍在用 position:absolute 的弹窗（登录/支付/外观等）。 */
.hx-composer-topbar { display: flex; align-items: center; justify-content: space-between; gap: var(--space-6); margin-bottom: var(--space-6); }
.hx-composer-topbar-start, .hx-composer-topbar-end { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.hx-composer-topbar .hx-drawer-close,
.hx-composer-topbar .hx-composer-popout-button,
.hx-composer-topbar .hx-composer-drafts-button { position: static; top: auto; right: auto; }
.hx-composer-topbar h2 { overflow: hidden; margin: 0; text-overflow: ellipsis; white-space: nowrap; }
.hx-composer-topbar-actions { display: flex; align-items: center; gap: var(--space-3); }
/* [data-composer-panel] .hx-primary-button 那条全局规则把发帖弹窗里所有
   主按钮强制 width:100%+margin-top（给贴底部的那条用的），挪进顶栏之后
   要按内容宽度收紧、不带上面的留白，覆盖范围只收在这个新容器里，不影响
   .hx-composer-actions 里桌面端原来的样子。 */
.hx-composer-topbar-actions .hx-primary-button,
.hx-composer-topbar-actions .hx-secondary-button { width: auto; min-height: var(--avatar-small); padding: 0 var(--space-6); margin-top: 0; font-size: var(--font-meta); }
/* 227-F：栏目/专题/权限/作者四选——手机端默认收进这个紧凑入口（原生
   details/summary，点开才铺开，收起时只占一行，把常驻的一整排下拉换成
   按需展开），桌面端（700px 断点，见下面覆盖）还原成原来一直展开的样子。 */
.hx-composer-selects-details { flex: 1; min-width: 0; }
/* 桌面端默认值：summary 这颗紧凑入口藏起来（JS ensureComposerActionsPlacement
   把 <details> 强制常开，桌面端只要四个下拉原来那一排，不要额外多一颗摘要
   chip），.hx-composer-selects 维持原来紧挨头像的单行样子，不要换行/不要
   额外 margin-top。手机端（700px 断点，见下方覆盖）才反过来：summary 可见、
   内容区允许换行 + 留一点顶部间距。 */
.hx-composer-selects-summary { display: none; }
.hx-composer-selects-summary::-webkit-details-marker { display: none; }
.hx-composer-selects-summary span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hx-composer-selects-summary svg { flex: none; width: var(--icon-small); height: var(--icon-small); transition: transform var(--motion-medium); }
.hx-composer-selects-details[open] .hx-composer-selects-summary svg { transform: rotate(180deg); }
/* 59-F (2)：草稿列表视图——替换头部选择区和正文表单（JS 切 hidden），本身
   跟着 [data-composer-form] 一样吃 .hx-composer-dialog 的 flex 布局，自己
   滚动（草稿可能很多条）。 */
.hx-composer-draft-list { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.hx-composer-draft-list-head { display: flex; align-items: center; gap: var(--space-6); margin-bottom: var(--space-6); font-weight: 700; }
.hx-composer-draft-back { color: var(--brand); font-size: var(--font-meta); font-weight: 700; }
.hx-composer-draft-status { margin: 0 0 var(--space-6); color: var(--ink-2); font-size: var(--font-meta); }
.hx-composer-draft-items { flex: 1; min-height: 0; margin: 0; padding: 0; overflow-y: auto; list-style: none; }
.hx-composer-draft-item { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-5) 0; border-bottom: var(--border-width) solid var(--line); }
.hx-composer-draft-item:last-child { border-bottom: 0; }
.hx-composer-draft-item-body { flex: 1; min-width: 0; text-align: left; }
.hx-composer-draft-item-title { overflow: hidden; color: var(--ink); font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.hx-composer-draft-item-time { margin-top: var(--space-1); color: var(--ink-2); font-size: var(--font-tab); }
.hx-composer-draft-item-delete { flex: none; color: var(--ink-2); font-size: var(--font-tab); }
.hx-composer-draft-item-delete:hover { color: var(--brand); }
.hx-composer-draft-item-confirm { display: flex; flex: none; align-items: center; gap: var(--space-3); font-size: var(--font-tab); }
.hx-composer-draft-item-confirm button { color: var(--brand); font-weight: 700; }
.hx-composer-draft-item-confirm [data-draft-delete-no] { color: var(--ink-2); font-weight: 400; }
/* 59-F (3)：关闭确认——盖住整个弹窗内容（position:absolute inset:0），
   .hx-composer-dialog 已经在上面补了 position:relative。 */
.hx-composer-close-confirm { position: absolute; inset: 0; z-index: 132; display: grid; place-items: center; padding: var(--space-8); background: rgba(0, 0, 0, .45); border-radius: inherit; }
.hx-composer-close-confirm-box { width: min(100%, 320px); padding: var(--space-8); border-radius: var(--radius-medium); background: var(--bg); text-align: center; }
.hx-composer-close-confirm-box p { margin: 0 0 var(--space-6); font-weight: 700; }
.hx-composer-close-confirm-actions { display: flex; gap: var(--space-4); }
.hx-composer-close-confirm-actions button { flex: 1; padding: var(--space-4) 0; border-radius: var(--radius-round); font-size: var(--font-meta); font-weight: 700; }
.hx-composer-close-confirm-actions [data-composer-close-confirm-secondary] { border: var(--border-width) solid var(--line); color: var(--ink); }
[data-composer-panel] .hx-primary-button:disabled { opacity: .5; cursor: not-allowed; }
.hx-composer-popup-success { margin: auto 0; text-align: center; }
.hx-composer-popup-success h2 { margin-bottom: var(--space-8); }
.hx-composer-popup-success-actions { display: flex; justify-content: center; gap: var(--space-4); margin-top: var(--space-6); }
.hx-composer-popup-login { width: min(100%, var(--modal-width)); margin: auto; padding: var(--space-10); border-radius: var(--radius-card); background: var(--bg-elev); box-shadow: var(--shadow); text-align: center; }
.hx-composer-popup-login p { margin: var(--space-4) 0 var(--space-8); color: var(--ink-2); }
.hx-composer-popup-login .hx-primary-button { display: inline-flex; width: auto; padding: 0 var(--space-10); }
.hx-composer-popup-root body { display: flex; min-height: 100dvh; overflow: hidden; background: var(--page); }
/* 220-F：选区工具条/链接弹层/插入弹层（composerToolbar、composerLinkPopover、
   composerDividerMenu、composerInsertTrigger、composerButtonPopover）第一次要
   显示时都会被各自的 ensureSharedToolbarDetached/ensureComposerInsertOverlaysDetached
   挪成 document.body 的直接子节点（见 hyboox-x.js 2059/2699 行注释，原因跟
   [data-composer-panel] 是同一个"祖先 hidden/裁剪"问题）——挪完之后它们也变成
   body 的直接子节点，被这条规则连坐隐藏：弹出窗口里选中正文文字后工具条确实
   被 JS 正确显示、定位，却因为这条 CSS 规则吞掉而看不见。补上这五个选择器。 */
.hx-composer-popup-root body > :not([data-composer-panel]):not([data-composer-popup-login]):not([data-composer-toolbar]):not([data-composer-link-popover]):not([data-composer-divider-menu]):not([data-composer-insert-trigger]):not([data-composer-button-popover]):not(script) { display: none; }
.hx-composer-popup-root [data-composer-panel] { position: fixed; inset: 0; display: grid; padding: 0; background: var(--page); }
.hx-composer-popup-root [data-composer-panel][hidden] { display: none; }
.hx-composer-popup-root .hx-composer-dialog { width: 100%; height: 100dvh; max-height: none; border-radius: 0; box-shadow: none; }
.hx-composer-popup-root .hx-composer-popout-button { display: none; }
.hx-composer-popup-root .hx-composer-popup-login:not([hidden]) { display: block; }

.hx-pay-plan-price { margin: 0 0 var(--space-6); color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; }
.hx-pay-status { display: flex; align-items: center; justify-content: center; gap: var(--space-2); margin: 0; font-size: var(--font-body); font-weight: 700; }
.hx-pay-status-icon { display: grid; width: var(--space-16); height: var(--space-16); place-items: center; color: var(--ink-2); }
.hx-pay-status-icon svg { width: 16px; height: 16px; }
[data-pay-status][data-state="activated"] .hx-pay-status-icon { color: #1DA968; }
.hx-pay-hint { margin: var(--space-2) 0 0; color: var(--ink-2); font-size: var(--font-meta); text-align: center; }
.hx-pay-wechat-qr { display: flex; justify-content: center; margin: 0 0 var(--space-3); }
.hx-pay-wechat-qr img { border-radius: var(--space-2); }
.hx-pay-error { margin: var(--space-2) 0 0; color: var(--brand); font-size: var(--font-meta); text-align: center; }
/* 181-F：PayPal 订单号+复制按钮这一行——跟弹窗里其它文字一样居中对齐
   （[data-pay-panel] > section 已经是 text-align:center），这里只处理横向
   并排和按钮本身的小胶囊样式，不影响整体居中。 */
.hx-pay-order-no { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: var(--space-3); margin: var(--space-4) 0 0; color: var(--ink-2); font-size: var(--font-meta); }
.hx-pay-order-no strong { color: var(--ink); font-weight: 700; word-break: break-all; }
.hx-pay-copy-button { flex: none; padding: var(--space-2) var(--space-4); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); color: var(--ink-2); font-size: var(--font-small); font-weight: 700; }
.hx-pay-copy-button:hover { background: var(--hover); color: var(--ink); }
[data-pay-panel] .hx-primary-button { width: 100%; margin-top: var(--space-6); }
[data-pay-panel] > section { text-align: center; }

.hx-shell svg { fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.hx-shell svg .is-fill, .hx-shell svg.is-fill, .hx-account-drawer svg .is-fill, .hx-mobile-dock svg .is-fill, .hx-appearance svg .is-fill, .hx-appearance svg.is-fill { fill: currentColor; stroke: none; }
/* 真机验收补：.hx-compose-mobile 在 default.hbs 里跟 .hx-mobile-dock 是平级
   兄弟节点（都在 .hx-shell 外面），不是它的子孙——之前只加了按钮本身的红圆底色，
   没吃到任何一条 svg 描边重置，「+」图标的 path 没有 stroke 就是不可见的空路径，
   看着像空心红圆。日历页的同款按钮渲染正常是因为它在 page-calendar.hbs 里，
   走 {{{body}}} 落在 .hx-shell 内部，能吃到上面 .hx-shell svg 那条重置。直接把
   .hx-compose-mobile 加进这条全局共用的重置列表，跟 .hx-account-drawer/
   .hx-mobile-dock 等其它几个「在 .hx-shell 外头、需要单独点名」的容器同一个路数，
   不新开一条重复规则。 */
.hx-account-drawer svg, .hx-mobile-dock svg, .hx-mobile-bar svg, .hx-appearance svg, .hx-compose-mobile svg { fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }

@media (max-width: 1300px) {
    .hx-shell { grid-template-columns: var(--left-compact-width) var(--center-width) var(--right-width); }
    .hx-shell[data-left-rail="closed"] { grid-template-columns: 0 var(--center-width) var(--right-width); }
    .hx-left-rail { align-items: center; padding-right: var(--space-2); }
    .hx-rail-tools { width: 100%; justify-content: center; }
    .hx-brand { align-items: center; padding: 0; }
    .hx-left-rail .hx-brand-text, .hx-nav-item span, .hx-nav-list .hx-account-nav-item span, .hx-account-copy { display: none; }
    .hx-nav-list { align-items: center; }
    .hx-compose { width: var(--compose-size); }
    .hx-compose span { display: none; }
    .hx-compose svg { display: block; }
    .hx-account { justify-content: center; padding: var(--space-4); }
}

@media (max-width: 1080px) {
    .hx-shell { grid-template-columns: var(--left-compact-width) minmax(0, var(--center-width)); }
    .hx-shell[data-left-rail="closed"] { grid-template-columns: 0 minmax(0, var(--center-width)); }
    .hx-right-rail, .hx-rail-reopen-right { display: none; }
    .hx-center { width: min(100%, var(--center-width)); }
}

@media (max-width: 700px) {
    .hx-shell, .hx-shell[data-left-rail="closed"], .hx-shell[data-right-rail="closed"] { display: block; min-height: 100vh; }
    .hx-left-rail, .hx-right-rail, .hx-rail-reopen { display: none; }
    .hx-center { width: 100%; min-height: 100vh; border: 0; }
    /* 57-F：顶栏收起动画要跟栏目条同一个 transform 一起动，sticky 定位和 transition
       挪给外层 .hx-mobile-header-group 统一管（见下面 body.is-scrolling-down 那条），
       这里只剩纯视觉属性。 */
    .hx-mobile-header-group { position: sticky; z-index: 35; top: 0; display: block; transition: transform var(--motion-medium); }
    body:has([data-calendar-page]) .hx-feed-tabs-channels, body:has([data-predict-page]) .hx-feed-tabs-channels { display: none; }
    .hx-mobile-bar { position: relative; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; height: var(--mobile-bar-height); padding: 0 var(--space-8); border-bottom: 0; background: var(--header-bg); backdrop-filter: blur(var(--space-6)); }
    .hx-mobile-avatar { display: grid; grid-column: 1; width: var(--button-height); height: var(--button-height); padding: var(--space-3); place-items: center; justify-self: start; border-radius: var(--radius-round); }
    .hx-mobile-avatar:hover { background: var(--nav-hover); }
    .hx-reader-avatar { display: grid; width: var(--avatar-small); height: var(--avatar-small); overflow: hidden; place-items: center; border-radius: var(--radius-round); background: var(--field); color: var(--ink-2); font-size: var(--font-meta); font-weight: 700; }
    .hx-reader-avatar img { width: 100%; height: 100%; object-fit: cover; }
    .hx-reader-avatar svg { width: var(--icon-small); }
    .hx-mobile-bar .hx-brand { grid-column: 2; padding: 0; align-items: center; }
    .hx-mobile-bar .hx-brand-text { display: inline; }
    .hx-mobile-bar .hx-brand-mark { width: var(--brand-mark-mobile); height: var(--brand-mark-mobile); }
    /* 2026-11-05：标语取消，.hx-mobile-slogan 这条规则（原来绝对定位贴在
       顶栏右上角那段文字）连同下面 365px/339px 两条专门为它算的窄屏断点
       一起删掉，不留死代码。 */
    /* 56-F：43-F/44-F/46-F 在这一带为顶栏右上角订阅入口写的规则（.hx-mobile-subscribe-slot
       grid 定位、.hx-mobile-subscribe 本体、[hidden] 覆盖、.is-subscribed 态）
       整个删掉——入口本身已经从 navigation.hbs 移除（见该文件 56-F 注释），不留
       死代码。 */
    .hx-feed-tabs { top: var(--mobile-bar-height); overflow-x: auto; scrollbar-width: none; transition: transform var(--motion-medium); }
    /* 63-F (14)：手机端用原生横滑查看栏目条（上面这条 overflow-x:auto 本来就有，
       41-F 就定了），不需要桌面那两个点击箭头，强制不显示。 */
    .hx-feed-tab-arrow { display: none !important; }
    /* 57-F：首页/栏目页的栏目条被 initChannelTrack 挪进 .hx-mobile-header-group，跟顶栏
       变成同一个容器下的两个孩子——这份不再需要自己 sticky，交给容器统一定位，避免嵌套
       sticky 在真机上的定位不确定性。日历筛选/搜索排序/账户标签等没被挪进去的 .hx-feed-tabs
       不受这条影响，还是吃上面那条自己的 sticky。 */
    .hx-mobile-header-group > .hx-feed-tabs { position: static; top: auto; }
    .hx-feed-tabs::-webkit-scrollbar { display: none; }
    /* 22-F 步骤2 无头 Chrome 实测发现：这个 top 是给栏目页量的——栏目页顶栏在
       .hx-feed-tabs 同一个文档滚动里，需要让栏目条粘在顶栏正下方。搜索页的
       .hx-search-scroll 是自己独立的一个滚动容器，顶栏根本不在这个容器里面，
       栏目条用同样的 top 值会被粘性定位硬顶下去 53px，正好盖住自己后面第一个
       面板的状态文字（两者外框几乎完全重叠）。这里按"栏目条在 .hx-search-scroll
       里"单独扣掉，顶到 0 就是对的，跟栏目页那份互不影响。 */
    .hx-search-scroll .hx-feed-tabs { top: 0; }
    .hx-feed-tab { flex: 1 1 auto; min-width: max-content; justify-content: center; padding: 0 var(--space-3); font-size: var(--font-tab); white-space: nowrap; }
    .hx-feed-tabs-channels > .hx-feed-tab[data-channel-key="discovery"] { flex: 1 1 auto; min-width: max-content; }
    /* 44-F 第三轮：上一轮这里有条 :has(+ .hx-feed-tab-caret) 专门把「海博机会」从
       flex-grow 里摘出来，因为那时候 chevron 是紧跟在它后面的独立兄弟节点，跟的是
       撑大的盒子边界不是文字边界。这一轮 chevron 改成写进 .hx-feed-tab 自己的
       <a> 内容里了（见 feed-tabs.hbs），不再有分开的兄弟节点，这条 :has() 摘出来的
       写法用不上了，删掉——「海博机会」跟其它两个 tab 一样正常参与 flex-grow 平分，
       gap 管的是它内部文字和图标的间距，不受盒子多宽影响。 */
    .hx-mobile-dock { position: fixed; z-index: 50; right: 0; bottom: 0; left: 0; display: grid; grid-template-columns: repeat(4, 1fr); height: var(--mobile-dock-height); border-top: var(--border-width) solid var(--line); background: var(--header-bg); backdrop-filter: blur(var(--space-6)); transition: transform var(--motion-medium); }
    .hx-mobile-dock a, .hx-mobile-dock button { display: grid; place-items: center; color: var(--ink-2); }
    .hx-mobile-dock a[aria-current="page"] { color: var(--ink); }
    .hx-mobile-dock svg { width: var(--icon-large); }
    .hx-mobile-dock-text { font-size: var(--font-tab); font-weight: 700; }
    .post-template .hx-mobile-bar { display: none; }
    /* 57-F：顶栏和栏目条被 initChannelTrack 挪进同一个 .hx-mobile-header-group 之后
       （首页/栏目页），收起动画只对这一个容器做一次 transform，两者必然同一帧一起动，
       不会再出现"顶栏先收、栏目条晚一步"。容器里只有顶栏一个孩子的页面（搜索/日历/
       文章等），translateY(-100%) 正好等于顶栏自己的高度，效果跟以前单独隐藏顶栏
       完全一样。 */
    body.is-scrolling-down .hx-mobile-header-group { transform: translateY(-100%); }
    /* 海博日历的频道条、标题搜索和筛选条属于固定操作区，浏览事件列表时不随
       移动端顶栏收起，保持整个日历页顶部区域可见。 */
    body:has([data-calendar-page]).is-scrolling-down .hx-mobile-header-group { transform: none; }
    /* 44-F：日历筛选/搜索排序/账户标签这些没被挪进 .hx-mobile-header-group 的
       .hx-feed-tabs，还是照原样各自滑走自己的高度——43-F 之前两个变量共用同一个值时
       看不出区别，拆开 --feed-tab-height 之后这里就得跟着改，不然栏目条只滑走一半、
       底下露一截。
       57-F：挪进了容器的那份（首页/栏目页）改成跟着容器整体移动，这里显式清空它自己
       的 transform，避免跟容器的位移叠加成两倍距离。 */
    body.is-scrolling-down .hx-feed-tabs { transform: translateY(calc(-1 * var(--feed-tab-height))); }
    body.is-scrolling-down .hx-mobile-header-group > .hx-feed-tabs { transform: none; }
    body.is-scrolling-down .hx-mobile-dock { transform: translateY(100%); }
    /* 21-F 无头 Chrome 截图实测发现：栏目条挪到 track 外面固定之后，顶栏+栏目条一起滑走时，
       track 自己的高度是 calc() 算死的，不会跟着变高去补那块空出来的背景色，会露一截空白。
       这里让 track 常驻多算出顶栏+栏目条的高度、默认用 translateY 往下顶到原来的位置，
       滑走时再把 translateY 归零——跟顶栏/栏目条自己的隐藏动画同步（用的是同一个
       --motion-medium），效果是 track 跟着一起"补位"，不需要额外补一个专门空白的元素。 */
    .hx-channel-track { transform: translateY(0); transition: transform var(--motion-medium); }
    /* 44-F：原来顶栏和栏目条一样高才能直接 *-2，拆成 --mobile-bar-height +
       --feed-tab-height 两段各自的高度分开算，道理跟上面 .hx-feed-tabs 那条一样。
       63-F (6)：这条原来只补了顶栏+栏目条收起后空出来的那块（把 track 往上顶到
       原位），底栏 .hx-mobile-dock 收起时空出来的那块从来没人补过——track 的高度
       一直定死在 calc(100dvh - var(--mobile-dock-height))，底栏滑走之后这段 height
       不会跟着变高，track 底边离视口底边还差 --mobile-dock-height 那么一截，露出
       后面 .hx-shell/body 的白底（无头 Chrome 实测：elementsFromPoint 在那块区域
       点不到任何文章节点，直接落到 .hx-shell 的白色背景，即"白条"）。这里补一条
       height:100dvh（滑起来时不再减 dock 高度），配合原有的 translateY 位移，track
       底边正好盖到/略过视口底边，不留缝。没有碰 createSwipeTrack 的触屏手势代码，
       纯 CSS，跟滑动切栏目的手势逻辑无关。 */
    body.is-scrolling-down .hx-channel-track { transform: translateY(calc(-1 * (var(--mobile-bar-height) + var(--feed-tab-height)))); height: 100dvh; }
    .hx-feed-wrap, .hx-post-main { padding-bottom: var(--mobile-dock-height); }
    .hx-post-card { padding-right: var(--space-8); padding-left: var(--space-8); }
    .hx-handle { max-width: 30vw; }
    .hx-article-bar { top: 0; }
    .hx-bookmarks-header { top: var(--mobile-bar-height); }
    /* 57-F：日历页的标题被 initCalendarHeaderGroup() 挪进 .hx-mobile-header-group，
       跟顶栏、筛选条变成同一个容器下的三个孩子——不再需要自己 sticky，交给容器统一
       定位/收起（见上面 body.is-scrolling-down .hx-mobile-header-group 那条）。收藏页/
       账户页的标题没被挪进去，不受这条影响，还是吃上面那条自己的 sticky，保持
       22-F/44-F 定的"这些页面标题不收起"。 */
    .hx-mobile-header-group > .hx-bookmarks-header { position: static; top: auto; }
    .hx-search-main { height: calc(100dvh - var(--mobile-bar-height) - var(--mobile-dock-height)); min-height: 0; } /* 30-F 无头 Chrome 实测发现：全局 .hx-main{min-height:100vh}（line 172）跟这条 height calc 打架——min-height 是地板，算出来的高度只要比 100vh 矮，实际渲染高度就会被顶回 100vh，这条 calc 形同虚设（换算顶栏高度前后都一样会被顶回去，不是这轮新引入的，只是这轮顺带验高度时才验出来）。这里补 min-height:0 松开地板，让 calc 真正生效，不然搜索结果列表底下一截会滚到固定 dock 底下 */
    /* 22-F 步骤2：搜索页三个排序（热门/最新/用户）横滑。跟栏目页的 .hx-channel-track
       不是一回事——这里没有独立的整屏高度，纵向滚动本来就交给外层已有的
       .hx-search-scroll，横向 track 只管 snap，高度跟着内容走，不用另外算。
       41-F 无头 Chrome 实测发现（真机模拟触屏 Input.synthesizeScrollGesture）：只写
       overflow-x:auto、不写 overflow-y 时，CSS 规范会把 overflow-y 也隐式推成 auto
       （overflow-x/overflow-y 只要有一个不是 visible，另一个是 visible 就会被强制
       改算成 auto——试过显式写 overflow-y:visible 想压掉这条推断，无头 Chrome 量出来
       computed style 纹丝不动还是 auto，说明这条规则比看起来的更死，改不了），这条
       track 自己因此也变成了一个纵向滚动容器——虽然它自己没有可滚的内容
       （scrollHeight==clientHeight），但触屏纵向手势的滚动链路是从离手指最近的可滚
       祖先开始找的，会先找到这个 track 而不是外层 .hx-search-scroll；真正让手势"卡住"
       的是后面这条 overscroll-behavior:contain——shorthand 把横纵两个方向都摁住了，
       这条 track 本来只是想不让横滑到头时把滚动链传给更外层触发系统手势/回弹，纵向
       完全不需要 contain。改成只在横向 contain（overscroll-behavior-x），纵向留默认
       auto，这条 track 自己"滚不动"时纵向手势就能正常继续往上传给 .hx-search-scroll，
       不用跟 overflow-x/y 的隐式推断较劲。 */
    .hx-search-track { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; overscroll-behavior-x: contain; overflow-anchor: none; contain: layout; align-items: flex-start; }
    .hx-search-track[hidden] { display: none; } /* 31-F 无头 Chrome 实测发现：清空搜索词回到空查询态时，track.hidden 被设成了 true，但这条 display:flex 跟全局 [hidden] 同优先级、源码顺序更靠后会赢，导致手机端历史卡片底下还露着一截上一次搜索的结果——28-F/31-F 在别处（.hx-search-box、.hx-search-history）都已经踩过同一个坑，这里是漏补的一处 */
    .hx-search-track::-webkit-scrollbar { display: none; }
    .hx-search-panel { flex: 0 0 100%; min-width: 0; }
    /* 36-F：海博日历四个筛选 tab 横滑，跟搜索页是同一种情况（不是栏目页/账户页那种
       整屏固定高度）——日历页是普通文档流页面，没有独立的纵向滚动容器，高度跟着
       内容走，交给页面本身滚动，横向 track 只管 snap，直接照抄 .hx-search-track/
       .hx-search-panel 这份，不用另算高度。
       41-F：手机端事件列表拉不动就是这份照抄带过来的同一个坑——.hx-search-track
       那边的 overscroll-behavior-x 修复说明见上面那条注释，日历页结构和症状完全一样
       （事件列表本来该交给整个文档滚动，被这层隐式 overflow-y:auto + 纵向也 contain
       截住），同样只在横向 contain。 */
    .hx-calendar-track { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; overscroll-behavior-x: contain; overflow-anchor: none; contain: layout; align-items: flex-start; }
    .hx-calendar-track[hidden] { display: none; }
    .hx-calendar-track::-webkit-scrollbar { display: none; }
    .hx-calendar-panel { flex: 0 0 100%; min-width: 0; }
    /* 193-F：我的收藏「文章」「机会」两栏横滑，跟日历页同一种情况（普通文档流
       页面，没有独立纵向滚动容器），直接照抄 .hx-search-track/.hx-search-panel
       这份，不用另算高度。 */
    .hx-bookmarks-track { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; overscroll-behavior-x: contain; overflow-anchor: none; contain: layout; align-items: flex-start; }
    .hx-bookmarks-track[hidden] { display: none; }
    .hx-bookmarks-track::-webkit-scrollbar { display: none; }
    .hx-bookmarks-panel { flex: 0 0 100%; min-width: 0; }
    /* 42-F：手机端最后一条事件贴着底栏、划到底就滑不动了——列表底下补约 1/3 屏高
       的留白，让最后一条也能滑到屏幕中间附近，不是紧贴 dock。只加这条 padding-bottom
       就够了：41-F 已经确认纵向滚动交给整个文档（overscroll-behavior-x 那次修复），
       这里撑高的是 .hx-calendar-list 的内容高度，多出来的空间自然被文档滚动接住，
       不用再单独算 track/panel 的高度。只在手机端媒体查询里加，桌面端表格布局
       （.hx-calendar-table）不碰。 */
    .hx-calendar-list { padding-bottom: 33dvh; }
    /* 45-F：发布按钮手机端形态——右下角悬浮圆按钮，压在底栏 dock 之上但不重叠
       （bottom 多算一个 dock 高度 + 间距）。跟 .hx-compose 共用同一个视觉语言
       （--brand 底色/圆角/白色图标），但这个是 position:fixed 独立元素，不像
       .hx-compose 那样是左栏里的一个正常流子项，样式不能直接复用那个类。
       第二轮：桌面端形态（.hx-calendar-publish-desktop）改挪进 .hx-left-rail 直接
       复用 .hx-compose 的样式了（navigation.hbs），这里手机端不用再管它——
       .hx-left-rail 本身在这条媒体查询里就是 display:none，挪进去的按钮自动跟着
       隐藏，不用像以前那样单独补一条 display:none。
       49-F：50-B 把 /api/post-permission 改成 AdminMember 专属之后，能看见这个
       日历发布钮的人和能看见新的全站发帖钮（.hx-compose-mobile，见下面/
       mobile-dock.hbs）变成了同一批人——两个钮原来的 bottom 坐标一样，同时出现
       在 /calendar/ 页会正好叠在一起。这里往上挪一层（多算一个 --compose-size +
       一份间距），发帳钮继续占原来紧贴 dock 的那一格，不改发帖钮的坐标（它是
       全站通用的，改了会牵动其它页面）。 */
    .hx-calendar-publish-mobile { display: grid; position: fixed; z-index: 60; right: var(--space-8); bottom: calc(var(--mobile-dock-height) + var(--compose-size) + (var(--space-8) * 2)); width: var(--compose-size); height: var(--compose-size); place-items: center; border: 0; border-radius: var(--radius-round); background: var(--brand); color: var(--white); box-shadow: var(--shadow); transition: transform var(--motion-medium); }
    .hx-calendar-publish-mobile svg { width: var(--icon); }
    .hx-calendar-publish-mobile[hidden] { display: none; } /* 跟本文件其它常驻元素同一个坑，这条自己定了 display:grid，得补 */
    body.is-scrolling-down .hx-calendar-publish-mobile { transform: translateY(calc(100% + var(--space-8))); }
    /* 49-F：发帖悬浮圆钮，全站通用（mobile-dock.hbs），跟 .hx-calendar-publish-mobile
       是同一份视觉语言，但坐标独立定义——不能共享类名，两者是否同时出现在同一个
       页面互不确定（比如 /calendar/ 页两个都可能显示）。 */
    .hx-compose-mobile { display: grid; position: fixed; z-index: 60; right: var(--space-8); bottom: calc(var(--mobile-dock-height) + var(--space-8)); width: var(--compose-size); height: var(--compose-size); place-items: center; border: 0; border-radius: var(--radius-round); background: var(--brand); color: var(--white); box-shadow: var(--shadow); transition: transform var(--motion-medium); }
    .hx-compose-mobile svg { width: var(--icon); }
    .hx-compose-mobile[hidden] { display: none; }
    body.is-scrolling-down .hx-compose-mobile { transform: translateY(calc(100% + var(--space-8))); }
    /* 45-F：编辑/删除共用菜单，手机端改成从底部滑上来的面板（照 X 的底部弹出样式）——
       宽度铺满、只有顶部两角带圆角，用 transform:translateY 配合 .is-open 做滑动，
       跟账户抽屉（.hx-account-drawer）同一套技巧，不是简单的 opacity 淡入淡出
       （桌面端下拉才用那种，见基础规则）。 */
    .hx-event-menu { top: auto; right: 0; bottom: 0; left: 0; min-width: 0; border-radius: var(--radius-card) var(--radius-card) 0 0; padding-bottom: env(safe-area-inset-bottom, 0); opacity: 1; transform: translateY(110%); }
    .hx-event-menu.is-open { transform: none; }
    /* 22-F 步骤3：我的账户（自选股/账号管理）横滑，高度按这页自己头上摞了几层算，
       不照抄栏目页也不照抄搜索页——这页顶栏比栏目页多一层 .hx-bookmarks-header
       （"我的账户"标题），比搜索页又多这里新建的 tab 条，三层 + 顶栏 = 4 层，
       只有顶栏 .hx-mobile-bar 自己会滚动隐藏，bookmarks-header/tab 条都不隐藏
       （这页原本的 .hx-bookmarks-header 就没有隐藏行为，保持一致，不新增），
       所以不需要栏目页那种 translateY 补位，高度直接减掉四层顶栏+dock 即可。 */
    /* 44-F：三层里只有顶栏 + bookmarks-header 两层是 --mobile-bar-height，account 页
       自己这条 tab 条是 --feed-tab-height，拆开算，不能再简单乘 3。 */
    .hx-account-track { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; overscroll-behavior: contain; overflow-anchor: none; contain: layout; height: calc(100dvh - (var(--mobile-bar-height) * 2) - var(--feed-tab-height) - var(--mobile-dock-height)); }
    /* 30-F：账户页跟搜索页是同一个坑——initAccountTrack() 只替换其中一个 section，
       不像栏目页整个换掉 .hx-main，外层 .hx-main{min-height:100vh}（line 172）还在，
       会把这条更矮的 track 高度顶出来的空间填成一截空白。用 :has() 只在真的接了
       account-track 的这个 .hx-main 上松开地板，不碰其它页面的 .hx-main。 */
    .hx-main:has(.hx-account-track) { min-height: 0; }
    .hx-account-track::-webkit-scrollbar { display: none; }
    .hx-account-panel { flex: 0 0 100%; min-width: 0; height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; scroll-snap-align: start; }
    /* 20-F 步骤2：CSS 原生 scroll-snap 接手横滑，浏览器自己管跟手/惯性/吸附/橡皮筋。
       flex 项是 .hx-channel-panel，不是 .hx-main——这样 .hx-main 全局继承的
       min-height:100vh 只会撑高面板内部可滚动的内容，不会撑高面板本身（面板高度由
       track 的 stretch 决定），避开 16-F 的高度冲突。contain:layout 是步骤1无头
       Chrome 实测发现的必须项：track 里其余栏目面板即使被 overflow 裁掉，浏览器算
       "理想布局视口宽度"时还是会把它们的实际宽度算进去，撑大所有
       position:fixed+width:100% 的元素（账户抽屉），不是可选的美化项。 */
    /* 21-F：高度只减 dock，不减顶栏+栏目条——那两个的高度靠上面的 translateY 补位来处理，
       不是靠减高度腾地方（减高度的话，滑走动画时 track 底下会露出提前截断的空白，见上面
       的 translateY 注释）。track 自己底下比可视区多出来的部分会被下方 fixed 的 dock
       盖住，不影响观感。 */
    /* 48-F：斜角滑动——track 横向、panel 纵向各自 overflow:auto，默认
       touch-action:auto 会让浏览器把一次斜向拖拽拆成两个分量同时喂给两层
       （track 吃横向分量、panel 吃纵向分量），看着就是"斜着也能划"。
       不是靠改 scroll-snap（20-F 用的原生 scroll-snap 本身不提供方向锁这个能力，
       它只管"横向滚到哪就吸到哪"，跟"这次手势该不该被判成横向"是两件事）——
       用 touch-action 分轴。真机模拟（CDP 合成 touchstart/move/end，不是
       Input.synthesizeScrollGesture 那种高层封装）测出来一个反直觉的点：
       只能在 track 上加 touch-action:pan-x，panel 不能跟着加 pan-y——两边都加
       会把纯横向的换栏目滑动也一起锁死（scrollLeft 纹丝不动），因为触摸落点
       在 panel 上，panel 的 touch-action 会跟它的祖先链做交集，一旦 panel 自己
       只认纵向，横向分量就没有任何元素愿意接了，不会像"轴锁"预期的那样交给
       上层 track。只在 track 单侧加 pan-x、panel 保持默认 auto，三种手势都验证
       过：纯横向正常换栏目、纯纵向正常滚动列表、斜向 45° 只有纵向真的动
       （scrollLeft 全程为 0）——效果反而更对，不用两层都设。具体测试脚本和
       四种组合的对照数据见 47/48-F 相关的验证记录，不是拍脑袋定的。 */
    /* 57-F：track 本身没设背景，默认透明——顶栏+栏目条收起动画时，track 自己用另一条
       translateY 补位（见上面 21-F/44-F 注释），补位的量是按 --mobile-bar-height +
       --feed-tab-height 这两个 token 算的，跟顶栏栏目条实际渲染高度之间有 1px 出入
       （栏目条自己带一条 1px 的 border-bottom，calc 里没算进去），这 1px 缝隙加上
       backdrop-filter 在合成层动画时的取样时机问题，露出来的就是"白条"。不追着把
       这两个数字掰到分毫不差，直接给 track 补上顶栏同色的底色（--header-bg，跟顶栏
       视觉上是同一片背景延伸下来），缝隙就算真露出来，颜色也和顶栏一致，看不出来。
       动画过渡本身是否顺滑（backdrop-filter 合成层那部分）无头 Chrome 测不出来，
       标"待真机验"。 */
    .hx-channel-track { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; overscroll-behavior: contain; overflow-anchor: none; contain: layout; touch-action: pan-x; height: calc(100dvh - var(--mobile-dock-height)); background: var(--header-bg); }
    .hx-channel-track::-webkit-scrollbar { display: none; }
    .hx-channel-panel { flex: 0 0 100%; min-width: 0; height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; scroll-snap-align: start; }
    .hx-channel-track .hx-feed-wrap { padding-bottom: 0; }

    /* 49-F (6)：手机端发帖弹窗改全屏（桌面端维持 .hx-appearance 原本居中卡片，
       这条只在 700px 以下生效，只影响 .hx-composer-modal，不动 login/pay/
       appearance 那几个共用同一套 .hx-appearance 壳子的弹窗）。
       75-F (F)：原来"三个下拉一排放不下时横向滚动"改成不再需要横向滚动——
       320/360/390 三档宽度下头像+三个下拉要能一行放完整（卡面 F 硬要求，
       不出屏、不换行）。三个原生 select 不设 width，浏览器按当前选中项文字
       宽度出（不是按最长 option 出），栏目/作者本来就短（最长"海博Live"/
       "九一董哥"/"董哥Live"都是 4 个字符左右），真正占地方的是原生下拉箭头
       的保留空间——appearance:none 换成一个 9x6 的自绘小箭头（背景图，不占
       文档流宽度），省下来的空间够三个都不截断；「公开」再单独收窄到 3.6em
       （卡面明确要求"收窄公开下拉"），配 text-overflow:ellipsis——默认选中
       项是「公开」（2 个字），完全装得下，只有手动选到"仅付费会员可见"这种
       长选项时才会省略号，属于卡面认可的取舍。下拉展开后的选项列表是浏览器
       原生渲染的独立浮层，不受这个 select 本身宽度限制，选项文字不会被截断
       （卡面"下拉仍可点开，选项文字不被截断"这条是原生行为自带的，不用另外
       处理）。只在这条 700px 断点内生效，桌面端不受影响。 */
    /* 227-F 真机复测踩的坑：.hx-appearance 基础规则是 display:grid;
       place-items:center——键盘是"盖在页面上不收缩布局视口"这种模式时
       （不少安卓厂商浏览器是这样，不只是新出的 interactive-widget 那个
       开关没支持的问题），dialog 自己的高度靠 var(--hx-composer-vh) 正确
       收缩了，但外层背景还是按没收缩的整页高度居中——结果就是矮下来的
       dialog 被居中摆到页面中间，上半截还在可见区域，下半截（包括正文框）
       被键盘倒扣住。改成顶对齐，不管 dialog 实际拿到的高度是多少，永远从
       可见区域最顶上开始摆，内容挤不下只会在底部被裁掉，不会把输入框barely
       推到键盘底下看不见。 */
    .hx-composer-modal { padding: 0; align-items: flex-start; }
    /* 真机验收补：整个 dialog 不再自己 overflow-y:auto——滚动收窄到内部的
       .hx-composer-scroll（基础规则那条 flex:1 布局在这个宽度下一样生效），
       不然会出现"外层弹窗 + 内层正文区"两层都能滚的双滚动条。高度用
       100dvh 兜底，键盘弹出时 dvh 会跟着可见视口缩小；var(--hx-composer-vh)
       是 visualViewport 兜底再兜底（hyboox-x.js 监听 resize 写进这个变量），
       没有值时退回 100dvh，两条都不支持的浏览器最终退到 100%——这条待真机验，
       目前只有 dvh 这一层能在这台机器上验证到。 */
    .hx-composer-modal .hx-composer-dialog { width: 100%; height: 100%; height: 100dvh; height: var(--hx-composer-vh, 100dvh); max-height: none; border-radius: 0; overflow: hidden; }
    .hx-composer-modal .hx-composer-preview-dialog { width: 100%; height: 100%; height: 100dvh; max-height: none; border-radius: 0; }
    /* 2026-11-03：首页"+"编辑层手机宽度下整页铺满，跟 .hx-composer-modal
       同一个写法（padding:0 + 内层 100dvh + border-radius:0）——没有输入框，
       不用担心键盘顶起来，不需要 var(--hx-composer-vh) 那层兜底，普通
       100dvh 够用。 */
    .hx-plus-editor { padding: 0; align-items: flex-start; }
    .hx-plus-editor > section { width: 100%; height: 100%; height: 100dvh; max-height: none; border-radius: 0; }
    .hx-composer-preview-width-switch { display: none; }
    .hx-composer-preview-scroll { padding: 0; }
    .hx-composer-preview-stage, .hx-composer-preview-stage[data-preview-width="phone"], .hx-composer-preview-stage[data-preview-width="desktop"] { width: 100%; border-right: 0; border-left: 0; }
    .hx-composer-preview-head { align-items: flex-start; }
    .hx-composer-preview-head h2 { min-width: 50%; }
    .hx-composer-opportunity { width: 100%; }
    .hx-composer-taxonomy-row .hx-composer-opportunity { width: auto; }
    .hx-composer-selects { flex-wrap: nowrap; gap: var(--space-2); }
    .hx-composer-select {
        flex: 0 1 auto;
        min-width: 0;
        max-width: 100%;
        padding: 0 16px 0 var(--space-3);
        -webkit-appearance: none;
        appearance: none;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23888' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
        background-repeat: no-repeat;
        background-position: right 4px center;
        background-size: 9px 6px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .hx-composer-select[data-composer-visibility] { flex: 0 0 auto; max-width: 3.6em; }
    /* 227-F：手机端反过来——summary 紧凑入口露出来，JS 不强制常开（见
       hyboox-x.js ensureComposerActionsPlacement 注释），<details> 默认收起；
       展开后内容允许换行、跟 summary 留一点间距，不用再硬塞进一行。 */
    .hx-composer-selects-summary { display: flex; align-items: center; gap: var(--space-2); width: fit-content; max-width: 100%; height: var(--button-height); padding: 0 var(--space-6); border: var(--border-width) solid var(--line); border-radius: var(--radius-round); background: var(--field); color: var(--ink); font-size: var(--font-meta); font-weight: 700; cursor: pointer; list-style: none; }
    .hx-composer-selects-details .hx-composer-selects { margin-top: var(--space-3); flex-wrap: wrap; }
    /* 227-F：手机端顶栏不再需要「发帖」这几个字——关闭×在左、发布在右，
       中间留给标题文字会显得拥挤，且 X 本身顶栏也没有这行字。标题还留着
       （aria-labelledby 指向它，视障用户用屏幕阅读器打开弹窗时仍然会读到
       "发帖"这个名字），只是视觉上用跟 .hx-visually-hidden 同款的裁切手法
       藏起来，不占布局空间——不能用 display:none，那样会把这个元素整个
       从无障碍树里摘掉，aria-labelledby 就读不到名字了。 */
    .hx-composer-topbar h2 { position: absolute; width: var(--border-width); height: var(--border-width); padding: 0; margin: calc(-1 * var(--border-width)); overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { scroll-behavior: auto; transition-duration: 0s; animation-duration: 0s; }
}

/* 62-F 收尾（5）：电脑右下角红色圆形按钮——headless Chrome 实测确认是 Ghost
   Portal 自带的浮动触发按钮（iframe.gh-portal-triggerbtn-iframe，
   data-testid="portal-trigger-frame"），不是本主题任何模板画出来的，由 Ghost
   后台 Portal 设置控制，不在主题这边的既有 CSS/HTML 里。全站已经有自己的登录/
   开通入口（顶栏、右栏产品卡、底栏、付费墙按钮等），不需要 Portal 再叠一个浮动
   入口，隐藏掉与改动前一致。 */
.gh-portal-triggerbtn-iframe { display: none !important; }
