/* ============================================================
 * 影视页 (css/tv.css) —— B 站网页端版式
 * ------------------------------------------------------------
 * 2026-11-11 按用户要求，照 bilibili 网页端（www.bilibili.com/video/…）
 * 重新设计。B 站那套视觉语言的要点，逐条对应到下面：
 *
 *  1) 页面底色不是纯白，是一层很浅的灰（B 站 #f1f2f3 / #f6f7f8），
 *     内容卡片浮在上面。站点是粉色主题，所以这里取粉调冷灰 --tv-page，
 *     而不是照抄 B 站的冷灰 —— 抄版式，不抄配色。
 *  2) 卡片：圆角 6px（B 站视频卡就是 6px，不是现在流行的 16px）、
 *     无边框、悬停只做轻微上浮 + 阴影，不描边。
 *  3) 海报比例 16:10 → B 站用 16:9 横图。但采集源给的是竖版海报
 *     （2:3），硬切成 16:9 会把人物脸裁掉，所以这一条**故意不抄**，
 *     仍用 2:3 并保留「已看/备注」角标。
 *  4) 二级页是 B 站经典的「左播放器 + 右选集」两栏：
 *     左列 = 播放器 → 标题 → 数据栏（播放量/弹幕那种灰字小图标行）
 *            → 操作条（返回列表/横屏）→ 简介卡
 *     右列 = 选集卡（吸顶、自己滚），线路做 tab、集数做网格。
 *  5) 播放器与页面之间不留白：B 站播放器是贴着内容区左上角的，
 *     所以二级页去掉原来的居中大留白和圆角外框。
 *  6) 交互色统一走 --tv-accent（站点粉 #fb7299 系），悬停反白。
 * ============================================================ */

/* 色板定义在 :root 上，不是 .tv 上。
   影视二级页的选集/线路按钮是 AppShell 建好层之后才 append 进去的，
   它们挂在 body 下、并不在任何一个 .tv 元素里面 —— 变量定义在 .tv 上时
   这几个按钮取不到值，background: var(--tv-accent) 整条失效变透明，
   而 color:#fff 照样生效，于是「当前集」白字白底、整块看不见。
   放 :root 就没有这个作用域问题。 */
:root {
    /* B 站的结构 + 站点的粉。B 站正文色 #18191c、次要文字 #9499a0，
       这里换成同明度的暖色版，免得在粉色页面上出现一片冷灰的字。 */
    --tv-page: #fdf4f7;        /* 页面浅底（B 站是 #f1f2f3） */
    --tv-card: #ffffff;
    --tv-line: #f6e3ea;        /* 分隔线，比边框弱 */
    --tv-border: #ffd9e4;      /* 需要描边时用 */
    --tv-text: #3a2530;        /* 正文（B 站 #18191c） */
    --tv-sub: #9c8390;         /* 次要文字（B 站 #9499a0） */
    --tv-accent: #fb7299;      /* B 站 #00AEEC 的位置换成站点粉 */
    --tv-accent-ink: #e35d85;
    --tv-accent-soft: #ffe8f0;
    --tv-radius: 6px;          /* B 站卡片圆角 */
}

/* ============================================================
 * 一、列表页
 * ============================================================ */

/* ---------- 搜索：B 站搜索框是圆角胶囊 + 独立的主题色按钮 ---------- */
.tv-search {
    position: relative;          /* 搜索历史下拉的定位基准 */
    display: flex;
    gap: 10px;
    margin-bottom: 18px;
}

.tv-search input {
    flex: 1;
    min-width: 0;
    padding: 10px 16px;
    border: 1px solid var(--tv-border);
    border-radius: 999px;
    background: var(--tv-card);
    color: var(--tv-text);
    font: inherit;
    font-size: .95rem;
}

.tv-search input::placeholder { color: var(--tv-sub); }

.tv-search input:focus {
    outline: none;
    border-color: var(--tv-accent);
    box-shadow: 0 0 0 3px var(--tv-accent-soft);
}

/* 搜索按钮沿用 .hub-chip，但它是主操作，描边去掉、填色 */
.tv-search .hub-chip {
    border-color: var(--tv-accent);
    background: var(--tv-accent);
    color: #fff;
    padding-inline: 22px;
}
.tv-search .hub-chip:hover { background: var(--tv-accent-ink); border-color: var(--tv-accent-ink); }

/* ---------- 筛选区：片源 + 分类，两行胶囊 ---------- */
.tv-srcbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0 0 10px;
}

.tv-srclabel {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--tv-sub);
    font-size: .8rem;
    white-space: nowrap;
}

.tv-srcbar .hub-chip.is-slow { opacity: .6; }

/* 分类是主筛选，和片源拉开一点距离 */
#tvCats.hub-chips { margin-bottom: 20px; }

/* ---------- 卡片网格 ----------
   B 站首页/搜索页的卡片是固定宽度、间距 20px 的网格；这里用
   auto-fill 让它自适应，但把最小宽度和间距对齐到 B 站的手感。 */
