/*
Theme Name: Sphotography
Theme URI: https://example.com/sphotography
Author: Shirazu Nagisa
Author URI: https://example.com
Description: A fullscreen map-based photography theme with article sidebar and photo grid.
Version: 1.4.9
License: GPL v2 or later
Text Domain: sphotography
*/

@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=Noto+Serif+SC:wght@400;500;600;700&display=swap');

/* 1. 设计令牌与 CSS 变量 */
:root {
    /* === Layout === */
    --sidebar-width: 300px;
    --panel-radius: var(--sphotography-card-radius, 16px);

    /* === Animation Tokens (动效令牌) === */
    --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
    --ease-in-out-sine: cubic-bezier(0.37, 0, 0.63, 1);
    --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
    --ease-draw: cubic-bezier(0.64, 0.04, 0.35, 1);

    --duration-instant: 100ms;
    --duration-fast: 200ms;
    --duration-normal: 300ms;
    --duration-slow: 500ms;
    --duration-cinematic: 800ms;

    /* === Button Size Tokens (按键尺寸令牌) === */
    --btn-s: 36px;   /* 次要操作：关闭、折叠 */
    --btn-m: 44px;   /* 主要操作：展开、关于 */
    --btn-l: 52px;   /* 强调操作 */

    /* === Shadow Depth System (阴影深度系统) === */
    --shadow-elevated: 0 8px 32px rgba(0,0,0,0.45), 0 2px 8px rgba(0,0,0,0.2);
    --shadow-floating: 0 4px 16px rgba(0,0,0,0.3), 0 1px 4px rgba(0,0,0,0.15);
    --shadow-subtle: 0 2px 8px rgba(0,0,0,0.15);

    /* === Glass Morphism Levels (毛玻璃分层) === */
    --glass-elevated-bg: rgba(15, 15, 15, 0.75);
    --glass-elevated-blur: blur(24px) saturate(1.2);
    --glass-floating-bg: rgba(15, 15, 15, 0.55);
    --glass-floating-blur: blur(16px) saturate(1.1);
    --glass-sidebar-bg: rgba(10, 10, 10, 0.65);
    --glass-sidebar-blur: blur(20px) saturate(1.15);

    /* === Color Semantics (色彩语义化) === */
    --text-primary: #ffffff;
    --text-secondary: #aaaaaa;
    --text-muted: #666666;
    --primary: var(--sphotography-primary-color, #1abc9c);
    --bg: var(--sphotography-bg, #0b0b0b);
    /* Form-field background (v1.3.8): dark by default, flipped to white in the
       light-mode blocks below so front-end inputs aren't dark-on-dark. */
    --field-bg: var(--bg);

    --color-surface: rgba(255,255,255,0.06);
    --color-surface-hover: rgba(255,255,255,0.10);
    --color-border: rgba(255, 255, 255, 0.08);
    --color-border-hover: rgba(255, 255, 255, 0.14);

    /* === Typography (字体层级) === */
    --font-display: 'Playfair Display', 'Noto Serif SC', Georgia, serif;
    --font-body: 'Noto Serif SC', 'Songti SC', 'STSong', Georgia, 'Times New Roman', serif;

    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.125rem;
    --text-xl: 1.5rem;
    --text-2xl: 2rem;

    --leading-tight: 1.2;
    --leading-normal: 1.6;
    --leading-relaxed: 1.75;

    /* === Legacy compat === */
    --panel-shadow: var(--shadow-floating);
    --panel-bg: var(--glass-sidebar-bg);
    --panel-blur: var(--glass-sidebar-blur);
    --panel-border: 1px solid var(--color-border);
}

/* Frontend font choice: 'WordPress default' swaps the serif stack for the
   system sans-serif stack globally (both display and body variables). Serif
   is the default and needs no override. */
body.sphotography-font-wordpress {
    --font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", sans-serif;
    --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", sans-serif;
}

/* v1.4.7 (item 6): 苹方 PingFang — Apple 系统原生无衬线。仅在 macOS/iOS/iPadOS
   上真正渲染为苹方，其余平台优雅回退到系统无衬线（微软雅黑等）。苹方受版权保护，
   不可内嵌为网页字体，故为系统字体栈。 */
body.sphotography-font-pingfang {
    --font-display: "PingFang SC", "PingFang TC", -apple-system, BlinkMacSystemFont, "Microsoft YaHei", "Hiragino Sans GB", "Segoe UI", sans-serif;
    --font-body: "PingFang SC", "PingFang TC", -apple-system, BlinkMacSystemFont, "Microsoft YaHei", "Hiragino Sans GB", "Segoe UI", sans-serif;
}

/* v1.4.7 (item 6): 宋体 Songti — 跨平台系统宋体（Mac 的 Songti SC、Windows 的
   SimSun/NSimSun），以 Noto Serif SC 作为通用回退，任意平台皆可显示衬线宋体风格。 */
body.sphotography-font-songti {
    --font-display: "Songti SC", "STSong", "SimSun", "NSimSun", "Noto Serif SC", Georgia, serif;
    --font-body: "Songti SC", "STSong", "SimSun", "NSimSun", "Noto Serif SC", Georgia, "Times New Roman", serif;
}

*, *::before, *::after { margin:0; padding:0; box-sizing:border-box; }

html, body {
    height: 100%;
    overflow: hidden;
    font-family: var(--font-body);
    background: var(--bg);
    color: var(--text-primary);
    -webkit-font-smoothing: antialiased;
}

img { display:block; max-width:100%; }

/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* 2. 玻璃面板基础 + 浅色模式 */

/* Base glass panel — 统一过渡 */
.glass-panel {
    background: var(--panel-bg);
    backdrop-filter: var(--panel-blur);
    -webkit-backdrop-filter: var(--panel-blur);
    border: var(--panel-border);
    border-radius: var(--panel-radius);
    box-shadow: var(--panel-shadow);
    transition:
        transform var(--duration-normal) var(--ease-out-expo),
        opacity var(--duration-normal) var(--ease-out-expo),
        box-shadow var(--duration-fast) ease,
        background var(--duration-fast) ease;
}

/* Opaque override */
body.opaque-panels .glass-panel {
    background: #1a1a1a;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* Light mode overrides */
body.sphotography-night-force-light {
    --glass-elevated-bg: rgba(245, 245, 245, 0.78);
    --glass-floating-bg: rgba(245, 245, 245, 0.60);
    --glass-sidebar-bg: rgba(240, 240, 240, 0.65);
    --field-bg: #ffffff;
    --color-border: rgba(0, 0, 0, 0.08);
    --color-border-hover: rgba(0, 0, 0, 0.14);
    --color-surface: rgba(0,0,0,0.04);
    --color-surface-hover: rgba(0,0,0,0.08);
    --text-primary: #1a1a1a;
    --text-secondary: #555555;
    --text-muted: #888888;
    --shadow-elevated: 0 8px 32px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06);
    --shadow-floating: 0 4px 16px rgba(0,0,0,0.08), 0 1px 4px rgba(0,0,0,0.04);
    --shadow-subtle: 0 2px 8px rgba(0,0,0,0.06);
}

@media (prefers-color-scheme: light) {
    body.sphotography-night-system {
        --glass-elevated-bg: rgba(245, 245, 245, 0.78);
        --glass-floating-bg: rgba(245, 245, 245, 0.60);
        --glass-sidebar-bg: rgba(240, 240, 240, 0.65);
        --field-bg: #ffffff;
        --color-border: rgba(0, 0, 0, 0.08);
        --color-border-hover: rgba(0, 0, 0, 0.14);
        --color-surface: rgba(0,0,0,0.04);
        --color-surface-hover: rgba(0,0,0,0.08);
        --text-primary: #1a1a1a;
        --text-secondary: #555555;
        --text-muted: #888888;
        --shadow-elevated: 0 8px 32px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06);
        --shadow-floating: 0 4px 16px rgba(0,0,0,0.08), 0 1px 4px rgba(0,0,0,0.04);
        --shadow-subtle: 0 2px 8px rgba(0,0,0,0.06);
    }
}

/* Dark mode transition */
body {
    transition: filter var(--duration-slow) var(--ease-in-out-sine),
                background-color var(--duration-slow) var(--ease-in-out-sine);
}

/* 3. 地图（全屏） */
#map {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
}

/* 4. 侧边栏 */
.sidebar {
    position: fixed;
    left: 16px;
    top: 16px;
    bottom: 16px;
    width: var(--sidebar-width);
    z-index: 100;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--glass-sidebar-bg);
    backdrop-filter: var(--glass-sidebar-blur);
    -webkit-backdrop-filter: var(--glass-sidebar-blur);
    transform: translateX(0);
    opacity: 1;
    transition:
        transform var(--duration-slow) cubic-bezier(0.32, 0.72, 0, 1),
        opacity var(--duration-normal) var(--ease-out-expo);
}

body.sidebar-collapsed .sidebar {
    transform: translateX(calc(-1 * (var(--sidebar-width) + 24px)));
    opacity: 0;
    pointer-events: none;
}

/* Search — 带宽度展开动效 */
.sidebar-search {
    position: relative;
    padding: 14px 16px 8px;
    flex-shrink: 0;
}

/* Search row: field + filter button share the width. */
.sidebar-search-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.sidebar-search-field {
    position: relative;
    flex: 1;
    min-width: 0;
}

.sidebar-search input {
    width: 100%;
    padding: 10px 52px 10px 36px;
    border-radius: 999px; /* v1.4.8 (item 3)：圆角条形（全圆角胶囊） */
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--text-primary);
    font-size: var(--text-sm);
    outline: none;
    transition:
        border-color var(--duration-fast) var(--ease-out-expo),
        background var(--duration-fast) var(--ease-out-expo);
}

.sidebar-search input:focus {
    border-color: var(--primary);
    background: var(--color-surface-hover);
}

.sidebar-search input::placeholder {
    color: var(--text-muted);
}

.sidebar-search-icon {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-muted);
    pointer-events: none;
    transition: color var(--duration-fast) ease;
}

.sidebar-search input:focus ~ .sidebar-search-icon {
    color: var(--primary);
}

/* Ctrl/⌘ + K shortcut hint */
.sidebar-search-kbd {
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    gap: 3px;
    pointer-events: none;
    transition: opacity var(--duration-fast) var(--ease-out-expo);
}

/* Filter button */
.sidebar-filter-btn {
    flex-shrink: 0;
    width: 38px;
    height: 38px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%; /* v1.4.8 (item 3)：永久圆形 */
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--text-secondary);
    cursor: pointer;
    transition:
        color var(--duration-fast) ease,
        background var(--duration-fast) var(--ease-out-expo),
        border-color var(--duration-fast) var(--ease-out-expo);
}
.sidebar-filter-btn:hover {
    color: var(--text-primary);
    border-color: var(--color-border-hover);
    background: var(--color-surface-hover);
}
.sidebar-filter-btn.is-active,
.sidebar-filter-btn[aria-expanded="true"] {
    color: #fff;
    background: var(--primary);
    border-color: var(--primary);
}

/* Filter panel — expands from the filter button (JS-driven FLIP). */
.sidebar-filter-panel {
    margin-top: 10px;
    padding: 12px;
    border-radius: 12px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    /* Top-left origin so the JS FLIP (expand from the filter button) maps the
       panel box exactly, matching the article panel's window motion. */
    transform-origin: top left;
    will-change: transform, opacity;
}
.sidebar-filter-panel[hidden] { display: none; }

.filter-panel-title {
    font-size: var(--text-xs);
    color: var(--text-muted);
    letter-spacing: 0.04em;
    margin-bottom: 10px;
}

.filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.filter-chip {
    padding: 5px 12px;
    border-radius: 999px;
    border: 1px solid var(--color-border);
    background: transparent;
    color: var(--text-secondary);
    font-size: var(--text-xs);
    font-family: var(--font-body);
    cursor: pointer;
    white-space: nowrap;
    transition:
        color var(--duration-fast) ease,
        background var(--duration-fast) var(--ease-out-expo),
        border-color var(--duration-fast) var(--ease-out-expo),
        transform var(--duration-instant) var(--ease-spring);
}
.filter-chip:hover {
    border-color: var(--color-border-hover);
    color: var(--text-primary);
}
/* Selected chip: theme-colour fill, white text for readability. */
.filter-chip.is-selected {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
}
.filter-chip:active { transform: scale(0.96); }
.filter-chips-empty {
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-style: italic;
}

/* v1.4.0: filter panel now has a header row (title + 清除 link) and
   two labelled chip groups (分类 + 地区). The active count badge sits
   on the filter button itself. */
.filter-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
    gap: 8px;
}
.filter-panel-header .filter-panel-title {
    margin-bottom: 0;
}
.filter-clear {
    background: transparent;
    border: none;
    color: var(--primary);
    cursor: pointer;
    font-size: var(--text-xs);
    padding: 2px 4px;
    border-radius: 4px;
    transition: background var(--duration-fast) var(--ease-out-expo);
}
.filter-clear:hover { background: var(--color-surface-hover); }
.filter-clear[hidden] { display: none; }
.filter-group { margin-bottom: 12px; }
.filter-group:last-child { margin-bottom: 0; }
.filter-group-label {
    font-size: var(--text-xs);
    color: var(--text-muted);
    letter-spacing: 0.04em;
    margin-bottom: 6px;
    text-transform: uppercase;
    font-weight: 600;
}
.sidebar-filter-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--primary);
    color: #fff;
    font-size: 0.6875rem;
    font-weight: 600;
    line-height: 18px;
    text-align: center;
    pointer-events: none;
    box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.sidebar-filter-btn { position: relative; }
.filter-chip { display: inline-flex; align-items: center; gap: 6px; }
.filter-chip .tag-chip-dot {
    width: 8px; height: 8px; border-radius: 50%;
    flex: 0 0 auto; box-shadow: 0 0 0 1px rgba(0,0,0,0.08);
}

.sidebar-search-kbd kbd {
    font-family: var(--font-body);
    font-size: 0.6875rem;
    line-height: 1;
    color: var(--text-muted);
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--color-border);
    border-radius: 5px;
    padding: 3px 5px;
    min-width: 16px;
    text-align: center;
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}

/* Hide the hint once the field is focused or contains a query. */
.sidebar-search input:focus ~ .sidebar-search-kbd,
.sidebar-search input:not(:placeholder-shown) ~ .sidebar-search-kbd {
    opacity: 0;
}

/* Post list (scrollable) — soft fade at the top/bottom edges instead of a
   hard divider line. The mask fades cards to transparent as they approach
   the boundaries, so content dissolves smoothly into the sidebar. */
.sidebar-posts {
    flex: 1;
    overflow-y: auto;
    padding: 14px 12px 18px;
    display: flex;
    flex-direction: column;
    gap: 8px;

    -webkit-mask-image: linear-gradient(
        to bottom,
        transparent 0,
        #000 22px,
        #000 calc(100% - 26px),
        transparent 100%
    );
    mask-image: linear-gradient(
        to bottom,
        transparent 0,
        #000 22px,
        #000 calc(100% - 26px),
        transparent 100%
    );
}

.sidebar-posts::-webkit-scrollbar { width: 3px; }
.sidebar-posts::-webkit-scrollbar-track { background: transparent; }
.sidebar-posts::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 2px; }

/* Post card inside sidebar — 左侧竖条 hover 效果 */
.post-card {
    display: flex;
    gap: 12px;
    padding: 10px 14px;
    border-radius: calc(var(--panel-radius) * 0.7);
    cursor: pointer;
    background: var(--color-surface);
    border: 1px solid transparent;
    border-left: 3px solid transparent;
    flex-shrink: 0;
    position: relative;
    overflow: hidden;
    transition:
        background var(--duration-fast) ease,
        border-color var(--duration-fast) var(--ease-out-expo),
        transform var(--duration-fast) var(--ease-spring),
        border-left-color var(--duration-fast) var(--ease-out-expo);
}

.post-card:hover {
    background: var(--color-surface-hover);
    border-color: var(--color-border-hover);
    border-left-color: var(--primary);
    transform: translateX(3px);
}

/* v1.4.6 (item 9): article cover — a strongly-blurred image bled behind the
   whole module, with a dark readability scrim. Text is forced light so it
   stays legible over an arbitrary photo in BOTH light and dark themes. */
.post-card-cover {
    position: absolute;
    inset: -24px; /* bleed past the edges so the blur never reveals the card bg */
    z-index: 0;
    background-size: cover;
    background-position: center;
    filter: blur(18px) saturate(1.15) brightness(0.92);
    transform: scale(1.12);
    pointer-events: none;
    transition: transform var(--duration-normal) var(--ease-out-expo);
}
.post-card-scrim {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    /* Heavier on the left where the text sits. */
    background: linear-gradient(to right, rgba(8,8,10,0.74), rgba(8,8,10,0.5));
}
.post-card.has-cover { background: #15151a; }
.post-card.has-cover .post-card-body { position: relative; z-index: 1; }
.post-card.has-cover:hover .post-card-cover { transform: scale(1.18); }
.post-card.has-cover .post-card-title { color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,0.45); }
.post-card.has-cover:hover .post-card-title { color: #fff; }
.post-card.has-cover .post-card-excerpt,
.post-card.has-cover .post-card-date,
.post-card.has-cover .post-card-date-item,
.post-card.has-cover .post-card-words,
.post-card.has-cover .post-card-views {
    color: rgba(255,255,255,0.88);
    text-shadow: 0 1px 2px rgba(0,0,0,0.4);
}

.post-card-thumb {
    width: 56px;
    height: 56px;
    border-radius: 10px;
    object-fit: cover;
    flex-shrink: 0;
    background: #1a1a1a;
    transition: transform var(--duration-normal) var(--ease-out-expo);
}

.post-card:hover .post-card-thumb {
    transform: scale(1.05);
}

.post-card-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
}

