/* ==========================================================================   打字工具（TypingKit）样式：测速 / 练习 / 榜单 / 我的成绩 / 全部成绩 / 成绩详情共用。
   引入点 = index/tools/typing/typing_css.html（缓存号只改那里）；颜色一律走 --bs-*。
   ========================================================================== */

/* ==========================================================================
   1. 测速 / 练习舞台（216 打字测速）：一个隐形输入框 + 逐字符渲染
   ========================================================================== */
.tp-stage{position:relative;border:1px solid var(--bs-border-color);border-radius:.6rem;background-color:var(--bs-body-bg);cursor:text;transition:border-color .15s ease,box-shadow .15s ease;}
.tp-stage.tp-active{border-color:var(--bs-primary);box-shadow:0 0 0 .2rem rgba(var(--bs-primary-rgb),.12);}
/* 暂停遮罩：暂停时半透明压暗正文并停用输入，避免误以为仍在计时（测速/练习两页共用） */
.tp-stage.tp-paused .tp-viewport{opacity:.35;pointer-events:none;}
/* 暂停中捕获输入框虽已 disabled，但仍整层盖住舞台拦截点击——让指针穿透，
   这样「点击舞台任意位置恢复」才可达（点击冒泡到 stage → pauseRun 恢复） */
.tp-stage.tp-paused .tp-capture{pointer-events:none;}
/* 视口：高度固定，只内部滚动、不显示滚动条，锁定的那一行永远在正中间 */
.tp-viewport{--tp-line-h:44px;--tp-viewport-h:220px;--tp-band-top:calc(50% - (var(--tp-line-h) / 2));position:relative;height:var(--tp-viewport-h);overflow-y:auto;padding:0 .9rem;scrollbar-width:none;-ms-overflow-style:none;
    background-image:linear-gradient(to bottom,transparent var(--tp-band-top),rgba(var(--bs-primary-rgb),.07) var(--tp-band-top),rgba(var(--bs-primary-rgb),.07) calc(var(--tp-band-top) + var(--tp-line-h)),transparent calc(var(--tp-band-top) + var(--tp-line-h)));}
.tp-viewport::-webkit-scrollbar{display:none;width:0;height:0;}
/* 上下留白把第一行也顶到锁定带中间 */
.tp-passage{white-space:pre-wrap;word-break:break-word;font-size:1.15rem;line-height:var(--tp-line-h);letter-spacing:.02em;
    padding-block:calc((var(--tp-viewport-h) - var(--tp-line-h)) / 2);}
.tp-passage .tp-char{border-radius:.15rem;}
.tp-passage .tp-char.tp-ok{color:var(--bs-success);background-color:rgba(var(--bs-success-rgb),.14);}
/* 错字 = 粉红底块：打错的字保持标红，光标已越过它继续前进（参考站样式）；
   打错瞬间整块闪红一次再回落，反馈更明显（背景色动画对 inline 元素生效，transform 不行） */
.tp-passage .tp-char.tp-wrong{color:var(--bs-danger);background-color:rgba(var(--bs-danger-rgb),.26);animation:tp-wrong-flash .5s ease-out;}
@keyframes tp-wrong-flash{0%{background-color:var(--bs-danger);color:#fff}100%{background-color:rgba(var(--bs-danger-rgb),.26);color:var(--bs-danger)}}
/* 当前字 = 蓝色下划线光标（参考站样式）；box-shadow 不改变布局，零重排 */
.tp-passage .tp-char.tp-current{position:relative;color:var(--bs-primary);
    box-shadow:inset 0 -2px 0 0 var(--bs-primary);}
/* 唯一真实输入：隐形铺满舞台，按键 / 点击 / 输入法都落在它身上 */
.tp-capture{position:absolute;inset:0;z-index:2;width:100%;height:100%;padding:0;border:0;outline:0;background-color:transparent;color:transparent;caret-color:transparent;opacity:.01;font-size:16px;}
.tp-overlay{position:absolute;left:0;right:0;bottom:.6rem;z-index:3;display:flex;justify-content:center;pointer-events:none;}
.tp-pill{font-weight:500;box-shadow:0 .25rem .75rem rgba(0,0,0,.12);}
/* 打字提示栏：练习文本上方、文本框内的紧凑提示条；pointer-events:none 不抢输入焦点 */
.tp-hintbar{position:relative;z-index:4;pointer-events:none;display:flex;align-items:center;gap:.5rem;
    padding:.45rem .9rem;border-bottom:1px solid var(--bs-border-color);
    background-color:var(--bs-tertiary-bg);border-radius:.6rem .6rem 0 0;}
.tp-hintbar-label{font-size:.78rem;font-weight:600;color:var(--bs-secondary-color);letter-spacing:.02em;}
.tp-hintbar-char{font-size:1.45rem;font-weight:700;line-height:1;color:var(--bs-primary);min-width:1.1em;text-align:center;}
/* 当前字用【】包住，贴近参考站「【家】」的样式 */
.tp-hintbar-char::before{content:'【';font-weight:400;opacity:.7;}
.tp-hintbar-char::after{content:'】';font-weight:400;opacity:.7;}
/* 打错时提示栏变红 + 左右抖动一下，纠错提示更醒目 */
.tp-hintbar.tp-hintbar-wrong{background-color:rgba(var(--bs-danger-rgb),.12);border-bottom-color:rgba(var(--bs-danger-rgb),.5);animation:tp-wrong-shake .35s ease-in-out;}
@keyframes tp-wrong-shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-4px)}40%{transform:translateX(4px)}60%{transform:translateX(-3px)}80%{transform:translateX(3px)}}
.tp-hintbar.tp-hintbar-wrong .tp-hintbar-char{color:var(--bs-danger);}
.tp-hintbar.tp-hintbar-wrong .tp-hintbar-key{color:var(--bs-danger);}
.tp-hintbar-key{font-size:1rem;color:var(--bs-body-color);font-family:var(--bs-font-monospace);}
/* 汉字头顶注音（ruby）：zh 标拼音、wubi 标五笔86 码。
   行高恒定（与无注音一致 44px）：注音 rt + 汉字约 31px，44px 行框放得下——
   之前注音状态单独放大行高（52px），开关提示时视口高度跟着跳 24px，观感突兀 */