.tv-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: 20px 16px;
    margin: 16px 0 22px;
}

.tv-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border: 0;
    border-radius: var(--tv-radius);
    background: var(--tv-card);
    text-decoration: none;
    /* B 站卡片悬停：整体轻微上浮 + 阴影，颜色不动 */
    transition: transform .2s ease, box-shadow .2s ease;
}

.tv-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(251, 114, 153, .22);
}

/* 海报仍用 2:3：采集源给的是竖版海报，切成 16:9 会裁掉人脸 */
.tv-poster {
    display: block;
    position: relative;
    flex: 0 0 auto;
    aspect-ratio: 2 / 3;
    background: var(--tv-accent-soft);
    overflow: hidden;
}

.tv-poster img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .3s ease;
}

.tv-card:hover .tv-poster img { transform: scale(1.04); }

/* B 站角标是左下角半透明黑底白字，不是现在的粉色实底 */
.tv-poster .tv-badge {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 22px 8px 6px;
    background: linear-gradient(180deg, transparent, rgba(0, 0, 0, .72));
    color: #fff;
    font-size: .72rem;
    line-height: 1.3;
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tv-name {
    /* 定死 40px = 两行（20px×2）硬裁。原来写的是 min-height + -webkit-line-clamp，
       但 .tv-name 的 display 被别处的样式覆盖成 flow-root，clamp 就不生效了，
       长标题（体育那种「9月15日25-26赛季亚冠…」）会溢出第三行、被卡片裁成半个字。
       58px = 上内边距 8 + 正文两行 40 + 下内边距 10（border-box）。 */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    height: 58px;
    overflow: hidden;
    padding: 8px 6px 10px;
    color: var(--tv-text);
    font-size: .88rem;
    line-height: 20px;
    font-weight: 500;
    word-break: break-all;
}

.tv-card:hover .tv-name { color: var(--tv-accent-ink); }

.tv-tip { padding: 24px 4px; color: var(--tv-sub); text-align: center; }

/* 海报拉不动时的降级占位 */
.tv-poster.is-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(145deg, var(--tv-accent-soft) 0%, #ffdae7 100%);
}
.tv-poster.is-empty::after {
    content: '🎬';
    font-size: 1.6rem;
    opacity: .75;
}

/* ---------- 分页：B 站是居中一排方块按钮 ---------- */
.tv-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    margin: 4px 0 22px;
    color: var(--tv-text);
}
/* 必须显式写：上面那条的 display:flex 权重高于浏览器给 [hidden] 的
   display:none，于是 JS 里设了 pager.hidden = true 也照样显示出来。
   结果就是「加载失败、一个结果都没有」的页面上，还摆着
   「上一页 / 1 / 1 / 下一页」，看着像加载完了只是内容空。 */
.tv-pager[hidden] { display: none; }

/* ---------- 加载中 / 加载失败 ----------
   有下限也有上限，不让空屏也不让一直转。 */
.tv-loading {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 46px 12px 42px;
    color: var(--tv-text);
    text-align: center;
}
.tv-loading p { margin: 0; font-size: .9rem; color: var(--tv-sub); }
.tv-loading .tv-spin {
    width: 34px;
    height: 34px;
    border: 3px solid var(--tv-accent-soft);
    border-top-color: var(--tv-accent);
    border-radius: 50%;
    animation: tv-spin .8s linear infinite;
}
@keyframes tv-spin { to { transform: rotate(360deg); } }
.tv-loading.is-fail i { font-size: 1.5rem; color: var(--tv-accent); }
/* 失败态没有转圈，上下的 46px 就变成两片空白，收紧一点 */
.tv-loading.is-fail { padding: 30px 12px 26px; gap: 10px; }
.tv-loading.is-fail p { max-width: 30ch; line-height: 1.6; }

.tv-grid > .tv-tip {
    grid-column: 1 / -1;
    margin: 0;
    padding: 30px 12px 26px;
    text-align: center;
}

.tv-retry {
    padding: 8px 18px;
    border: 1px solid var(--tv-border);
    border-radius: var(--tv-radius);
    background: var(--tv-card);
    color: var(--tv-text);
    font: inherit;
    font-size: .88rem;
    cursor: pointer;
    transition: background .2s, border-color .2s;
}
.tv-retry:hover { background: var(--tv-accent-soft); border-color: var(--tv-accent); }
.tv-loading-hint { font-size: .78rem !important; color: var(--tv-sub) !important; }

/* 看板娘（右下角 live2d）会压住失败卡上的重试按钮和提示文字 ——
   它是 position:fixed 的，不占布局，只能把这块内容整体往上往左收。 */
@media (max-width: 768px) {
  .tv-loading.is-fail {
    align-items: flex-start;
    text-align: left;
    padding: 26px 12px 64px;
    max-width: 82%;
  }
  .tv-loading.is-fail p { max-width: 26ch; }
}