.post-card-title {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-primary);
    line-height: var(--leading-tight);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    transition: color var(--duration-fast) ease;
}

.post-card:hover .post-card-title {
    color: var(--text-primary);
}

.post-card-date {
    font-size: var(--text-xs);
    color: var(--text-muted);
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 10px;
}

/* v1.3.5 — date / 字数 / 阅读量 share one line; each item pairs its icon with
   its value, and long lines wrap gracefully on narrow sidebars. */
.post-card-date-item,
.post-card-words,
.post-card-views {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
}
.post-card-words svg,
.post-card-views svg,
.post-card-date-item svg { flex: 0 0 auto; opacity: 0.85; }

/* Large article card variant — title + full excerpt, vertical size ~2x the
   small card (small ≈ 76px → large min 152px). Toggled by the backend
   "文章卡片尺寸" setting. */
.post-card--large {
    min-height: 152px;
    align-items: stretch;
}

.post-card--large .post-card-thumb {
    width: 72px;
    height: 72px;
    align-self: center;
}

.post-card--large .post-card-body {
    justify-content: flex-start;
    padding: 6px 0;
    gap: 6px;
}

.post-card--large .post-card-title {
    font-size: var(--text-base);
    -webkit-line-clamp: 2;
}

.post-card-excerpt {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    line-height: var(--leading-normal);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.post-card--large .post-card-date {
    margin-top: auto;
}

/* Sidebar profile row (v1.2.9) — one-line avatar + nickname, above the footer */
/* Sidebar profile (v1.3.2): the one-line row is the trigger; an absolutely
   positioned panel expands upward over the article list on click. The old
   hard border-top is gone — the article list's existing bottom mask fade
   provides the same soft transition as the search↔posts boundary. */
.sidebar-profile {
    position: relative;
    margin: 0 4px;
    flex-shrink: 0;
}

.sidebar-profile-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 12px;
    background: transparent;
    border: none;
    border-radius: calc(var(--panel-radius) * 0.6);
    cursor: pointer;
    text-align: left;
    font: inherit;
    color: inherit;
    transition: background var(--duration-fast) ease;
}

.sidebar-profile-row:hover { background: var(--color-surface); }

.sidebar-profile-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: var(--color-surface);
}

.sidebar-profile-avatar--placeholder {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-family: var(--font-display);
    font-size: 0.875rem;
    font-weight: 600;
    background: var(--primary);
}

.sidebar-profile-name {
    flex: 1;
    min-width: 0;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text-primary);
    font-family: var(--font-display);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Expanded panel — grows upward from the profile row, over the posts. Its top
   edge dissolves into the article list via the same 22px mask fade used at
   the search↔posts boundary (task 3). Height is set inline by JS. */
.sidebar-profile-panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 6;
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    transform: translateY(10px);
    pointer-events: none;
    background: var(--glass-sidebar-bg);
    backdrop-filter: var(--glass-sidebar-blur);
    -webkit-backdrop-filter: var(--glass-sidebar-blur);
    border-radius: calc(var(--panel-radius) * 0.7);
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22px, #000 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 22px, #000 100%);
    transition:
        max-height var(--duration-normal) var(--ease-out-expo),
        opacity var(--duration-normal) var(--ease-out-expo),
        transform var(--duration-normal) var(--ease-out-expo);
}

/* When expanded the panel covers the one-line row (it shows its own
   avatar + name at the top), so fade the row out to avoid a doubled name. */
.sidebar-profile.is-expanded .sidebar-profile-panel {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

.sidebar-profile-row {
    transition: background var(--duration-fast) ease, opacity var(--duration-fast) ease;
}

.sidebar-profile.is-expanded .sidebar-profile-row {
    opacity: 0;
    pointer-events: none;
}

/* ---- Shared expanded-profile content (card + sidebar) ---- */
.profile-expand-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 8px;
    /* Top padding clears the mask fade so the avatar isn't clipped. */
    padding: 30px 18px 18px;
}

.profile-expand-avatar {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--color-surface);
}

.profile-expand-avatar--placeholder {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-weight: 600;
    background: var(--primary);
}

.profile-expand-name {
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-primary);
}

.profile-expand-bio {
    font-size: 0.8125rem;
    line-height: var(--leading-normal);
    color: var(--text-secondary);
}

.profile-expand-stats {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    margin-top: 2px;
}

.profile-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
}

.profile-stat-num {
    font-family: var(--font-display);
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1.1;
}

.profile-stat-label {
    font-size: 0.6875rem;
    color: var(--text-muted);
}

.profile-stat-sep {
    width: 1px;
    height: 20px;
    background: var(--color-border);
}

.profile-expand-links {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    width: 100%;
    margin-top: 4px;
}

.profile-expand-link {
    display: block;
    padding: 7px 12px;
    font-size: 0.8125rem;
    color: var(--text-secondary);
    text-decoration: none;
    text-align: center;
    background: var(--color-surface);
    border: 1px solid transparent;
    border-radius: calc(var(--panel-radius) * 0.5);
    transition: background var(--duration-fast) ease, color var(--duration-fast) ease, border-color var(--duration-fast) ease;
}

.profile-expand-link:hover {
    color: var(--text-primary);
    background: var(--color-surface-hover);
    border-color: var(--color-border-hover);
}

/* Sidebar footer */
.sidebar-footer {
    padding: 6px 10px 10px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 6px;
}

/* 统一 Icon Button 样式 — Sidebar Toggle (btn-s) */
.sidebar-toggle-mini {
    width: var(--btn-s);
    height: var(--btn-s);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%; /* v1.4.2: 永久圆形 */
    border: 1px solid var(--color-border);
    background: var(--glass-floating-bg);
    backdrop-filter: var(--glass-floating-blur);
    -webkit-backdrop-filter: var(--glass-floating-blur);
    color: var(--text-secondary);
    cursor: pointer;
    flex-shrink: 0;
    box-shadow: var(--shadow-subtle);
    transition:
        background var(--duration-fast) var(--ease-out-expo),
        transform var(--duration-instant) var(--ease-spring),
        box-shadow var(--duration-fast) ease,
        color var(--duration-fast) ease,
        border-color var(--duration-fast) ease;
}

.sidebar-toggle-mini:hover {
    background: var(--color-surface-hover);
    color: var(--text-primary);
    transform: translateY(-1px);
    box-shadow: var(--shadow-floating);
    border-color: var(--color-border-hover);
}

.sidebar-toggle-mini:active {
    transform: translateY(0) scale(0.96);
}

.sidebar-brand {
    flex: 1;
    font-size: 0.8125rem;
    color: var(--text-muted);
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: 0.03em;
}

/* 统一 Icon Button 样式 — GitHub Link (btn-m) */
.sidebar-github-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--btn-m);
    height: var(--btn-m);
    color: var(--text-secondary);
    border-radius: 50%; /* v1.4.6 (item 4): 永久圆形，与展开按钮一致 */
    border: 1px solid transparent;
    transition:
        color var(--duration-fast) ease,
        background var(--duration-fast) var(--ease-out-expo),
        transform var(--duration-instant) var(--ease-spring);
}

.sidebar-github-link:hover {
    color: var(--text-primary);
    background: var(--color-surface-hover);
    transform: translateY(-1px);
}

/* 统一 Icon Button 样式 — Expand Button (btn-m) */
.sidebar-expand-btn {
    position: fixed;
    left: 16px;
    bottom: 24px;
    z-index: 95;
    width: var(--btn-m);
    height: var(--btn-m);
    display: none;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    border: var(--panel-border);
    background: var(--glass-floating-bg);
    backdrop-filter: var(--glass-floating-blur);
    -webkit-backdrop-filter: var(--glass-floating-blur);
    color: var(--text-secondary);
    border-radius: 50%; /* v1.4.2: 永久圆形 */
    box-shadow: var(--shadow-floating);
    transition:
        background var(--duration-fast) var(--ease-out-expo),
        color var(--duration-fast) ease,
        transform var(--duration-instant) var(--ease-spring),
        box-shadow var(--duration-fast) ease;
}

.sidebar-expand-btn:hover {
    color: var(--text-primary);
    background: rgba(40,40,40,0.85);
    transform: translateY(-2px);
    box-shadow: var(--shadow-elevated);
}

.sidebar-expand-btn:active {
    transform: translateY(0) scale(0.96);
}

body.sidebar-collapsed .sidebar-expand-btn {
    display: inline-flex;
    animation: expandBtnPop var(--duration-normal) var(--ease-spring);
}

@keyframes expandBtnPop {
    0% { transform: scale(0.5); opacity: 0; }
    60% { transform: scale(1.1); }
    100% { transform: scale(1); opacity: 1; }
}

/* v1.4.6 (item 8): 折叠边栏时，展开按钮正上方的博主头像。圆形，尺寸与展开按钮一致，
   非交互（暂不可点击，预留未来点击功能）。仅在 body.sidebar-collapsed 时出现。 */
.sidebar-expand-avatar {
    position: fixed;
    left: 16px;
    bottom: 78px; /* 展开按钮 bottom:24 + 高44 + 间隙10 */
    z-index: 95;
    width: var(--btn-m);
    height: var(--btn-m);
    border-radius: 50%;
    object-fit: cover;
    display: none;
    border: var(--panel-border);
    box-shadow: var(--shadow-floating);
    pointer-events: none; /* 暂不可点击 */
    background: var(--glass-floating-bg);
}
body.sidebar-collapsed .sidebar-expand-avatar {
    display: block;
    animation: expandBtnPop var(--duration-normal) var(--ease-spring);
}

/* 5. 文章面板 */
.article-panel {
    position: fixed;
    left: 340px;
    top: 16px;
    bottom: 16px;
    width: min(600px, calc(100vw - 380px));
    z-index: 110;
    padding: 24px 28px;
    overflow-y: auto;
    overflow-x: hidden;
    /* Hide the scrollbar visually while keeping scroll functional. */
    scrollbar-width: none;
    -ms-overflow-style: none;
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    border-radius: var(--panel-radius);
    box-shadow: var(--shadow-elevated);
    transform: translateX(20px) scale(0.98);
    opacity: 0;
    pointer-events: none;
    transition:
        transform var(--duration-slow) var(--ease-out-expo),
        opacity var(--duration-slow) var(--ease-out-expo);
}

.article-panel::-webkit-scrollbar { width: 0; height: 0; }
.article-panel::-webkit-scrollbar-thumb,
.article-panel::-webkit-scrollbar-track { background: transparent; }

.article-panel.active {
    transform: translateX(0) scale(1);
    opacity: 1;
    pointer-events: auto;
}

/* ============================================
   v1.4.8 (item 2)：边栏展开页 + 圆形展开按钮 + 丰富统计面板
   ============================================ */
/* 个人信息行容器：左侧个人信息按钮收窄，右侧留出常驻圆形展开按钮 */
.sidebar-profile-bar {
    display: flex;
    align-items: center;
    gap: 6px;
    /* v1.4.9 (item 5)：右内边距 6px，使右端展开按钮与页脚 GitHub 图标（页脚右内边距 10px、
       个人信息区 margin 4px → 4+6=10）在同一竖直中线上下对齐。 */
    padding-right: 6px;
}
.sidebar-profile-bar .sidebar-profile-row {
    flex: 1 1 auto;
    min-width: 0;
}
.sidebar-expandpage-btn {
    flex: 0 0 auto;
    width: var(--btn-m); /* v1.4.9 (item 5)：尺寸与 GitHub 按钮一致，右端对齐后中线重合 */
    height: var(--btn-m);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--text-secondary);
    cursor: pointer;
    transition:
        color var(--duration-fast) ease,
        background var(--duration-fast) var(--ease-out-expo),
        border-color var(--duration-fast) var(--ease-out-expo),
        transform var(--duration-fast) var(--ease-out-expo);
}
.sidebar-expandpage-btn:hover {
    color: var(--text-primary);
    border-color: var(--color-border-hover);
    background: var(--color-surface-hover);
    transform: translateY(-1px);
}
.sidebar-expandpage-btn[aria-expanded="true"] {
    color: #fff;
    background: var(--primary);
    border-color: var(--primary);
}

/* 丰富统计面板：与简单个人信息面板一样自个人信息行向上展开，铺满其上方边栏区域 */
.sidebar-stats-panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 7;
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    transform: translateY(10px);
    pointer-events: none;
    background: var(--glass-sidebar-bg);
    backdrop-filter: var(--glass-sidebar-blur);
    -webkit-backdrop-filter: var(--glass-sidebar-blur);
    border-radius: calc(var(--panel-radius) * 0.7);
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22px, #000 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 22px, #000 100%);
    transition:
        max-height var(--duration-normal) var(--ease-out-expo),
        opacity var(--duration-normal) var(--ease-out-expo),
        transform var(--duration-normal) var(--ease-out-expo);
}
.sidebar-profile.stats-open .sidebar-stats-panel {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}
.sidebar-profile.stats-open .sidebar-profile-bar,
.sidebar-profile.is-expanded .sidebar-profile-bar {
    opacity: 0;
    pointer-events: none;
}
/* v1.4.9: full-height flex column — JS sets its height inline to fill the sidebar
   area above the profile bar. A top-anchored scrolling region holds the stats; a
   sticky links footer is pinned to the bottom. NO overflow here (the region scrolls). */
.stats-panel-inner {
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    overflow: hidden;
}
/* Top region: current stats, unchanged proportions, anchored to the top; scrolls if
   it outgrows the space. flex:1 makes it claim all room above the footer, so the
   footer sits at the very bottom and any spare space is an empty gap in between. */
.stats-scroll {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
    /* Top padding clears the panel's 22px mask fade so the avatar isn't dimmed. */
    padding: 26px 16px 12px;
    overflow-y: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
}
.stats-scroll::-webkit-scrollbar { width: 0; height: 0; }
.stats-bio {
    text-align: center;
    margin-top: -2px;
}
/* Sticky links footer: non-scrolling flex sibling, always visible at the panel
   bottom. Panel-matched background + top separator keep scrolling stats from
   showing through. Buttons reuse the .profile-expand-link pill stack. */
.stats-links-footer {
    flex: 0 0 auto;
    padding: 12px 16px 14px;
    border-top: 1px solid var(--color-border);
    background: var(--glass-sidebar-bg);
    backdrop-filter: var(--glass-sidebar-blur);
    -webkit-backdrop-filter: var(--glass-sidebar-blur);
}
.stats-links-footer .profile-expand-links { margin-top: 0; }
.stats-avatar {
    width: 56px; height: 56px; border-radius: 50%;
    object-fit: cover; align-self: center; background: var(--color-surface);
}
.stats-avatar--placeholder {
    display: inline-flex; align-items: center; justify-content: center;
    color: #fff; font-family: var(--font-display); font-size: 1.5rem; font-weight: 600;
    background: var(--primary);
}
.stats-name {
    text-align: center; font-family: var(--font-display);
    font-size: 1rem; font-weight: 600; color: var(--text-primary);
}
.stats-counts {
    display: flex; align-items: stretch; justify-content: center; gap: 0;
}
.stats-count-cell {
    flex: 1 1 0; display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 4px 2px;
}
.stats-count-num { font-family: var(--font-display); font-size: 1.15rem; font-weight: 700; color: var(--text-primary); }
.stats-count-label { font-size: 0.7rem; color: var(--text-muted); }
.stats-count-sep { width: 1px; background: var(--color-border); margin: 4px 0; align-self: stretch; }
.stats-row {
    display: flex; align-items: center; justify-content: space-between;
    font-size: 0.8rem; color: var(--text-secondary);
    padding: 4px 2px; border-top: 1px solid var(--color-border);
}
.stats-row-label { color: var(--text-muted); }
.stats-row-value { font-family: var(--font-display); font-weight: 600; color: var(--text-primary); }
.stats-uptime-value { font-variant-numeric: tabular-nums; }
/* 地区分布饼状图 */
.stats-pie-wrap { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 4px 0; }
.stats-pie {
    width: 120px; height: 120px;
    transform-origin: 50% 50%;
}
.sidebar-profile.stats-open .stats-pie.is-revealing {
    animation: statsPieCCW 900ms var(--ease-out-expo) both;
}
@keyframes statsPieCCW {
    from { transform: rotate(-360deg); opacity: 0; }
    to   { transform: rotate(0deg); opacity: 1; }
}
.stats-pie circle { transition: stroke-dasharray 200ms ease; }
.stats-legend { display: flex; flex-wrap: wrap; gap: 4px 10px; justify-content: center; }
.stats-legend-item { display: inline-flex; align-items: center; gap: 5px; font-size: 0.72rem; color: var(--text-secondary); }
.stats-legend-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.stats-pie-empty { font-size: 0.78rem; color: var(--text-muted); padding: 12px 0; }

