/* t-20261026-wybz game：顶栏的单元进度条（像多邻国一课的进度条，按当前单元），点一下长出整门课的学习路线。
   左边一枚单元序号章，中间一条进度（填色是那支蓝笔，上沿一道亮线），右边是这个单元拿到的勋章（最多叠三枚）。
   t-20261027-pz69 up：进度条按知识点算——这个单元有几个知识点，条就分几段（段间一道小缝，两端圆头）：
   没练是空槽；练过还没答对是浅一档的蓝加一粒小红点（danger，同路线图上「要再看」的红点）；答对过是实心蓝。
   t-20261027-q08a：学生主动跳过的知识点画满，但是比答对过浅一档的蓝（--up-skipped，介于练过和答对过之间，不加高光、不加小红点），
   和「没碰过」的空槽分得开；单元学完时答对过的转绿，跳过了的仍是这一档蓝，两样一眼分得开。
   学完时答对过的段转绿、序号章换成勾，没练到的段如实空着。第一个没练的段在讲解时会走一点（最多半段，浅蓝）。
   悬停或键盘聚焦这条出浮层，列出知识点和各自的状态。
   动效分级（kaopy.css）：T1 往前走了的那一段鼓一下、亮光扫过一次；答对那一段闪一下绿；T2 勋章飞进来时槽外一圈光；
   T3 单元学完：答对过的段转绿、光从左到右扫过、序号章翻成勾；换到下一个单元：槽不动，序号章从右边滑进来，条整条重画。
   只用 token；减少动态时只画终态。 */

.topbar { --up-w: clamp(220px, 28vw, 380px); }
.unit-progress {
  position: absolute; left: 50%; top: 50%; z-index: 1;
  display: flex; align-items: center; gap: 10px;
  width: var(--up-w); height: 34px; padding: 0 6px 0 4px;
  border: 0; border-radius: var(--r-pill); background: transparent; color: var(--ink);
  font: inherit; cursor: pointer; transform: translate(-50%, -50%);
  transition: background-color 150ms ease;
}
.unit-progress[hidden] { display: none; }
.unit-progress:hover, .unit-progress[aria-expanded="true"] { background: var(--surface-subtle); }
/* 焦点环收在条里：条离视口顶只有几像素，往外画会被视口切掉 */
.unit-progress:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* 阶段名（讲解 / 实战）挪到进度条左边，两样一起居中 */
@media (min-width: 1024px) {
  .topbar:has(> .unit-progress:not([hidden])) .topbar-stage { left: auto; right: calc(50% + var(--up-w) / 2 + 8px); transform: none; }
}
@media (min-width: 560px) and (max-width: 1023px) {
  .topbar { --up-w: clamp(160px, 26vw, 240px); }
  .topbar:has(> .unit-progress:not([hidden])) .topbar-stage { display: none; }
}