/* ============================================================
 * 二、播放页（二级页）：B 站「左播放器 + 右选集」两栏
 * ------------------------------------------------------------
 * #tvPlayer 的三个孩子，DOM 顺序就是 B 站的阅读顺序：
 *   .tvp      播放器（自带 js/tv-player.js 那套皮肤）
 *   .tv-info  标题 → 数据栏 → 操作条 → 简介
 *   .tv-aside 选集（线路 tab + 集数网格）
 *
 * 桌面 ≥993px：两栏 grid，左列自适应、右列 360px（B 站是 350~400px）。
 * 移动 ≤992px：单栏，靠 DOM 顺序天然成立。
 * ============================================================ */

.tv-player { margin-top: 0; }

.tv-info { padding: 14px 2px 2px; }

/* 片名：B 站是 18px/600，行高 1.5 */
.tv-title {
    margin: 0 0 10px;
    font-size: 1.12rem;
    font-weight: 600;
    line-height: 1.5;
    color: var(--tv-text);
    word-break: break-word;
}

/* 数据栏：B 站那排「▶ 12.3万 · 💬 456 · 🕐 2024-01-01」的灰字小图标行 */
.tv-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    align-items: center;
    margin-bottom: 12px;
    color: var(--tv-sub);
    font-size: .82rem;
}

.tv-meta .tv-remark {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--tv-sub);
    font-size: .82rem;
}

/* 操作条：B 站的「点赞/投币/收藏/分享」那一排圆角图标按钮 */
.tv-acts {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0 0 14px;
}

.tv-act {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 16px;
    border: 1px solid var(--tv-border);
    border-radius: 999px;
    background: var(--tv-card);
    color: var(--tv-text);
    font: inherit;
    font-size: .84rem;
    cursor: pointer;
    transition: background .18s, color .18s, border-color .18s;
}

.tv-act:hover {
    background: var(--tv-accent-soft);
    border-color: var(--tv-accent);
    color: var(--tv-accent-ink);
}

/* 「横屏观看」只在手机出现 */
.tv-rotate { display: none; }

/* ---------- 右栏：选集卡 ---------- */
.tv-aside {
    margin-top: 16px;
    padding: 14px 16px;
    border: 1px solid var(--tv-line);
    border-radius: var(--tv-radius);
    background: var(--tv-card);
}

.tv-aside-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 12px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--tv-line);
}

.tv-aside-head b { font-size: .95rem; font-weight: 600; color: var(--tv-text); }

.tv-aside-head span { font-size: .78rem; color: var(--tv-sub); }

.tv-episodes { max-height: 240px; overflow-y: auto; margin-bottom: 0; }

/* ---- 线路 tab ---- */
.tv-ep-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }

.tv-ep-tab {
    padding: 5px 12px;
    border: 1px solid var(--tv-border);
    border-radius: var(--tv-radius);
    background: var(--tv-card);
    color: var(--tv-sub);
    font: inherit;
    font-size: .78rem;
    cursor: pointer;
    transition: background .18s, color .18s, border-color .18s;
}

.tv-ep-tab:hover { background: var(--tv-accent-soft); color: var(--tv-accent-ink); }

.tv-ep-tab.is-on {
    background: var(--tv-accent);
    border-color: var(--tv-accent);
    color: #fff;
}

.tv-ep-tab.is-html { color: var(--tv-sub); opacity: .8; }
.tv-ep-tab.is-html.is-on { color: #fff; opacity: 1; }

/* ---- 集数网格：B 站那种一排排数字方块 ---- */
.tv-ep-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(58px, 1fr));
    gap: 8px;
}

.tv-ep-grid[hidden] { display: none; }

.tv-ep {
    padding: 8px 4px;
    border: 1px solid var(--tv-line);
    border-radius: var(--tv-radius);
    background: var(--tv-accent-soft);
    color: var(--tv-text);
    font: inherit;
    font-size: .84rem;
    font-weight: 600;
    text-align: center;
    cursor: pointer;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition: background .18s, color .18s, border-color .18s;
}

.tv-ep:hover { background: #ffd9e7; border-color: var(--tv-accent); }

.tv-ep.is-on {
    background: var(--tv-accent);
    border-color: var(--tv-accent);
    color: #fff;
}

/* ---- 侧栏里的「本次搜索结果」：带回列表页那批命中，在层里直接横跳 ---- */
/* 「本次搜索结果」在选集【下面】：改成上分隔线（原来是下边线，
   因为它以前排在选集之前）。 */
.tv-sb-hits {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--tv-line);
}

.tv-sb-hits[hidden] { display: none; }

.tv-sb-list { display: flex; flex-direction: column; gap: 8px; }

.tv-sb-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 6px;
    border: 1px solid transparent;
    border-radius: var(--tv-radius);
    background: transparent;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    transition: background .18s;
}

.tv-sb-item:hover { background: var(--tv-accent-soft); }
.tv-sb-item.is-on { background: var(--tv-accent-soft); border-color: var(--tv-accent); }

.tv-sb-poster {
    flex: 0 0 auto;
    width: 40px;
    height: 56px;
    overflow: hidden;
    border-radius: 4px;
    background: var(--tv-accent-soft);
}