/* 边栏展开页大卡片：与文章面板同一位置/尺寸，置于最上层 */
.expand-page {
    position: fixed;
    left: 340px;
    top: 16px;
    bottom: 16px;
    width: min(600px, calc(100vw - 380px));
    z-index: 200; /* 高于页面链接栏(118)/公告(117)/文章面板(110) */
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    border-radius: var(--panel-radius);
    box-shadow: var(--shadow-elevated);
    transform: translateX(16px) scale(0.98);
    opacity: 0;
    pointer-events: none;
    transition:
        transform var(--duration-slow) var(--ease-out-expo),
        opacity var(--duration-slow) var(--ease-out-expo);
}
.expand-page.is-open {
    transform: translateX(0) scale(1);
    opacity: 1;
    pointer-events: auto;
}
.expand-page-header {
    flex: 0 0 auto;
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 18px 20px 14px;
    /* 上边框模糊过渡 */
}
.expand-page-title {
    flex: 0 0 auto;
    margin: 0;
    font-family: var(--font-display);
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text-primary);
}
.expand-page-search-field {
    /* v1.4.9：搜索栏跟随标题左侧排布，但设上限不再填满整行——右端与关闭按钮之间
       留出干净的间距（关闭按钮由 margin-left:auto 钉在右上角）。 */
    flex: 0 1 360px;
    max-width: 360px;
    position: relative;
    min-width: 0;
    margin: 0 14px;
}
.expand-page-search-input {
    width: 100%;
    padding: 8px 14px 8px 34px;
    border-radius: 999px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--text-primary);
    font-size: var(--text-sm);
    outline: none;
    transition: border-color var(--duration-fast) ease, background var(--duration-fast) ease;
}
.expand-page-search-input:focus { border-color: var(--primary); background: var(--color-surface-hover); }
.expand-page-search-icon {
    position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
    color: var(--text-muted); pointer-events: none;
}
.expand-page-close { position: static; flex: 0 0 auto; margin-left: auto; }
.expand-page-grid {
    flex: 1 1 auto;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: none;
    -ms-overflow-style: none;
    column-count: 2;
    column-gap: 18px;
    padding: 10px 22px 22px;
    /* 上下边框模糊过渡，与其他面板一致 */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%);
}
.expand-page-grid::-webkit-scrollbar { width: 0; height: 0; }
.expand-page-grid.cols-3 { column-count: 3; column-gap: 18px; }
.expand-page-empty { padding: 40px 20px; text-align: center; color: var(--text-muted); font-size: 0.85rem; }

/* v1.4.9 (item 3)：卡片整体加大、留白更足；高度由 JS 按文本长度+有无封面连续设定（inline
   min-height），body 以 flex 填满卡片高度，文本沉底，封面铺满——形成错落有致的瀑布流。 */
.expand-card {
    display: flex;
    width: 100%;
    break-inside: avoid;
    margin: 0 0 18px;
    padding: 0;
    position: relative;
    /* v1.4.9: no min-height — the card is exactly as tall as its excerpt content. */
    border: 1px solid var(--color-border);
    border-radius: calc(var(--panel-radius) * 0.75);
    overflow: hidden;
    background: var(--color-surface);
    color: inherit;
    text-align: left;
    cursor: pointer;
    transition: transform var(--duration-fast) var(--ease-out-expo), box-shadow var(--duration-fast) ease, border-color var(--duration-fast) ease;
}
.expand-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-floating); border-color: var(--color-border-hover); }
.expand-card-cover {
    position: absolute; inset: -24px;
    background-size: cover; background-position: center;
    filter: blur(18px) saturate(1.15) brightness(0.92);
    transform: scale(1.12);
    transition: transform var(--duration-normal) var(--ease-out-expo);
}
.expand-card:hover .expand-card-cover { transform: scale(1.18); }
.expand-card-scrim { position: absolute; inset: 0; background: linear-gradient(to top, rgba(8,8,10,0.82), rgba(8,8,10,0.42) 60%, rgba(8,8,10,0.28)); }
.expand-card-body { position: relative; z-index: 1; flex: 1 1 auto; width: 100%; box-sizing: border-box; padding: 18px 18px 16px; display: flex; flex-direction: column; justify-content: flex-end; }
.expand-card.has-cover .expand-card-title { color: #fff; }
.expand-card.has-cover .expand-card-meta { color: rgba(255,255,255,0.78); }
/* 无封面：无色模块（不加主题色调） */
.expand-card--plain .expand-card-title { color: var(--text-primary); }
.expand-card--plain .expand-card-meta { color: var(--text-muted); }
.expand-card-title { font-family: var(--font-display); font-size: 1.02rem; font-weight: 600; line-height: 1.35; }
/* v1.4.9: no line-clamp — the full excerpt shows and the card sizes to it. */
.expand-card-excerpt { margin-top: 8px; font-size: 0.8rem; line-height: 1.55; opacity: 0.92; }
.expand-card-meta { margin-top: 10px; font-size: 0.72rem; display: flex; gap: 10px; }

/* v1.4.9 (item 6)：两屏推进容器。列表屏与文章屏绝对定位铺满展开页，通过 translateX 推进切换；
   二者互斥（同一时刻只见一屏）。文章从容器内部滑入/滑出，而非叠在展开页之上。 */
.expand-page-screen {
    position: absolute;
    inset: 0;
    overflow: hidden;
    transition: transform 440ms var(--ease-out-expo);
}
.expand-page-screen--list {
    display: flex;
    flex-direction: column;
    transform: translateX(0);
}
.expand-page-screen--article {
    transform: translateX(100%);
}
.expand-page.showing-article .expand-page-screen--list { transform: translateX(-100%); }
.expand-page.showing-article .expand-page-screen--article { transform: translateX(0); }

/* 迁入文章屏的真实 #article-panel：撑满该屏（覆盖其 fixed 定位），随屏一起推进。 */
.article-panel.article-panel--in-screen {
    position: absolute;
    inset: 0;
    left: 0; top: 0; right: 0; bottom: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    border: none;
    border-radius: 0;
    box-shadow: none;
    z-index: auto;
    transform: none !important;
    opacity: 1 !important;
    pointer-events: auto !important;
}
/* Lift the body-mounted nav overlay (close / scroll buttons / TOC) above the
   expand-page (z 200) while an article is shown inside it. */
body.expand-article-open .article-nav-overlay { z-index: 216; }

@media (max-width: 767px) {
    /* 移动端：展开页近全屏，瀑布流单列 */
    .expand-page {
        left: 8px; right: 8px; top: 8px; bottom: 8px;
        width: auto;
    }
    .expand-page-grid, .expand-page-grid.cols-3 { column-count: 1; }
    .expand-page-header { flex-wrap: wrap; }
    .expand-page-search-field { order: 3; flex: 1 1 100%; max-width: none; width: 100%; margin: 8px 0 0; }
    .expand-page-close { margin-left: auto; }
}

/* ============================================
   Article scroll controls (v1.3.6)
   Body-mounted overlay whose box is JS-synced to the panel's on-screen rect.
   The overlay is NOT a scroll container and carries no transform, so the
   absolutely-positioned buttons inside it stay pinned to the panel frame while
   the panel content scrolls underneath.
   ============================================ */
/* v1.4.4 (item 2): 阅读进度 100% 判定改为「正文末尾抵达面板中点」。当正文下方
   的分享条+评论不足以把末尾推到中点时，用这块透明留白补足滚动空间，保证进度总能
   到 100%、底部三按键总能消失。高度由 JS(ensureReadingTailSpace) 动态设定。 */
.article-reading-tail {
    display: block;
    width: 100%;
    pointer-events: none;
}

.article-nav-overlay {
    position: fixed;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
    z-index: 130;
    pointer-events: none;
    opacity: 0;
    overflow: hidden;
    border-radius: var(--panel-radius);
    transition: opacity 200ms var(--ease-out-expo, ease);
}
.article-nav-overlay.is-active { opacity: 1; }

/* Frosted gradient bands (v1.3.7) — reused by the article panel and the
   友链 / 留言 panels. A backdrop-blurred strip masked to fade toward the
   content, so text blurs as it scrolls under the nav-button rows. */
.sp-frost-band {
    position: absolute;
    left: 0;
    right: 0;
    height: 84px;
    z-index: 4;
    pointer-events: none;
    opacity: 0;
    backdrop-filter: blur(7px);
    -webkit-backdrop-filter: blur(7px);
    transition: opacity 260ms var(--ease-out-expo, ease);
}
.sp-frost-band.is-visible { opacity: 1; }
.sp-frost-band--top {
    top: 0;
    background: linear-gradient(to bottom, var(--glass-elevated-bg), transparent);
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 42%, transparent 100%);
            mask-image: linear-gradient(to bottom, #000 0%, #000 42%, transparent 100%);
}
.sp-frost-band--bottom {
    bottom: 0;
    background: linear-gradient(to top, var(--glass-elevated-bg), transparent);
    -webkit-mask-image: linear-gradient(to top, #000 0%, #000 42%, transparent 100%);
            mask-image: linear-gradient(to top, #000 0%, #000 42%, transparent 100%);
}
/* v1.4.2: 滚动到最底部时淡出底部毛玻璃带——下方已无内容需要虚化，避免最后一行被糊住。
   与 .is-visible 同特异度，靠后声明取胜；沿用上方 260ms opacity 过渡平滑淡出。 */
.sp-frost-band--bottom.is-at-bottom { opacity: 0; }

/* ============================================
   v1.4.6 (item 10): In-article TOC bar + index region
   ============================================ */
/* v1.4.7 (item 2): the collapsed index bar is a fuller rounded pill, TOP-ANCHORED
   near the panel's top-right and only as tall as the collapsed index (JS sets
   top/left/height). Widened so it reads as solid. It fades out while the region
   is open, so the region visually IS the bar extended to the right. */
.article-toc-bar {
    position: fixed;
    z-index: 205; /* above map controls; below the region */
    width: 28px; /* v1.4.8 (item 4A)：索引条再加宽，读作更实的胶囊 */
    padding: 0;
    border: var(--panel-border);
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border-radius: 999px;
    box-shadow: var(--shadow-floating);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    pointer-events: none;
    transform: translateX(6px);
    transition:
        opacity 200ms var(--ease-out-expo),
        transform 200ms var(--ease-out-expo),
        width 160ms var(--ease-out-expo),
        background 160ms ease;
}
.article-toc-bar.is-active:not(.is-hidden) { opacity: 1; pointer-events: auto; transform: translateX(0); }
.article-toc-bar.is-hidden { display: none; }
.article-toc-bar:hover { background: var(--color-surface-hover); width: 30px; }
/* While the region is open the bar disappears (the region takes its place). */
.article-toc-bar.is-open { opacity: 0 !important; pointer-events: none; }
.article-toc-bar-grip {
    display: block;
    width: 4px;
    height: 30px;
    max-height: 60%;
    border-radius: 999px;
    background: var(--primary);
    opacity: 0.72;
}

/* v1.4.7 (item 2/2E): the expanded index. It shares the bar's exact top-left and
   animates its WIDTH from the bar's width out to full (extends rightward) and its
   HEIGHT from the collapsed size to the content size (unfurls downward). Closing
   reverses both, narrowing back into the bar. JS sets top/left/width/height; the
   width/height transitions produce the morph. Sits above the page. */
.article-toc-region {
    position: fixed;
    z-index: 9998;
    box-sizing: border-box;
    border: var(--panel-border);
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border-radius: var(--panel-radius);
    box-shadow: var(--shadow-elevated);
    overflow: hidden;
    opacity: 0;
    transform-origin: left top;
    pointer-events: none;
    transition:
        width 280ms var(--ease-out-expo),
        height 280ms var(--ease-out-expo),
        opacity 180ms ease;
}
.article-toc-region.is-open { opacity: 1; pointer-events: auto; }
/* Inner content fades/slides in slightly after the box starts widening, so the
   morph reads as the bar growing rather than text popping. */
.article-toc-region-inner {
    display: flex; flex-direction: column; height: 100%;
    min-width: 180px;              /* keep text from reflowing during the width morph */
    padding: 14px 8px 10px 12px;
    opacity: 0;
    transition: opacity 160ms ease 90ms;
}
.article-toc-region.is-open .article-toc-region-inner { opacity: 1; }
.article-toc-title {
    flex: 0 0 auto;
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
    padding: 0 8px 8px;
}
/* v1.4.8 (item 4B)：永久去掉索引页面的上下/左右进度挪动条——内容仍可滚动，仅隐藏轨道；
   横向不溢出（标题过长直接换行）。 */
.article-toc-list { flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; scrollbar-width: none; -ms-overflow-style: none; padding-right: 2px; }
.article-toc-list::-webkit-scrollbar { width: 0; height: 0; }
.article-toc-row { display: flex; align-items: flex-start; gap: 2px; }
.article-toc-item {
    flex: 1 1 auto;
    display: block;
    text-align: left;
    background: transparent;
    border: none;
    border-left: 2px solid transparent;
    color: var(--text-secondary);
    font-size: 0.85rem;
    line-height: 1.45;
    padding: 6px 8px 6px 14px;
    border-radius: 8px;
    cursor: pointer;
    white-space: normal;        /* long titles wrap instead of truncating */
    overflow-wrap: anywhere;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}
.article-toc-item--top { font-weight: 600; color: var(--text-primary); }
.article-toc-item:hover { background: var(--color-surface-hover); color: var(--text-primary); }
.article-toc-item.is-current {
    color: var(--primary);
    border-left-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 12%, transparent);
}
.article-toc-chevron {
    flex: 0 0 auto;
    width: 26px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    border-radius: 6px;
    transition: transform 200ms var(--ease-out-expo), color 140ms ease;
}
.article-toc-chevron:hover { color: var(--text-primary); }
.article-toc-group.is-collapsed > .article-toc-row .article-toc-chevron { transform: rotate(-90deg); }
.article-toc-children {
    overflow: hidden;
    max-height: 3000px;
    opacity: 1;
    transition: max-height 280ms var(--ease-out-expo), opacity 200ms ease;
}
.article-toc-group.is-collapsed > .article-toc-children { max-height: 0; opacity: 0; }
@media (prefers-reduced-motion: reduce) {
    .article-toc-bar, .article-toc-region, .article-toc-region-inner,
    .article-toc-children, .article-toc-chevron { transition: none; }
}
/* Mobile: the bar is a compact floating toggle and the region a right-side sheet.
   JS skips the desktop morph geometry when isMobile, so fixed geometry here wins. */
@media (max-width: 767px) {
    .article-toc-bar {
        left: auto !important;
        right: 12px;
        top: 50% !important;
        height: 46px !important;
        width: 40px !important;
        border-radius: 14px;
        transform: translate(6px, -50%);
    }
    .article-toc-bar.is-active:not(.is-hidden) { transform: translate(0, -50%); }
    .article-toc-bar:hover { width: 40px !important; }
    .article-toc-bar-grip { width: 16px; height: 3px; }
    .article-toc-region {
        left: auto !important;
        right: 12px !important;
        top: 64px !important;
        height: auto !important;
        max-height: calc(100vh - 128px) !important;
        width: min(320px, 84vw) !important;
        transition: opacity 200ms ease !important;
    }
    .article-toc-region-inner { min-width: 0; }
}

.article-nav-btn {
    position: absolute;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border-radius: 50%;
    border: var(--panel-border);
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    box-shadow: var(--shadow-elevated);
    color: var(--primary);
    cursor: pointer;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1;
    -webkit-tap-highlight-color: transparent;
    transition: opacity 220ms var(--ease-out-expo, ease),
                transform 220ms var(--ease-out-expo, ease),
                filter 160ms ease;
}
.article-nav-btn:hover { filter: brightness(1.08); }
button.article-nav-btn:active { transform: scale(0.94); }

/* v1.4.5 (item 7): the article close button, relocated into the fixed nav
   overlay. The overlay is pointer-events:none, so re-enable events here; z-index
   sits above the frost bands (z-index:4) so the top band never blurs over it.
   Keeps all .panel-close-btn visuals (position/top/right/hover rotate). */
.article-nav-close {
    pointer-events: auto;
    z-index: 12;
}

/* Back-to-top: pinned to the top-centre of the panel, over the text. Hidden
   until the reader scrolls down. */
.article-nav-top {
    top: 14px;
    left: 50%;
    transform: translateX(-50%) translateY(-8px);
    opacity: 0;
    pointer-events: none;
    z-index: 6;
}
.article-nav-top.is-visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
    pointer-events: auto;
}

/* Bottom trio: centred at the panel bottom. */
.article-nav-bottom {
    position: absolute;
    bottom: 16px;
    left: 50%;
    transform: translateX(-50%) translateY(8px);
    display: flex;
    align-items: center;
    gap: 10px;
    opacity: 0;
    pointer-events: none;
    z-index: 6;
    transition: opacity 220ms var(--ease-out-expo, ease),
                transform 220ms var(--ease-out-expo, ease);
}
.article-nav-bottom.is-visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
    pointer-events: auto;
}
.article-nav-bottom .article-nav-btn { position: static; }
.article-nav-progress { cursor: default; pointer-events: none; }
.article-nav-progress-num { font-variant-numeric: tabular-nums; }

/* ============================================
   AI full-text summary card (v1.3.6)
   Sits between the header and the content; typewritten on first open.
   ============================================ */
.article-summary {
    margin: 0 0 22px;
    padding: 14px 16px;
    border: 1px solid color-mix(in srgb, var(--primary) 32%, transparent);
    border-left: 3px solid var(--primary);
    border-radius: 12px;
    background: color-mix(in srgb, var(--primary) 7%, transparent);
}
.article-summary-label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 7px;
    color: var(--primary);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.02em;
}
.article-summary-label svg { flex: none; }
.article-summary-text {
    font-size: 0.9rem;
    line-height: 1.75;
    color: var(--text-secondary, var(--text-muted));
}
/* Blinking caret while the typewriter is running. */
.article-summary.is-typing .article-summary-text::after {
    content: '';
    display: inline-block;
    width: 2px;
    height: 1em;
    margin-left: 1px;
    vertical-align: -0.15em;
    background: var(--primary);
    animation: sp-summary-caret 1s steps(1) infinite;
}
@keyframes sp-summary-caret { 50% { opacity: 0; } }

