/* layout.css — 外壳布局 + 两级动态栏目 + 共享原子类
   ------------------------------------------------------------------
   分节：
     1. 页面骨架（页头让位 / 栏目让位 / 正文 / 页脚）
     2. 页头 .app-header
     3. 共享原子类 .card / .form-* / .btn* / .flash*
     4. 栏目公共骨架 .rail / .rail__strip / .rail__panel / .rail__item
     5. 一级栏目配色 .rail--primary（深蓝）
     6. 二级栏目配色 .rail--secondary（浅蓝，整体浅一档）
     7. 响应式断点
     8. 登录欢迎动画 .welcome
   令牌一律来自 theme.css，本文件只引用不重定义（断点处按契约调整尺寸变量）。
   ------------------------------------------------------------------ */

/* ════════════════════════════════════════════════════════════════
   1. 页面骨架
   ════════════════════════════════════════════════════════════════ */

/* ── 栏目占位量：三个量，别合并 ──

   --rail1-span   一级栏「此刻」占的宽度。鼠标划过一级栏时它变成面板宽，
                  二级栏的窄条据此滑到面板右缘 —— **两级永不互相遮挡**。
                  这是唯一一个会随开合变的量。

   --gutter-1/2   这个栏目**存在**就给正文留的宽度，恒为窄条宽。
                  --content-offset 是两者之和，**与开合无关** ——
                  否则鼠标每次划过左边，整页正文都要跟着跳一下。

   没有「固定展开」这回事。base.html 只按「有没有分类 / 有没有选中分类」
   在 <body> 上打 has-rail-1 / has-rail-2，不表达任何开合状态。 */

body {
    --rail1-span: 0px;
    --gutter-1: 0px;
    --gutter-2: 0px;
    --content-offset: calc(var(--gutter-1) + var(--gutter-2));
}

body.has-rail-1 { --rail1-span: var(--rail-w); --gutter-1: var(--rail-w); }
body.has-rail-2 { --gutter-2: var(--rail-w); }

/* 一级栏展开 → 二级栏的窄条滑到它右缘（二级栏自己照旧收着）。
   :has() 让这条也走纯 CSS：面板靠 :hover 滑出，二级栏同时让位，
   不必再往 <body> 上同步一个 JS 状态类。三级选择器覆盖
   鼠标、键盘、以及 JS 给触摸设备补的 .is-expanded 三条路径。 */
body:has(.rail--primary:hover),
body:has(.rail--primary:focus-within),
body:has(.rail--primary.is-expanded) {
    --rail1-span: var(--panel-w);
}

.app-body {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    padding-top: var(--header-h);              /* 固定页头让位 */
    padding-left: var(--content-offset);       /* 固定栏目让位 */
}

.app-main {
    flex: 1 0 auto;
    width: 100%;
    max-width: 1100px;
    margin: 0 auto;                            /* 在剩余空间内居中 */
    padding: 40px 32px 64px;
    min-height: calc(100vh - var(--header-h) - 72px);
    position: relative;
    z-index: 1;
}

.app-footer {
    flex-shrink: 0;
    text-align: center;
    padding: 20px 24px;
    font-size: 0.82rem;
    color: var(--text-light);
    border-top: 1px solid var(--border);
    background: rgba(255,255,255,0.55);
}

/* ════════════════════════════════════════════════════════════════
   2. 页头
   ════════════════════════════════════════════════════════════════ */

.app-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--header-h);
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 0 20px;
    background: rgba(255,255,255,0.82);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--border);
}

.app-header__brand {
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    color: var(--text);
    font-weight: 700;
    font-size: 1.12rem;
    letter-spacing: -0.01em;
    border-radius: var(--radius-sm);
}

/* 六边形 Logo（页头与栏目面板共用同一套图形类） */
.hex-mark,
.app-header__logo {
    display: block;
    width: 30px;
    height: 30px;
    flex-shrink: 0;
}

.hex-mark__outline {
    fill: none;
    stroke: var(--primary);
    stroke-width: 2;
    stroke-linejoin: round;
    transition: var(--transition);
}