.tv-sb-poster img { display: block; width: 100%; height: 100%; object-fit: cover; }

.tv-sb-body { display: flex; min-width: 0; flex-direction: column; gap: 4px; }

.tv-sb-name {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    color: var(--tv-text);
    font-size: .84rem;
    font-weight: 500;
    line-height: 1.35;
}

.tv-sb-remark { color: var(--tv-sub); font-size: .74rem; }

#tvVideo {
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: 0;
    background: #000;
}

.tv-note { margin-top: 10px; color: var(--tv-sub); font-size: .82rem; word-break: break-all; }
.tv-note a { color: var(--tv-accent-ink); }

/* ---------- 桌面：两栏（左播放器 + 右选集） ---------- */
@media (min-width: 993px) {
    /* 影视二级页铺满整屏：盖住底下的列表页，而不是在中间留一张窄卡片。
       AppShell 给桌面端加的 .detail-head 正好就是顶部返回栏。 */
    .detail-view.tv-detail {
        max-width: none;
        width: 100%;
        margin: 0;
        padding: 0 0 40px;
        border-left: 0;
        border-right: 0;
        box-shadow: none;
        background: var(--tv-page);
    }

    .detail-view.tv-detail .detail-head {
        margin: 0 0 20px;
        padding: 12px 40px;
        border-bottom: 1px solid var(--tv-line);
        background: var(--tv-card);
    }

    /* 正文居中限一档：B 站播放页内容区是 1140~1400px，这里取 1400px */
    .detail-view.tv-detail .tv-player {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 360px;
        gap: 16px 24px;
        align-items: start;
        max-width: 1400px;
        margin: 0 auto;
        padding: 0 40px;
    }

    .detail-view.tv-detail .tvp { grid-column: 1; grid-row: 1; }

    /* B 站播放器是方角贴边的，桌面端也把圆角收掉 */
    .detail-view.tv-detail .tvp { border-radius: var(--tv-radius); }

    .detail-view.tv-detail .tv-info { grid-column: 1; grid-row: 2; padding-top: 0; }

    /* 选集常驻右栏，自己滚；页面滚到哪它都在（B 站也是这个行为） */
    .detail-view.tv-detail .tv-aside {
        grid-column: 2;
        grid-row: 1 / span 2;
        position: sticky;
        top: 16px;
        margin-top: 0;
        max-height: calc(100vh - 40px);
        overflow-y: auto;
    }

    /* 滚动交给 .tv-aside，选集自己别再滚一层 */
    .detail-view.tv-detail .tv-episodes { max-height: none; overflow: visible; }
}

/* ============================================================
 * 三、移动端（照 B 站 App / 手机网页端）
 * ------------------------------------------------------------
 * B 站手机播放页的骨架，从上到下：
 *   播放器（16:9 贴顶、方角、纯黑）
 *   片名（16px 粗体，两行截断）
 *   数据栏（灰字小图标：播放量 · 弹幕 · 日期）
 *   操作条（一排可横滑的胶囊：点赞/投币/收藏/分享）
 *   简介卡（默认收两行，点「展开」）
 *   —— 选集不是常驻的一块，而是**底部抽屉**，由底部「选集」按钮拉起
 *
 * 我们这里逐个对应：
 *   · 播放器 / 片名 / 数据栏 / 操作条 —— 一一照做
 *   · 选集：B 站是抽屉，抽屉里还能切线路。这里同样做成抽屉，
 *     并在底部常驻一条操作栏（选集 + 返回列表），
 *     这样手机上不用滚到底才能换集。
 *   · B 站底部是「发弹幕…」输入框，本站没有弹幕，
 *     那一格换成「选集（第 N 集）」，位置和手势一致。
 * ============================================================ */

/* 选集抽屉的遮罩与面板：默认不占位，只在移动端由 JS 切 open 类 */
.tvmask {
    position: fixed;
    inset: 0;
    /* 二级页 .detail-view 是 z-index:9999，抽屉和底部栏必须比它高，
       否则会被整层盖住（同权重时后出现的 .detail-view 也会赢）。 */
    z-index: 10000;
    background: rgba(0, 0, 0, .45);
    opacity: 0;
    visibility: hidden;
    transition: opacity .24s ease, visibility .24s;
}
/* 必须显式关掉：下面 .tvbar/.tvsheet 都写了 display:flex，
   权重高于浏览器给 [hidden] 的 display:none —— 不给这条，
   JS 里设了 hidden=true 抽屉和底栏照样显示（.tv-pager 踩过同一个坑）。 */
.tvmask[hidden] { display: none; }

.tvmask.is-open { opacity: 1; visibility: visible; }

.tvsheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 10001;
    display: flex;
    flex-direction: column;
    max-height: 68vh;
    padding: 0 0 calc(env(safe-area-inset-bottom, 0px));
    border-radius: 16px 16px 0 0;
    background: var(--tv-card);
    box-shadow: 0 -8px 32px rgba(58, 37, 48, .22);
    transform: translateY(100%);
    transition: transform .28s cubic-bezier(.22, .61, .36, 1);
    will-change: transform;
}
.tvsheet[hidden] { display: none; }