.article-panel-header {
    margin-bottom: 20px;
    padding-right: 32px;
    /* Cascade entrance: header slides up last */
    transform: translateY(12px);
    opacity: 0;
    transition:
        transform var(--duration-slow) var(--ease-out-expo) 80ms,
        opacity var(--duration-normal) var(--ease-out-expo) 80ms;
}

.article-panel.active .article-panel-header {
    transform: translateY(0);
    opacity: 1;
}

.article-panel-header h3 {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 700;
    line-height: var(--leading-tight);
    margin-bottom: 8px;
}

.article-meta {
    font-size: 0.8125rem;
    color: var(--text-muted);
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}
/* Writing-location chip in the article meta line (v1.3.4) */
.article-wloc {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--text-muted);
}
.article-wloc svg { opacity: 0.75; }
/* View-count chip in the article meta line (v1.3.5) */
.article-views {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--text-muted);
}
.article-views svg { opacity: 0.75; }

.article-content {
    font-size: 0.9375rem;
    line-height: var(--leading-relaxed);
    color: var(--text-secondary);
    /* Cascade entrance: content fades in after header */
    transform: translateY(16px);
    opacity: 0;
    transition:
        transform var(--duration-slow) var(--ease-out-expo) 160ms,
        opacity var(--duration-slow) var(--ease-out-expo) 160ms;
}

.article-panel.active .article-content {
    transform: translateY(0);
    opacity: 1;
}

/* ---------------------------------------------------------------
   Social share bar (v1.3.4) — sits after the article body and
   before the comments section.
   --------------------------------------------------------------- */
.article-share {
    margin: 28px 0 4px;
    padding-top: 20px;
    border-top: 1px solid var(--color-border);
}
.share-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}
.share-bar-label {
    font-size: 0.8125rem;
    color: var(--text-muted);
    letter-spacing: 0.02em;
}
.share-bar-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.share-btn {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    /* v1.3.5 — monochrome icons that follow the theme colour. */
    color: var(--primary);
    line-height: 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: transform 160ms var(--ease-out-expo), border-color 160ms ease, background 160ms ease, filter 160ms ease;
}
.share-btn svg { display: block; }
.share-btn:hover {
    transform: translateY(-2px);
    border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 10%, var(--color-surface));
}
.share-btn.is-copied {
    border-color: var(--primary);
    color: #fff;
    background: var(--primary);
}
/* WeChat QR: floats above the button on hover; absolutely positioned so it
   never pushes the share row around. */
.share-wechat-wrap { position: relative; display: inline-flex; }
.share-qr {
    position: absolute;
    bottom: calc(100% + 12px);
    left: 50%;
    transform: translateX(-50%);
    z-index: 20;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 12px;
    border-radius: 14px;
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    box-shadow: var(--shadow-elevated);
}
.share-qr[hidden] { display: none; }
.share-qr::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    border: 7px solid transparent;
    border-top-color: var(--glass-elevated-bg);
}
.share-qr-code {
    background: #fff;
    padding: 10px;
    border-radius: 10px;
    box-shadow: var(--sphotography-card-shadow, 0 2px 12px rgba(0,0,0,0.15));
    line-height: 0;
}
.share-qr-code img,
.share-qr-code svg { display: block; width: 160px; height: 160px; image-rendering: pixelated; }
.share-qr-code svg { image-rendering: auto; }
.share-qr-hint { font-size: var(--text-xs); color: var(--text-muted); margin: 0; }

/* ---------------------------------------------------------------
   WordPress content compatibility.
   Covers both the classic editor and Gutenberg (wp-block-*) output:
   headings, text formatting, lists, quotes, tables, code, media,
   galleries, embeds, buttons, columns, alignments and more, so any
   post renders correctly inside the article panel.
   --------------------------------------------------------------- */
.article-content > *:first-child { margin-top: 0; }
.article-content > *:last-child { margin-bottom: 0; }

/* Headings */
.article-content h1,
.article-content h2,
.article-content h3,
.article-content h4,
.article-content h5,
.article-content h6 {
    font-family: var(--font-display);
    color: var(--text-primary);
    line-height: var(--leading-tight);
    margin: 1.5em 0 0.6em;
    font-weight: 700;
}
.article-content h1 { font-size: 1.7rem; }
.article-content h2 { font-size: 1.4rem; }
.article-content h3 { font-size: 1.2rem; }
.article-content h4 { font-size: 1.05rem; }
.article-content h5 { font-size: 0.95rem; }
.article-content h6 { font-size: 0.85rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }

/* Paragraphs & inline text */
.article-content p { margin-bottom: 1.2em; }
.article-content strong, .article-content b { color: var(--text-primary); font-weight: 700; }
.article-content em, .article-content i { font-style: italic; }
.article-content small { font-size: 0.8125rem; color: var(--text-muted); }
.article-content del, .article-content s { text-decoration: line-through; opacity: 0.7; }
.article-content ins { text-decoration: none; border-bottom: 1px dashed var(--primary); }
.article-content sub, .article-content sup { font-size: 0.7em; line-height: 0; }
.article-content mark {
    background: rgba(26, 188, 156, 0.28);
    color: var(--text-primary);
    padding: 0.05em 0.25em;
    border-radius: 3px;
}
.article-content abbr[title] { text-decoration: underline dotted; cursor: help; }
.article-content kbd {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8em;
    background: rgba(255,255,255,0.1);
    border: 1px solid var(--color-border);
    border-radius: 4px;
    padding: 0.1em 0.4em;
}

/* Links */
.article-content a {
    color: var(--primary);
    text-decoration: underline;
    text-underline-offset: 2px;
    word-break: break-word;
    transition: opacity var(--duration-fast) ease;
}
.article-content a:hover { opacity: 0.8; }

/* Lists */
.article-content ul, .article-content ol {
    padding-left: 1.4em;
    margin-bottom: 1.2em;
}
.article-content li { margin-bottom: 0.4em; }
.article-content li > ul, .article-content li > ol { margin: 0.4em 0 0.4em; }
.article-content dl { margin-bottom: 1.2em; }
.article-content dt { font-weight: 700; color: var(--text-primary); }
.article-content dd { margin: 0 0 0.6em 1.2em; }

/* Quotes & pullquotes */
.article-content blockquote,
.article-content .wp-block-quote {
    border-left: 3px solid var(--primary);
    padding-left: 16px;
    margin: 1.4em 0;
    color: var(--text-secondary);
    font-style: italic;
}
.article-content blockquote p:last-child { margin-bottom: 0.4em; }
.article-content cite,
.article-content .wp-block-quote cite {
    display: block;
    font-size: 0.8125rem;
    font-style: normal;
    color: var(--text-muted);
    margin-top: 6px;
}
.article-content .wp-block-pullquote {
    border: none;
    border-top: 2px solid var(--primary);
    border-bottom: 2px solid var(--primary);
    padding: 1.2em 0;
    margin: 1.6em 0;
    text-align: center;
    font-style: italic;
    font-size: 1.15rem;
}

/* Media: images, figures, captions */
.article-content img {
    max-width: 100%;
    height: auto;
    border-radius: 12px;
}
/* Geolocated photos are clickable — they fly the map to their location. */
.article-content img.article-geo-img { cursor: pointer; }
/* v1.4.6 (item 5): theme-colored hint under each clickable (geo) photo. */
.article-content .article-geo-hint {
    margin: 6px 0 2px;
    text-align: center;
    font-size: var(--text-sm, 0.82rem);
    line-height: 1.4;
    color: var(--primary);
    letter-spacing: 0.02em;
    opacity: 0.9;
}
.article-content p > img,
.article-content figure { margin: 1.5em 0; }
.article-content figure { max-width: 100%; }
.article-content figcaption,
.article-content .wp-caption-text {
    text-align: center;
    font-size: 0.8125rem;
    color: var(--text-muted);
    margin-top: 6px;
    font-style: italic;
}
.article-content .wp-caption { max-width: 100%; }

/* Video, audio, iframes & embeds — responsive */
.article-content iframe,
.article-content video,
.article-content audio,
.article-content embed,
.article-content object {
    max-width: 100%;
    border-radius: 12px;
}
.article-content video, .article-content iframe { height: auto; }
.article-content audio { width: 100%; }
.article-content .wp-block-embed { margin: 1.5em 0; }
.article-content .wp-block-embed__wrapper { position: relative; }
.article-content .wp-has-aspect-ratio .wp-block-embed__wrapper { padding-top: 56.25%; }
.article-content .wp-has-aspect-ratio .wp-block-embed__wrapper iframe {
    position: absolute; inset: 0; width: 100%; height: 100%;
}
.article-content .wp-embed-aspect-4-3 .wp-block-embed__wrapper { padding-top: 75%; }

/* Galleries */
.article-content .wp-block-gallery,
.article-content .blocks-gallery-grid,
.article-content .gallery {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 8px;
    margin: 1.5em 0;
    padding: 0;
    list-style: none;
}
.article-content .wp-block-gallery.columns-2,
.article-content .gallery-columns-2 { grid-template-columns: repeat(2, 1fr); }
.article-content .wp-block-gallery.columns-3,
.article-content .gallery-columns-3 { grid-template-columns: repeat(3, 1fr); }
.article-content .wp-block-image,
.article-content .blocks-gallery-item,
.article-content .gallery-item {
    margin: 0;
    list-style: none;
}
.article-content .wp-block-gallery figure,
.article-content .gallery-item figure { margin: 0; }
.article-content .wp-block-gallery img,
.article-content .gallery img { width: 100%; object-fit: cover; }

/* Tables */
.article-content table,
.article-content .wp-block-table table {
    width: 100%;
    border-collapse: collapse;
    margin: 1.5em 0;
    font-size: 0.875rem;
}
.article-content .wp-block-table { overflow-x: auto; margin: 1.5em 0; }
.article-content th, .article-content td {
    border: 1px solid var(--color-border);
    padding: 8px 12px;
    text-align: left;
}
.article-content th { background: var(--color-surface); color: var(--text-primary); font-weight: 700; }
.article-content tbody tr:nth-child(even),
.article-content tr:nth-child(even) { background: rgba(255,255,255,0.02); }
.article-content .wp-block-table figcaption { margin-top: 8px; }

/* Code & preformatted */
.article-content code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85em;
    background: rgba(255,255,255,0.08);
    padding: 0.15em 0.4em;
    border-radius: 4px;
    word-break: break-word;
}
.article-content pre,
.article-content .wp-block-code,
.article-content .wp-block-preformatted {
    background: rgba(0,0,0,0.4);
    border: 1px solid var(--color-border);
    border-radius: 10px;
    padding: 14px 16px;
    margin: 1.4em 0;
    overflow-x: auto;
    font-size: 0.85rem;
    line-height: 1.6;
}
.article-content pre code,
.article-content .wp-block-code code {
    background: none;
    padding: 0;
    font-size: inherit;
}
.article-content .wp-block-verse {
    font-family: var(--font-display);
    white-space: pre-wrap;
    margin: 1.4em 0;
}

/* Separators */
.article-content hr,
.article-content .wp-block-separator {
    border: none;
    height: 1px;
    background: var(--color-border);
    margin: 2em 0;
}
.article-content .wp-block-separator.is-style-dots {
    background: none;
    text-align: center;
}
.article-content .wp-block-separator.is-style-wide { margin: 2.4em 0; }

/* Buttons */
.article-content .wp-block-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 1.5em 0;
}
.article-content .wp-block-button__link,
.article-content .wp-block-button a {
    display: inline-block;
    background: var(--primary);
    color: #fff;
    padding: 10px 22px;
    border-radius: 999px;
    text-decoration: none;
    font-size: 0.875rem;
    transition: opacity var(--duration-fast) ease;
}
.article-content .wp-block-button.is-style-outline .wp-block-button__link {
    background: transparent;
    border: 1px solid var(--primary);
    color: var(--primary);
}
.article-content .wp-block-button__link:hover { opacity: 0.85; }

/* Columns & groups */
.article-content .wp-block-columns {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin: 1.5em 0;
}
.article-content .wp-block-column { flex: 1 1 0; min-width: 140px; }
.article-content .wp-block-group { margin: 1.5em 0; }
.article-content .wp-block-media-text {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    align-items: center;
    margin: 1.5em 0;
}
.article-content .wp-block-media-text img { width: 100%; }

/* Cover block */
.article-content .wp-block-cover {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 240px;
    padding: 24px;
    border-radius: 12px;
    overflow: hidden;
    margin: 1.5em 0;
    color: #fff;
    text-align: center;
}
.article-content .wp-block-cover img,
.article-content .wp-block-cover video {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: 0; margin: 0;
}
.article-content .wp-block-cover__inner-container { position: relative; z-index: 1; }

/* File block */
.article-content .wp-block-file {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin: 1.4em 0;
}

/* Alignments */
.article-content .aligncenter { display: block; margin-left: auto; margin-right: auto; text-align: center; }
.article-content .alignleft {
    float: left; margin: 0.4em 1.2em 1em 0; max-width: 50%;
}
.article-content .alignright {
    float: right; margin: 0.4em 0 1em 1.2em; max-width: 50%;
}
.article-content .alignwide {
    margin-left: -14px; margin-right: -14px; max-width: calc(100% + 28px); width: calc(100% + 28px);
}
.article-content .alignfull {
    margin-left: -28px; margin-right: -28px; max-width: calc(100% + 56px); width: calc(100% + 56px); border-radius: 0;
}
.article-content .alignfull img { border-radius: 0; }
.article-content .has-text-align-center { text-align: center; }
.article-content .has-text-align-right { text-align: right; }
.article-content .has-text-align-left { text-align: left; }
.article-content::after { content: ""; display: table; clear: both; }

/* Screen-reader text (WP core utility) */
.article-content .screen-reader-text {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---------------------------------------------------------------
   Article comments — matches the glass/serif frontend aesthetic.
   Shares the article panel's cascade entrance so it fades in with
   the rest of the content.
   --------------------------------------------------------------- */
.article-comments {
    margin-top: 28px;
    padding-top: 4px;
    transform: translateY(16px);
    opacity: 0;
    transition:
        transform var(--duration-slow) var(--ease-out-expo) 220ms,
        opacity var(--duration-slow) var(--ease-out-expo) 220ms;
}
.article-panel.active .article-comments {
    transform: translateY(0);
    opacity: 1;
}

.comments-section {
    border-top: 1px solid var(--color-border);
    padding-top: 22px;
}

.comments-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 18px;
    flex-wrap: wrap;
}
.comments-title {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--text-primary);
    display: flex;
    align-items: baseline;
    gap: 8px;
}
.comment-sort {
    display: inline-flex;
    gap: 4px;
    flex-shrink: 0;
}
.comment-sort-btn {
    font-family: var(--font-body);
    font-size: var(--text-xs);
    color: var(--text-muted);
    background: transparent;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    padding: 4px 12px;
    cursor: pointer;
    transition: color var(--duration-fast) ease, border-color var(--duration-fast) ease, background var(--duration-fast) ease;
    white-space: nowrap;
}
.comment-sort-btn:hover { color: var(--text-primary); border-color: var(--text-muted); }
.comment-sort-btn.is-active {
    color: #fff;
    background: var(--primary, var(--color-accent, #1abc9c));
    border-color: transparent;
}
.comments-count {
    font-size: var(--text-sm);
    color: var(--text-muted);
    font-family: var(--font-body);
    font-weight: 400;
}

.comment-list {
    list-style: none;
    margin: 0 0 24px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 16px;
}
.comments-empty {
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-style: italic;
    padding: 4px 0;
}

.comment-item {
    display: flex;
    gap: 12px;
    align-items: flex-start;
}
.comment-avatar {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: var(--color-surface);
}
.comment-avatar--placeholder {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-family: var(--font-display);
    font-size: var(--text-base);
    background: var(--primary);
}
.comment-body {
    flex: 1;
    min-width: 0;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: calc(var(--panel-radius) * 0.7);
    padding: 10px 14px;
}
.comment-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 4px;
}
.comment-author {
    font-weight: 600;
    font-size: var(--text-sm);
    color: var(--text-primary);
}
.comment-date {
    font-size: var(--text-xs);
    color: var(--text-muted);
}
.comment-text {
    font-size: 0.875rem;
    line-height: var(--leading-normal);
    color: var(--text-secondary);
    word-wrap: break-word;
    overflow-wrap: break-word;
}
.comment-text p { margin: 0 0 8px; }
.comment-text p:last-child { margin-bottom: 0; }

/* Comment form */
.comment-form {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: calc(var(--panel-radius) * 0.7);
    padding: 16px;
}
.comment-identity {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-bottom: 12px;
}
.comment-identity strong { color: var(--primary); }
.comment-fields {
    display: flex;
    gap: 10px;
    margin-bottom: 10px;
}
.comment-input,
.comment-textarea {
    width: 100%;
    padding: 9px 12px;
    border-radius: 9px;
    border: 1px solid var(--color-border);
    background: var(--field-bg);
    color: var(--text-primary);
    font-size: var(--text-sm);
    font-family: var(--font-body);
    outline: none;
    transition: border-color var(--duration-fast) var(--ease-out-expo),
                background var(--duration-fast) var(--ease-out-expo);
}
.comment-textarea {
    resize: vertical;
    min-height: 72px;
    line-height: var(--leading-normal);
}
.comment-input:focus,
.comment-textarea:focus {
    border-color: var(--primary);
    background: var(--color-surface-hover);
}
.comment-input::placeholder,
.comment-textarea::placeholder { color: var(--text-muted); }

/* Markdown toolbar + live preview (v1.3.7) */
.comment-md-toolbar {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-bottom: 6px;
    flex-wrap: wrap;
}
.comment-md-btn,
.comment-md-preview-toggle {
    font-family: var(--font-body);
    font-size: var(--text-xs);
    line-height: 1;
    color: var(--text-muted);
    background: transparent;
    border: 1px solid var(--color-border);
    border-radius: 7px;
    padding: 5px 8px;
    min-width: 28px;
    cursor: pointer;
    transition: color var(--duration-fast) ease, border-color var(--duration-fast) ease, background var(--duration-fast) ease;
}
.comment-md-btn:hover,
.comment-md-preview-toggle:hover { color: var(--text-primary); border-color: var(--text-muted); }
.comment-md-preview-toggle { margin-left: auto; }
.comment-md-preview-toggle.is-active {
    color: #fff;
    background: var(--primary);
    border-color: transparent;
}
.comment-md-preview {
    width: 100%;
    min-height: 72px;
    padding: 9px 12px;
    border-radius: 9px;
    border: 1px dashed var(--color-border);
    background: var(--field-bg);
    color: var(--text-primary);
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
    overflow-wrap: anywhere;
}
.comment-md-preview > :first-child { margin-top: 0; }
.comment-md-preview > :last-child { margin-bottom: 0; }
.comment-md-preview-empty { color: var(--text-muted); }

.comment-form-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 10px;
}
.comment-feedback {
    font-size: var(--text-xs);
    color: var(--text-muted);
    flex: 1;
    min-width: 0;
}
.comment-feedback.is-error { color: #e05a4d; }
.comment-feedback.is-success { color: var(--primary); }

.comment-submit {
    flex-shrink: 0;
    padding: 9px 20px;
    border-radius: 9px;
    border: none;
    cursor: pointer;
    background: var(--primary);
    color: #fff;
    font-size: var(--text-sm);
    font-weight: 600;
    font-family: var(--font-body);
    transition: filter var(--duration-fast) ease,
                transform var(--duration-instant) var(--ease-spring);
}
.comment-submit:hover { filter: brightness(1.08); transform: translateY(-1px); }
.comment-submit:active { transform: translateY(0); }
.comment-submit:disabled { opacity: 0.6; cursor: default; transform: none; filter: none; }

.comments-closed {
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-style: italic;
}

@media (max-width: 767px) {
    .comment-fields { flex-direction: column; }
}

/* ============================================
   5b. Comments v1.3.1 — threading, badges, actions,
       emoji, captcha, likes, pinning, folding, history
   ============================================ */

/* Avatar: Gravatar layered over a text-avatar / placeholder fallback. */
.comment-avatar-wrap {
    position: relative;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
}
.comment-avatar-wrap .comment-avatar {
    width: 38px;
    height: 38px;
    margin: 0;
}
.comment-avatar-wrap .comment-gravatar {
    position: absolute;
    inset: 0;
}
.comment-text-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 600;
    border-radius: 50%;
}