/* 序号章：浅蓝底、蓝字；学完换成绿底白勾；练习单元是圆角方块 */
.up-badge { position: relative; flex: none; display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: var(--accent-soft); color: var(--accent-strong); font-size: 12px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.up-badge svg { display: none; width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
.unit-progress.is-practice .up-badge { border-radius: 7px; }
.unit-progress.is-done .up-badge { background: var(--success); color: var(--surface); }
.unit-progress.is-done .up-badge svg { display: block; }
.unit-progress.is-done .up-badge__num { display: none; }

/* 进度条：胖一点、圆头；答对过的段填色上沿一道亮线（像多邻国那条）。没有知识点可数时是一整条（只有一段） */
.unit-progress { --up-tried: color-mix(in srgb, var(--accent) 42%, var(--progress-track)); --up-skipped: color-mix(in srgb, var(--progress-fill) 68%, var(--progress-track)); --up-ring: var(--canvas); }
.unit-progress:hover, .unit-progress[aria-expanded="true"] { --up-ring: var(--surface-subtle); }
.up-track { --n: 1; position: relative; flex: 1; min-width: 0; display: flex; gap: 4px; height: 12px; border-radius: var(--r-pill); }
/* 每一段两端都是完整圆头（半径是条高的一半），段间留 4px 的缝，相邻两段之间是一个明显的 V 形缺口（gap 的值同 workspace-unit-progress.js 的 NORMAL_GAP） */
.up-seg { --w: 0; --i: 0; position: relative; flex: 1 1 0; min-width: 0; border-radius: var(--r-pill); background: var(--progress-track); }
.up-seg__fill {
  position: absolute; inset: 0 auto 0 0; width: calc(var(--w) * 100%); min-width: 0; overflow: hidden;
  border-radius: inherit; background: var(--up-tried);
  transition: width var(--motion-step) var(--ease-out), background-color 200ms ease;
}
.up-seg__fill::before { content: ''; position: absolute; left: 6px; right: 6px; top: 3px; height: 3px; border-radius: var(--r-pill); background: color-mix(in srgb, var(--progress-shine) 38%, transparent); opacity: 0; transition: opacity 200ms ease; }
.up-seg[data-state="got"] .up-seg__fill { background: var(--progress-fill); }
.up-seg[data-state="skipped"] .up-seg__fill { background: var(--up-skipped); }
.up-seg[data-state="got"] .up-seg__fill::before { opacity: 1; }
.unit-progress.is-done .up-seg[data-state="got"] .up-seg__fill { background: var(--success); }
.up-track { --up-got: var(--progress-fill); --up-flat: none; }
.unit-progress.is-done .up-track { --up-got: var(--success); }
/* 学完：没练到的段（包括讲解走的那一点）直接空着，不再播收回去的过渡 */
.unit-progress.is-done .up-seg__fill { transition: background-color 200ms ease; }
/* 练过还没答对：右上角一粒小红点 */
.up-seg__dot {
  position: absolute; right: -1px; top: -5px; width: 8px; height: 8px; border-radius: 50%; background: var(--danger); box-shadow: 0 0 0 2px var(--up-ring);
  opacity: 0; transform: scale(.4); pointer-events: none; transition: opacity 160ms ease, transform 160ms var(--ease-back);
}
.up-seg[data-state="tried"] .up-seg__dot { opacity: 1; transform: none; }
/* 段多（练习单元、窄条）：每段不到 NORMAL_MIN 宽时缝收成 1px、段间小圆角、小红点不画（浮层清单里还在）；
   每段不到 3px 时（平板上的 51 段）去掉缝，连成一整条色块，状态靠色调分（空槽 / 浅蓝 / 跳过了的中蓝 / 实心蓝）。两端仍是圆头 */
.up-track[data-density] { gap: 1px; }
.up-track[data-density] .up-seg { border-radius: 1px; }
.up-track[data-density] .up-seg:first-child { border-top-left-radius: var(--r-pill); border-bottom-left-radius: var(--r-pill); }
.up-track[data-density] .up-seg:last-child { border-top-right-radius: var(--r-pill); border-bottom-right-radius: var(--r-pill); }
.up-track[data-density] .up-seg__dot { display: none; }
.up-track[data-density] .up-seg__fill::before { left: 3px; right: 3px; }
.up-track[data-density="flat"] { gap: 0; background: var(--up-flat); }
/* 连续色块里不画一段段的盒子（段宽不是整像素，各段各自贴像素格会漏出发丝线）：整条槽的底就是按每段状态连成的渐变色块
   （workspace-unit-progress.js flatGradient，相邻同状态的段并成一整块），段本身只留着给读屏和浮层数 */
.up-track[data-density="flat"] .up-seg { visibility: hidden; }
/* 亮光：一道斜着的白光从左扫到填色的头上 */
.up-glint { position: absolute; top: 0; bottom: 0; left: -40%; width: 40%; background: linear-gradient(100deg, transparent, color-mix(in srgb, var(--progress-shine) 70%, transparent), transparent); opacity: 0; }

/* 勋章槽：这个单元拿到的最多叠三枚，后来的压在上面；刚拿到的那枚先空着位置等它飞过来（is-awaiting），
   换单元时上一个单元的勋章往左缩小、淡出，收进路线（is-stowing） */
.up-slot { flex: none; display: flex; align-items: center; min-width: 0; height: 30px; }
.up-medal { position: relative; display: block; width: 26px; height: 26px; }
.up-medal + .up-medal { margin-left: -6px; }
.up-medal .k-medal { --m-gap: var(--up-ring); width: 26px; height: 26px; }
.up-medal.is-awaiting { visibility: hidden; }
.up-slot > .up-medal.is-stowing { animation: up-stow 260ms var(--ease-out) both; }

/* 悬停、键盘聚焦：进度条下面出单元名，下面一个知识点一行（小条的样子、名字、状态）。--tip-x 是它横向的挪法（手机上铺满条宽）。
   悬停只在真有指针悬停的设备上出（触屏点一下以后浏览器会把 :hover 一直留着，浮层收不回去），触屏点一下是打开学习路线 */
.up-tip {
  --tip-x: -50%;
  position: absolute; left: 50%; top: calc(100% + 6px); z-index: 2; display: flex; flex-direction: column; gap: 2px;
  width: max-content; max-width: min(330px, calc(100vw - 32px)); padding: 6px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); box-shadow: var(--shadow-pop);
  color: var(--ink); font-size: var(--fs-caption); font-weight: 400; line-height: 1.5; text-align: left;
  opacity: 0; visibility: hidden; pointer-events: none; transform: translate(var(--tip-x), -4px);
  transition: opacity 120ms ease, transform 140ms ease, visibility 120ms;
}
/* 按钮和浮层之间那一小段空隙也算在里面，指针从条挪到浮层上不会断 */
.up-tip::before { content: ''; position: absolute; left: 0; right: 0; top: -8px; height: 8px; }
.up-tip__head { overflow: hidden; padding: 2px 8px 4px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.up-tip__list { display: flex; flex-direction: column; max-height: min(260px, 50vh); overflow-y: auto; overscroll-behavior: contain; }
/* 放不下要滚时底下一道渐隐，露出来的最后一行淡下去，提示还有；滚到底就没有（workspace-unit-progress.js 切 is-more） */
.up-tip__list.is-more { -webkit-mask-image: linear-gradient(#000 calc(100% - 34px), transparent); mask-image: linear-gradient(#000 calc(100% - 34px), transparent); }
.up-tip__row { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 4px 8px; }
.up-tip__mark { position: relative; width: 22px; height: 8px; border-radius: 4px; background: var(--progress-track); }
.up-tip__mark[data-state="tried"] { background: var(--up-tried); }
.up-tip__mark[data-state="skipped"] { background: var(--up-skipped); }
.up-tip__mark[data-state="got"] { background: var(--progress-fill); }
.up-tip__dot { display: none; position: absolute; right: -2px; top: -3px; width: 7px; height: 7px; border-radius: 50%; background: var(--danger); box-shadow: 0 0 0 2px var(--surface); }
.up-tip__mark[data-state="tried"] .up-tip__dot { display: block; }
.up-tip__word { color: var(--faint); font-size: 12px; white-space: nowrap; }
.unit-progress:focus-visible:not([aria-expanded="true"]) .up-tip { opacity: 1; visibility: visible; pointer-events: auto; transform: translate(var(--tip-x), 0); transition-delay: 300ms; }
@media (hover: hover) {
  .unit-progress:hover:not([aria-expanded="true"]) .up-tip { opacity: 1; visibility: visible; pointer-events: auto; transform: translate(var(--tip-x), 0); transition-delay: 300ms; }
}

/* T1：往前走了的那一段鼓一下，亮光扫过一次；和判分那一笔钩同一刻起、同一刻停（280ms，t-20261026-wybz fixA） */
.up-seg.is-bump .up-seg__fill { animation: up-hop var(--motion-step) var(--ease-back); }
.up-seg.is-bump .up-glint { animation: up-glint var(--motion-step) ease-out both; }
/* 答对的那一段：填色闪一下绿 */
.unit-progress.is-correct .up-seg.is-bump .up-seg__fill { animation: up-hop var(--motion-step) var(--ease-back), up-correct var(--motion-step) ease both; }
/* T2 勋章落进槽里：槽外一圈光；金勋章是金光（落下和这一圈一共 ≈220ms）。落下那一下是盖章的轻按（1.2 → .96 → 1，章面占满整格，放太大会撑出条外），飞来的和就地落下的是同一下，减少动态时不播 */
.unit-progress.is-glow .up-track { animation: up-glow var(--motion-land) ease-out; }
.unit-progress.is-gold-glow .up-track { animation: up-gold-glow var(--motion-land) ease-out; }
.up-medal.is-landing { animation: up-land var(--motion-land) var(--ease-back); }
/* T3 单元学完：答对过的段转绿、光从左到右一段接一段扫过（--n 段，整串 ≈400ms）、序号章翻成勾、外面一圈绿光（≈560ms，和完成卡上的章一起播完） */
.unit-progress.is-finishing .up-seg[data-state="got"] .up-glint { animation: up-glint 280ms ease-out calc(var(--i) * 120ms / var(--n)) both; }
.unit-progress.is-finishing .up-badge { animation: up-badge-flip 320ms var(--ease-back) 100ms both; }
.unit-progress.is-finishing .up-track { animation: up-done-glow 460ms ease-out 100ms; }
/* 换到下一个单元：槽一直在，只换条和序号——上一单元的条整条清掉重画，新的序号章从右边滑进来，勋章收进路线（.is-stowing）。
   槽（.up-track）这一下不变色、不发光、不闪：上一单元收尾的光在换单元之前就停了（fixA） */
.unit-progress.is-rolling .up-seg__fill { transition: none; }
.unit-progress.is-rolling .up-track { animation: none; }
.unit-progress.is-rolling .up-badge { animation: up-roll 260ms var(--ease-out) both; }

/* 飞行中的勋章：从刚选中的那个选项的钩上鼓出来，沿一道弧飞到槽里（workspace-unit-progress.js 用 Web Animations 驱动）。
   鼓出来的那一下长在勋章自己身上：一圈金光往外扩开、六粒金屑迸开（T2 的局部迸发，不另起一层，fixA） */
.up-flyer { position: fixed; left: 0; top: 0; z-index: 310; width: 26px; height: 26px; pointer-events: none; will-change: transform; }
.up-flyer .k-medal { position: relative; width: 26px; height: 26px; filter: drop-shadow(0 2px 4px var(--reward-shadow)); }
.up-flyer__burst { position: absolute; left: 50%; top: 50%; width: 0; height: 0; }
.up-flyer__burst::before {
  content: ''; position: absolute; left: -15px; top: -15px; width: 30px; height: 30px;
  border: 2px solid var(--reward-gold); border-radius: 50%; opacity: 0;
  animation: up-burst-ring 260ms var(--ease-out) both;
}
.up-flyer__burst i {
  position: absolute; left: -1.5px; top: -3px; width: 3px; height: 6px; border-radius: 1px; background: var(--reward-gold); opacity: 0;
  transform: rotate(var(--a)) translateY(-8px);
  animation: up-burst-bit 260ms var(--ease-out) both;
}
.up-flyer__burst i:nth-child(even) { background: var(--reward-gold-deep); }
@keyframes up-burst-ring { 0% { opacity: .95; transform: scale(.4); } 100% { opacity: 0; transform: scale(1.25); } }
@keyframes up-burst-bit { 0% { opacity: 1; transform: rotate(var(--a)) translateY(-6px); } 100% { opacity: 0; transform: rotate(var(--a)) translateY(-19px); } }

@keyframes up-hop { 0% { transform: scaleY(1); } 35% { transform: scaleY(1.3); } 100% { transform: scaleY(1); } }
@keyframes up-glint { 0% { opacity: 0; left: -40%; } 20% { opacity: 1; } 100% { opacity: 0; left: 100%; } }
@keyframes up-correct { 0%, 60% { background-color: var(--success); } 100% { background-color: var(--progress-fill); } }
@keyframes up-glow { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent); } 100% { box-shadow: 0 0 0 8px color-mix(in srgb, var(--accent) 0%, transparent); } }
@keyframes up-gold-glow { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--reward-gold) 70%, transparent); } 100% { box-shadow: 0 0 0 10px color-mix(in srgb, var(--reward-gold) 0%, transparent); } }
@keyframes up-done-glow { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 60%, transparent); } 100% { box-shadow: 0 0 0 12px color-mix(in srgb, var(--success) 0%, transparent); } }
@keyframes up-land { 0% { transform: scale(1.2); } 60% { transform: scale(.96); } 100% { transform: none; } }
@keyframes up-badge-flip { 0% { transform: rotateY(0) scale(1); } 50% { transform: rotateY(90deg) scale(1.2); } 100% { transform: rotateY(0) scale(1); } }
@keyframes up-roll { 0% { opacity: 0; transform: translateX(18px); } 100% { opacity: 1; transform: none; } }
@keyframes up-stow { 0% { opacity: 1; transform: none; } 100% { opacity: 0; transform: translateX(-28px) scale(.3); } }