.tvsheet.is-open { transform: none; }

/* 抽屉顶部的抓手：B 站那个小横条，同时也是「可下拉关闭」的视觉暗示 */
.tvsheet-grip {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 22px;
    cursor: grab;
}
.tvsheet-grip::before {
    content: '';
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: #e6d3db;
}

.tvsheet-head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 2px 16px 12px;
    border-bottom: 1px solid var(--tv-line);
}
.tvsheet-head b { font-size: .95rem; font-weight: 600; color: var(--tv-text); }
.tvsheet-head span { font-size: .78rem; color: var(--tv-sub); }
.tvsheet-close {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border: 0;
    border-radius: 50%;
    background: var(--tv-accent-soft);
    color: var(--tv-accent-ink);
    font-size: .9rem;
    cursor: pointer;
}

.tvsheet-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: 14px 16px 18px;
}

/* ---------- 底部常驻操作栏（B 站「发弹幕…」那一行） ---------- */
.tvbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 10000;  /* 高于 .detail-view(9999)，否则被整层盖住 */
    display: none;   /* 只在移动端由下面的媒体查询打开 */
    align-items: center;
    gap: 10px;
    padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
    background: rgba(255, 255, 255, .96);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-top: 1px solid var(--tv-line);
}
.tvbar[hidden] { display: none; }
.tvbar-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 40px;
    padding: 0 16px;
    border: 1px solid var(--tv-border);
    border-radius: 999px;
    background: var(--tv-card);
    color: var(--tv-text);
    font: inherit;
    font-size: .86rem;
    font-weight: 500;
    cursor: pointer;
    white-space: nowrap;
}
.tvbar-btn:active { background: var(--tv-accent-soft); }
/* 主按钮：占满剩余宽度，像 B 站那个长条输入框 */
.tvbar-main {
    flex: 1 1 auto;
    min-width: 0;
    justify-content: flex-start;
    padding-left: 14px;
    background: var(--tv-accent-soft);
    border-color: transparent;
    color: var(--tv-accent-ink);
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------- 移动端：单栏铺满 ---------- */
@media (max-width: 992px) {
    .tv-rotate { display: inline-flex; align-items: center; gap: 6px; }

    .detail-view .tv-player {
        display: flex;
        flex-direction: column;
        margin: 0;
        /* 给底部操作栏让出位置，否则最后一排集数会被压住 */
        min-height: 100%;
        padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px));
        background: var(--tv-page);
    }

    /* 视频和信息按原样占高，剩下全给选集 */
    .detail-view .tvp,
    .detail-view .tv-info { flex: 0 0 auto; }

    /* B 站手机端播放器贴顶、方角、纯黑，不留圆角边框 */
    .detail-view .tvp,
    .detail-view #tvVideo { border-radius: 0; }

    /* B 站手机端片名是 16px 粗体、左右 12px 内边距 */
    .detail-view .tv-info { padding: 12px 12px 0; }
    .detail-view .tv-title { font-size: 1rem; line-height: 1.45; margin-bottom: 8px; }

    /* 数据栏：B 站是一行灰字，字号 12px 左右 */
    .detail-view .tv-meta { font-size: .78rem; gap: 4px 12px; margin-bottom: 12px; }
    .detail-view .tv-meta .tv-remark { font-size: .78rem; }

    /* 操作条：B 站那排胶囊在手机上允许横滑，不折行 */
    .detail-view .tv-acts {
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        overscroll-behavior-x: contain;
        scrollbar-width: none;
        margin: 0 -12px 14px;
        padding: 0 12px 2px;
        -webkit-overflow-scrolling: touch;
        /* 右侧渐隐，暗示还能往右划 */
        -webkit-mask-image: linear-gradient(to right, #000 0%, #000 94%, transparent 100%);
        mask-image: linear-gradient(to right, #000 0%, #000 94%, transparent 100%);
        -webkit-mask-repeat: no-repeat;
        mask-repeat: no-repeat;
        -webkit-mask-size: 100% 100%;
        mask-size: 100% 100%;
    }
    .detail-view .tv-acts::-webkit-scrollbar { display: none; }
    .detail-view .tv-acts .tv-act { flex: 0 0 auto; scroll-snap-align: start; }

    /* 选集在抽屉里，正文里不再常驻 —— 收起成「选集 (N 集)」一个入口 */
    .detail-view .tv-aside { display: none; }

    /* 进度条别被 iPhone 底部那条横杠压住 */
    .detail-view .tvp-bar { padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); }

    /* 底部操作栏打开 */
    .tvbar { display: flex; }
}

/* ============================================================
 * 四、移动端列表页
 * ============================================================ */