/* Avatar vertical position (居上 default / 居中). */
.comments-section[data-cc-align="center"] .comment-item { align-items: center; }

/* Threading layout. */
.comment-item { flex-wrap: wrap; }
.comment-children {
    list-style: none;
    flex-basis: 100%;
    width: 100%;
    margin: 12px 0 0 50px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.comment-item--child .comment-avatar-wrap,
.comment-item--child .comment-avatar-wrap .comment-avatar { width: 32px; height: 32px; }

.comments-loading {
    color: var(--text-muted);
    font-size: var(--text-sm);
    padding: 4px 0;
}

/* Head / sub-line, badges, reply-to. */
.comment-sub {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 6px;
}
.comment-badge {
    display: inline-block;
    font-size: 0.6875rem;
    line-height: 1.4;
    padding: 1px 7px;
    border-radius: 999px;
    font-weight: 600;
}
.comment-badge-admin { background: var(--primary); color: #fff; }
.comment-badge-pin { background: rgba(230, 126, 34, 0.15); color: #e67e22; }
.comment-badge-private { background: var(--color-surface-hover); color: var(--text-muted); border: 1px solid var(--color-border); }
.comment-reply-to {
    font-size: var(--text-xs);
    color: var(--primary);
}
.comment-ua {
    font-size: var(--text-xs);
    color: var(--text-muted);
}
/* IP region (归属地) badge in the comment meta line (v1.3.4) */
.comment-ip-loc {
    font-size: var(--text-xs);
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    gap: 3px;
}
.comment-ip-loc::before {
    content: "";
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--primary);
    opacity: 0.7;
}
.comment-edited {
    font-size: var(--text-xs);
    color: var(--text-muted);
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    text-decoration: underline dotted;
}
.comment-edited:hover { color: var(--primary); }

/* Markdown rendering inside comment text. */
.comment-text pre {
    background: var(--color-surface-hover);
    border: 1px solid var(--color-border);
    border-radius: 8px;
    padding: 10px 12px;
    overflow-x: auto;
    font-size: 0.8125rem;
    margin: 8px 0;
}
.comment-text code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85em;
    background: var(--color-surface-hover);
    padding: 1px 5px;
    border-radius: 5px;
}
.comment-text pre code { background: none; padding: 0; }
.comment-text blockquote {
    margin: 8px 0;
    padding: 4px 12px;
    border-left: 3px solid var(--primary);
    color: var(--text-muted);
}
.comment-text a { color: var(--primary); text-decoration: underline; }
.comment-text ul, .comment-text ol { margin: 8px 0; padding-left: 22px; }

/* Folding. */
.comment-text.is-folded {
    overflow: hidden;
    position: relative;
}
.comment-text.is-folded::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 46px;
    background: linear-gradient(to bottom, transparent, var(--color-surface));
    pointer-events: none;
}
.comment-text.is-expanded::after { display: none; }
.comment-fold-toggle {
    background: none;
    border: none;
    color: var(--primary);
    font-size: var(--text-xs);
    cursor: pointer;
    padding: 4px 0 0;
    font-family: var(--font-body);
}

/* Action row. */
.comment-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin-top: 8px;
}
.comment-act {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-family: var(--font-body);
    transition: color var(--duration-fast) ease;
}
.comment-act:hover { color: var(--primary); }
.comment-like.is-liked { color: #e05a4d; }
.comment-like.is-liked:hover { color: #e05a4d; }

/* Inline reply / edit slot. */
.comment-reply-slot:not(:empty) { margin-top: 12px; }
.comment-form--reply,
.comment-form--edit { padding: 12px; }

/* Emoji panel. */
.comment-emoji-btn {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 1.15rem;
    line-height: 1;
    padding: 4px;
    flex-shrink: 0;
}
.comment-emoji-panel[hidden] { display: none; } /* v1.4.2: display:flex below would otherwise override the [hidden] attribute, leaving the panel always open. */
.comment-emoji-panel {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    margin-top: 8px;
    padding: 8px;
    border: 1px solid var(--color-border);
    border-radius: 9px;
    background: var(--field-bg);
    max-height: 148px;
    overflow-y: auto;
}
.comment-emoji {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 1.15rem;
    line-height: 1;
    padding: 4px;
    border-radius: 6px;
}
.comment-emoji:hover { background: var(--color-surface-hover); }

/* Captcha. */
.comment-captcha-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
}
.comment-captcha-q {
    font-size: var(--text-sm);
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.comment-captcha-input { width: 90px; flex-shrink: 0; }
.comment-captcha-refresh {
    background: none;
    border: 1px solid var(--color-border);
    border-radius: 8px;
    cursor: pointer;
    color: var(--text-muted);
    width: 34px;
    height: 34px;
    flex-shrink: 0;
}
.comment-captcha-refresh:hover { color: var(--primary); border-color: var(--primary); }

/* Options (private / notify). */
.comment-options {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin-top: 10px;
}
.comment-opt {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--text-xs);
    color: var(--text-secondary);
    cursor: pointer;
}
.comment-opt input { accent-color: var(--primary); }

/* Pager. */
.comment-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    margin: 0 0 22px;
}
.comment-page-btn {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 9px;
    padding: 8px 18px;
    cursor: pointer;
    color: var(--text-secondary);
    font-size: var(--text-sm);
    font-family: var(--font-body);
    transition: border-color var(--duration-fast) ease, color var(--duration-fast) ease;
}
.comment-page-btn:hover { border-color: var(--primary); color: var(--primary); }
.comment-page-cur { font-size: var(--text-sm); color: var(--text-muted); }

/* Edit history. */
.comment-history {
    margin-top: 10px;
    padding: 10px 12px;
    border: 1px dashed var(--color-border);
    border-radius: 9px;
    background: var(--color-surface-hover);
}
.comment-history-title {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-muted);
    margin-bottom: 8px;
}
.comment-history-item {
    font-size: 0.8125rem;
    color: var(--text-secondary);
    padding: 6px 0;
    border-top: 1px solid var(--color-border);
}
.comment-history-item:first-of-type { border-top: none; }
.comment-history-date {
    display: block;
    font-size: var(--text-xs);
    color: var(--text-muted);
    margin-bottom: 3px;
}
.comment-history-empty {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

@media (max-width: 767px) {
    .comment-children { margin-left: 24px; }
    .comment-captcha-row { flex-wrap: wrap; }
}

/* 6. 图片网格面板 */
.photo-panels {
    position: fixed;
    inset: 0;
    z-index: 105;
    pointer-events: none;
}

.photo-grid-panel {
    position: fixed;
    z-index: 105;
    padding: 12px 16px;
    opacity: 0;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    max-height: 80vh;
    overflow: hidden;
    transform: scale(0.92) translateY(10px);
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    border-radius: var(--panel-radius);
    box-shadow: var(--shadow-elevated);
    transition:
        opacity var(--duration-slow) var(--ease-out-expo),
        transform var(--duration-slow) var(--ease-out-expo);
}

.photo-grid-panel.active {
    opacity: 1;
    transform: scale(1) translateY(0);
    pointer-events: auto;
}

.photo-grid-title {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: 600;
    margin-bottom: 12px;
    padding-right: 32px;
    /* Staggered entrance */
    transform: translateY(8px);
    opacity: 0;
    transition:
        transform var(--duration-normal) var(--ease-out-expo) 60ms,
        opacity var(--duration-normal) var(--ease-out-expo) 60ms;
}

.photo-grid-panel.active .photo-grid-title {
    transform: translateY(0);
    opacity: 1;
}

.photo-grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    overflow-y: auto;
    flex: 1;
    padding: 4px 2px;
}

.photo-grid-container::-webkit-scrollbar { width: 3px; }
.photo-grid-container::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 2px; }

/* Photo grid items — staggered reveal */
.photo-grid-item {
    aspect-ratio: 1;
    border-radius: 10px;
    overflow: hidden;
    cursor: pointer;
    background: #1a1a1a;
    position: relative;
    opacity: 0;
    transform: scale(0.85);
    transition:
        transform var(--duration-fast) var(--ease-spring),
        opacity var(--duration-normal) var(--ease-out-expo),
        box-shadow var(--duration-fast) ease;
}

.photo-grid-panel.active .photo-grid-item {
    opacity: 1;
    transform: scale(1);
}

/* Stagger delay via nth-child */
.photo-grid-panel.active .photo-grid-item:nth-child(1) { transition-delay: 80ms; }
.photo-grid-panel.active .photo-grid-item:nth-child(2) { transition-delay: 120ms; }
.photo-grid-panel.active .photo-grid-item:nth-child(3) { transition-delay: 160ms; }
.photo-grid-panel.active .photo-grid-item:nth-child(4) { transition-delay: 200ms; }
.photo-grid-panel.active .photo-grid-item:nth-child(5) { transition-delay: 240ms; }
.photo-grid-panel.active .photo-grid-item:nth-child(6) { transition-delay: 280ms; }
.photo-grid-panel.active .photo-grid-item:nth-child(7) { transition-delay: 320ms; }
.photo-grid-panel.active .photo-grid-item:nth-child(8) { transition-delay: 360ms; }
.photo-grid-panel.active .photo-grid-item:nth-child(9) { transition-delay: 400ms; }

.photo-grid-item:hover {
    transform: scale(1.05);
    box-shadow: 0 6px 20px rgba(0,0,0,0.4);
    z-index: 2;
}

.photo-grid-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--duration-slow) var(--ease-out-expo);
}

.photo-grid-item:hover img {
    transform: scale(1.08);
}

.photo-grid-item .photo-item-overlay {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 20px 8px 6px;
    background: linear-gradient(transparent, rgba(0,0,0,0.7));
    font-size: var(--text-xs);
    color: var(--text-primary);
    opacity: 0;
    transform: translateY(4px);
    transition:
        opacity var(--duration-normal) var(--ease-out-expo),
        transform var(--duration-normal) var(--ease-out-expo);
}

.photo-grid-item:hover .photo-item-overlay {
    opacity: 1;
    transform: translateY(0);
}

/* 7. 图片详情面板 */
.panel-close-btn {
    position: absolute;
    top: 12px;
    right: 12px;
    width: var(--btn-s);
    height: var(--btn-s);
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--glass-floating-bg);
    backdrop-filter: var(--glass-floating-blur);
    -webkit-backdrop-filter: var(--glass-floating-blur);
    color: var(--text-secondary);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    z-index: 10;
    box-shadow: var(--shadow-subtle);
    transition:
        background var(--duration-fast) var(--ease-out-expo),
        color var(--duration-fast) ease,
        transform var(--duration-instant) var(--ease-spring),
        border-color var(--duration-fast) ease;
}

.panel-close-btn:hover {
    background: rgba(180, 60, 50, 0.25); /* 红调提示关闭 */
    color: #e74c3c;
    border-color: rgba(231, 76, 60, 0.3);
    transform: rotate(90deg) scale(1.05);
}

.panel-close-btn:active {
    transform: rotate(90deg) scale(0.95);
}

.panel-close-btn.small {
    width: calc(var(--btn-s) - 10px);
    height: calc(var(--btn-s) - 10px);
}

/* v1.4.9: back-arrow variant of the article close button. Applied (via .is-back) only
   when the article was opened INSIDE the sidebar expand-page, where the button returns
   to the list instead of closing the view. Swap the ✕ for a chevron and drop the
   destructive red/90°-rotate hover in favour of a neutral "go back" affordance. */
.panel-close-icon-back { display: none; }
.panel-close-btn.is-back .panel-close-icon-x { display: none; }
.panel-close-btn.is-back .panel-close-icon-back { display: block; }
.panel-close-btn.is-back:hover {
    background: var(--color-surface-hover);
    color: var(--text-primary);
    border-color: var(--color-border-hover);
    transform: translateX(-2px);
}
.panel-close-btn.is-back:active { transform: translateX(-2px) scale(0.95); }

/* 8. 右下角个人信息卡片 */
.detail-sheet {
    position: fixed;
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    width: 100%;
    max-height: 70vh;
    z-index: 110;
    border-radius: 24px 24px 0 0; /* 初始胶囊形 */
    border-top: var(--panel-border);
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    box-shadow: 0 -4px 32px rgba(0,0,0,0.35);
    transform: translateY(100%);
    opacity: 0;
    /* v1.4.5 (item 7): the sheet itself no longer scrolls — .detail-content does.
       This keeps #close-detail (a non-scrolling child of the sheet) pinned to the
       top-right with no JS chase (retires pinScrollingPanelClose for the sheet). */
    overflow: hidden;
    transition:
        transform var(--duration-slow) var(--ease-out-expo),
        opacity var(--duration-slow) var(--ease-out-expo),
        border-radius var(--duration-slow) var(--ease-out-expo);
}

.detail-sheet.active {
    transform: translateY(0);
    opacity: 1;
    border-radius: var(--panel-radius) var(--panel-radius) 0 0; /* 展开为卡片形 */
}

.detail-content {
    padding: 16px 20px 24px;
    /* v1.4.5 (item 7): the scroll container (was #detail-sheet). Clamp to the
       sheet's max height and scroll internally so the close button stays pinned. */
    max-height: 70vh;
    overflow-y: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
}
.detail-content::-webkit-scrollbar { width: 0; height: 0; }

.drag-handle {
    width: 36px; height: 4px;
    background: rgba(255,255,255,0.2);
    border-radius: 2px;
    margin: 6px auto 12px;
    /* 弹性拉伸指示 */
    transition:
        transform var(--duration-normal) var(--ease-spring),
        background var(--duration-fast) ease;
    transform-origin: center;
}

.detail-sheet.active .drag-handle {
    animation: dragHandlePulse 1.2s var(--ease-spring);
}

@keyframes dragHandlePulse {
    0% { transform: scaleX(0.6); }
    50% { transform: scaleX(1.15); background: rgba(255,255,255,0.35); }
    100% { transform: scaleX(1); }
}

.detail-img {
    width:100%; height:200px; object-fit:cover;
    border-radius:12px; margin-bottom:14px;
    background:#1a1a1a;
    /* 图片模糊渐显加载态 */
    animation: imageReveal var(--duration-slow) var(--ease-out-expo);
}

@keyframes imageReveal {
    from { filter: blur(12px); opacity: 0.6; }
    to { filter: blur(0); opacity: 1; }
}

.detail-title {
    font-family:var(--font-display); font-size:1.25rem;
    font-weight:700; margin-bottom:10px;
}

.detail-meta {
    display:flex; flex-wrap:wrap; gap:6px 14px;
    font-size:0.78125rem; color:var(--text-secondary);
    margin-bottom:12px;
}