/* 手机：顶栏放不下，进度条贴在顶栏下沿通栏一条：实心纸底铺满整宽（内容左右留 16px），条下只留很短的一段渐隐。
   中间一栏（对话、页底的卡片）从条的下沿开始，第一条消息不压在条下面；手机上展开的庆祝便签也排进这一摞，
   对话从便签下沿开始（--ca-note-h 由 workspace-celebrate.js 写，便签收起时清掉）。
   勋章整枚留在条里（28px，章面占满整格，不出 34px 高的条），叠成一摞：后拿到的压在上面，每枚外面隔一圈条的底色，只叠住前一枚的边，记号（连对的数字）整枚露着，同宽屏 */
@media (max-width: 559px) {
  .unit-progress { left: 0; right: 0; top: 100%; width: auto; height: 34px; padding: 0 16px; gap: 10px; border-radius: 0; background: var(--canvas); transform: none; }
  .unit-progress::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 6px; background: linear-gradient(var(--canvas), color-mix(in srgb, var(--canvas) 0%, transparent)); pointer-events: none; }
  .unit-progress:hover, .unit-progress[aria-expanded="true"] { background: var(--canvas); --up-ring: var(--canvas); }
  .center { padding-top: var(--ca-note-h); }
  body:has(> .topbar > .unit-progress:not([hidden])) .center { padding-top: calc(34px + var(--ca-note-h)); }
  /* 便签那一条出现、展开、收起时对话跟着滑开（打开页面、换课程时直接到位，不滑） */
  body:has(> .ca-note.is-open) .center { transition: padding-top 220ms var(--ease-out); }
  .up-badge { width: 22px; height: 22px; font-size: 12px; }
  .up-track { height: 10px; }
  .up-seg__fill::before { left: 5px; right: 5px; top: 2px; height: 2.5px; }
  .up-seg__dot { top: -4px; width: 7px; height: 7px; }
  .up-slot { height: 32px; }
  .up-medal, .up-medal .k-medal { width: 28px; height: 28px; }
  .up-medal + .up-medal { margin-left: -7px; }
  /* 浮层铺满条宽（条两边各留 16px），在条下面；悬停、键盘聚焦才出（触屏点一下是打开学习路线） */
  .up-tip { --tip-x: 0px; left: 16px; right: 16px; width: auto; max-width: none; }
  /* 右侧抽屉（背包、旁问）从顶栏下沿铺满：顶栏抬到抽屉上面，挂在它下沿的这一条会压住抽屉的标签行。
     抽屉开着时对话整个被盖住，这一条跟着对话一起让开 */
  body.right-open .unit-progress { visibility: hidden; }
  /* 左侧抽屉（一列圆按钮）也一样：抽屉的关闭键坐在顶栏下沿，这一条让开，不被它压住序号章 */
  body.left-open .unit-progress { visibility: hidden; }
}

@media (prefers-reduced-motion: reduce) {
  .unit-progress, .unit-progress *, .up-tip { animation: none !important; transition: none !important; }
  .center { transition: none !important; }
  .up-flyer { display: none; }
}