.hex-mark__inner {
    fill: var(--primary-light);
    transition: var(--transition);
}

.app-header__brand:hover .hex-mark__outline { stroke: var(--primary-dark); }
.app-header__brand:hover .hex-mark__inner   { fill: #dce3f2; }

.app-header__nav {
    display: flex;
    align-items: center;
    gap: 4px;
}

.app-header__user {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-right: 6px;
    font-size: 0.90rem;
    font-weight: 500;
    color: var(--text);
}

.app-header__avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--primary-light);
    color: var(--primary-dark);
    font-size: 0.76rem;
    font-weight: 700;
    line-height: 1;
}

.app-header__link {
    padding: 6px 14px;
    border-radius: var(--radius-pill);
    font-size: 0.92rem;
    font-weight: 500;
    color: var(--text-muted);
    transition: var(--transition);
}

.app-header__link:hover {
    color: var(--primary-dark);
    background: var(--primary-light);
}

.app-header__link--cta {
    color: var(--primary-dark);
    background: var(--primary-light);
}

.app-header__link--cta:hover {
    background: #dce3f2;
}

/* ── 语言切换（页头右侧，用户区之前）──
   两种语言并排做成一段分段控件：当前那个填底，另一个只留文字，悬停再给浅底。
   两个选项不值得藏进下拉里——下拉要先点开才知道有哪几种语言，
   而这里访客一眼就能看到「另一版叫什么、怎么过去」。 */
.lang-switch {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-right: 8px;
    padding: 2px;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
}

.lang-switch__item {
    padding: 3px 10px;
    border-radius: var(--radius-pill);
    font-size: 0.80rem;
    font-weight: 600;
    line-height: 1.6;
    color: var(--text-muted);
    transition: var(--transition);
}

.lang-switch__item:hover {
    color: var(--primary-dark);
    background: var(--primary-light);
}

/* 当前语言：底色与 hover 态一致，所以停在上面不会闪；
   真正的「不可点」由 aria-current 表达，样式上只做区分。 */
.lang-switch__item.is-active {
    color: var(--primary-dark);
    background: var(--primary-light);
}

/* ════════════════════════════════════════════════════════════════
   3. 共享原子类（认证页与正文页都引用，全局唯一实现处）
   ════════════════════════════════════════════════════════════════ */