.detail-meta-item { display:flex; align-items:center; gap:4px; }
.detail-meta-item svg { width:13px; height:13px; opacity:0.5; }
.detail-desc { font-size:0.875rem; line-height:1.7; color:#c0c0c0; margin-bottom:12px; }
.detail-tags { display:flex; flex-wrap:wrap; gap:6px; }

.detail-tag {
    padding:3px 10px; font-size:0.7rem;
    background:rgba(26,188,156,0.12); color:var(--primary);
    border:1px solid rgba(26,188,156,0.2); border-radius:10px;
    transition:
        transform var(--duration-instant) var(--ease-spring),
        background var(--duration-fast) ease,
        border-color var(--duration-fast) ease;
}

.detail-tag:hover {
    transform: scale(1.06);
    background: rgba(26,188,156,0.2);
    border-color: rgba(26,188,156,0.35);
}

/* "View article" action — links a photo marker back to its parent post.
   Margin lives on the button so a hidden button leaves no empty gap. */
.detail-actions { display: flex; }

.detail-view-article-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 18px;
    padding: 9px 16px;
    font-family: var(--font-body);
    font-size: 0.8125rem;
    font-weight: 600;
    color: #fff;
    background: var(--primary);
    border: none;
    border-radius: calc(var(--panel-radius) * 0.55);
    cursor: pointer;
    box-shadow: var(--shadow-subtle);
    transition:
        transform var(--duration-fast) var(--ease-out-expo),
        box-shadow var(--duration-fast) var(--ease-out-expo),
        filter var(--duration-fast) ease;
}

.detail-view-article-btn svg { flex-shrink: 0; }

.detail-view-article-btn:hover {
    transform: translateY(-1px);
    filter: brightness(1.06);
    box-shadow: var(--shadow-floating);
}

.detail-view-article-btn:active { transform: translateY(0); }

.detail-view-article-btn[hidden] { display: none; }

/* 9. 页面链接栏（右上角） */

/* v1.4.8：右下角个人信息卡片（.about-card / .about-hitokoto / .about-card-expand）
   已随「个人信息展示方式」选项一并移除，个人信息统一由边栏一行展示。 */

/* ============================================
   9b. Cursor Style — 点+圆环光标 (v1.2.8, cursor_style = dot)
   Replaces the OS arrow with a small dot ringed by a thin circle. The SVG
   carries a dark stroke under a light stroke so it reads on any map backdrop.
   !important is required to override MapLibre's inline canvas cursor and the
   pointer cursors set on interactive elements, so the dot is consistent
   everywhere; text inputs keep the I-beam so typing stays usable.
   ============================================ */
body.sphotography-cursor-dot,
body.sphotography-cursor-dot * {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Ccircle cx='14' cy='14' r='8' fill='none' stroke='%23000' stroke-width='2.6'/%3E%3Ccircle cx='14' cy='14' r='8' fill='none' stroke='%23fff' stroke-width='1.2'/%3E%3Ccircle cx='14' cy='14' r='1.6' fill='%23000' stroke='%23fff' stroke-width='0.5'/%3E%3C/svg%3E") 14 14, auto !important;
}
body.sphotography-cursor-dot input[type="text"],
body.sphotography-cursor-dot input[type="search"],
body.sphotography-cursor-dot input[type="url"],
body.sphotography-cursor-dot input[type="number"],
body.sphotography-cursor-dot textarea {
    cursor: text !important;
}

/* ============================================
   9c. Rounded / magnetic cursor (v1.4.5, cursor_style = rounded, default)
   JS (app.js §20b) drives two fixed follower elements — an idle rounded V-arrow
   and an adsorb ring that hugs interactive elements. `sp-rounded-cursor-on` is
   added by JS only on hover+fine-pointer devices after the intro, so touch
   devices never lose their native behaviour. The OS cursor is hidden while on;
   focused text fields get the I-beam back so typing stays usable.
   ============================================ */
body.sp-rounded-cursor-on,
body.sp-rounded-cursor-on * { cursor: none !important; }
body.sp-rounded-cursor-on input:focus,
body.sp-rounded-cursor-on textarea:focus,
body.sp-rounded-cursor-on [contenteditable]:focus { cursor: text !important; }

.sp-cursor-arrow,
.sp-cursor-ring {
    position: fixed;
    left: 0; top: 0;
    z-index: 2147483000; /* above every panel / overlay / map control */
    pointer-events: none;
    will-change: transform;
}
/* v1.4.7 (item 7): idle cursor is an iPadOS-style soft round dot — theme-tinted
   translucent fill + subtle theme ring, soft shadow reads as frosted. Centred on
   the pointer hotspot (margin = -half its size). No sharp corners. */
.sp-cursor-arrow {
    width: 24px; height: 24px;
    margin: -12px 0 0 -12px; /* centre the dot on the pointer hotspot */
    border-radius: 50%;
    background: color-mix(in srgb, var(--primary) 30%, transparent);
    border: 1.5px solid color-mix(in srgb, var(--primary) 60%, transparent);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.22);
    opacity: 0;
    transition: opacity 180ms ease;
}
.sp-cursor-arrow svg { display: block; }
.sp-cursor-arrow.is-on { opacity: 1; }
.sp-cursor-arrow.is-faded { opacity: 0; }           /* hidden while adsorbed */
.sp-cursor-arrow.is-hidden,
.sp-cursor-ring.is-hidden { opacity: 0 !important; } /* yielding to a focused field */

/* Adsorb ring — hugs the target's exact border (theme stroke, faint theme fill). */
.sp-cursor-ring {
    width: 10px; height: 10px;
    border: 2px solid var(--primary);
    border-radius: 8px;
    background: color-mix(in srgb, var(--primary) 12%, transparent);
    opacity: 0;
}
.sp-cursor-ring.is-adsorbed { opacity: 1; }
/* Transition only during the grow/release morph, not per-frame tracking. */
.sp-cursor-ring.is-morphing {
    transition:
        width 300ms var(--ease-out-expo, cubic-bezier(0.16,1,0.3,1)),
        height 300ms var(--ease-out-expo, cubic-bezier(0.16,1,0.3,1)),
        border-radius 300ms var(--ease-out-expo, cubic-bezier(0.16,1,0.3,1)),
        transform 300ms var(--ease-out-expo, cubic-bezier(0.16,1,0.3,1)),
        opacity 220ms ease;
}
/* Smooth the parallax drift + snap-back on elements that lack their own
   transform transition. */
.sp-magnet-active { transition: transform 220ms var(--ease-out-expo, cubic-bezier(0.16,1,0.3,1)); }

@media (prefers-reduced-motion: reduce) {
    .sp-cursor-arrow { transition: opacity 120ms ease; }
    .sp-cursor-ring.is-morphing { transition: opacity 120ms ease; }
}

/* 10. 弹窗与通知 */
.map-footer {
    position: fixed;
    left: 50%;
    bottom: 16px;
    transform: translateX(-50%);
    z-index: 30;
    padding: 8px 20px;
    font-size: 0.8125rem;
    color: var(--text-secondary);
    text-align: center;
    white-space: nowrap;
    max-width: 80vw;
    overflow: hidden;
    text-overflow: ellipsis;
    background: var(--glass-floating-bg);
    backdrop-filter: var(--glass-floating-blur);
    -webkit-backdrop-filter: var(--glass-floating-blur);
    border: var(--panel-border);
    border-radius: 999px; /* 胶囊形页脚 */
    box-shadow: var(--shadow-subtle);
    transition:
        opacity var(--duration-normal) var(--ease-out-expo),
        transform var(--duration-normal) var(--ease-out-expo);
}

/* Mobile: footer above about trigger */
@media (max-width: 767px) {
    .map-footer {
        bottom: 68px;
        max-width: calc(100vw - 100px);
        font-size: 0.75rem;
        padding: 6px 14px;
    }
}

/* 11. 加载动画 */
.loading-overlay {
    position: fixed; inset: 0; z-index: 9999;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    /* 径向渐变背景 — 主题色微光 */
    background:
        radial-gradient(
            ellipse at 50% 50%,
            color-mix(in srgb, var(--primary) 6%, transparent) 0%,
            var(--bg) 70%
        );
    transition:
        opacity var(--duration-cinematic) var(--ease-in-out-sine),
        visibility var(--duration-cinematic);
}

.loading-overlay.fade-out {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* 光圈动画容器 */
.loading-aperture {
    position: relative;
    width: 88px;
    height: 88px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* 外圈旋转光圈环 — SVG 绘制 */
.loading-aperture-ring {
    position: absolute;
    width: 100%;
    height: 100%;
    animation: apertureRotate 8s linear infinite;
}

.loading-aperture-ring circle {
    fill: none;
    stroke-width: 1.5;
    stroke-linecap: round;
}

.loading-aperture-ring .ring-outer {
    stroke: rgba(255,255,255,0.12);
    stroke-dasharray: 220 60;
    animation: ringDash 3s var(--ease-in-out-sine) infinite;
}

.loading-aperture-ring .ring-inner {
    stroke: var(--primary);
    stroke-dasharray: 140 80;
    opacity: 0.6;
    animation: ringDashInner 2.5s var(--ease-in-out-sine) infinite reverse;
}

/* 内圈呼吸圆点 */
.loading-aperture-core {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--primary);
    box-shadow: 0 0 20px var(--primary), 0 0 40px color-mix(in srgb, var(--primary) 40%, transparent);
    animation: apertureBreathe 2s var(--ease-in-out-sine) infinite;
}

/* 站点名称优雅淡入 */
.loading-site-name {
    margin-top: 28px;
    font-family: var(--font-display);
    font-size: 0.9rem;
    font-weight: 600;
    letter-spacing: 0.3em;
    text-transform: uppercase;
    color: rgba(255,255,255,0.45);
    opacity: 0;
    animation: siteNameFadeIn var(--duration-slow) var(--ease-out-expo) 0.6s forwards;
}

/* 随机加载提示文案 — 光圈下方，柔和淡入淡出切换 */
.loading-tip {
    margin-top: 12px;
    min-height: 1.2em;
    font-family: var(--font-body);
    font-size: 0.85rem;
    letter-spacing: 0.02em;
    color: rgba(255,255,255,0.55);
    opacity: 0;
    transition: opacity var(--duration-normal) var(--ease-in-out-sine);
    text-align: center;
    padding: 0 24px;
}
.loading-tip.is-visible {
    opacity: 1;
}

/* 进度条（底部极细） */
.loading-progress {
    position: absolute;
    bottom: 0;
    left: 0;
    height: 2px;
    width: 0%;
    background: linear-gradient(90deg, transparent, var(--primary), transparent);
    animation: progressExpand 2.5s var(--ease-in-out-sine) infinite;
    transition: opacity var(--duration-fast) ease;
}

.loading-overlay.fade-out .loading-progress {
    animation: progressComplete 0.5s var(--ease-out-expo) forwards;
}

/* === Loading Keyframes === */
@keyframes apertureRotate {
    to { transform: rotate(360deg); }
}

@keyframes ringDash {
    0% { stroke-dashoffset: 0; }
    50% { stroke-dashoffset: -80; }
    100% { stroke-dashoffset: -280; }
}

@keyframes ringDashInner {
    0% { stroke-dashoffset: 0; }
    50% { stroke-dashoffset: 60; }
    100% { stroke-dashoffset: -220; }
}

@keyframes apertureBreathe {
    0%, 100% { transform: scale(1); opacity: 0.7; }
    50% { transform: scale(1.4); opacity: 1; }
}

@keyframes siteNameFadeIn {
    from { opacity: 0; transform: translateY(8px); letter-spacing: 0.5em; }
    to { opacity: 1; transform: translateY(0); letter-spacing: 0.3em; }
}

@keyframes progressExpand {
    0% { width: 0%; opacity: 0; left: 0; }
    10% { opacity: 0.6; }
    90% { opacity: 0.6; }
    100% { width: 70%; opacity: 0; left: 30%; }
}

@keyframes progressComplete {
    to { width: 100%; opacity: 0; }
}

/* 兼容旧 spinner（降级） */
.loading-spinner {
    display: none; /* 被新的光圈动画替代 */
}

/* ============================================
   11b. Loading Overlay — 流光穿越（Flythrough）
   站点名称流光登场 → 加载完成后镜头穿过文字进入地图。
   结构由 template-map.php 输出，SVG 镂空遮罩由 app.js 动态构建。
   ============================================ */
.loading-overlay--flythrough {
    /* 不透明底色完全遮住地图，保证揭晓前是干净的悬念；中央叠加一层主题色微光。
       --ft-map-bg 由 app.js 写入为当前底图（dark-matter / positron）的确切背景色，
       使穿越落地与地图零色差。揭晓时由 .is-revealing 置为透明，让镂空处透出地图。 */
    background-color: var(--ft-map-bg, var(--bg));
    background-image:
        radial-gradient(
            ellipse at 50% 45%,
            color-mix(in srgb, var(--primary) 12%, transparent) 0%,
            transparent 60%
        );
}

/* 加载中的站点名称：主题色 + 循环右向流光 */
.loading-overlay--flythrough .ft-name {
    max-width: 80vw;
    /* 纵向留白 + 更高行盒：容纳 y/g/p/j 等深下摆字形，避免 background-clip:text
       与滤镜区域把下摆裁掉。 */
    padding: 0.22em 24px 0.34em;
    text-align: center;
    font-family: var(--font-display, var(--font-body, serif));
    font-size: clamp(1.5rem, 7vw, 4rem);
    font-weight: 700;
    line-height: 1.5;
    letter-spacing: 0.04em;
    word-break: break-word;
    /* 主题色文字，中段为提亮高光，随背景位移形成流光 */
    background-image: linear-gradient(
        100deg,
        var(--primary) 0%,
        var(--primary) 35%,
        color-mix(in srgb, var(--primary) 30%, #ffffff) 50%,
        var(--primary) 65%,
        var(--primary) 100%
    );
    background-size: 220% 100%;
    background-position: 120% 0;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    /* 入场前隐藏，由 .is-in 触发优雅登场 */
    opacity: 0;
    transform: translateY(6px);
    filter: blur(6px);
    will-change: opacity, transform, filter, background-position;
}

/* 优雅登场：模糊到清晰 + 字距收拢 */
.loading-overlay--flythrough .ft-name.is-in {
    animation:
        ftNameIn 640ms var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)) forwards;
}

/* 流光循环（仅在允许动效时启用；登场后叠加播放） */
@media (prefers-reduced-motion: no-preference) {
    .loading-overlay--flythrough .ft-name.is-in {
        animation:
            ftNameIn 640ms var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)) forwards,
            ftShine 2.4s linear 640ms infinite;
    }
}

/* 镂空穿越遮罩（由 JS 注入 <svg class="ft-mask-svg">） */
.loading-overlay--flythrough .ft-mask-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    transform: scale(1);
    transform-origin: center center;
    will-change: transform, opacity;
    pointer-events: none;
}
/* 满屏纯色填充块（颜色对齐底图背景），文字部分被遮罩镂空，透出其后的地图 */
.loading-overlay--flythrough .ft-mask-fill {
    fill: var(--ft-map-bg, var(--bg));
}

/* 慢载提示（>8s 由 app.js 注入），站点名下方一行小字 */
.loading-overlay--flythrough .ft-hint {
    margin-top: 22px;
    font-family: var(--font-body, sans-serif);
    font-size: 0.85rem;
    letter-spacing: 0.08em;
    color: color-mix(in srgb, var(--primary) 60%, #888888);
    opacity: 0;
    transition: opacity 500ms var(--ease-in-out-sine, ease);
}
.loading-overlay--flythrough .ft-hint.is-in {
    opacity: 0.8;
}

/* 揭晓阶段（两段式）：
   1) 底色瞬时透明，站点名原地「溶解」淡出，透过镂空字洞渐露地图；
   2) 遮罩延迟启动，加速冲刺、末段减速并淡出——镜头穿过文字进入地图。 */
.loading-overlay--flythrough.is-revealing {
    background: transparent;
    pointer-events: none;
}
.loading-overlay--flythrough.is-revealing .ft-name {
    animation: ftDissolve 460ms var(--ease-in-out-sine, ease) forwards;
}
.loading-overlay--flythrough.is-revealing .ft-hint {
    opacity: 0;
}
.loading-overlay--flythrough.is-revealing .ft-mask-svg {
    opacity: 1;
    /* 340ms 延迟让文字先溶解；cubic-bezier 起步慢(加速)、收尾缓(减速) */
    animation: ftFlythrough 1050ms cubic-bezier(0.5, 0, 0.2, 1) 340ms forwards;
}

@keyframes ftNameIn {
    from { opacity: 0; transform: translateY(6px); filter: blur(6px); letter-spacing: 0.16em; }
    to   { opacity: 1; transform: translateY(0);   filter: blur(0);   letter-spacing: 0.04em; }
}

@keyframes ftShine {
    from { background-position: 120% 0; }
    to   { background-position: -120% 0; }
}

/* 文字溶解：不透明 → 透明，并带轻微模糊，柔化「变透明」的衔接 */
@keyframes ftDissolve {
    from { opacity: 1; filter: blur(0); }
    to   { opacity: 0; filter: blur(5px); }
}

@keyframes ftFlythrough {
    0%   { transform: scale(1);  opacity: 1; }
    65%  { opacity: 1; }
    100% { transform: scale(18); opacity: 0; }
}

/* 12. 响应式与移动端适配 */
@media (max-width: 1199px) {
    .sidebar {
        width: 260px;
        left: 12px;
        top: 12px;
        bottom: 12px;
    }
    .article-panel {
        left: 290px;
        width: min(480px, calc(100vw - 320px));
    }
    .side-panel { width: min(520px, calc(100vw - 300px)); }
}

/* 13. 地图样式覆盖 */
.page-links-bar {
    position: fixed;
    top: 12px;
    right: 58px; /* clear the MapLibre zoom/compass control on the far right */
    z-index: 118;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 5px;
    max-width: calc(100vw - 120px);
    border-radius: 999px;
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    box-shadow: var(--shadow-elevated);
    overflow-x: auto;
    scrollbar-width: none;
}
.page-links-bar::-webkit-scrollbar { width: 0; height: 0; }
.page-link-btn {
    position: relative; /* v1.4.2: 抬到共享药丸之上 */
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: var(--text-primary);
    font-size: var(--text-sm);
    font-family: var(--font-body);
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: color var(--duration-fast) ease;
}
/* v1.4.2: 悬停高亮改由共享滑动药丸提供，按钮本身只切换文字色（不再各自铺背景）。 */
.page-link-btn:hover { color: var(--primary); }

/* v1.4.2 — iOS-26 风格“流动高亮”:一枚共享药丸,hover 时滑动+变宽到目标按钮下方,
   高亮在按钮间流动而非各自独立点亮。离开整栏时淡出;尊重 reduced-motion。 */
.page-link-pill {
    position: absolute;
    left: 0;
    top: 0;
    border-radius: 999px;
    background: var(--color-surface-hover);
    opacity: 0;
    pointer-events: none;
    z-index: 0;
    transition:
        transform 300ms var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)),
        width 300ms var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)),
        opacity 220ms ease;
}
.page-links-bar.pill-active .page-link-pill { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .page-link-pill { transition: opacity 120ms ease; }
}
.page-link-ico { display: inline-flex; color: var(--primary); }