.tp-passage ruby{ruby-align:center;}
/* 输入提示显隐：拼音/五笔两套 rt 同驻 DOM，切文章区类名即切显隐（不重建 DOM，无闪烁）。
   注意：不随提示开关改行高——行高恒定，否则开关时文章区高度会跳动 */
.tp-passage.tp-hint-py ruby rt.tp-rt-wb{display:none;}
.tp-passage.tp-hint-wb ruby rt.tp-rt-py{display:none;}
.tp-passage.tp-hint-off ruby rt{display:none;}
.tp-passage rt{font-size:.78rem;color:var(--bs-secondary-color);font-family:var(--bs-font-monospace);
    font-weight:500;line-height:1;letter-spacing:.02em;user-select:none;}
/* 五笔字根：目标字连同字根码保持一行 */
.tp-unit{display:inline-block;text-align:center;margin-right:.35rem;line-height:1.6;}
.tp-unit .tp-unit-hint{display:block;font-size:.7rem;line-height:1.15;color:var(--bs-secondary-color);}
/* 虚拟键盘 */
.tp-key{min-width:2.1rem;padding:.2rem .45rem;user-select:none;}
.tp-key-space{min-width:12rem;}
.tp-key.tp-active{background-color:var(--bs-primary)!important;border-color:var(--bs-primary)!important;color:var(--bs-white)!important;}

/* ==========================================================================
   2. 视频跟打（216 内）：本地视频 + 本地 SRT / VTT 字幕
   ========================================================================== */