@media (max-width: 768px) {
    /* B 站手机端首页是两列大卡片，间距很小 */
    .tv-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 8px; }
    .tv-search { flex-wrap: wrap; }

    /* ---------- 搜索栏吸顶 ----------
       B 站手机端搜索框是常驻顶部的。这里让它吸住，
       向下翻片单时随时能改关键词，不用先滚回顶部。 */
    .tv-search {
        position: sticky;
        top: calc(var(--sat, 0px));
        z-index: 5;
        margin: 0 -2px 10px;
        padding: 8px 2px;
        background: rgba(255, 255, 255, .94);
        backdrop-filter: blur(10px);
        -webkit-backdrop-filter: blur(10px);
    }
    /* 有 App Bar 时让开它的高度（顶栏是 fixed 的） */
    html.has-appbar .tv-search { top: calc(var(--appbar-h, 48px) + var(--sat, 0px)); }

    /* ---------- 筛选区改成横向滚动 ----------
       实测（390px 宽、10 个片源 8 个分类）：片源行折成多行能占 300px+，
       分类行再占一截 —— 超过视口的一半，用户还没看到任何一部片子，
       首屏就被筛选区吃光了。所以两行都改成「一行 + 横滑」。

       横滑时右侧刻意留 8% 渐隐，暗示「还能往右划」——
       否则用户看到一排胶囊整整齐齐收在屏幕边，会以为就这么多了。 */
    /* 注意 #tvSrcs 是 .tv-srcbar 的子元素（.tv-srcbar 里还有一枚「片源」
       静态标签），横滑要加在真正装胶囊的那个盒子上。 */
    #tvSrcs.hub-chips,
    #tvCats.hub-chips {
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        overscroll-behavior-x: contain;
        scroll-snap-type: x proximity;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding-bottom: 2px;
        /* 渐隐用 mask 而不是盖一层渐变：盖层要么挡点击，要么就得
           pointer-events:none 再加一堆 z-index 去凑，反而容易出问题。 */
        -webkit-mask-image: linear-gradient(to right, #000 0%, #000 92%, transparent 100%);
        mask-image: linear-gradient(to right, #000 0%, #000 92%, transparent 100%);
        -webkit-mask-size: 100% 100%;
        mask-size: 100% 100%;
        -webkit-mask-repeat: no-repeat;
        mask-repeat: no-repeat;
    }
    #tvSrcs.hub-chips::-webkit-scrollbar,
    #tvCats.hub-chips::-webkit-scrollbar { display: none; }

    #tvSrcs.hub-chips > *,
    #tvCats.hub-chips > * { flex: 0 0 auto; scroll-snap-align: start; }

    /* 片源胶囊收一号：它比分类更次要。这里必须 !important —— hub.css 的
       .hub-chip 也吃了 style.css 那条给全站按钮定的 padding !important。 */
    #tvSrcs.hub-chips .hub-chip {
        padding: 7px 11px !important;
        font-size: .8rem !important;
    }
    #tvSrcs.hub-chips .hub-chip i { font-size: .78rem; }

    #tvCats.hub-chips { margin-bottom: 14px; }

    /* B 站手机端卡片：海报铺满、片名在下方两行 */
    .tv-card { border-radius: 8px; }
    .tv-name {
        height: auto;
        min-height: 40px;
        padding: 7px 2px 8px;
        font-size: .82rem;
        line-height: 20px;
        -webkit-line-clamp: 2;
    }

    /* 手指按得准：抽屉里的选集方块 44px 高 */
    .tv-ep-grid { grid-template-columns: repeat(auto-fill, minmax(62px, 1fr)); gap: 9px; }
    /* 纵向内边距撑出 44px 的触控高度；横向只留 1px —— 格子只有 66px 宽。
       !important 是为了压住 style.css 里给全站按钮定的 1.1rem 横向内边距。 */
    .tv-ep-grid .tv-ep {
        padding: 11px 1px !important;
        font-size: .84rem !important;
        letter-spacing: -.01em;
    }
    .tv-ep-tab { padding: 7px 14px; font-size: .82rem; }

    /* 抽屉里的选集列表：手机上别再套一层滚动，抽屉自己滚 */
    .tvsheet .tv-episodes { max-height: none; overflow: visible; }

    /* 抽屉里的「本次搜索结果」也收窄一点 */
    .tvsheet .tv-sb-poster { width: 44px; height: 62px; }

    /* 列表页底部留白：底部 tab 是 fixed 的，别盖住最后一行卡片 */
    .main-card { padding-bottom: calc(var(--tab-h, 56px) + 16px) !important; }
}

/* 已经转成横屏（但还没进全屏）时纵向空间紧张，把留白收掉 */
@media (max-width: 992px) and (orientation: landscape) {
    .detail-view .tv-info { padding-top: 8px; }
    .detail-view .tv-aside { padding-top: 10px; }
    /* 横屏时抽屉别占满，留出画面 */
    .tvsheet { max-height: 86vh; }
}

/* ============================================================
 * 五、本地功能：继续观看 / 追剧 / 收藏按钮
 * ------------------------------------------------------------
 * 全部数据只在 localStorage 里，没有服务端。配色沿用上面的色板。
 * ============================================================ */