.card {
    background: var(--surface);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    padding: 36px 32px;
    position: relative;
    overflow: hidden;
    animation: cardFloat 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes cardFloat {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* 卡片左上角几何装饰 */
.card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 80px;
    height: 80px;
    border-left: 2px solid #e0e8f4;
    border-top: 2px solid #e0e8f4;
    border-radius: var(--radius-lg) 0 0 0;
    pointer-events: none;
}

.card-header {
    margin-bottom: 28px;
    text-align: center;
}

.card-header h1 {
    font-size: 1.55rem;
    font-weight: 700;
    color: var(--text);
    letter-spacing: -0.01em;
}

.card-header p {
    margin-top: 6px;
    font-size: 0.90rem;
    color: var(--text-muted);
}

/* 标题下渐变装饰线 */
.card-header::after {
    content: "";
    display: block;
    width: 48px;
    height: 3px;
    margin: 16px auto 0;
    border-radius: 2px;
    background: var(--primary-gradient);
    opacity: 0.60;
}

/* ── 表单 ── */
.form-group {
    margin-bottom: 20px;
}

.form-group label {
    display: block;
    margin-bottom: 6px;
    font-size: 0.88rem;
    font-weight: 500;
    color: var(--text);
}

.form-input {
    width: 100%;
    padding: 11px 14px;
    font-size: 0.95rem;
    font-family: inherit;
    color: var(--text);
    background: var(--surface);
    border: 1.5px solid var(--border);
    border-radius: var(--radius-sm);
    outline: none;
    transition: var(--transition);
}

.form-input:focus {
    border-color: var(--border-focus);
    box-shadow: 0 0 0 3px rgba(123,154,208,0.12);
}

.form-input::placeholder {
    color: var(--text-light);
}

.form-input.is-error {
    border-color: var(--error);
    background: var(--error-bg);
}

.form-input.is-error:focus {
    box-shadow: 0 0 0 3px rgba(231,76,60,0.10);
}

.field-error {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-top: 5px;
    font-size: 0.82rem;
    color: var(--error);
    animation: errorSlide 0.25s ease both;
}

@keyframes errorSlide {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ── 按钮 ── */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px 28px;
    font-size: 0.95rem;
    font-weight: 600;
    font-family: inherit;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: var(--transition);
    letter-spacing: 0.01em;
    text-decoration: none;
}

.btn-primary {
    width: 100%;
    color: #fff;
    background: var(--primary-gradient);
    background-size: 200% 100%;
    background-position: 0% 50%;
    box-shadow: var(--shadow-btn);
}

.btn-primary:hover {
    background-position: 100% 50%;
    box-shadow: 0 4px 16px rgba(107,140,191,0.35);
    transform: translateY(-1px);
}

.btn-primary:active {
    transform: translateY(0) scale(0.98);
    box-shadow: var(--shadow-sm);
}

.btn-link {
    background: none;
    color: var(--primary);
    padding: 6px 10px;
    font-weight: 500;
    text-decoration: none;
    border-radius: var(--radius-sm);
    transition: var(--transition);
}

.btn-link:hover {
    background: var(--primary-light);
    color: var(--primary-dark);
}

/* ── Flash 消息 ── */
.flash-stack,
.flash-container {
    position: fixed;
    top: calc(var(--header-h) + 16px);
    right: 24px;
    z-index: 300;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: 380px;
}

.flash {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 14px 18px;
    border-radius: var(--radius-sm);
    font-size: 0.88rem;
    font-weight: 500;
    background: var(--primary-light);
    border: 1px solid #cdd9ed;
    color: #3a4f6b;
    box-shadow: var(--shadow-md);
    cursor: pointer;
    animation: flashIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes flashIn {
    from { opacity: 0; transform: translateX(40px); }
    to   { opacity: 1; transform: translateX(0); }
}

/* 收起动画：js/flash.js 4 秒后加类，两种命名都认 */
.flash--dismissing,
.flash-dismissing {
    animation: flashOut 0.25s ease forwards;
}

@keyframes flashOut {
    to { opacity: 0; transform: translateX(40px); }
}

.flash__icon {
    flex-shrink: 0;
    line-height: 1.5;
}

/* 语义色：契约用 BEM 双横线，同时兼容旧的单横线写法 */
.flash--success, .flash-success {
    background: var(--success-bg);
    border-color: var(--success-border);
    color: #22543d;
}

.flash--error, .flash-error {
    background: var(--error-bg);
    border-color: var(--error-border);
    color: #742a2a;
}

.flash--info, .flash-info {
    background: var(--primary-light);
    border-color: #cdd9ed;
    color: #3a4f6b;
}

.flash--warning, .flash-warning {
    background: var(--warning-bg);
    border-color: var(--warning-border);
    color: #744210;
}

/* ════════════════════════════════════════════════════════════════
   4. 栏目公共骨架
   收起 = 只有 .rail__strip 窄条；展开 = .rail__panel 从左滑出。
   展开靠三层触发：CSS :hover（鼠标）、:focus-within（键盘）、
   .is-expanded（JS 补触摸与 aria 状态）。
   ════════════════════════════════════════════════════════════════ */

.rail {
    position: fixed;
    top: var(--header-h);
    bottom: 0;
    width: var(--rail-w);
    z-index: 40;
}

/* 二级栏紧贴一级栏的**右缘**：一级栏收起时贴 70px 的窄条，划开时
   跟着滑到 260px 面板的右边 —— 两级栏目的窄条与面板因此永不重叠。
   过渡加在 left 上，扫过一级栏时二级栏是「让开」而不是「瞬移」。

   延迟**只在「交接」那一瞬间存在**，这是关键 —— 别写成不分情况的固定延迟。

   ▸ 平时（含一级栏展开时）：**零延迟**。窄条左缘和一级栏面板右缘按同一条缓动走，
     窄条左缘 = 70 + 190e、面板右缘 = 260e，两者之差恒为 70(1-e) ≥ 0 ——
     窄条正好贴着面板的右缘一起往右让，全程不重叠、也不落后。
     只要延迟不是 0，就会出现「面板先开、窄条后跟」的一先一后
     （用户 2026-09-29 点名要修的就是这个）。

   ▸ 交接瞬间必须延迟 0.1s，给鼠标留出落脚点。指针从一级栏面板往右扫进二级栏
     窄条（260~330px）时，一级栏立刻收起 → --rail1-span 从 260px 掉回 70px，
     二级栏整体要往左走 190px；面板的 transform 过渡不带延迟，这一拍里它已经
     从 260px 往右铺开了，而窄条自己压着不动，指针底下始终有东西。

     这不是保险起见，是**必需**的 —— 按 0.28s + cubic-bezier(0.22,0.61,0.36,1)
     算过：不加延迟时「被覆盖的最右端」在 t≈0.03s 就从 330px 缩到 **280px**，
     指针停在 280~330px（窄条外侧那 50px）会当场丢掉 :hover，二级栏一闪就收；
     加了延迟这条线全程不低于 330px。改缓动或改过渡时长要重算这个数。

   「交接瞬间」就一条判据：**指针正落在二级栏上**。它成立时延迟，其余一律不延迟
   —— 指针直接从栏目挪去正文时二级栏压根没人指着，窄条跟不跟得上无所谓。
   属性变化时取的是**变化之后**的 computed 值，而 :hover 的进出是在同一次样式重算
   里一起结算的，所以指针越过 260px 那一瞬，「一级栏掉 hover」和「二级栏得 hover」
   是同一拍结算的，这条规则当场生效。

   > 别写成 `:not(:has(.rail--primary:hover))` 那种嵌套。`:has()` 本身是硬需求
   > （--rail1-span 那三条就靠它），但 `:not(:has())` 是**嵌套**用法，Firefox 的
   > 支持情况各家说法不一，且一旦不支持是**整条规则被静默丢弃**——延迟没了，
   > 交接就闪。这里只用不嵌套的 `:has()`，跟上面那三条同一个支持级别。 */
.rail--primary   { left: 0;                 z-index: 50; }
.rail--secondary { left: var(--rail1-span); z-index: 40;
                   transition: left var(--rail-ease); }

/* 指针已经在二级栏上 → 窄条多等一拍再走，给鼠标留落脚点 */
body:has(.rail--secondary:hover) .rail--secondary {
    transition-delay: 0.1s;
}

/* 面板展开时的半透明遮罩（仅窄屏出现，只做视觉压暗，点击穿透）。
   z-index 夹在 .app-main(1) 与 .rail(40/50) 之间：只压暗正文，
   两级栏目的窄条和面板都在它上面，谁也不会被谁压暗。
   （早先它是 .rail 的子元素，会被算进一级栏 z-index:50 的层叠上下文，
     一级栏一展开就把二级栏的窄条一起压暗了 —— 正好违反「谁也别盖谁」。） */
.scrim {
    position: fixed;
    inset: var(--header-h) 0 0 0;
    z-index: 30;
    background: rgba(23,36,64,0.40);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--rail-ease);
}

/* ── 常驻窄条：可交互的展开开关 ── */
.rail__strip {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    width: 100%;
    border: 0;
    font: inherit;
    text-align: center;
    cursor: pointer;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
    transition: background-color var(--transition), color var(--transition);
}

.rail__icon {
    display: block;
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    opacity: 0.9;
}

/* 竖排的栏目名：收起状态下也能看出当前在哪个栏目 */
.rail__strip-text {
    writing-mode: vertical-rl;
    font-size: 0.74rem;
    font-weight: 500;
    letter-spacing: 0.18em;
    max-height: 9em;
    overflow: hidden;
    user-select: none;
    opacity: 0.8;
}

/* 窄条底部的「›」：提示这里可以展开 */
.rail__chevron {
    position: absolute;
    left: 50%;
    bottom: 18px;
    width: 15px;
    height: 15px;
    transform: translateX(-50%);
    opacity: 0.5;
    transition: opacity var(--transition), transform var(--rail-ease);
}

.rail__strip:hover .rail__chevron,
.rail__strip:focus-visible .rail__chevron {
    opacity: 0.95;
    transform: translateX(-50%) translateX(2px);
}

/* ── 展开面板：默认整体滑出到视口左侧 ── */
.rail__panel {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 3;
    width: var(--panel-w);
    display: flex;
    flex-direction: column;
    transform: translateX(-100%);
    transition: transform var(--rail-ease);
    will-change: transform;
}

/* 展开的三条路径：鼠标划过 / 键盘聚焦 / JS 补的 .is-expanded（触摸设备点窄条）。
   三条都只是把面板滑出来，谁都不改布局 —— 正文不会跟着动。 */
.rail:hover .rail__panel,
.rail:focus-within .rail__panel,
.rail.is-expanded .rail__panel {
    transform: translateX(0);
}

/* Escape 强制收起（sidebar.js 加类）：窄条自己也是可聚焦元素，
   焦点回到窄条上时必须压掉 :focus-within，否则按 Esc 收不起来。
   本条必须排在上面那组之后 —— 两者特异性相同，靠顺序取胜。 */
.rail.is-suppressed .rail__panel {
    transform: translateX(-100%);
}

.rail__head {
    flex-shrink: 0;
    padding: 20px 18px 16px;
}

.rail__head-mark {
    display: block;
    width: 20px;
    height: 20px;
    margin-bottom: 10px;
    opacity: 0.45;
}

.rail__head-title {
    display: block;
    font-size: 1.02rem;
    font-weight: 600;
    letter-spacing: 0.01em;
}

.rail__head-sub {
    display: block;
    margin-top: 3px;
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    opacity: 0.6;
}

/* ── 列表：超高时内部滚动，滚动条瘦身 ── */
.rail__list {
    flex: 1 1 auto;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 8px 0 16px;
    scrollbar-width: thin;
    overscroll-behavior: contain;
}

.rail__list::-webkit-scrollbar { width: 6px; }
.rail__list::-webkit-scrollbar-track { background: transparent; }

/* ── 条目 ── */
.rail__item {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 10px;
    padding: 10px 16px;
    font-size: 0.90rem;
    line-height: 1.35;
    text-decoration: none;
    transition: background-color var(--transition),
                color var(--transition),
                transform var(--rail-ease);
}

/* 左侧 3px 强调条：hover 半亮，active 全亮 */
.rail__item::before {
    content: "";
    position: absolute;
    left: 0;
    top: 8px;
    bottom: 8px;
    width: 3px;
    border-radius: 0 3px 3px 0;
    opacity: 0;
    transform: scaleY(0.3);
    transition: opacity var(--transition),
                transform var(--rail-ease),
                left var(--rail-ease);
}

.rail__item-icon {
    display: block;
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    opacity: 0.72;
    transition: opacity var(--transition);
}

.rail__label {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.rail__summary {
    grid-column: 1 / -1;
    margin-top: 2px;
    font-size: 0.76rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    opacity: 0.62;
}

.rail__count,
.rail__date {
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    opacity: 0.6;
}

.rail__empty {
    padding: 10px 18px;
    font-size: 0.82rem;
    opacity: 0.6;
}

/* ════════════════════════════════════════════════════════════════
   5. 一级栏目（深蓝）
   ════════════════════════════════════════════════════════════════ */

.rail--primary .rail__strip {
    background: var(--rail1-strip);
    color: var(--rail1-text);
    border-right: 1px solid rgba(255,255,255,0.05);
}

.rail--primary .rail__strip:hover,
.rail--primary .rail__strip:focus-visible {
    background: var(--rail1-bg-soft);
    color: var(--rail1-text-on);
}

.rail--primary .rail__panel {
    background: var(--rail1-bg);
    color: var(--rail1-text);
    box-shadow: 6px 0 28px rgba(23,36,64,0.18);
}

.rail--primary .rail__head {
    border-bottom: 1px solid rgba(255,255,255,0.08);
}

.rail--primary .rail__head-title { color: var(--rail1-text-on); }

.rail--primary .rail__list {
    scrollbar-color: rgba(168,184,212,0.35) transparent;
}

.rail--primary .rail__list::-webkit-scrollbar-thumb {
    background: rgba(168,184,212,0.30);
    border-radius: 3px;
}

/* 一级条目：图标 | 标题 | 条目数（二级没有图标，沿用两列） */
.rail--primary .rail__item {
    grid-template-columns: auto minmax(0, 1fr) auto;
    color: var(--rail1-text);
}

.rail--primary .rail__item::before { background: var(--rail1-accent); }

/* 落点突出（hover = 过渡态）。鼠标态用 :hover，.is-hovered 留给 JS 补标 */
.rail--primary .rail__item:hover:not(.is-active),
.rail--primary .rail__item.is-hovered:not(.is-active) {
    background: var(--rail1-bg-soft);
    color: var(--rail1-text-on);
    font-weight: 600;
    transform: translateX(4px);
}

/* 强调条反向补偿 4px，保持在面板左缘不跟着漂 */
.rail--primary .rail__item:hover:not(.is-active)::before,
.rail--primary .rail__item.is-hovered:not(.is-active)::before {
    left: -4px;
    opacity: 0.65;
    transform: scaleY(1);
}

.rail--primary .rail__item:hover:not(.is-active) .rail__item-icon,
.rail--primary .rail__item.is-hovered:not(.is-active) .rail__item-icon,
.rail--primary .rail__item:hover:not(.is-active) .rail__count,
.rail--primary .rail__item.is-hovered:not(.is-active) .rail__count {
    opacity: 0.95;
}

/* 点击后保持（active = 确定态，底色更实 + 内描边） */
.rail--primary .rail__item.is-active {
    background: var(--rail1-active-bg);
    color: var(--rail1-text-on);
    font-weight: 600;
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.07);
}

.rail--primary .rail__item.is-active::before {
    left: 0;
    opacity: 1;
    transform: scaleY(1);
}

.rail--primary .rail__item.is-active .rail__item-icon,
.rail--primary .rail__item.is-active .rail__count {
    opacity: 0.95;
}

/* ════════════════════════════════════════════════════════════════
   6. 二级栏目（浅蓝，比一级整体浅一档）
   ════════════════════════════════════════════════════════════════ */

.rail--secondary .rail__strip {
    background: var(--rail2-strip);
    color: var(--rail2-text);
    border-right: 1px solid rgba(58,79,107,0.08);
}

.rail--secondary .rail__strip:hover,
.rail--secondary .rail__strip:focus-visible {
    background: var(--rail2-bg-soft);
    color: var(--rail2-text-on);
}

.rail--secondary .rail__panel {
    background: var(--rail2-bg);
    color: var(--rail2-text);
    box-shadow: 6px 0 24px rgba(30,45,77,0.12);
}

.rail--secondary .rail__head {
    border-bottom: 1px solid rgba(58,79,107,0.10);
}

.rail--secondary .rail__head-title { color: var(--rail2-text-on); }
.rail--secondary .rail__head-mark  { opacity: 0.35; }

.rail--secondary .rail__list {
    scrollbar-color: rgba(58,79,107,0.25) transparent;
}

.rail--secondary .rail__list::-webkit-scrollbar-thumb {
    background: rgba(58,79,107,0.22);
    border-radius: 3px;
}

.rail--secondary .rail__item { color: var(--rail2-text); }

.rail--secondary .rail__item::before { background: var(--rail2-accent); }

/* 二级条目标题最多两行，避免 hover 加粗时行高跳动 */
.rail--secondary .rail__label {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    white-space: normal;
}

.rail--secondary .rail__item:hover:not(.is-active),
.rail--secondary .rail__item.is-hovered:not(.is-active) {
    background: var(--rail2-bg-soft);
    color: var(--rail2-text-on);
    font-weight: 600;
    transform: translateX(4px);
}

.rail--secondary .rail__item:hover:not(.is-active)::before,
.rail--secondary .rail__item.is-hovered:not(.is-active)::before {
    left: -4px;
    opacity: 0.65;
    transform: scaleY(1);
}

.rail--secondary .rail__item:hover:not(.is-active) .rail__summary,
.rail--secondary .rail__item.is-hovered:not(.is-active) .rail__summary,
.rail--secondary .rail__item:hover:not(.is-active) .rail__date,
.rail--secondary .rail__item.is-hovered:not(.is-active) .rail__date {
    opacity: 0.85;
}

.rail--secondary .rail__item.is-active {
    background: var(--rail2-active-bg);
    color: var(--rail2-text-on);
    font-weight: 600;
    box-shadow: inset 0 0 0 1px rgba(30,45,77,0.07);
}

.rail--secondary .rail__item.is-active::before {
    left: 0;
    opacity: 1;
    transform: scaleY(1);
}

.rail--secondary .rail__item.is-active .rail__summary,
.rail--secondary .rail__item.is-active .rail__date {
    opacity: 0.85;
}

/* ════════════════════════════════════════════════════════════════
   7. 响应式
   ════════════════════════════════════════════════════════════════ */

/* ≤1024px：正文只让开两条窄条（140px），滑出的面板会盖住正文。
   这个宽度以下必须压暗背景，否则面板和正文糊在一起分不清层次。
   三个选择器和 --rail1-span 那条一一对应，开合判定只有这一处。 */
@media (max-width: 1024px) {
    body:has(.rail:hover, .rail:focus-within, .rail.is-expanded) .scrim {
        opacity: 1;
    }
}

@media (max-width: 960px) {
    .app-main { padding: 32px 22px 56px; }
}

@media (max-width: 768px) {
    :root {
        --rail-w:  57px;    /* 原 52px +10% */
        --panel-w: 220px;   /* 原 200px +10% */
    }

    .rail__strip-text { font-size: 0.70rem; letter-spacing: 0.14em; }
    .rail__chevron { bottom: 14px; }

    .app-main { padding: 28px 18px 48px; }
}

@media (max-width: 600px) {
    .app-main { padding: 20px 14px 40px; }

    .card { padding: 28px 20px; }

    .flash-stack,
    .flash-container {
        left: 12px;
        right: 12px;
        max-width: none;
    }

    .app-header__title { font-size: 1rem; }

    /* 窄屏页头挤：语言切换收窄一点，别把登录/退出顶到换行 */
    .lang-switch { margin-right: 4px; }
    .lang-switch__item { padding: 3px 7px; font-size: 0.74rem; }
    .app-header__link { padding: 6px 10px; font-size: 0.88rem; }
}

/* ════════════════════════════════════════════════════════════════
   8. 登录欢迎动画（结构在 base.html，收尾在 js/welcome.js）
   ────────────────────────────────────────────────────────────────
   只在「刚登录成功」的那一次渲染里出现：auth 路由往 session 里放一次性开关，
   落到目标页时由 app/__init__.py 的 inject_welcome 弹出来。

   时间轴全靠 CSS animation-delay，JS 不参与计时（它只负责最后把节点摘掉）：

       0.00s → 1.50s   welcome ▸ 装饰线 渐显（错开 0.14s）
                       六边形同时「从一个点旋转两圈长大」
       1.50s → 2.30s   停留
       2.30s → 3.10s   整层淡出，六边形「旋转两圈缩回一个点」
       3.10s → 3.90s   左侧两个栏目 + 中间正文 + 页脚渐显

   背景刻意只铺纯 --bg 加一团很淡的光晕，不铺微点阵：整层淡出时，
   body::before 的点阵是「内容一起浮现」的，不必去对齐点阵相位。
   ════════════════════════════════════════════════════════════════ */

.welcome {
    position: fixed;
    inset: var(--header-h) 0 0 0;      /* 页头留着，其余部分整层盖住 */
    z-index: 400;                       /* 高于栏目(50)和 flash(300) */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 20px;
    pointer-events: none;               /* 纯展示层，绝不挡点击 */
    background: var(--bg);
    background-image: radial-gradient(circle at 50% 42%,
                                      rgba(107,140,191,0.12), transparent 58%);
    /* 整层淡出。填充分只用 forwards：用了 backwards 的话，它从第 0 秒起
       就会拿 from 帧压住下面那几条渐显动画。 */
    animation: welcomeVeilOut 0.8s ease 2.3s forwards;
}

@keyframes welcomeVeilOut {
    from { opacity: 1; }
    to   { opacity: 0; }
}

/* 文字和装饰线共用一组动画，只错开出场顺序，读起来才是一组而不是两块。
   1.5s 的渐显不配 easeOutExpo —— 那种曲线 0.5 秒就走完九成，看不出「逐渐」；
   用默认的 ease 才有匀称的淡入感。 */
.welcome__text,
.welcome__rule {
    animation:
        welcomeIn  1.5s ease both,
        welcomeOut 0.8s ease 2.3s forwards;
}

@keyframes welcomeIn {
    from { opacity: 0; transform: translateY(12px) scale(0.96); }
    to   { opacity: 1; transform: none; }
}

/* from 帧必须等于 welcomeIn 的 to 帧，两条动画在 2.3s 交接时才不会跳 */
@keyframes welcomeOut {
    from { opacity: 1; transform: none; }
    to   { opacity: 0; transform: translateY(-10px); }
}

.welcome__text {
    font-size: clamp(2.1rem, 8vw, 3.75rem);   /* 桌面 3.75rem = 原 2.5rem 的 150% */
    font-weight: 300;
    letter-spacing: 0.24em;
    text-indent: 0.24em;               /* 抵消末字符后的字距，视觉居中 */
    background: var(--primary-gradient);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation-delay: 0.14s, 2.3s;
}

.welcome__rule {
    width: 180px;
    height: 3px;
    border-radius: 2px;
    background: var(--primary-gradient);
    animation-delay: 0.28s, 2.3s;
}

/* 六边形不跟着文字平移淡入，它走自己的一套：从中心一个很小的点，
   正转两圈（720°）同时长大到原尺寸，停住；淡出时再正转两圈缩回那个点。
   角度只增不减（720 → 1440），否则第二段会倒着转回去。

   收尾那一帧用 ease-in-out 而不是默认的 ease：加速→减速读起来才像
   「转起来，稳住」，而不是「弹一下」。 */
.welcome__mark {
    width: 76px;
    height: 76px;
    transform-origin: center;
    animation:
        welcomeMarkIn  1.5s cubic-bezier(0.4, 0, 0.2, 1) both,
        welcomeMarkOut 0.8s cubic-bezier(0.4, 0, 0.2, 1) 2.3s forwards;
}

/* 12% 处把不透明度提到 1：这时的六边形只有几个像素，如果还跟着整段
   1.5s 线性淡入，前 0.4 秒等于看不见，白转了一圈。 */
@keyframes welcomeMarkIn {
    from { opacity: 0; transform: rotate(0deg)    scale(0.05); }
    12%  { opacity: 1; }
    to   { opacity: 1; transform: rotate(720deg)  scale(1); }
}

/* from 帧 = welcomeMarkIn 的 to 帧（含 720deg，不是 0deg） */
@keyframes welcomeMarkOut {
    from { opacity: 1; transform: rotate(720deg)  scale(1); }
    to   { opacity: 0; transform: rotate(1440deg) scale(0.05); }
}

/* 内容渐显。.rail 自己带 position:fixed + z-index，动画期间 opacity < 1
   会新建一个层叠上下文，但 z-index 就写在 .rail 身上，层级关系不变。 */
body.is-welcoming .rail,
body.is-welcoming .app-main,
body.is-welcoming .app-footer {
    animation: welcomeContentIn 0.8s ease 3.1s both;
}

@keyframes welcomeContentIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* 系统关了动效就别让人对着空白等 3.1 秒：动画整个取消，遮罩直接不显示，
   内容立刻就在。theme.css 的全局规则只把 duration 压到 0.01ms，
   管不到 animation-delay，所以这里必须显式 animation: none。 */
@media (prefers-reduced-motion: reduce) {
    body.is-welcoming .rail,
    body.is-welcoming .app-main,
    body.is-welcoming .app-footer {
        animation: none;
    }

    .welcome { display: none; }
}