.tp-video-frame{max-width:720px;margin:0 auto;aspect-ratio:16/9;background-color:#000;border-radius:.5rem;overflow:hidden;display:flex;align-items:center;justify-content:center;}
.tp-video{width:100%;height:100%;display:block;background-color:#000;}
.tp-video-empty{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.3rem;padding:1rem;text-align:center;background-color:var(--bs-body-bg);border:1px dashed var(--bs-border-color);border-radius:.5rem;}
.tp-video-empty .fa{font-size:2rem;color:var(--bs-secondary-color);}
.tp-cue-box{border:1px dashed var(--bs-border-color);border-radius:.5rem;padding:.6rem .75rem;background-color:var(--bs-tertiary-bg);}
.tp-cue-text{margin-top:.2rem;min-height:1.7em;font-size:1.05rem;line-height:1.7;word-break:break-word;}
.tp-cue-muted{color:var(--bs-secondary-color);font-style:italic;}
.tp-seg-input{width:5.6rem;}
/* 字幕句子短：舞台跟着收窄，不白占一整屏高度 */
.tp-stage.tp-compact .tp-viewport{--tp-viewport-h:132px;--tp-line-h:40px;}
/* 练习页：锁定带固定在视口顶部 8px 处（不再垂直居中）——当前行永远在带里，
   下方连排 4 行正文，首行与提示栏之间只有 8px 空隙且不随行数变大；
   视口高 = 8 + 4行 + 8，正文底部内边距留足三行，保证最后一行也能滚进带里。
   ⚠️ 行数与行高都必须与注音开关无关（恒定 4 行 / 44px）：注音 rt + 汉字约 31px
   在 44px 行框内放得下；之前「注音时改 4 行」的 :has() 规则导致测速页开关
   提示时视口在 3↔4 行间跳变（练习页注音常驻掩盖了这一问题），已删除 */
#td_stage:not(.tp-compact) .tp-viewport{--tp-band-top:8px;--tp-viewport-h:calc(var(--tp-band-top) + 4 * var(--tp-line-h) + 8px);}
#td_stage:not(.tp-compact) .tp-passage{padding-block:var(--tp-band-top) calc(3 * var(--tp-line-h) + 8px);}
/* 测速页：同款——锁定带固定顶部 8px、连排 4 行；视频紧凑面板保持居中带不动 */
#tp_stage:not(.tp-compact) .tp-viewport{--tp-band-top:8px;--tp-viewport-h:calc(var(--tp-band-top) + 4 * var(--tp-line-h) + 8px);}
#tp_stage:not(.tp-compact) .tp-passage{padding-block:var(--tp-band-top) calc(3 * var(--tp-line-h) + 8px);}
.tp-passage .tp-char.tp-blind{color:var(--bs-secondary-color);}
/* ============ 错字动效：抖动 + 视口红色微闪（打错瞬间触发，动画一次性自结束） ============ */
@keyframes tp-wrong-judder{
    0%{transform:translateX(0);}
    25%{transform:translateX(-2px);}
    50%{transform:translateX(2px);}
    75%{transform:translateX(-1px);}
    100%{transform:translateX(0);}
}
.tp-passage .tp-char.tp-wrong-judder{animation:tp-wrong-judder .18s ease;}
@keyframes tp-error-flash{
    0%{box-shadow:inset 0 0 0 2px rgba(220,53,69,.28);}
    100%{box-shadow:inset 0 0 0 2px rgba(220,53,69,0);}
}
.tp-stage.tp-error-flash .tp-viewport{animation:tp-error-flash .35s ease-out;}
@media (prefers-reduced-motion:reduce){
    .tp-passage .tp-char.tp-wrong-judder,.tp-stage.tp-error-flash .tp-viewport{animation:none;}
}
/* 主指标独占第一张卡 */
#tp_stat_speed{font-size:2.5rem;line-height:1.1;}
#tp_res_speed{font-size:2.25rem;line-height:1.1;}

/* ==========================================================================   3. 虚拟键盘（TypingKit）：60 列 CSS Grid，标准键宽 = 4 列，宽度只用 tk-kb-w* 类
   ========================================================================== */
/* 键盘外层滚动容器：窄屏放不下时横向滚动，键面尺寸与字号不再被 gap 挤扁 */
.tk-kb-wrap{overflow-x:auto;overflow-y:hidden;padding-bottom:.15rem;}
.tk-kb{display:flex;flex-direction:column;gap:.3rem;min-width:35rem;}
.tk-kb-row{display:grid;grid-template-columns:repeat(60,1fr);gap:.3rem;}
/* 键面用正文字号 + 正文色（原来 .82rem 且是次要色，小屏上确实看不清） */
.tk-kb-key{grid-column:span 4;height:2.6rem;display:flex;align-items:center;justify-content:center;
    border:1px solid var(--bs-border-color);border-radius:.4rem;font-size:1rem;font-weight:500;
    color:var(--bs-body-color);background-color:var(--bs-body-bg);
    transition:background-color .12s ease,color .12s ease;}
.tk-kb-w1{grid-column:span 4;}
.tk-kb-w1_25{grid-column:span 5;}
.tk-kb-w1_5{grid-column:span 6;}
.tk-kb-w1_75{grid-column:span 7;}
.tk-kb-w2{grid-column:span 8;}
.tk-kb-w2_25{grid-column:span 9;}
.tk-kb-w2_75{grid-column:span 11;}
.tk-kb-w6_25{grid-column:span 25;}
/* 特殊键（Backspace/Tab/Caps…）是单词，比单个字母小一号但仍是正文色系 */
.tk-kb-special{font-size:.78rem;font-weight:400;}
/* 手指分区（整组淡色）+ 目标键（实色）+ 命中 / 按错反馈 */
.tk-kb-key.is-group{background-color:var(--bs-warning-bg-subtle);border-color:var(--bs-warning-border-subtle);color:var(--bs-emphasis-color);}
.tk-kb-key.is-target{background-color:var(--bs-primary);border-color:var(--bs-primary);color:var(--bs-white);font-weight:600;}
.tk-kb-key.is-hit{background-color:var(--bs-success-bg-subtle);border-color:var(--bs-success-border-subtle);}
.tk-kb-key.is-miss{background-color:var(--bs-danger-bg-subtle);border-color:var(--bs-danger-border-subtle);}
/* 需要 Shift 时反手那个 Shift 键也点亮 */
.tk-kb-key.is-shift{background-color:var(--bs-warning-bg-subtle);border-color:var(--bs-warning-border-subtle);}

/* ==========================================================================   4. 双手（TypingKit hands）：俯视角；掌部固定、只有手指伸出去够键。
      姿态/动画全在 CSS，JS 只写 --tk-x / --tk-y（位移）/ --tk-curl（弯曲度）。
      五段动画 = 两类 class：.is-target（lift+reach）/ .is-pressing（press），去掉即 release+return。
   ========================================================================== */
.tk-hands{display:flex;flex-wrap:wrap;justify-content:center;gap:.5rem 1.5rem;}
.tk-hand{width:min(46%,180px);height:auto;}
.tk-wrist{fill:var(--bs-tertiary-bg);stroke:var(--bs-border-color);stroke-width:2;}
.tk-palm{fill:var(--bs-secondary-bg);stroke:var(--bs-border-color);stroke-width:2;}
.tk-finger,.tk-finger-shaft,.tk-seg{transform-box:fill-box;}
.tk-finger{transition:transform .17s cubic-bezier(.34,1.28,.64,1);}
/* 关节只绕自己的顶端转（顶端 = 关节），父节一转子节跟着走 */
.tk-finger-shaft,.tk-seg{transform-origin:top center;transition:transform .17s cubic-bezier(.34,1.28,.64,1);}
.tk-seg rect{fill:var(--bs-body-bg);stroke:var(--bs-border-color);stroke-width:2;}
/* 静止姿态：轻微自然弯曲，不是一根直棍 */
.tk-seg-1{transform:rotate(calc(var(--tk-curl, 9) * .45deg));}
.tk-seg-2{transform:rotate(calc(var(--tk-curl, 9) * .8deg));}
.tk-seg-3{transform:rotate(calc(var(--tk-curl, 9) * .6deg));}
/* 拇指：静态转向掌侧（挂在与动画元素不同的 .tk-finger-shaft 上，两者互不覆盖） */
.tk-finger-side.tk-side-right{transform:rotate(-90deg);}
.tk-finger-side.tk-side-left{transform:rotate(90deg);}
/* 目标键：整根手指平移过去够它 */
.tk-finger.is-target{transform:translate(calc(var(--tk-x, 0) * 1px), calc(var(--tk-y, 0) * 1px));animation:tk-finger-pulse 1.1s ease-in-out infinite;}
.tk-finger.is-target .tk-seg rect{fill:var(--bs-primary-bg-subtle);stroke:var(--bs-primary);}
@keyframes tk-finger-pulse{
    0%,100%{transform:translate(calc(var(--tk-x, 0) * 1px), calc(var(--tk-y, 0) * 1px));}
    50%{transform:translate(calc(var(--tk-x, 0) * 1px), calc(var(--tk-y, 0) * 1px - 2px));}
}
/* 反手小指伸到 Shift 键上 */
.tk-finger.is-shift{transform:translate(calc(var(--tk-x, 0) * 1px), calc(var(--tk-y, 0) * 1px));}
.tk-finger.is-shift .tk-seg rect{fill:var(--bs-warning-bg-subtle);stroke:var(--bs-warning);}
/* 按下：指尖多压一点、关节内收，过渡更短（像真的敲下去） */
.tk-finger.is-pressing,.tk-finger.is-pressing .tk-seg,.tk-finger.is-pressing .tk-finger-shaft{animation:none;transition-duration:.07s;}
.tk-finger.is-pressing{transform:translate(calc(var(--tk-x, 0) * 1px), calc(var(--tk-y, 0) * 1px + 5px));}
.tk-finger.is-pressing .tk-seg-1{transform:rotate(calc((var(--tk-curl, 9) * .45 + 4) * 1deg));}
.tk-finger.is-pressing .tk-seg-2{transform:rotate(calc((var(--tk-curl, 9) * .8 + 8) * 1deg));}
.tk-finger.is-pressing .tk-seg-3{transform:rotate(calc((var(--tk-curl, 9) * .6 + 10) * 1deg));}
.tk-finger.is-pressing .tk-seg rect{fill:var(--bs-success-bg-subtle);stroke:var(--bs-success);}
/* 按错：闪红的是「你实际按下的那根手指」，目标手指继续保持提示 */
.tk-finger.is-miss .tk-seg rect{fill:var(--bs-danger-bg-subtle);stroke:var(--bs-danger);}
.tk-finger-label{font-size:9px;fill:var(--bs-secondary-color);}
.tk-hand-label{font-size:11px;font-weight:600;fill:var(--bs-secondary-color);}
/* 榜单刷新态：翻页/切档保留旧列表半透明显示，防止整卡塌陷→弹开造成页面抖动 */
#tk_list.is-refreshing{opacity:.55;pointer-events:none}

/* §防抖动：榜单卡体限高内滚——异步数据填充不再推动下方内容（卡片高度有界） */
.rank-body{max-height:75vh;overflow-y:auto;scrollbar-gutter:stable;scrollbar-width:thin}

/* 榜单首载骨架屏（高度逼近真实行，数据到达不再从一行 spinner 弹开） */
.tk-skel{display:flex;align-items:center;gap:.75rem;padding:.6rem .75rem;border-bottom:1px solid var(--bs-border-color)}
.tk-skel .sk{display:inline-block;background:var(--bs-tertiary-bg);border-radius:.35rem}
.tk-skel .sk-rank{width:2rem;height:1.4rem}
.tk-skel .sk-avatar{width:2rem;height:2rem;border-radius:50%}
.tk-skel .sk-lines{flex:1;display:flex;flex-direction:column;gap:.35rem}
.tk-skel .sk-l1{height:.8rem;width:60%}
.tk-skel .sk-l2{height:.7rem;width:85%}
.tk-skel .sk-speed{width:3.5rem;height:2rem}

/* 异步填充容器：加载前预留最小高度（身份条实测 100px / 五笔分区按钮两行），消除弹入抖动
   ⚠️ 四个身份盒（tp/tk/tm/tr）统一占位——只给 tk 设会导致其它页进入时盒子先塌缩再撑开、下方内容跳动 */
#tp_user_box,#tk_user_box,#tm_user_box,#tr_user_box{min-height:6.25rem}
#wr_zones{min-height:4.6rem}

@media (prefers-reduced-motion:reduce){.tk-finger.is-target{animation:none;}}

/* ==========================================================================
   5. 键盘练习区（216）
   ========================================================================== */
.tk-pr-opts .form-check-label{font-size:.82rem;}
.tk-pr-hands-select{width:auto;}/* UI 收敛：当前键位是视觉中心（字更大 + 留白），输入方式/手指退到小号 chip */
.tk-pr-char{font-size:4.2rem;font-weight:700;line-height:1.1;font-family:var(--bs-font-monospace);letter-spacing:.1em;
    margin:.3rem 0 .45rem;}/* 统计：只突出速度/正确率；时间/错误降档（id 权重稳压 fs-4/fw-bold/text-*，无需 !important） */
#tp_stat_time,#tp_stat_errors{font-size:1.05rem;font-weight:600;color:var(--bs-secondary-color);}
#tp_stat_speed,#tp_stat_accuracy{font-size:1.65rem;}/* 逐级课程：默认只露前 4 级，checkbox + label 纯 CSS 展开（buildChoose() 不动）；单指专项 8 个不折叠 */
#tp_course > *:nth-child(n+5){display:none;}
#tp_course_more:checked ~ #tp_course > *:nth-child(n+5){display:block;}
.tk-course-icon{transition:transform .15s ease;}
.tk-course-toggle .tk-course-hide{display:none;}
#tp_course_more:checked ~ .tk-course-toggle .tk-course-show{display:none;}
#tp_course_more:checked ~ .tk-course-toggle .tk-course-hide{display:inline;}
#tp_course_more:checked ~ .tk-course-toggle .tk-course-icon{transform:rotate(180deg);}/* 练习序列：滑动窗口只渲染当前附近一小段 ⇒ 不会再出现「当前字符被裁出视野」 */
.tk-pr-seq{display:flex;flex-wrap:nowrap;gap:.3rem;justify-content:center;overflow:hidden;padding-top:1.15rem;
    font-family:var(--bs-font-monospace);}
.tk-pr-seq span{position:relative;flex:0 0 auto;min-width:2.2rem;padding:.15rem .4rem;border:1px solid var(--bs-border-color);
    border-radius:.45rem;font-size:1.5rem;line-height:1.45;text-align:center;color:var(--bs-secondary-color);
    background-color:var(--bs-body-bg);transition:background-color .12s ease,color .12s ease,border-color .12s ease;}/* 打过的格子整块上色 + 顶上 ✓ / ✗ 角标（只改字色太轻，看不出哪一格错了）
   ⚠️ 角标绝对定位在格子上方 ⇒ 行必须留 padding-top，否则被 overflow:hidden 裁掉 */
.tk-pr-seq span.is-ok{color:var(--bs-success);border-color:var(--bs-success);background-color:var(--bs-success-bg-subtle);}
.tk-pr-seq span.is-bad{color:var(--bs-danger);border-color:var(--bs-danger);background-color:var(--bs-danger-bg-subtle);}
.tk-pr-seq span.is-ok::before,.tk-pr-seq span.is-bad::before{position:absolute;left:50%;top:-1.05rem;transform:translateX(-50%);
    font-size:.85rem;line-height:1;font-weight:700;}
.tk-pr-seq span.is-ok::before{content:'\2713';color:var(--bs-success);}
.tk-pr-seq span.is-bad::before{content:'\2717';color:var(--bs-danger);}
/* 当前格子：主色底 + 主色下沿（对齐「这一格在等你」的读法） */
.tk-pr-seq span.is-current{color:var(--bs-primary);background-color:var(--bs-primary-bg-subtle);
    border-color:var(--bs-primary);font-weight:700;box-shadow:inset 0 -.2rem 0 0 var(--bs-primary);}/* 按错瞬间的当前格：红色必须压过主色 —— 用双类（0,3,1）显式提权，
   别挪到 is-current 之前（两条规则同权重，靠顺序决胜负） */
.tk-pr-seq span.is-current.is-bad{color:var(--bs-danger);background-color:var(--bs-danger-bg-subtle);
    border-color:var(--bs-danger);box-shadow:inset 0 -.2rem 0 0 var(--bs-danger);}
.tk-pr-lesson{font-family:var(--bs-font-monospace);}
.tk-pr-course{font-family:var(--bs-font-monospace);}
/* ==========================================================================   5b. 线描手指引导层：曲线画在键盘上、指尖落在真实键位（容器 = 键盘卡片 + .tk-guide-host）
   ========================================================================== */
.tk-guide-host{position:relative;}
.tk-guide-host .tk-guide{display:none;}
/* 显示引导时给卡片留出下方「手掌 + 腕端」的空间（掌长 2.6 行 + 指根离键盘的间隙） */
.tk-guide-host.tk-guide-on{padding-bottom:8rem;}
.tk-guide-host.tk-guide-on .tk-guide{display:block;}
.tk-guide{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:2;overflow:visible;
    transform:translateX(var(--tk-guide-shift,0px));}   /* 键盘横向滚动时由脚本改这个变量跟着平移 */
/* 线描手：手掌 + 手指都是空心描边（不遮键面），静止时是一副干净的线稿 */
.tk-guide-palm{fill:none;stroke:var(--bs-secondary-color);stroke-opacity:.3;stroke-width:1.8;stroke-linejoin:round;stroke-linecap:round;
    transform:translateX(var(--tk-guide-hx,0px));}   /* 手腕随动：整只手掌跟着目标挪 0~1.6px */
.tk-guide-finger{fill:none;stroke:var(--bs-secondary-color);stroke-opacity:.5;stroke-width:1.6;stroke-linejoin:round;
    transition:stroke .12s ease,stroke-opacity .12s ease,stroke-width .12s ease;}
.tk-guide-finger.is-target{stroke:var(--bs-primary);stroke-opacity:1;stroke-width:2.4;}
.tk-guide-finger.is-shift{stroke:var(--bs-warning);stroke-opacity:1;stroke-width:2.2;}
.tk-guide-finger.is-pressing{stroke:var(--bs-success);stroke-opacity:1;stroke-width:2.4;}
.tk-guide-finger.is-miss{stroke:var(--bs-danger);stroke-opacity:1;stroke-width:2.2;}
/* 指节横纹：两道细线就有「手指」而不是「管子」的感觉，跟着手指一起动 */
.tk-guide-joint{fill:none;stroke:var(--bs-secondary-color);stroke-opacity:.28;stroke-width:1;stroke-linecap:round;
    transition:stroke .12s ease,stroke-opacity .12s ease;}
.tk-guide-joint.is-target{stroke:var(--bs-primary);stroke-opacity:.5;}
.tk-guide-joint.is-shift{stroke:var(--bs-warning);stroke-opacity:.5;}
.tk-guide-joint.is-pressing{stroke:var(--bs-success);stroke-opacity:.55;}
.tk-guide-joint.is-miss{stroke:var(--bs-danger);stroke-opacity:.5;}/* 指尖圆点（指腹）：圆心 = 目标键正中央，所以**必须半透明** —— 实色填充会把键帽字母整颗盖掉。
   现为「极淡白色按压印 + 白色按压环」：环在字母外围、印只提亮不压字 ⇒ 指尖可见、字母照旧清晰。 */
.tk-guide-tip{fill:var(--bs-white);fill-opacity:0;stroke:var(--bs-secondary-color);stroke-width:1.5;stroke-opacity:.5;opacity:0;
    transition:fill .12s ease,fill-opacity .12s ease,stroke .12s ease,stroke-opacity .12s ease,opacity .12s ease;}
.tk-guide-tip.is-target{fill-opacity:.18;stroke:var(--bs-white);stroke-opacity:.85;opacity:1;}
.tk-guide-tip.is-shift{fill-opacity:.24;stroke:var(--bs-warning);stroke-opacity:.95;opacity:1;}
.tk-guide-tip.is-pressing{fill-opacity:.3;stroke:var(--bs-success);stroke-opacity:1;opacity:1;}
.tk-guide-tip.is-miss{fill-opacity:.28;stroke:var(--bs-danger);stroke-opacity:1;opacity:1;}

/* 语言层：「输入方式 · 手指 · 键位」三层提示的小胶囊 */
.tk-pr-chip{display:inline-flex;align-items:center;gap:.25rem;padding:.08rem .5rem;border:1px solid var(--bs-border-color);
    border-radius:999px;background-color:var(--bs-tertiary-bg);color:var(--bs-secondary-color);font-size:.76rem;line-height:1.7;}
/* 序列格：只在"当前"那格用力，已完成/错误交给 ✓ ✗ 与字色 —— 不再每格一个重边框 */
.tk-pr-seq span:not(.is-current){border-color:transparent;background-color:transparent;}
/* 辅助 chip 收小：键位 chip 仍是主角（当前键的字号见 .tk-pr-char） */
.tk-pr-chip{font-size:.75rem;}
.tk-pr-chip-key{background-color:var(--bs-primary-bg-subtle);border-color:var(--bs-primary);color:var(--bs-primary);font-weight:600;font-size:.85rem;}
/* 编码输入（拼音 / 五笔）：一颗键帽 + 它下面负责的手指 */
.tk-pr-code-key{display:inline-flex;flex-direction:column;align-items:center;line-height:1.15;}
.tk-pr-code-cap{min-width:1.6rem;padding:0 .3rem;border:1px solid var(--bs-border-color);border-radius:.3rem;
    background-color:var(--bs-body-bg);font-weight:600;}
.tk-pr-code-finger{font-size:.68rem;color:var(--bs-secondary-color);white-space:nowrap;}

/* ==========================================================================   6. 榜单行（213）：桌面像表格对齐，移动端堆成卡片（不强制横向滚动）
   ========================================================================== */
.tk-row{display:flex;align-items:center;gap:.6rem;padding:.55rem .75rem;border-bottom:1px solid var(--bs-border-color);}
.tk-row:last-child{border-bottom:0;}
.tk-row:hover{background-color:var(--bs-tertiary-bg);}
.tk-rank{flex:0 0 2.4rem;}
.tk-user{flex:1 1 auto;min-width:0;}
.tk-user .tk-meta{font-size:.8rem;}
.tk-speed{flex:0 0 auto;text-align:right;line-height:1.15;min-width:5.2rem;}
.tk-avatar{width:1.9rem;height:1.9rem;font-size:.85rem;line-height:1.9rem;}
.tk-avatar-img{padding:0;border:0;object-fit:cover;background:var(--bs-body-bg);}

/* ==========================================================================   7. 成绩列表共用（214 / 215 同一套行渲染）：桌面真表格，移动端换卡片，不横向滚动
   ========================================================================== */
.tk-rec-table{white-space:nowrap;}
.tk-rec-table td,.tk-rec-table th{vertical-align:middle;}
.tk-rec-avatar{display:inline-flex;align-items:center;justify-content:center;width:1.4rem;height:1.4rem;border-radius:50%;font-size:.72rem;}
.tk-rec-card{display:block;padding:.6rem .75rem;border-bottom:1px solid var(--bs-border-color);}
.tk-rec-card:last-child{border-bottom:0;}
.tk-rec-card:hover{background-color:var(--bs-tertiary-bg);}
.tk-rec-speed{flex:0 0 auto;min-width:5rem;}

/* ==========================================================================
   8. 我的成绩（214）
   ========================================================================== */
.tm-stat{font-size:1.6rem;line-height:1.1;}
/* 我的最佳成绩：三种类型各一张卡（桌面三列 / 移动端堆叠），「我的排名」直接落在卡里 */
.tm-best-card{height:100%;}
.tm-best-speed{font-size:1.5rem;line-height:1;}
/* 最近成绩只露出全站流水的一角：移动端 8 条、桌面端 10 条，更多的去 215 看 */
@media (max-width: 991.98px){.tm-recent-cards li:nth-child(n+9){display:none;}}

/* ==========================================================================
   9. 成绩详情（typing-result/{id}）
   ========================================================================== */
.tr-hero{font-size:3rem;line-height:1.05;}
.tr-metric{height:100%;}
.tr-metric .tr-metric-value{font-size:1.15rem;font-weight:600;}

/* ==========================================================================
   10. 曲线入口（列表行 / 详情页共用同一个弹窗入口颜色）
   ========================================================================== */
.tk-curve{color:var(--bs-primary);}
.tp-curve{color:var(--bs-primary);}

/* ==========================================================================
   11. 窄屏
   ========================================================================== */
@media (max-width:575.98px){
    /* 窄屏靠横向滚动保持键面尺寸，字号不再下调（下调就回到「看不清」）。
       行高同样与注音开关无关（38px 恒定，注音内容放得下），避免视口高度跳动 */
    .tp-viewport{--tp-line-h:38px;--tp-viewport-h:176px;}
    /* 提示栏窄屏适配：内容放不下时整块掉到第二行，而不是把「打字提示」
       和「拼音：wang 五笔：mqqi」从中间硬切带切行 */
    .tp-hintbar{flex-wrap:wrap;row-gap:.1rem;padding:.4rem .6rem;}
    .tp-hintbar-label{white-space:nowrap;flex-shrink:0;}
    .tp-hintbar-char{flex-shrink:0;}
    .tp-hintbar-key{font-size:.9rem;}
    .tp-stage.tp-compact .tp-viewport{--tp-line-h:36px;--tp-viewport-h:120px;}
    .tp-passage{font-size:1rem;}
    #tp_stat_speed{font-size:2rem;}
    #tp_res_speed{font-size:1.9rem;}
    .tp-key{min-width:1.7rem;padding:.15rem .3rem;font-size:.75rem;}
    .tp-key-space{min-width:7rem;}
    /* 双手在窄屏各占一行仍看得清手指名 */
    .tk-hands{gap:.25rem 1rem;}
    .tk-hand{width:min(88%,240px);}
    /* 练习序列：窗口 9 格 —— 窄屏收小一档，320px 视口（含卡片内边距）也放得下不裁切 */
    .tk-pr-seq{gap:.22rem;padding-top:1rem;}
    .tk-pr-seq span{min-width:1.7rem;padding:.1rem .24rem;font-size:1.2rem;}
    .tk-pr-seq span.is-ok::before,.tk-pr-seq span.is-bad::before{font-size:.75rem;top:-.95rem;}
    .tk-pr-char{font-size:3rem;}
}

/* ==========================================================================   10. 五笔字根专项（typing_wubi_root.html）：学习模式下键盘即「五笔字根表」
       （键帽带口诀 + 五区配色，放大到能排下 3 行小字）；走「页面侧装饰」，没有改共享 kit。
   ========================================================================== */
/* 字根卡列：当前卡（绿框 + 描红大字 + 小键帽）+ 后面两张淡色预览卡（一眼看清下一个练什么） */
.wr-cards{display:flex;align-items:flex-start;justify-content:center;gap:.9rem;flex-wrap:wrap;margin-bottom:.25rem;}
.wr-card{display:flex;flex-direction:column;align-items:center;gap:.25rem;min-width:7.5rem;
    padding:.4rem .4rem .35rem;border:1px solid var(--bs-border-color);border-radius:.75rem;background-color:var(--bs-body-bg);}
.wr-card.is-current{border-color:var(--bs-success);border-width:2px;background-color:var(--bs-success-bg-subtle);}
.wr-card.is-next{opacity:.5;}
.wr-card-key{font-size:.72rem;font-weight:600;color:var(--bs-secondary-color);}
.wr-card.is-current .wr-card-key{color:var(--bs-success);}
.wr-card-cap{display:inline-block;min-width:1.6rem;padding:.1rem .35rem;border:1px solid var(--bs-warning-border-subtle);
    border-radius:.3rem;background-color:var(--bs-warning-bg-subtle);color:var(--bs-body-color);
    font-size:.8rem;font-weight:600;line-height:1.2;}
.wr-glyph-next{font-size:2rem;}
/* 闭卷（关掉「显示键位提示」）：键位与小键帽是答案 ⇒ 藏内容但保留占位（卡片尺寸不跳） */
.wr-cards:not(.is-hint) .wr-card-key,
.wr-cards:not(.is-hint) .wr-card-cap{visibility:hidden;}
/* 描红大字根：楷体 + 描边中空；不支持 -webkit-text-stroke 的浏览器退回实心红字 */
.wr-glyph{font-family:"KaiTi","STKaiti","Kaiti SC","楷体",serif;font-size:3.4rem;line-height:1.1;
    font-weight:400;color:var(--bs-danger);}
@supports (-webkit-text-stroke:1px){
    .wr-glyph{color:transparent;-webkit-text-stroke:2px var(--bs-danger);}
}
/* 学习模式：键帽放大（1 单位 = 60 列里的 4 列 ⇒ 52rem 键盘上单键约 55px），才排得下口诀小字 */
.wr-chart .tk-kb{min-width:52rem;}
.wr-chart .tk-kb-key{height:5.8rem;flex-direction:column;gap:.08rem;padding:.1rem .12rem;}
.wr-chart .tk-kb-cap{font-size:.95rem;line-height:1;font-weight:600;}
/* 键名汉字（王 / 土 / 目 …）：跟在键位后面，一眼读完"这个键叫什么" */
.wr-chart .wr-cap-name{margin-left:.18rem;font-size:.82em;font-weight:500;}
/* 字根（加粗、正文色）与口诀（次要色）分两段 —— 键帽上"哪些是字根、哪句是助记"一眼分清 */
.wr-chart .wr-cap-list{font-size:.52rem;font-weight:600;line-height:1.1;text-align:center;
    color:var(--bs-emphasis-color);word-break:break-all;}
.wr-chart .wr-cap-roots{font-size:.5rem;line-height:1.08;text-align:center;color:var(--bs-secondary-color);
    word-break:break-all;}
/* 五区配色（与常见五笔字根表一致：横红 / 竖蓝 / 撇黄 / 捺绿 / 折青） */
.wr-chart .tk-kb-key.wr-cap-h{background-color:var(--bs-danger-bg-subtle);border-color:var(--bs-danger-border-subtle);}
.wr-chart .tk-kb-key.wr-cap-s{background-color:var(--bs-primary-bg-subtle);border-color:var(--bs-primary-border-subtle);}
.wr-chart .tk-kb-key.wr-cap-p{background-color:var(--bs-warning-bg-subtle);border-color:var(--bs-warning-border-subtle);}
.wr-chart .tk-kb-key.wr-cap-n{background-color:var(--bs-success-bg-subtle);border-color:var(--bs-success-border-subtle);}
.wr-chart .tk-kb-key.wr-cap-z{background-color:var(--bs-info-bg-subtle);border-color:var(--bs-info-border-subtle);}
/* 状态必须压过分区色：目标键 / 命中 / 按错（权重比上面三条高，写在后面即可） */
.wr-chart .tk-kb-key.is-target{background-color:var(--bs-primary);border-color:var(--bs-primary);color:var(--bs-white);}
.wr-chart .tk-kb-key.is-target .wr-cap-roots,
.wr-chart .tk-kb-key.is-target .wr-cap-list,
.wr-chart .tk-kb-key.is-target .wr-cap-name{color:var(--bs-white);}
.wr-chart .tk-kb-key.is-hit{background-color:var(--bs-success);border-color:var(--bs-success);color:var(--bs-white);}
.wr-chart .tk-kb-key.is-hit .wr-cap-roots,
.wr-chart .tk-kb-key.is-hit .wr-cap-list,
.wr-chart .tk-kb-key.is-hit .wr-cap-name{color:var(--bs-white);}
.wr-chart .tk-kb-key.is-miss{background-color:var(--bs-danger);border-color:var(--bs-danger);color:var(--bs-white);}
.wr-chart .tk-kb-key.is-miss .wr-cap-roots,
.wr-chart .tk-kb-key.is-miss .wr-cap-list,
.wr-chart .tk-kb-key.is-miss .wr-cap-name{color:var(--bs-white);}/* 完整字根表：行 = 五区（11~15 / 21~25 / 31~35 / 41~45 / 51~55），列 = 该区 5 键，每格 4 行
   （区位号 / 键位+键名 / 字根 / 口诀）；⚠️ 配色只在这里，JS 只负责加类。 */
.wr-table-wrap{overflow-x:auto;}
.wr-table{width:100%;min-width:52rem;border-collapse:separate;border-spacing:.3rem;table-layout:fixed;}
.wr-table td{border:1px solid var(--bs-border-color);border-radius:.5rem;padding:.4rem .3rem;
    text-align:center;vertical-align:top;background-color:var(--bs-body-bg);}
.wr-table .wr-td-no{display:block;font-size:.62rem;font-weight:600;color:var(--bs-secondary-color);}
.wr-table .wr-td-head{display:block;line-height:1.2;}
.wr-table .wr-td-key{display:inline-block;font-size:1.05rem;font-weight:700;line-height:1.15;}
.wr-table .wr-td-name{display:inline-block;margin-left:.18rem;font-size:.85rem;font-weight:600;color:var(--bs-secondary-color);}
.wr-table .wr-td-roots{display:block;font-size:.82rem;line-height:1.35;}
.wr-table .wr-td-verse{display:block;margin-top:.2rem;font-size:.6rem;line-height:1.15;color:var(--bs-secondary-color);}
/* 五区配色（横红 / 竖蓝 / 撇黄 / 捺绿 / 折青 —— 与键帽同源） */
.wr-table tr.wr-zone-1 td{background-color:var(--bs-danger-bg-subtle);border-color:var(--bs-danger-border-subtle);}
.wr-table tr.wr-zone-2 td{background-color:var(--bs-primary-bg-subtle);border-color:var(--bs-primary-border-subtle);}
.wr-table tr.wr-zone-3 td{background-color:var(--bs-warning-bg-subtle);border-color:var(--bs-warning-border-subtle);}
.wr-table tr.wr-zone-4 td{background-color:var(--bs-success-bg-subtle);border-color:var(--bs-success-border-subtle);}
.wr-table tr.wr-zone-5 td{background-color:var(--bs-info-bg-subtle);border-color:var(--bs-info-border-subtle);}
/* 状态写在分区色之后、同权重（tr td 两层）⇒ 稳压分区色：
   当前题目那格实色高亮（答错也不灭），按错那格红闪（450ms 后由 JS 摘掉） */
.wr-table tr td.is-current{background-color:var(--bs-primary);border-color:var(--bs-primary);}
.wr-table tr td.is-current .wr-td-no,
.wr-table tr td.is-current .wr-td-key,
.wr-table tr td.is-current .wr-td-name,
.wr-table tr td.is-current .wr-td-roots,
.wr-table tr td.is-current .wr-td-verse{color:var(--bs-white);}
.wr-table tr td.is-miss{background-color:var(--bs-danger);border-color:var(--bs-danger);}
.wr-table tr td.is-miss .wr-td-no,
.wr-table tr td.is-miss .wr-td-key,
.wr-table tr td.is-miss .wr-td-name,
.wr-table tr td.is-miss .wr-td-roots,
.wr-table tr td.is-miss .wr-td-verse{color:var(--bs-white);}
/* 折叠按钮：文案随 aria-expanded 切换（纯 CSS，不加事件） */
.wr-table-toggle .wr-t-hide{display:none;}
.wr-table-toggle[aria-expanded="true"] .wr-t-show{display:none;}
.wr-table-toggle[aria-expanded="true"] .wr-t-hide{display:inline;}
/* 窄屏：kit 自带横向滚动容器，这里只把最小宽度与字号收一档 */
@media (max-width:575.98px){
    .wr-chart .tk-kb{min-width:44rem;}
    .wr-chart .tk-kb-key{height:5rem;}
    .wr-chart .wr-cap-list{font-size:.46rem;}
    .wr-chart .wr-cap-roots{font-size:.44rem;}
    .wr-glyph{font-size:2.8rem;}
}

/* ==========================================================================
   文章练习（216 测速页「练习这篇」跳转）：隐藏输入框承接输入。
   不能 display:none（无法聚焦、IME 不工作）：固定定位挪出屏幕 + 透明。
   ========================================================================== */
.tp-article-input{position:fixed;left:-9999px;top:0;width:10px;height:1.2rem;opacity:0;border:0;padding:0;background:transparent;}

/* ==========================================================================
   专注模式（打字测速 / 练习两页共用）：仅切换 body 类，隐藏导航 / 身份 / 配置 / 键盘卡，
   只保留成绩、文章、暂停与退出。与浏览器全屏解耦——用户可再自行决定是否全屏。
   ========================================================================== */
body.tp-focus-mode nav.navbar,
body.tp-focus-mode .index_position,
body.tp-focus-mode #tp_user_box,
body.tp-focus-mode #tp_config_card,
body.tp-focus-mode #tp_keyboard_card,
body.tp-focus-mode #td_panel,
body.tp-focus-mode #td_kb_card { display: none !important; }
/* 进入专注后顶部留白收紧，让文章区更靠近视口中心 */
body.tp-focus-mode main.container { padding-top: 1rem; }

/* msg5 优化 UI：专注模式额外隐藏实时数据行与历史卡；关闭逐字提示；成绩弱化 */
body.tp-focus-mode #tp_history { display: none !important; }
.tp-passage.tp-no-hint .tp-unit-hint { display: none !important; }
.tp-passage.tp-no-hint ruby rt { display: none !important; }
.tp-stats-muted { opacity: .4; transition: opacity .2s ease; }

/* 配置区行内分组标签（紧凑版式：标签与控件同行，不再每个分组独占一行大标题） */
.tp-cfg-label {
    font-size: .8125rem;
    font-weight: 600;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}
/* 练习页专注模式：额外隐藏实时数据行与今日练习摘要 */
body.tp-focus-mode #td_today { display: none !important; }