/* ---------- 列表页的「继续观看 / 我的追剧」 ---------- */
.tv-my {
    margin: 0 0 22px;
    padding: 14px 16px;
    border: 1px solid var(--tv-line);
    border-radius: var(--tv-radius);
    background: var(--tv-card);
}

.tv-my[hidden] { display: none; }

.tv-my-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 12px;
}

.tv-my-head b {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--tv-text);
    font-size: .92rem;
    font-weight: 600;
}

.tv-my-head b i { color: var(--tv-accent); font-size: .86rem; }
.tv-my-head span { color: var(--tv-sub); font-size: .78rem; }

.tv-my-clear {
    padding: 4px 10px;
    border: 1px solid var(--tv-border);
    border-radius: 999px;
    background: transparent;
    color: var(--tv-sub);
    font: inherit;
    font-size: .74rem;
    cursor: pointer;
    transition: background .18s, color .18s, border-color .18s;
}
.tv-my-clear:hover { background: var(--tv-accent-soft); color: var(--tv-accent-ink); border-color: var(--tv-accent); }

/* 横滑一排卡片：B 站首页那种「继续观看」也是横着划的 */
.tv-my-row {
    display: flex;
    gap: 10px;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* 右侧渐隐，暗示还能往右划 */
    -webkit-mask-image: linear-gradient(to right, #000 0%, #000 95%, transparent 100%);
    mask-image: linear-gradient(to right, #000 0%, #000 95%, transparent 100%);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
}
.tv-my-row::-webkit-scrollbar { display: none; }
.tv-my-row:empty { display: none; }

/* 第二块（追剧）的标题要跟上面那排卡片拉开距离。
   实测不加这条 gap=0，「我的追剧」会紧贴在片名底下、看着像它的副标题。 */
#tvFavHead { margin-top: 20px; }
/* 第一块为空时（只有追剧），标题不该再顶出 20px 的空 */
#tvMyRow:empty + #tvFavHead { margin-top: 0; }

.tv-my-item {
    position: relative;
    flex: 0 0 auto;
    width: 108px;
    padding: 0;
    border: 0;
    background: transparent;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    scroll-snap-align: start;
}

.tv-my-poster {
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: 2 / 3;
    overflow: hidden;
    border-radius: var(--tv-radius);
    background: var(--tv-accent-soft);
}
.tv-my-poster img { display: block; width: 100%; height: 100%; object-fit: cover; }
.tv-my-poster.is-empty::after {
    content: '🎬';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    opacity: .7;
}

/* 进度条压在缩略图底部：一眼看出看到哪了 */
.tv-my-bar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background: rgba(0, 0, 0, .45);
}
.tv-my-bar i {
    display: block;
    height: 100%;
    background: var(--tv-accent);
}

/* 追剧列表上的删除叉 */
.tv-my-del {
    position: absolute;
    right: 4px;
    top: 4px;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, .55);
    color: #fff;
    font-size: .68rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}
.tv-my-del:hover { background: var(--tv-accent); }

.tv-my-name {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 6px;
    color: var(--tv-text);
    font-size: .78rem;
    line-height: 1.35;
    word-break: break-all;
}
.tv-my-sub { display: block; margin-top: 3px; color: var(--tv-sub); font-size: .7rem; }