/* v1.4.4 (item 6): 公告面板。右上角浮层，页面链接栏正下方、右侧地图控件左边。
   left/top/width 由 JS(syncAnnouncementGeom) 同步为页面链接栏的实际位置与宽度；
   高度随内容变化，内容超长时 .announcement-content 内部滚动（关闭键+标题常驻）。
   出现/消失：从顶部缩放淡入淡出（transform-origin 顶部，贴着栏“长出”）。 */
.announcement-panel {
    position: fixed;
    z-index: 117; /* 在页面链接栏(118)之下、右半侧面板(116)之上 */
    display: flex;
    flex-direction: column;
    max-height: min(60vh, calc(100vh - 120px));
    padding: 20px 24px 20px; /* v1.4.7 (item 1): 内边距再放宽，四周留白更通透 */
    border-radius: var(--panel-radius);
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    box-shadow: var(--shadow-elevated);
    transform-origin: top center;
    transform: translateY(-8px) scale(0.96);
    opacity: 0;
    pointer-events: none;
    transition:
        transform var(--duration-slow) var(--ease-out-expo),
        opacity var(--duration-slow) var(--ease-out-expo);
}
.announcement-panel.active {
    transform: translateY(0) scale(1);
    opacity: 1;
    pointer-events: auto;
}
/* v1.4.7 (item 1): 标题「公告」独占一行、正文「欢迎」另起一行，二者拉开明显间距；
   全部左对齐，放宽行高/字间距，避免文字上下堆叠拥挤。标题右侧留足关闭按钮空间。 */
.announcement-header {
    /* v1.4.8 (item 5)：标题独占一行，与正文拉开更明确的间距。分隔线 + 充足下外边距，
       避免正文首行（尤其当正文以标题/加粗大标题开头时）紧贴「公告」标题。 */
    margin: 0 0 28px;
    padding: 0 48px 16px 0;
    text-align: left;
    /* 一条淡分隔线把「公告」标题与下方正文清晰分成两块 */
    border-bottom: 1px solid var(--color-border, rgba(128, 128, 128, 0.16));
}
.announcement-header h3 {
    margin: 0;
    font-family: var(--font-display);
    font-size: 1.05rem;
    line-height: 1.5;
    letter-spacing: 0.02em;
    text-align: left;
    color: var(--text-primary);
}
.announcement-content {
    overflow-y: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    color: var(--text-secondary);
    font-size: 0.9rem;
    line-height: 1.9;          /* v1.4.7: 行距再放宽，阅读更舒展 */
    letter-spacing: 0.01em;    /* v1.4.7: 轻微字间距，避免密排 */
    text-align: left;          /* v1.4.7: 明确左对齐 */
    padding-bottom: 2px;
}
.announcement-content::-webkit-scrollbar { width: 0; height: 0; }
/* v1.4.8 (item 5)：不再把正文首个元素的上外边距清零——当正文以标题/加粗大标题开头时，
   保留其自然上间距，使其与「公告」标题之间留有呼吸空间，不再挤在一起。仅保证一个最小上间距。 */
.announcement-content > :first-child { margin-top: 14px; }
.announcement-content > :last-child { margin-bottom: 0; }
.announcement-content h3, .announcement-content h4, .announcement-content h5 {
    color: var(--text-primary);
    margin: 0.9em 0 0.4em;
}
.announcement-content p { margin: 0.6em 0; }
.announcement-content a { color: var(--primary); }
.announcement-content ul, .announcement-content ol { margin: 0.6em 0; padding-left: 1.4em; }
.announcement-content li { margin: 0.2em 0; }
.announcement-content img { max-width: 100%; border-radius: 8px; }
.announcement-panel .announcement-close { top: 12px; right: 12px; }
@media (prefers-reduced-motion: reduce) {
    .announcement-panel { transition: opacity 140ms ease; transform: none; }
    .announcement-panel.active { transform: none; }
}
@media (max-width: 767px) {
    .announcement-panel { max-height: min(70vh, calc(100vh - 90px)); }
}

/* Right-half side panels — mirror the article panel motion but slide from the
   right; they coexist with an open article on the left. */
.side-panel {
    position: fixed;
    /* Right edge aligned with the page-links bar (v1.3.9) so the panel clears
       the MapLibre top-right controls column (zoom / compass / night toggle). */
    right: 58px;
    /* Sits just below the page-links bar (v1.3.8); the open/close FLIP grows
       from / shrinks to the clicked bar button. */
    top: 62px;
    bottom: 16px;
    /* Trimmed 42px (the right-edge shift from 16→58) so the left edge stays put. */
    width: min(578px, calc(100vw - 422px));
    z-index: 116;
    overflow: hidden;
    border-radius: var(--panel-radius);
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    box-shadow: var(--shadow-elevated);
    transform: translateX(20px) scale(0.98);
    opacity: 0;
    pointer-events: none;
    transition:
        transform var(--duration-slow) var(--ease-out-expo),
        opacity var(--duration-slow) var(--ease-out-expo);
}
.side-panel.active {
    transform: translateX(0) scale(1);
    opacity: 1;
    pointer-events: auto;
}
/* During the JS FLIP the Web-Animations API drives transform/opacity; kill the
   CSS transition so the two don't fight. */
.side-panel--instant { transition: none !important; }
.side-panel-scroll {
    position: absolute;
    inset: 0;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 24px 26px;
    scrollbar-width: none;
    -ms-overflow-style: none;
}
.side-panel-scroll::-webkit-scrollbar { width: 0; height: 0; }
.side-panel-close { position: absolute; top: 14px; right: 14px; z-index: 8; }
.side-panel-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 18px;
}
.side-panel-header h3 {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 700;
    margin: 0;
    color: var(--text-primary);
}

/* 友链 cards: three per row, thumbnail with a frosted bottom-half name. */
.friend-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 14px;
}
.friend-card {
    position: relative;
    display: block;
    aspect-ratio: 4 / 3;
    border-radius: 12px;
    overflow: hidden;
    background: var(--color-surface-hover);
    text-decoration: none;
    box-shadow: var(--sphotography-card-shadow, 0 2px 10px rgba(0,0,0,0.12));
    transition: transform var(--duration-fast) ease, box-shadow var(--duration-fast) ease;
}
.friend-card:hover { transform: translateY(-3px); box-shadow: 0 8px 22px rgba(0,0,0,0.2); }
.friend-card-thumb { position: absolute; inset: 0; }
.friend-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.friend-card-thumb--empty { background: linear-gradient(135deg, var(--primary), transparent); opacity: 0.45; }
.friend-card-name {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 22px 10px 9px;
    color: #fff;
    font-size: var(--text-sm);
    font-weight: 600;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    background: linear-gradient(to top, rgba(0,0,0,0.6), transparent);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    -webkit-mask-image: linear-gradient(to top, #000 55%, transparent);
            mask-image: linear-gradient(to top, #000 55%, transparent);
}
.friend-empty, .friend-loading { color: var(--text-muted); padding: 24px 0; text-align: center; }

.friend-apply {
    margin-top: 26px;
    padding-top: 18px;
    border-top: 1px solid var(--color-border);
}
.friend-apply-title {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: 700;
    margin: 0 0 12px;
    color: var(--text-primary);
}
.friend-apply-form { display: flex; flex-direction: column; gap: 10px; }
.friend-apply-form input,
.friend-apply-form textarea {
    width: 100%;
    padding: 9px 12px;
    border-radius: 9px;
    border: 1px solid var(--color-border);
    background: var(--field-bg);
    color: var(--text-primary);
    font-size: var(--text-sm);
    font-family: var(--font-body);
    outline: none;
    resize: vertical;
    transition: border-color var(--duration-fast) ease;
}
.friend-apply-form input:focus,
.friend-apply-form textarea:focus { border-color: var(--primary); }
.friend-apply-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.friend-apply-feedback { font-size: var(--text-xs); color: var(--text-muted); flex: 1; min-width: 0; }
.friend-apply-feedback.is-error { color: #e05a4d; }
.friend-apply-feedback.is-success { color: var(--primary); }
.friend-apply-submit {
    padding: 8px 18px;
    border: none;
    border-radius: 999px;
    background: var(--primary);
    color: #fff;
    font-size: var(--text-sm);
    cursor: pointer;
    white-space: nowrap;
}
.friend-apply-submit:disabled { opacity: 0.6; cursor: default; }

/* 留言 list + composer */
.side-panel--guestbook .side-panel-scroll { padding-bottom: 150px; }
.gb-head { align-items: center; }
.gb-sort-wrap { margin-left: auto; }
.gb-list { list-style: none; margin: 0; padding: 0; }
.gb-loading, .gb-empty { color: var(--text-muted); text-align: center; padding: 22px 0; list-style: none; }
.gb-more { text-align: center; margin-top: 14px; }
.gb-showall, .gb-loadmore {
    padding: 8px 18px;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    background: transparent;
    color: var(--text-muted);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: color var(--duration-fast) ease, border-color var(--duration-fast) ease;
}
.gb-showall:hover, .gb-loadmore:hover { color: var(--primary); border-color: var(--primary); }

.gb-composer {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 7;
    padding: 14px 18px 16px;
}
.gb-composer::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(to top, var(--glass-elevated-bg), transparent);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    -webkit-mask-image: linear-gradient(to top, #000 62%, transparent);
            mask-image: linear-gradient(to top, #000 62%, transparent);
}
.gb-composer-inner { display: flex; flex-direction: column; gap: 8px; }
.gb-fields { display: flex; gap: 8px; }
.gb-email, .gb-nick {
    flex: 1;
    min-width: 0;
    padding: 8px 14px;
    border-radius: 999px;
    border: 1px solid var(--color-border);
    background: var(--field-bg);
    color: var(--text-primary);
    font-size: var(--text-sm);
    font-family: var(--font-body);
    outline: none;
    transition: border-color var(--duration-fast) ease;
}
.gb-nick { flex: 0 0 40%; }
.gb-msg-row { display: flex; align-items: flex-end; gap: 8px; }
.gb-msg {
    flex: 1;
    min-width: 0;
    padding: 9px 15px;
    border-radius: 18px;
    border: 1px solid var(--color-border);
    background: var(--field-bg);
    color: var(--text-primary);
    font-size: var(--text-sm);
    font-family: var(--font-body);
    line-height: 1.4;
    outline: none;
    resize: none;
    max-height: 140px;
    overflow-y: auto;
}
.gb-email:focus, .gb-nick:focus, .gb-msg:focus { border-color: var(--primary); }
.gb-send {
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: none;
    background: var(--primary);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: transform var(--duration-fast) ease, filter var(--duration-fast) ease;
}
.gb-send:hover { filter: brightness(1.08); }
.gb-send:active { transform: scale(0.94); }
.gb-send:disabled { opacity: 0.6; cursor: default; }
.gb-feedback { font-size: var(--text-xs); color: var(--text-muted); min-height: 1em; }
.gb-feedback.is-error { color: #e05a4d; }
.gb-feedback.is-success { color: var(--primary); }

/* ============================================
   12c. 照片墙 (photo wall) — v1.3.9
   ============================================ */
.pw-container { display: flex; flex-direction: column; gap: 26px; }
.pw-group-header {
    font-family: var(--font-display);
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--text-secondary, var(--text-muted));
    margin-bottom: 10px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--color-border);
}
.pw-group-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 8px;
}
.pw-cell {
    position: relative;
    display: block;
    padding: 0;
    border: none;
    border-radius: 8px;
    /* v1.4.0: overflow visible so the absolutely-positioned side popup
       can extend to the LEFT of the cell without being clipped. The image
       inside now carries its own border-radius for the rounded-corner
       visual. */
    overflow: visible;
    aspect-ratio: 1 / 1;
    background: var(--color-surface);
    cursor: pointer;
    box-shadow: var(--sphotography-card-shadow, 0 1px 6px rgba(0,0,0,0.12));
    transition: transform var(--duration-fast) ease, box-shadow var(--duration-fast) ease;
}
.pw-cell:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0,0,0,0.2); }
.pw-img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 8px; }
.pw-empty { color: var(--text-muted); text-align: center; padding: 30px 0; }
.pw-more { min-height: 20px; text-align: center; color: var(--text-muted); font-size: var(--text-xs); padding: 8px 0; }

/* v1.4.1: per-photo popup is a column of circular icon buttons
   portal-mounted to <body> via #pw-popup-portal (see the .pw-popup-portal
   rules below) and position: fixed at the cell's viewport position. JS
   sets `left`/`top` on the popup from cell.getBoundingClientRect(), and
   flips it to the right of the cell when the cell is the leftmost in
   its row (so the popup never gets clipped by the side panel's left
   edge or covered by its border). Animation is owned by JS via the
   Web Animations API (Element.animate), not by CSS, so the open /
   close stagger is precise and the close path has no
   animation-fill-mode flicker. */