/* ---------- 播放页操作条上的「追剧」已收藏态 ---------- */
.tv-act.is-on {
    background: var(--tv-accent);
    border-color: var(--tv-accent);
    color: #fff;
}
.tv-act.is-on:hover { background: var(--tv-accent-ink); border-color: var(--tv-accent-ink); color: #fff; }

/* ---------- 移动端 ---------- */
@media (max-width: 768px) {
    .tv-my { padding: 12px 12px; margin-bottom: 16px; }
    .tv-my-item { width: 96px; }
    .tv-my-name { font-size: .74rem; }
}

/* ============================================================
 * 六、骨架屏 / 加载更多 / 搜索历史 / 续播角标
 * ------------------------------------------------------------
 * 这一节是「交互打磨」加的，都是列表页的反馈细节：
 * 骨架屏替掉原来那句「已等 N 秒」——空灰块能立刻说明「内容是这个形状、正在来」，
 * 而一句转圈文案看两秒就开始让人怀疑卡死了。
 * ============================================================ */

/* ---------- 骨架屏 ---------- */
.tv-sk-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: 14px 12px;
}
.tv-sk { display: block; }
.tv-sk-poster {
    display: block;
    aspect-ratio: 2 / 3;
    border-radius: var(--tv-radius);
    background: linear-gradient(100deg, #f7e9ee 30%, #fdf3f6 50%, #f7e9ee 70%);
    background-size: 220% 100%;
    animation: tv-sk-shine 1.4s ease-in-out infinite;
}
.tv-sk-line {
    display: block;
    height: 12px;
    margin-top: 8px;
    border-radius: 3px;
    background: #f3e6eb;
}
@keyframes tv-sk-shine {
    from { background-position: 120% 0; }
    to { background-position: -120% 0; }
}
.tv-sk-hint { margin-top: 14px; text-align: center; }
.tv-sk-hint span { margin: 0 3px; }

/* 尊重「减少动效」偏好：骨架屏的流光关掉，只留静态灰块 */
@media (prefers-reduced-motion: reduce) {
    .tv-sk-poster { animation: none; }
}

/* ---------- 「加载更多」（聚合模式专用，替掉假分页） ---------- */
.tv-more {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin: 18px 0 4px;
    color: var(--tv-sub);
    font-size: .86rem;
}
.tv-more-sec { font-size: .78rem; opacity: .8; }
.tv-more-btn { min-width: 140px; }
/* 追加成功后底部那颗按钮闪一下，让用户确认「确实是新加了一批」 */
.tv-more-btn.is-done { animation: tv-more-pop .5s ease; }
@keyframes tv-more-pop {
    0% { transform: scale(1); }
    40% { transform: scale(1.06); }
    100% { transform: scale(1); }
}

/* ---------- 搜索历史下拉 ---------- */
.tv-hist {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    z-index: 30;
    padding: 6px;
    border: 1px solid var(--tv-border);
    border-radius: 12px;
    background: var(--tv-card);
    box-shadow: 0 10px 28px rgba(244, 93, 138, .16);
    max-height: 320px;
    overflow-y: auto;
}
.tv-hist[hidden] { display: none; }
.tv-hist-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 4px 8px 8px;
    color: var(--tv-sub);
    font-size: .78rem;
}
.tv-hist-head button {
    border: 0;
    background: none;
    color: var(--tv-sub);
    font: inherit;
    font-size: .78rem;
    cursor: pointer;
    padding: 2px 6px;
    border-radius: 6px;
}
.tv-hist-head button:hover { color: var(--tv-accent-ink); background: var(--tv-accent-soft); }
.tv-hist-row { display: flex; align-items: center; gap: 2px; }
.tv-hist-word {
    flex: 1;
    min-width: 0;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--tv-text);
    font: inherit;
    font-size: .88rem;
    text-align: left;
    padding: 9px 10px;
    cursor: pointer;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tv-hist-word:hover, .tv-hist-word.is-cur { background: var(--tv-accent-soft); color: var(--tv-accent-ink); }
.tv-hist-del {
    flex: 0 0 auto;
    width: 28px; height: 28px;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--tv-sub);
    font-size: .8rem;
    cursor: pointer;
}
.tv-hist-del:hover { background: var(--tv-accent-soft); color: var(--tv-accent-ink); }

/* ---------- 续播角标：卡在海报上，一眼看出这部看过 ---------- */
.tv-resume {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 4px 6px;
    background: linear-gradient(0deg, rgba(0, 0, 0, .78), transparent);
    color: #fff;
    font-size: .68rem;
    line-height: 1.2;
    text-align: left;
}

@media (max-width: 768px) {
    .tv-sk-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 8px; }
    .tv-hist { max-height: 46vh; }
}

/* ---------------- 详情页「换源」一排 ----------------
   同一部片在多个采集源都有时（网关聚合时记在条目的 _alts 上），
   让用户自己挑一条能放的 —— 各源画质/速度/是否放得出来差别很大。 */
.tv-alt-srcs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin: 0 0 10px;
}
.tv-alt-label {
    color: var(--tv-sub);
    font-size: .78rem;
    margin-right: 2px;
}
.tv-alt-btn {
    border: 1px solid var(--tv-line, rgba(255, 255, 255, .14));
    background: transparent;
    color: inherit;
    font: inherit;
    font-size: .78rem;
    padding: 4px 10px;
    border-radius: 999px;
    cursor: pointer;
    transition: background .18s, border-color .18s, color .18s;
}
.tv-alt-btn:hover { border-color: var(--tv-accent, #6cf); }
.tv-alt-btn.is-on {
    background: var(--tv-accent, #6cf);
    border-color: var(--tv-accent, #6cf);
    color: #08121c;
    font-weight: 600;
    cursor: default;
}
@media (max-width: 768px) {
    .tv-alt-btn { padding: 6px 12px; }
}

/* ============================================================
   搜索结果二级页（.tv-sv）
   ------------------------------------------------------------
   搜索结果开在 AppShell 的层里，而不是塞进主滚动的 #tvGrid。
   原来那套要用户往下翻过 hero 大图、榜单、分类，才看得见第一条结果。

   这里只负责层**内部**的排版（页头 + 结果网格）；
   层的外框、返回按钮、下滑关闭全由 AppShell 提供 ——
   和 archives / journal / music 那几页共用同一套，不重复造。
   ============================================================ */
.tv-sv-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 4px 2px 14px;
    margin-bottom: 4px;
    border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.tv-sv-head b {
    font-size: 17px;
    font-weight: 700;
    /* 关键词可能很长，别把「多少条」挤到下一行去 */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.tv-sv-head span {
    flex: none;
    font-size: 12.5px;
    opacity: .62;
}
.tv-sv-body { padding-top: 4px; }