.pw-popup-portal {
    position: fixed;
    inset: 0;
    /* The empty portal must not eat clicks for the page below. */
    pointer-events: none;
    /* Above every panel / article / map control. The panel's z-index
       is 116, the article is 110, map controls top out around 200. */
    z-index: 9999;
}
.pw-popup--side {
    position: fixed;
    /* `left` and `top` are set in JS from the cell's bounding rect. */
    display: flex;
    flex-direction: column;
    gap: 8px;
    z-index: 9999;
    pointer-events: auto;
    /* Sit slightly above the row of cells on the same horizontal line. */
    padding: 2px 0;
}
.pw-side-btn {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    border: 1px solid rgba(255,255,255,0.18);
    background: rgba(20,20,22,0.78);
    color: #fff;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    box-shadow: 0 4px 14px rgba(0,0,0,0.35);
    /* v1.4.1: defensive default — keeps the button invisible for the
       sub-millisecond window between appendChild and the first
       Element.animate() call. JS sets opacity/transform during the
       open animation; with `fill: 'forwards'` the close animation's
       final state is also held by JS, not by static CSS. */
    opacity: 0;
    transform: translateX(8px);
    transition: background 160ms ease, transform 160ms ease, box-shadow 160ms ease;
    flex: 0 0 auto;
    padding: 0;
}
.pw-side-btn:hover {
    background: var(--primary, #1abc9c);
    border-color: transparent;
    transform: scale(1.08);
    box-shadow: 0 6px 18px rgba(0,0,0,0.45);
}
.pw-side-btn:active { transform: scale(0.94); }
.pw-side-btn:focus-visible {
    outline: 2px solid var(--primary, #1abc9c);
    outline-offset: 2px;
}
/* Legacy v1.3.9 row-style popup kept as a no-op so any stale references
   don't break. The v1.3.9 text-button popup is superseded by the side
   icon buttons. */
.pw-popup:not(.pw-popup--side) { display: none; }

/* Full-panel detail overlay */
.pw-detail {
    position: absolute;
    inset: 0;
    z-index: 9;
    display: none;
    flex-direction: column;
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border-radius: var(--panel-radius);
    padding: 20px;
}
.pw-detail.is-open { display: flex; }
.pw-detail-exit {
    position: absolute; top: 12px; right: 12px; z-index: 3;
    width: 34px; height: 34px; border-radius: 50%;
    border: var(--panel-border); background: var(--glass-elevated-bg);
    color: var(--text-primary); cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
}
.pw-detail-stage { position: relative; flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; }
.pw-detail-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 8px; }
.pw-detail-arrow {
    position: absolute; top: 50%; transform: translateY(-50%);
    width: 42px; height: 42px; border-radius: 50%;
    border: var(--panel-border); background: var(--glass-elevated-bg);
    color: var(--text-primary); cursor: pointer; z-index: 2;
    display: inline-flex; align-items: center; justify-content: center;
    transition: filter var(--duration-fast) ease;
}
.pw-detail-arrow:hover { filter: brightness(1.08); }
.pw-detail-arrow--prev { left: 4px; }
.pw-detail-arrow--next { right: 4px; }
.pw-detail-arrow:disabled { opacity: 0.3; cursor: default; }
.pw-detail-info { flex: 0 0 auto; padding-top: 16px; display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; }
.pw-detail-params { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.pw-detail-row { display: flex; gap: 10px; font-size: var(--text-sm); }
.pw-detail-row span { color: var(--text-muted); flex: 0 0 68px; }
.pw-detail-row b { color: var(--text-primary); font-weight: 600; overflow-wrap: anywhere; }
.pw-detail-actions { display: flex; gap: 10px; flex: 0 0 auto; }
.pw-detail-round {
    width: 42px; height: 42px; border-radius: 50%;
    border: none; background: var(--primary); color: #fff;
    cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
    transition: transform var(--duration-fast) ease, filter var(--duration-fast) ease;
}
.pw-detail-round:hover { filter: brightness(1.08); }
.pw-detail-round:active { transform: scale(0.94); }

/* ============================================
   13. Responsive: Mobile (< 768px)
   ============================================ */
@media (max-width: 767px) {
    :root { --sidebar-width: 100vw; }

    #map { left: 0; }

    .sidebar {
        left: 0; top: 0; bottom: 0;
        border-radius: 0;
        z-index: 120;
        transform: translateX(-100%);
    }
    .sidebar.open {
        transform: translateX(0);
        opacity: 1;
        pointer-events: auto;
    }
    body.sidebar-collapsed .sidebar { transform: translateX(-100%); }

    .sidebar-expand-btn {
        left: 8px;
        width: var(--btn-s);
        height: 56px;
        z-index: 115;
        border-radius: 10px;
    }

    .article-panel {
        left: 8px; right: 8px; top: 8px; bottom: 8px;
        width: auto;
        border-radius: var(--panel-radius);
        z-index: 130;
        padding: 20px;
    }

    /* Side panels go near-fullscreen; the page-links bar stays but shrinks to
       icons to clear the map controls. */
    .side-panel {
        left: 8px; right: 8px; top: 8px; bottom: 8px;
        width: auto;
        z-index: 128;
    }
    .side-panel-scroll { padding: 18px 16px; }
    .friend-grid { gap: 10px; }
    .pw-group-grid { grid-template-columns: repeat(3, 1fr); }
    .pw-detail-info { flex-direction: column; align-items: stretch; }
    .page-links-bar { top: 8px; right: 52px; padding: 4px; gap: 2px; }
    .page-link-btn { padding: 7px 9px; }
    .page-link-txt { display: none; }
    .page-link-ext .page-link-txt { display: none; }

    .photo-grid-panel {
        width: calc(100vw - 24px);
        max-width: calc(100vw - 24px);
        max-height: 50vh;
        transform: none;
        border-radius: var(--panel-radius) var(--panel-radius) 0 0;
    }

    .detail-sheet {
        z-index: 130;
    }

    .panel-close-btn {
        width: var(--btn-s);
        height: var(--btn-s);
    }
}

/* ============================================
   14. Animation Support
   ============================================ */

/* 需求1: macOS Genie Effect — 动画期间辅助状态 */
.article-panel--animating {
    pointer-events: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: var(--glass-elevated-bg);
    transition: none !important;
}

/* 需求2: 照片网格面板位置平滑过渡（聚合/散开时） */
.photo-grid-panel {
    /* 基础无过渡（首次创建时不需要过渡） */
    will-change: transform, opacity, left, top;
    /* Top-left origin so the JS window-scale FLIP (grow from / shrink into the
       map point) maps the panel box exactly, like the article panel. */
    transform-origin: top left;
}
.photo-grid-panel--positioned {
    /* 聚合/散开后位置变化使用丝滑过渡 */
    transition:
        left var(--duration-slow) var(--ease-out-expo),
        top var(--duration-slow) var(--ease-out-expo),
        opacity var(--duration-slow) var(--ease-out-expo),
        transform var(--duration-slow) var(--ease-out-expo);
}

/* 需求2+3: 照片网格面板关闭动画（丝滑消失） */
.photo-grid-panel--dismiss {
    opacity: 0 !important;
    transform: scale(0.92) translateY(8px) !important;
    pointer-events: none;
    transition:
        opacity var(--duration-normal) var(--ease-in-out-sine),
        transform var(--duration-normal) var(--ease-out-expo);
}

/* 需求3: 未激活的面板收起状态（互斥逻辑下的非活跃面板） */
.photo-grid-panel:not(.active):not(.photo-grid-panel--dismiss) {
    opacity: 0.35;
    transform: scale(0.95);
    pointer-events: none;
    transition:
        opacity var(--duration-fast) ease,
        transform var(--duration-fast) var(--ease-spring);
}

/* Scroll reveal */
.animate-on-scroll {
    opacity: 0;
    transform: translateY(16px);
    transition:
        opacity var(--duration-slow) var(--ease-out-expo),
        transform var(--duration-slow) var(--ease-out-expo);
}
.animate-on-scroll.animated {
    opacity: 1;
    transform: translateY(0);
}

/* ============================================
   15. MapLibre Overrides
   ============================================ */
.maplibregl-ctrl-top-right { top: 12px; right: 12px; }
.maplibregl-ctrl-group {
    background: rgba(20,20,20,0.75) !important;
    backdrop-filter: blur(8px);
    border: 1px solid rgba(255,255,255,0.06) !important;
    border-radius: 10px !important;
    overflow: hidden;
    box-shadow: var(--shadow-subtle);
}
.maplibregl-ctrl-group button {
    background:transparent !important;
    border-color:rgba(255,255,255,0.06) !important;
    transition: background var(--duration-fast) ease;
}
.maplibregl-ctrl-group button:hover { background:rgba(255,255,255,0.08) !important; }
.maplibregl-ctrl-group button span { filter:invert(1); }

/* Night-mode three-segment switch (v1.3.2) — vertical icon buttons stacked
   in the top-right, below the zoom/compass group. */
.sp-night-switch .sp-night-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 29px;
    height: 29px;
    padding: 0;
    color: rgba(255,255,255,0.68);
    cursor: pointer;
    transition: background var(--duration-fast) ease, color var(--duration-fast) ease;
}
.sp-night-switch .sp-night-btn + .sp-night-btn {
    border-top: 1px solid rgba(255,255,255,0.08) !important;
}
.sp-night-switch .sp-night-btn:hover { color: #fff; }
.sp-night-switch .sp-night-btn.is-active {
    color: var(--primary);
    background: rgba(255,255,255,0.10) !important;
}
.sp-night-switch .sp-night-btn svg { display: block; }

/* Language three-segment switch (v1.4.3) — 中 / A / あ text glyphs, stacked
   directly below the night switch. Mirrors .sp-night-switch sizing/behaviour. */
.sp-lang-switch .sp-lang-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 29px;
    height: 29px;
    padding: 0;
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
    font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans", "Microsoft YaHei", sans-serif;
    color: rgba(255,255,255,0.68);
    cursor: pointer;
    transition: background var(--duration-fast) ease, color var(--duration-fast) ease;
}
.sp-lang-switch .sp-lang-btn + .sp-lang-btn {
    border-top: 1px solid rgba(255,255,255,0.08) !important;
}
.sp-lang-switch .sp-lang-btn:hover { color: #fff; }
.sp-lang-switch .sp-lang-btn.is-active {
    color: var(--primary);
    background: rgba(255,255,255,0.10) !important;
}

/* Dynamic-content translation states (v1.4.3): dimmed while a block awaits its
   translation, fade-in when the result arrives, and a floating "翻译中…" badge
   shown while any request is in flight. */
[data-sp-tr].sp-tr-loading { opacity: 0.5; transition: opacity var(--duration-fast) ease; }
.sp-tr-fade { animation: sp-tr-fade-in 0.28s ease; }
@keyframes sp-tr-fade-in { from { opacity: 0; } to { opacity: 1; } }
#sp-tr-indicator {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 9999;
    padding: 8px 14px;
    border-radius: 999px;
    background: rgba(20,20,22,0.82);
    color: #fff;
    font-size: 0.8125rem;
    letter-spacing: 0.02em;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    box-shadow: 0 6px 20px rgba(0,0,0,0.25);
    opacity: 0;
    transform: translateY(8px) scale(0.98);
    pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease;
}
#sp-tr-indicator.is-active { opacity: 1; transform: translateY(0) scale(1); }
@media (prefers-reduced-motion: reduce) {
    .sp-tr-fade { animation: none; }
    #sp-tr-indicator { transition: opacity 0.25s ease; transform: none; }
}

/* Marker hover enhancement — 地图标记交互增强 */
.maplibregl-marker {
    transition: transform var(--duration-fast) var(--ease-spring);
    cursor: pointer;
}
.maplibregl-marker:hover {
    transform: scale(1.15) translateY(-2px);
    z-index: 10;
}

/* ---------------------------------------------------------------
   Water-droplet (gooey / metaball) cluster markers.
   The WebGL circle layers stay in the map (invisible) for hit-testing;
   these HTML droplets are the visible markers and animate merge/split.
   --------------------------------------------------------------- */
.droplet-goo-defs { position: absolute; }

.droplet-goo-layer,
.droplet-label-layer {
    position: fixed;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    overflow: hidden;
}

/* The gooey blend is only needed while droplets are close (merging /
   splitting); enabled just for the transition to keep it cheap. */
.droplet-goo-layer.goo-active {
    -webkit-filter: url(#droplet-goo);
    filter: url(#droplet-goo);
}

.droplet {
    position: absolute;
    left: 0;
    top: 0;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    /* Solid, single colour so neighbouring blobs fuse cleanly. A soft
       radial highlight gives a water-like sheen. --droplet-color is set
       per-element when tag colouring is on; otherwise it falls back to the
       theme primary. */
    background:
        radial-gradient(circle at 35% 30%, rgba(255,255,255,0.55), rgba(255,255,255,0) 45%),
        var(--droplet-color, var(--primary));
    will-change: transform, opacity;
    transform: translate3d(-100px, -100px, 0) translate(-50%, -50%) scale(1);
}

/* Transition class is applied only to merging / splitting droplets so
   panning (which repositions every frame) never rubber-bands. Duration and
   easing are driven by the resolved motion personality (set on :root by
   app.js), falling back to the standard-tier values. */
.droplet--transit {
    transition:
        transform var(--sp-droplet-duration, 620ms) var(--sp-droplet-ease, cubic-bezier(0.22, 1, 0.36, 1)),
        opacity var(--sp-droplet-duration, 620ms) var(--sp-droplet-ease, cubic-bezier(0.22, 1, 0.36, 1));
}

.droplet-label {
    position: absolute;
    left: 0;
    top: 0;
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0,0,0,0.35);
    pointer-events: none;
    will-change: transform, opacity;
    transform: translate3d(-100px, -100px, 0) translate(-50%, -50%);
}

.droplet-label--transit {
    transition: transform var(--sp-droplet-duration, 620ms) var(--sp-droplet-ease, cubic-bezier(0.22, 1, 0.36, 1)), opacity 300ms ease;
}

@media (prefers-reduced-motion: reduce) {
    .droplet--transit,
    .droplet-label--transit { transition: none; }
}

/* ---------------------------------------------------------------
   Photo pulse dot (v1.2.6, region mode) — marks the exact coordinate an
   article image flew to, since region mode has no droplets. A small solid
   core with a periodically expanding ring for visibility. Default is a black
   dot (light UI / non-auto basemaps); the --light modifier switches to white
   for the dark auto basemap.
   --------------------------------------------------------------- */
.sp-pulse-dot {
    position: relative;
    width: 14px;
    height: 14px;
    --sp-pulse-color: #111;
    pointer-events: none;
}
.sp-pulse-dot--light { --sp-pulse-color: #fff; }
.sp-pulse-core {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--sp-pulse-color);
    box-shadow: 0 0 0 1.5px rgba(0,0,0,0.25);
    transform: translate(-50%, -50%);
}
.sp-pulse-dot--light .sp-pulse-core { box-shadow: 0 0 0 1.5px rgba(255,255,255,0.35); }
.sp-pulse-ring {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 2px solid var(--sp-pulse-color);
    transform: translate(-50%, -50%) scale(1);
    opacity: 0.7;
    animation: spPulseRing 1.8s ease-out infinite;
}
@keyframes spPulseRing {
    0%   { transform: translate(-50%, -50%) scale(0.6); opacity: 0.75; }
    70%  { transform: translate(-50%, -50%) scale(2.6); opacity: 0; }
    100% { transform: translate(-50%, -50%) scale(2.6); opacity: 0; }
}
.sp-pulse-dot--static .sp-pulse-ring { animation: none; opacity: 0.5; }
@media (prefers-reduced-motion: reduce) {
    .sp-pulse-ring { animation: none; opacity: 0.5; }
}

/* v1.4.4 (item 4): location popup hanging below the pulse dot. Shows lng/lat
   immediately + a reverse-geocoded place name once it resolves. Grows from /
   shrinks toward the dot (transform-origin at the top, near the dot). The
   MapLibre marker anchors the popup's top edge just under the dot. */
.sp-loc-popup {
    /* v1.4.5 (item 5): the marker root is left untouched so MapLibre owns its
       transform (translate positioning); animation lives on .sp-loc-popup-inner. */
    pointer-events: none;
    max-width: 260px;
}
.sp-loc-popup-inner {
    transform-origin: top center;
    filter: drop-shadow(0 6px 20px rgba(0,0,0,0.28));
}
.sp-loc-popup-arrow {
    display: block;
    width: 0; height: 0;
    margin: 0 auto -1px;
    border-left: 7px solid transparent;
    border-right: 7px solid transparent;
    border-bottom: 7px solid var(--glass-elevated-bg);
}
.sp-loc-popup-body {
    padding: 8px 12px;
    border-radius: 12px;
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    text-align: center;
    white-space: nowrap;
    max-width: 260px;
    overflow: hidden;
}
.sp-loc-popup-coord {
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 0.72rem;
    letter-spacing: 0.02em;
    color: var(--text-secondary);
}
.sp-loc-popup-name {
    margin-top: 2px;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--text-primary);
    max-width: 236px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: normal;
    word-break: break-word;
}

/* ---------------------------------------------------------------
   Tag colour legend (v1.2.5) — a compact, collapsible key of the
   region_tags present on the map. Glass panel bottom-left; on mobile it
   collapses to a "图例" pill.
   --------------------------------------------------------------- */
.tag-legend {
    position: fixed;
    left: 16px;
    bottom: 16px;
    z-index: 6;
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-width: 220px;
    padding: 10px 12px;
    border-radius: 14px;
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    box-shadow: var(--shadow-elevated);
    color: var(--text-primary);
    transition: max-width 260ms cubic-bezier(0.16, 1, 0.3, 1), padding 260ms cubic-bezier(0.16, 1, 0.3, 1);
}
.tag-legend-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
    color: var(--text-primary);
    font: inherit;
    font-size: 0.75rem;
    font-weight: 600;
}
.tag-legend-toggle-icon {
    width: 12px;
    height: 12px;
    border-radius: 4px;
    flex: 0 0 auto;
    background: linear-gradient(135deg, #e74c3c 0 33%, #3498db 33% 66%, #2ecc71 66% 100%);
}
.tag-legend-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 40vh;
    overflow-y: auto;
}
.tag-legend-item {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.75rem;
    line-height: 1.2;
}
.tag-legend-swatch {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    flex: 0 0 auto;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12) inset;
}
.tag-legend-name {
    color: var(--text-secondary, var(--text-primary));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Collapsed → pill: hide the list, show only the toggle. */
.tag-legend--collapsed {
    max-width: max-content;
}
.tag-legend--collapsed .tag-legend-list {
    display: none;
}

/* region_tag chip colour dot in the article panel (v1.2.5). */
.tag-chip-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: 4px;
    vertical-align: baseline;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12) inset;
}
.article-term-chip {
    display: inline-flex;
    align-items: center;
}

/* ---------------------------------------------------------------
   Windows-style article minimize / restore motion.
   The .motion-card is a full-resolution clone of the article panel
   that scales + translates as one rigid rectangle (origin top-left).
   Only transform / opacity animate, so there is no reflow or warp.
   --------------------------------------------------------------- */
.motion-layer {
    position: fixed;
    inset: 0;
    z-index: 145;
    pointer-events: none;
    overflow: hidden;
}

.motion-card {
    position: fixed;
    left: 0;
    top: 0;
    /* Mirror .article-panel so the clone is visually identical. */
    background: var(--glass-elevated-bg);
    backdrop-filter: var(--glass-elevated-blur);
    -webkit-backdrop-filter: var(--glass-elevated-blur);
    border: var(--panel-border);
    box-shadow: var(--shadow-elevated);
    border-radius: var(--panel-radius);
    overflow: hidden;
    transform-origin: top left;
    will-change: transform, opacity;
    contain: layout paint;
}

.motion-card-content {
    width: 100%;
    height: 100%;
    padding: 24px 28px;
    box-sizing: border-box;
    overflow: hidden;
    color: var(--text-primary);
}

.motion-card-content img {
    max-width: 100%;
    border-radius: 12px;
}

/* The clone reuses the panel's markup; force its cascade elements into
   their visible resting state (the .article-panel.active rules don't
   apply to the clone root). */
.motion-card .article-panel-header,
.motion-card .article-content,
.motion-card .article-comments {
    transform: none;
    opacity: 1;
    transition: none;
}

/* Instant, transition-less handoff between the clone and the real panel. */
.article-panel--instant,
.article-panel--instant .article-panel-header,
.article-panel--instant .article-content {
    transition: none !important;
}
