/* t-20261002-ca3a 正反馈：像对答案一样被肯定。
   颜色只用 kaopy.css 的 token；对和完成用绿，行动和两处一次性庆祝用蓝。
   基础样式就是终态：刷新、回看和减少动态（kaopy.css 全局关掉动画）都直接显示它；
   只有挂了 .ca-live 的块才播，--ca-off 是回执到达后已经过去的时间（负数），重画时接着播。 */

.ca-live,
.ca-strip,
.ca-unit-record {
  --ca-off: 0ms;
  --ca-ease: cubic-bezier(.2, .8, .2, 1);
}
/* t-20261007-brxy：弹入用的回弹曲线（略过头再回来） */
:root { --ca-back: cubic-bezier(.34, 1.56, .64, 1); }

/* ---------- 钩 ---------- */
.ca-tick { width: 18px; height: 18px; flex: none; fill: none; stroke: var(--success); stroke-width: 2.25; stroke-linecap: round; stroke-linejoin: round; }
.ca-tick path { stroke-dasharray: 1; stroke-dashoffset: 0; }

/* ---------- 答对一题：判分是一样，同一刻只有它和顶栏进度条两样在动，字最后出现（t-20261026-wybz fixA）：
   0–260ms 选对的那一行转绿、右侧落一笔钩，题卡右上角的大钩跟着盖下（kaopy31.css 的章，这里排时刻）；同一刻进度条跳一格（280ms）
   → 280–380ms「回答正确」和连对签、订正章一起淡入，出现后不再动。不弹、不迸光线；整串不到 400ms（T1 ≤600ms），
   连对、先错再对的勋章在字淡入时从这一笔钩上鼓出来（learning-route.js、workspace-unit-progress.js） ---------- */
.api-question-section .api-question-option.ca-correct {
  border-color: var(--success);
  background: var(--success-soft);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--success);
}
.api-question-section .api-question-option.ca-correct .opt-key { color: var(--success); }
.api-question-section .api-question-option.ca-correct::before { border-color: var(--success); background: var(--success); box-shadow: inset 0 0 0 3.5px var(--surface); }
.api-question-section .api-question-option.ca-correct .ca-tick { align-self: center; }
.ca-live .api-question-option.ca-correct { animation: ca-turn 120ms ease var(--ca-off) both; }
.ca-live .api-question-option.ca-correct .ca-tick path { animation: ca-draw 160ms var(--ca-ease) calc(40ms + var(--ca-off)) both; }
/* 题卡右上角的大钩 / 叉（kaopy31.css）：选项落完钩再盖下，不和选项、进度条叠成三样 */
#chatScroll .ca-live.api-question-section[data-quiz-verdict]::after { animation: quiz-stamp-in 200ms var(--ca-back) calc(60ms + var(--ca-off)) both; }
/* 钩上、订正章上不再迸光线（fixA：粒子收紧，判分行只剩钩和字） */
.api-question-option > .ca-spark,
.ca-fix > .ca-spark,
.ca-chip > .ca-spark { display: none; }
.ca-live .quiz-verdict { animation: ca-appear 100ms linear calc(280ms + var(--ca-off)) both; }

/* 判分行后面的小签：连对、订正。跟着「回答正确」一起淡入（280ms），出现后不动 */
.quiz-verdict .ca-chip,
.quiz-verdict .ca-fix { margin-left: 8px; vertical-align: 1px; }
.quiz-verdict .ca-fix { vertical-align: -6px; }
.ca-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 9px;
  border-radius: var(--r-pill);
  background: var(--success-soft);
  color: var(--success);
  font-size: var(--fs-caption);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* brxy：连对分三档。3–4 绿签；5–9 蓝底白字；10 起深蓝渐变。签只淡入，不弹、不亮圈、不扫光（fixA） */
.ca-chip.is-hot { background: var(--accent); color: var(--on-accent); }
.ca-chip.is-blaze {
  overflow: visible;
  background: linear-gradient(100deg, var(--accent-strong), var(--accent) 55%, var(--accent-strong));
  color: var(--on-accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
.ca-chip.is-hot::after,
.ca-chip.is-blaze::after { content: ''; position: absolute; inset: 0; border-radius: inherit; opacity: 0; pointer-events: none; }
.ca-chip.is-blaze::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--surface) 55%, transparent) 50%, transparent 70%) no-repeat;
  background-size: 250% 100%;
  background-position: 120% 0;
  pointer-events: none;
}


/* 订正（t-20261026-wybz fixA）：原来「✗ 今天 ✓ 订正」一串字要猜，改成一枚绿色的「先错再对」小勋章——和飞进顶栏进度条、
   挂在路线上、错题本里那一枚同一个样子（印章：12 瓣浅扇贝的绿边、淡绿纸面、正中一道 U 形回头的箭头，workspace-medals.js comeback，
   B12 起一起换成印章）。字只留给读屏（workspace.js 给这枚签写了 aria-label），里面那两段字不显示。跟着「回答正确」一起淡入，不动 */
.ca-fix {
  --ca-seal-edge: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9.33 2.05A3.31 3.31 0 0 1 14.67 2.05A3.31 3.31 0 0 1 19.28 4.72A3.31 3.31 0 0 1 21.95 9.33A3.31 3.31 0 0 1 21.95 14.67A3.31 3.31 0 0 1 19.28 19.28A3.31 3.31 0 0 1 14.67 21.95A3.31 3.31 0 0 1 9.33 21.95A3.31 3.31 0 0 1 4.72 19.28A3.31 3.31 0 0 1 2.05 14.67A3.31 3.31 0 0 1 2.05 9.33A3.31 3.31 0 0 1 4.72 4.72A3.31 3.31 0 0 1 9.33 2.05z'/%3E%3C/svg%3E");
  --ca-seal-face: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3C/svg%3E");
  --ca-seal-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 7.4v5.4a4.2 4.2 0 0 0 8.4 0V8.4M12.9 10.9l2.5-2.5 2.5 2.5' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  position: relative;
  display: inline-block;
  width: 22px;
  height: 22px;
  vertical-align: -6px;
  /* 整枚的外形（扇贝边）当遮罩；底色是边的绿，纸面、纸面上的记号各叠一层 */
  background: var(--success);
  -webkit-mask: var(--ca-seal-edge) center / contain no-repeat;
  mask: var(--ca-seal-edge) center / contain no-repeat;
}
.ca-fix > :not(.ca-spark) { display: none; }
.ca-fix::before,
.ca-fix::after { content: ''; position: absolute; inset: 0; }
.ca-fix::before { background: var(--success-soft); -webkit-mask: var(--ca-seal-face) center / contain no-repeat; mask: var(--ca-seal-face) center / contain no-repeat; }
.ca-fix::after { background: color-mix(in srgb, var(--success) 82%, var(--ink)); -webkit-mask: var(--ca-seal-mark) center / contain no-repeat; mask: var(--ca-seal-mark) center / contain no-repeat; }
.ca-live .ca-fix { animation: ca-appear 100ms linear calc(280ms + var(--ca-off)) both; }

/* ---------- brxy 局部迸发：挂在 .ca-live 块里的小粒子，跟 --ca-off 接着播；不拦截点击，读屏不读 ----------
   rays：从中心射出一圈短光线；confetti：向上弹起、翻转、再落下的小纸片。--ca-at 是相对回执的起点 */
.ca-spark { --ca-at: 0ms; position: absolute; left: 50%; top: 50%; z-index: 1; width: 0; height: 0; pointer-events: none; }
.ca-spark i { --ca-d: 0ms; position: absolute; left: -1.5px; top: -4px; width: 3px; height: 8px; border-radius: 2px; background: currentColor; color: var(--accent); opacity: 0; }
.ca-spark i.is-deep { color: var(--accent-strong); }
.ca-spark i.is-light { color: color-mix(in srgb, var(--accent) 45%, var(--accent-soft)); }
.ca-spark.is-success i { color: var(--success); }
.ca-spark.is-success i.is-deep { color: var(--accent); }
.ca-spark.is-rays i {
  --ca-a: 0deg;
  --ca-r: 24px;
  transform: rotate(var(--ca-a)) translateY(calc(var(--ca-r) * -1));
  animation: ca-ray 560ms cubic-bezier(.15, .7, .3, 1) calc(var(--ca-at) + var(--ca-off, 0ms)) both;
}
/* 判分行上的那几把（钩上、订正签尾）收短：判分行整串不到 800ms */
.ca-spark.is-quick i { animation-duration: 320ms; }
.ca-spark.is-confetti i {
  --ca-dx: 0px;
  --ca-dy: -40px;
  --ca-fall: 24px;
  --ca-spin: 360deg;
  left: -2.5px;
  width: 5px;
  border-radius: 1px;
  animation: ca-confetti 1000ms linear calc(var(--ca-at) + var(--ca-d) + var(--ca-off, 0ms)) both;
}

/* ---------- 收起按钮 × ---------- */
.ca-x {
  flex: none;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--faint);
  font: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.ca-x:hover { background: var(--surface-subtle); color: var(--ink); }
.ca-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---------- 全屏粒子层：第一道题彩带、第一个单元礼花。不拦截点击，结束后由脚本移除 ---------- */
.ca-burst {
  position: fixed;
  inset: 0;
  z-index: 300;
  overflow: hidden;
  pointer-events: none;
  contain: strict;
}
.ca-burst .is-deep { color: var(--accent-strong); }
.ca-burst .is-light { color: color-mix(in srgb, var(--accent) 45%, var(--accent-soft)); }

/* 彩带：窄长的卷曲丝带从视口顶部上方飘落，左右摆、边落边翻（第一道题；全部单元完成时跟在烟花后面） */
.ca-ribbon {
  --ca-len: 36px;
  --ca-sway: 24px;
  --ca-r0: 0deg;
  --ca-spin: 0deg;
  position: absolute;
  top: -60px;
  width: 10px;
  height: var(--ca-len);
  color: var(--reward-gold);
  opacity: 0;
  animation: ca-ribbon 1400ms linear both;
}
.ca-ribbon svg { display: block; width: 100%; height: 100%; overflow: visible; }
.ca-ribbon path { fill: none; stroke: currentColor; stroke-width: 4; stroke-linecap: round; vector-effect: non-scaling-stroke; }

/* 礼花：小纸片从左下、右下两角斜向上喷出。外层 i 管横向，内层 b 管纵向与翻转 */
.ca-fireworks i {
  --ca-dx: 30vw;
  position: absolute;
  left: 0;
  bottom: -12px;
  animation: ca-fw-x 2600ms cubic-bezier(.12, .7, .3, 1) both;
}
.ca-fireworks i.is-from-right { left: auto; right: 0; }
.ca-fireworks b {
  --ca-peak: 60vh;
  --ca-w: 7px;
  --ca-h: 10px;
  --ca-r0: 0deg;
  --ca-spin: 360deg;
  display: block;
  width: var(--ca-w);
  height: var(--ca-h);
  border-radius: 1px;
  background: currentColor;
  color: var(--accent);
  opacity: 0;
  animation: ca-fw-y 2600ms linear both;
}

/* brxy 礼炮（之后每个单元）：纸屑从完成记录行的钩上向上弹开、左右散，再落下；起点有一圈冲击环。
   起点 --ca-ox / --ca-oy 由脚本按钩的位置写在层上 */
.ca-pop { --ca-ox: 50vw; --ca-oy: 70vh; }
.ca-pop i {
  --ca-dx: 0px;
  position: absolute;
  left: var(--ca-ox);
  top: var(--ca-oy);
  animation: ca-fw-x 2000ms cubic-bezier(.12, .7, .3, 1) both;
}
.ca-pop b {
  --ca-peak: 200px;
  --ca-fall: 120px;
  --ca-w: 7px;
  --ca-h: 10px;
  --ca-r0: 0deg;
  --ca-spin: 360deg;
  display: block;
  width: var(--ca-w);
  height: var(--ca-h);
  margin: calc(var(--ca-h) / -2) 0 0 calc(var(--ca-w) / -2);
  border-radius: 1px;
  background: currentColor;
  color: var(--accent);
  opacity: 0;
  animation: ca-pop-y 2000ms linear both;
}
.ca-pop b.is-dot { width: 7px; height: 7px; border-radius: 50%; }
.ca-pop .ca-ring { animation-duration: 460ms; }

/* 冲击环：礼炮起点、每发烟花炸开处 */
.ca-ring {
  --ca-size: 200px;
  position: absolute;
  width: var(--ca-size);
  height: var(--ca-size);
  margin: calc(var(--ca-size) / -2) 0 0 calc(var(--ca-size) / -2);
  border: 2px solid currentColor;
  border-radius: 50%;
  color: var(--accent);
  opacity: 0;
  text-decoration: none;
  animation: ca-ring 760ms cubic-bezier(.2, .8, .2, 1) both;
}

/* brxy 烟花（全部单元完成）：一道亮线升空，到点炸成一圈亮点，亮点向外散开、略往下坠、变淡 */
.ca-trail {
  --ca-rise: 60vh;
  position: absolute;
  width: 3px;
  height: 28px;
  margin-left: -1.5px;
  border-radius: 3px;
  background: linear-gradient(to bottom, currentColor, transparent);
  color: var(--accent);
  opacity: 0;
  animation: ca-trail 460ms cubic-bezier(.2, .7, .3, 1) both;
}
.ca-shell { position: absolute; width: 0; height: 0; }
.ca-shell b {
  --ca-dx: 0px;
  --ca-dy: 0px;
  position: absolute;
  left: -3px;
  top: -3px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 6px currentColor;
  color: var(--accent);
  opacity: 0;
  animation: ca-shell 1500ms cubic-bezier(.1, .75, .3, 1) both;
}

/* ---------- 便签：暖纸、胶带、× 收起；浮在对话区右上，不加遮罩，不挡输入框 ---------- */
.ca-note {
  --ca-note-top: 72px;
  --ca-note-right: 24px;
  position: fixed;
  top: var(--ca-note-top);
  right: var(--ca-note-right);
  z-index: 301;
  width: min(300px, calc(100vw - 32px));
  pointer-events: none;
}
.ca-note.is-open {
  padding: 22px 36px 16px 18px;
  border: 1px solid var(--board-border);
  border-radius: var(--r-sm);
  background: var(--board-paper);
  box-shadow: var(--shadow-pop);
  color: var(--ink);
  transform: rotate(-1.5deg);
  pointer-events: auto;
  animation: ca-note-in 280ms var(--ca-ease, cubic-bezier(.2, .8, .2, 1)) both;
}
/* 胶带：顶部居中一条半透明蓝 */
.ca-note.is-open::before {
  content: '';
  position: absolute;
  top: -9px;
  left: 50%;
  width: 84px;
  height: 20px;
  border-left: 1px dashed color-mix(in srgb, var(--accent) 30%, transparent);
  border-right: 1px dashed color-mix(in srgb, var(--accent) 30%, transparent);
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  transform: translateX(-50%) rotate(2deg);
}
.ca-note__x { position: absolute; top: 6px; right: 6px; }
.ca-note__quote { margin: 0; font-family: ui-serif, Georgia, "STKaiti", "KaiTi", serif; font-size: var(--fs-body); line-height: 1.6; }
.ca-note__quote cite { display: block; margin-top: 2px; color: var(--faint); font-family: var(--font-sans); font-size: var(--fs-caption); font-style: normal; text-align: right; }
.ca-note__ours { margin: 10px 0 0; padding-top: 10px; border-top: 1px dashed var(--board-border); font-size: var(--fs-ui); line-height: 1.6; }
/* 窄屏：对话区占满宽度，便签水平居中 */
@media (max-width: 767px) {
  .ca-note { right: calc((100vw - min(300px, calc(100vw - 32px))) / 2); }
}
/* 手机（t-20261026-wybz game）：便签收成顶栏进度条正下方通栏的一条暖纸（紧贴进度条下沿由 workspace-celebrate.js placeNote 算），
   实心纸底，和进度条连成一体，下沿和进度条一样一小段渐隐；不贴胶带、不歪。只露名言那一行；对话从它下沿开始
   （unit-progress.css 的 --ca-note-h），不压住任何一行正文和正在等作答的卡片。
   点这条或右边的小箭头往下展开整张，对话跟着推开同样的高度，再点收回。宽屏没有这枚箭头 */
/* 手机上便签那一条占的高度：workspace-celebrate.js 便签开着时写在 <html> 上，关掉时清掉，回到这里的 0 */
:root { --ca-note-h: 0px; }
.ca-note__more { display: none; }
@media (max-width: 559px) {
  .ca-note { left: 0; right: 0; width: auto; }
  .ca-note.is-open { padding: 9px 76px 10px 16px; border-width: 0; border-radius: 0; box-shadow: none; transform: none; cursor: pointer; animation-name: ca-note-unroll; animation-duration: 220ms; }
  .ca-note.is-open::before { display: none; }
  .ca-note.is-open::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 6px; background: linear-gradient(var(--board-paper), color-mix(in srgb, var(--board-paper) 0%, transparent)); pointer-events: none; }
  .ca-note.is-open:not(.is-expanded) .ca-note__quote { overflow: hidden; font-size: var(--fs-ui); line-height: 1.6; white-space: nowrap; text-overflow: ellipsis; }
  .ca-note.is-open:not(.is-expanded) :is(.ca-note__quote cite, .ca-note__ours) { display: none; }
  .ca-note.is-expanded { padding-bottom: 14px; }
  .ca-note__x { top: 8px; right: 10px; }
  .ca-note__more { display: grid; position: absolute; top: 8px; right: 40px; }
  .ca-note__more svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: transform 160ms ease; }
  .ca-note.is-expanded .ca-note__more svg { transform: rotate(180deg); }
}
@keyframes ca-note-unroll { 0% { clip-path: inset(0 0 100% 0); } 100% { clip-path: inset(0 0 -8px 0); } }

/* ---------- 一组 / 一卷：每题一个方格，依次出现；全对时分数下面画一道绿线 ---------- */
.ca-strip { --ca-step: 60ms; display: flex; flex-wrap: wrap; gap: 4px; margin-top: 10px; }
.ca-cell { --ca-i: 0; width: 14px; height: 14px; border: 1px solid var(--border-strong); border-radius: 3px; background: var(--surface); }
.ca-cell.is-right { border-color: var(--success); background: var(--success); }
.ca-cell.is-wrong { border-color: var(--danger); background: var(--danger-soft); }
.ca-cell.is-pending { border-style: dashed; }
.ca-live .ca-cell { animation: ca-cell-in 260ms var(--ca-back) calc(var(--ca-i) * var(--ca-step) + var(--ca-off)) both; }
/* brxy：全对时方格出完，一道亮光从左到右扫过每个绿格 */
.ca-strip.is-full { --ca-wave: 0ms; }
.ca-live .ca-strip.is-full .ca-cell.is-right {
  animation:
    ca-cell-in 260ms var(--ca-back) calc(var(--ca-i) * var(--ca-step) + var(--ca-off)) both,
    ca-cell-glow 520ms ease-out calc(var(--ca-wave) + var(--ca-i) * 40ms + var(--ca-off)) both;
}

/* brxy 印章：一组全对、整卷全对或客观题得分 ≥ 80% 时，卡片右上角盖一枚蓝色圆章（终态也在，刷新照样有） */
.ca-stamp-host { position: relative; }
.api-quiz-summary.ca-stamp-host { min-height: 92px; padding-right: 96px; }
.api-quiz-paper-card.ca-stamp-host { padding-right: 104px; }
.ca-stamp {
  --ca-after: 400ms;
  position: absolute;
  top: 10px;
  right: 12px;
  display: grid;
  place-content: center;
  width: 72px;
  height: 72px;
  border: 2px solid var(--accent);
  border-radius: 50%;
  box-shadow: inset 0 0 0 3px var(--surface), inset 0 0 0 4px var(--accent);
  color: var(--accent-strong);
  text-align: center;
  transform: rotate(-12deg);
  pointer-events: none;
}
.ca-stamp b { display: block; font-size: 17px; font-weight: 800; line-height: 1.1; letter-spacing: .02em; white-space: nowrap; }
.ca-stamp small { display: block; margin-top: 2px; font-size: 10px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; opacity: .85; }
.ca-stamp.is-long b { font-size: 13px; }
.ca-live .ca-stamp { animation: ca-stamp 460ms cubic-bezier(.3, 1.35, .5, 1) calc(var(--ca-after) + var(--ca-off)) both; }
.ca-stamp > .ca-spark { left: 50%; top: 50%; }
.ca-full-score { --ca-after: 400ms; position: relative; display: inline-block; }
.ca-full-score::after { content: ''; position: absolute; left: 0; right: 0; bottom: -3px; height: 2px; border-radius: 2px; background: var(--success); transform-origin: left; }
.ca-live .ca-full-score::after { animation: ca-strike 280ms var(--ca-ease) calc(var(--ca-after) + var(--ca-off)) both; }
.ca-compare { --ca-after: 400ms; margin: 6px 0 0; color: var(--success); font-size: var(--fs-caption); font-weight: 600; }
.api-quiz-summary p.ca-compare { color: var(--success); font-size: var(--fs-caption); }
.ca-live .ca-compare { animation: ca-appear 160ms ease calc(var(--ca-after) + var(--ca-off)) both; }
.ca-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; color: var(--faint); font-size: var(--fs-caption); }
.ca-legend span { display: inline-flex; align-items: center; gap: 4px; }
.ca-legend i { display: inline-block; width: 10px; height: 10px; border: 1px solid var(--border-strong); border-radius: 2px; }
.ca-legend i.is-right { border-color: var(--success); background: var(--success); }
.ca-legend i.is-wrong { border-color: var(--danger); background: var(--danger-soft); }
.ca-legend i.is-pending { border-style: dashed; }
.api-quiz-paper-card .ca-strip { margin-top: 2px; }

/* ---------- 单元完成：聊天里一行完成记录 ---------- */
.ca-unit-record {
  position: relative;
  margin: 14px 0;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--success);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--surface);
  color: var(--ink);
}
.ca-unit-record__main { display: flex; align-items: flex-start; gap: 10px; }
.ca-unit-record__main .ca-tick { width: 20px; height: 20px; margin-top: 1px; }
.ca-unit-record strong { display: block; font-size: var(--fs-ui); line-height: 1.5; }
.ca-unit-record__meta { display: block; margin-top: 2px; color: var(--muted); font-size: var(--fs-caption); line-height: 1.5; font-variant-numeric: tabular-nums; }
.ca-unit-record__all { display: block; margin-top: 4px; color: var(--muted); font-size: var(--fs-ui); line-height: 1.5; }
.ca-unit-record.ca-live { animation: ca-rise 280ms var(--ca-ease) var(--ca-off) both; }
.ca-unit-record.ca-live .ca-unit-record__main .ca-tick path { animation: ca-draw 260ms var(--ca-ease) calc(120ms + var(--ca-off)) both; }
/* brxy：钩外一圈绿环扩开，钩上迸一圈光线；左边的绿边从上往下描出来 */
.ca-unit-record::before { content: ''; position: absolute; left: -3px; top: -1px; bottom: -1px; width: 3px; background: var(--success); transform-origin: top; transform: scaleY(1); }
.ca-unit-record.ca-live::before { animation: ca-grow-y 360ms var(--ca-ease) var(--ca-off) both; }
/* 先放下（跳过）的单元不是完成：边条用中性色，不用完成绿（t-20261009-f8nf） */
.ca-unit-record.is-skipped { border-left-color: var(--muted); }
.ca-unit-record.is-skipped::before { background: var(--muted); }
.ca-unit-record.ca-live { border-left-color: transparent; }
.ca-unit-record__mark { position: relative; flex: none; display: grid; width: 20px; height: 20px; margin-top: 1px; }
.ca-unit-record__mark::after { content: ''; position: absolute; inset: -6px; border: 2px solid var(--success); border-radius: 50%; opacity: 0; pointer-events: none; }
.ca-unit-record.ca-live .ca-unit-record__mark::after { animation: ca-halo-ring 700ms ease-out calc(260ms + var(--ca-off)) both; }
.ca-unit-record__mark > .ca-spark { left: 50%; top: 50%; }
.ca-unit-record__main .ca-unit-record__mark .ca-tick { margin-top: 0; }

/* 「下一个单元」卡：单元学完那一刻章、进度条、礼炮、桌宠已经排满了，它直接在页底，不再另起一下（fixA）——
   不升起、关卡币不弹、这一格不亮圈（teaching-flow.css .is-arriving 那一串和章、进度条、礼炮同一刻，会叠成三样） */
.next-unit-card.ca-enter,
#nextUnitCard.next-unit-card.is-arriving,
#nextUnitCard.is-arriving .next-unit-card__badge,
#nextUnitCard.is-arriving .unit-track i.is-here { animation: none; }
/* 「开始」从灰变蓝（能点了）这一下也直接到位，不渐变 */
#nextUnitCard.is-arriving .next-unit-card__go { transition: none; }

/* ---------- 关键帧 ---------- */
@keyframes ca-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes ca-appear { from { opacity: 0; } to { opacity: 1; } }
@keyframes ca-rise { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes ca-strike { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes ca-cell-in { from { opacity: 0; transform: scale(.3); } to { opacity: 1; transform: none; } }
/* brxy */
@keyframes ca-pop-in { from { opacity: 0; transform: scale(.55); } to { opacity: 1; transform: none; } }
@keyframes ca-halo-ring { 0% { opacity: .9; transform: scale(.55); } 100% { opacity: 0; transform: scale(1.55); } }
@keyframes ca-grow-y { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes ca-cell-glow {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 60%, transparent); }
  45% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--success) 28%, transparent); transform: translateY(-2px); }
  100% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--success) 0%, transparent); transform: none; }
}
@keyframes ca-stamp {
  0% { opacity: 0; transform: scale(2.2) rotate(-2deg); }
  55% { opacity: 1; transform: scale(.9) rotate(-13deg); }
  100% { opacity: 1; transform: rotate(-12deg); }
}
@keyframes ca-ray {
  0% { opacity: 0; transform: rotate(var(--ca-a)) translateY(-5px) scaleY(.4); }
  18% { opacity: 1; }
  100% { opacity: 0; transform: rotate(var(--ca-a)) translateY(calc(var(--ca-r) * -1)) scaleY(.7); }
}
@keyframes ca-confetti {
  0% { opacity: 0; transform: translate(0, 0) rotate(0deg); animation-timing-function: cubic-bezier(.15, .7, .35, 1); }
  4% { opacity: 1; }
  42% { transform: translate(var(--ca-dx), var(--ca-dy)) rotate(calc(var(--ca-spin) * .5)); animation-timing-function: cubic-bezier(.45, 0, .8, .5); }
  80% { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(var(--ca-dx) * 1.2), calc(var(--ca-dy) + var(--ca-fall) + 28px)) rotate(var(--ca-spin)); }
}
/* 礼炮：先向上冲到 --ca-peak，再加速落到起点以下 --ca-fall */
@keyframes ca-pop-y {
  0% { opacity: 1; transform: translateY(0) rotate(var(--ca-r0)) rotateX(0deg); animation-timing-function: cubic-bezier(.15, .65, .35, 1); }
  34% { transform: translateY(calc(var(--ca-peak) * -1)) rotate(calc(var(--ca-r0) + var(--ca-spin) * .35)) rotateX(240deg); animation-timing-function: cubic-bezier(.45, 0, .8, .55); }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translateY(var(--ca-fall)) rotate(calc(var(--ca-r0) + var(--ca-spin))) rotateX(900deg); }
}
/* 起点不亮：还没轮到它炸开的那几圈（烟花后几发）等待时不先露出一个个空心小圈 */
@keyframes ca-ring { 0% { opacity: 0; transform: scale(.08); border-width: 5px; } 6% { opacity: .85; } 100% { opacity: 0; transform: scale(1); border-width: 1px; } }
@keyframes ca-trail {
  0% { opacity: 0; transform: translateY(var(--ca-rise)); }
  15% { opacity: 1; }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translateY(0); }
}
@keyframes ca-shell {
  0% { opacity: 0; transform: translate(0, 0) scale(1.5); }
  5% { opacity: 1; }
  62% { opacity: 1; transform: translate(calc(var(--ca-dx) * .9), calc(var(--ca-dy) * .9)) scale(1); }
  100% { opacity: 0; transform: translate(var(--ca-dx), calc(var(--ca-dy) + 44px)) scale(.45); }
}
@keyframes ca-turn {
  from { border-color: var(--accent); background-color: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent); }
  to { border-color: var(--success); background-color: var(--success-soft); box-shadow: inset 0 0 0 1px var(--success); }
}
/* 彩带：边落边摆、绕竖轴来回转（rotateY 让丝带时宽时窄，像在翻面） */
@keyframes ca-ribbon {
  0% { opacity: 0; transform: translate3d(0, 0, 0) rotate(var(--ca-r0)) rotateY(0deg); }
  6% { opacity: 1; }
  25% { transform: translate3d(var(--ca-sway), 27vh, 0) rotate(calc(var(--ca-r0) + var(--ca-spin) * .25)) rotateY(55deg); }
  50% { transform: translate3d(calc(var(--ca-sway) * -1), 54vh, 0) rotate(calc(var(--ca-r0) + var(--ca-spin) * .5)) rotateY(-50deg); }
  75% { transform: translate3d(var(--ca-sway), 81vh, 0) rotate(calc(var(--ca-r0) + var(--ca-spin) * .75)) rotateY(55deg); }
  90% { opacity: 1; }
  100% { opacity: 0; transform: translate3d(0, calc(100vh + 80px), 0) rotate(calc(var(--ca-r0) + var(--ca-spin))) rotateY(-30deg); }
}
/* 礼花：横向先快后慢；纵向先冲到最高点（约 1/3 处），再加速落下，边落边翻 */
@keyframes ca-fw-x { from { transform: translateX(0); } to { transform: translateX(var(--ca-dx)); } }
@keyframes ca-fw-y {
  0% { opacity: 1; transform: translateY(0) rotate(var(--ca-r0)) rotateX(0deg); animation-timing-function: cubic-bezier(.15, .65, .35, 1); }
  32% { transform: translateY(calc(var(--ca-peak) * -1)) rotate(calc(var(--ca-r0) + var(--ca-spin) * .35)) rotateX(240deg); animation-timing-function: cubic-bezier(.45, 0, .8, .55); }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translateY(calc(var(--ca-peak) * -.1)) rotate(calc(var(--ca-r0) + var(--ca-spin))) rotateX(900deg); }
}
/* 便签入场：轻轻落下，再回正到 -1.5° */
@keyframes ca-note-in {
  0% { opacity: 0; transform: translateY(-14px) rotate(2deg); }
  65% { opacity: 1; transform: translateY(2px) rotate(-2.5deg); }
  100% { opacity: 1; transform: rotate(-1.5deg); }
}

/* ================= t-20261026-wybz game：动效分级与勋章 =================
   级别与时长见 kaopy.css「动效分级」；这里只是各级的形态。局部效果挂在 .k-fx（跟着 origin 的定位点）上，
   全屏层沿用 .ca-burst。都不拦截点击、读屏不读 */

/* T0：按钮按下回弹（谁要都可以加 .k-press）。用独立的 scale，不覆盖元素自己的 transform 定位 */
.k-press { transition: scale var(--motion-t0) var(--ease-back); }
.k-press:active:not([disabled]) { scale: .94; transition-duration: 80ms; }

.k-fx { position: fixed; z-index: 300; width: 0; height: 0; pointer-events: none; }
.k-fx .ca-spark { left: 0; top: 0; }
/* 全屏层（第一道题的彩带、T3 单元学完的礼炮、T4 的礼花和烟花）只用奖励色：金、深金两种，克制（t-20261026-wybz fixA）。
   不再铺屏幕四边的光、不铺暖色底，页面的白纸和正文不变色 */
.ca-burst,
.ca-burst b,
.ca-burst .ca-ribbon,
.ca-burst .ca-ring,
.ca-burst .ca-trail,
.ca-burst .ca-shell b { color: var(--reward-gold); }
.ca-burst .is-deep,
.ca-burst b.is-deep,
.ca-burst .ca-ring.is-deep { color: var(--reward-gold-deep); }
.ca-burst .is-light,
.ca-burst b.is-light { color: color-mix(in srgb, var(--reward-gold) 70%, var(--reward-gold-deep)); }
/* T2 的纸片三色：蓝、绿、金 */
.ca-spark.is-rich i { color: var(--accent); }
.ca-spark.is-rich i.is-deep { color: var(--success); }
.ca-spark.is-rich i.is-light { color: var(--reward-gold); }
.ca-spark.is-rich.is-confetti i { width: 6px; height: 9px; left: -3px; }
/* T2 的纸片朝外侧扇形散开、竖向压扁（workspace-celebrate.js burstAt 的 fan），不落回 origin、不落到上下的字上，≈800ms 内全部熄灭 */
.k-fx .ca-spark.is-rich.is-confetti i { animation-name: k-confetti; animation-duration: 680ms; animation-timing-function: cubic-bezier(.15, .75, .3, 1); }

/* T0 / T1 / T2：一圈涟漪（T1 圈里一层淡底一闪，第二圈细一些、晚一点；T2 是勋章外面一圈金色描边） */
.k-ripple {
  --k-size: 104px;
  position: absolute; left: 0; top: 0; width: var(--k-size); height: var(--k-size);
  margin: calc(var(--k-size) / -2) 0 0 calc(var(--k-size) / -2);
  border: 3px solid currentColor; border-radius: 50%; color: var(--accent); opacity: 0;
  background: color-mix(in srgb, currentColor 9%, transparent);
  animation: k-ripple var(--motion-t1) var(--ease-out) both;
}
.k-ripple.is-success { color: var(--success); }
.k-ripple.is-gold { color: var(--reward-gold); }
.k-ripple.is-t0 { --k-size: 48px; border-width: 2.5px; background: none; animation-duration: var(--motion-t0); }
.k-ripple.is-t1.is-late { --k-size: 136px; border-width: 1.5px; background: none; animation-delay: 90ms; animation-duration: calc(var(--motion-t1) - 90ms); }
/* T2：只留一圈描边，不铺底色；套在刚鼓出来的勋章外面，只比勋章大一圈（直径同 workspace-celebrate.js T2_RING），
   不扫到旁边签上的字和上面选项的边 */
.k-ripple.is-t2 { --k-size: 56px; border-width: 2.5px; background: none; animation-duration: var(--motion-t2); }

/* 贴边光环（T0 按下、T1 答对、窄屏放不下时的 T2）：贴着 origin 的边缘、跟着它的圆角往外扩 --k-reach，
   只在它四周的空白里，不压在按钮自己的字上，也扫不到上一行正文。T0 的颜色是按钮自己的（脚本写在 .k-fx 上） */
.k-fx.k-fx--halo { color: var(--accent); }
.k-halo {
  --k-reach: 8px;
  position: absolute; inset: 0; border: 2.5px solid currentColor; border-radius: inherit; opacity: 0;
  animation: k-halo var(--motion-t1) var(--ease-out) both;
}
.k-halo.is-success { color: var(--success); }
.k-halo.is-gold { color: var(--reward-gold); }
.k-halo.is-late { border-width: 1.5px; animation-delay: 90ms; animation-duration: calc(var(--motion-t1) - 90ms); }
.k-fx--halo.is-t0 .k-halo { border-width: 2px; animation-duration: var(--motion-t0); }
.k-fx--halo.is-t2 .k-halo { animation-duration: 640ms; }
.k-fx--halo.is-t2 .k-halo.is-late { animation-duration: 560ms; }

/* T3 金勋章（连对 5 / 10 的倍数、满分卷）：只在勋章鼓出来的那一点（workspace-celebrate.js medalAura，挂在 .k-fx 上），
   一团金光、两圈金环（金、深金）、八粒金色四角星往近处迸开，约 0.65 秒；比 T2（勋章自己带的一圈光、六粒金屑）多一团光、一层环，光圈更大。
   不铺底色、不画屏幕四边的光，星点不出勋章四周几十像素。--k-fit 由脚本按离屏幕边的余量写（1 = 整套，手机上勋章靠右时按比例收小） */
.k-aura { --k-fit: 1; }
.k-aura__glow {
  --k-size: calc(150px * var(--k-fit));
  position: absolute; left: 0; top: 0; width: var(--k-size); height: var(--k-size);
  margin: calc(var(--k-size) / -2) 0 0 calc(var(--k-size) / -2); border-radius: 50%; opacity: 0;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--reward-gold) 46%, transparent), color-mix(in srgb, var(--reward-gold) 16%, transparent) 62%, transparent);
  animation: k-aura-glow 560ms var(--ease-out) both;
}
.k-ripple.is-t3 { --k-size: calc(112px * var(--k-fit, 1)); border-width: 2.5px; background: none; animation-duration: 560ms; }
.k-ripple.is-t3.is-late { --k-size: calc(152px * var(--k-fit, 1)); border-width: 1.5px; color: var(--reward-gold-deep); animation-delay: 90ms; animation-duration: 520ms; }
.k-aura__star {
  --ca-size: 10px; --ca-dx: 0px; --ca-dy: -36px; --ca-spin: 90deg;
  position: absolute; left: 0; top: 0; width: var(--ca-size); height: var(--ca-size);
  margin: calc(var(--ca-size) / -2) 0 0 calc(var(--ca-size) / -2); background: var(--reward-gold); opacity: 0;
  clip-path: polygon(50% 0, 64% 36%, 100% 50%, 64% 64%, 50% 100%, 36% 64%, 0 50%, 36% 36%);
  animation: k-aura-star 520ms cubic-bezier(.15, .75, .3, 1) both;
}
.k-aura__star.is-deep { background: var(--reward-gold-deep); }

/* 勋章（印章）：像纸上盖的章——外圈一道 12 瓣浅扇贝的边（workspace-medals.js EDGE，关卡圆点、序号章都是平滑的圆，章靠花边认出来），
   里面一张淡淡带色的纸面，记号刻在正中。蓝、绿两档是 T2（纸面是 --*-soft、边是本色）；金是 T3，也是唯一的实心金面（金面、深金边），
   所以金只给最难得的一档，蓝、绿章不带金边。--m-gap 是叠放、压在路上时章外面隔开下面东西的那一圈底色（边外 1.3 单位），平时没有 */
.k-medal { --m-face: var(--accent-soft); --m-edge: var(--accent); --m-ink: var(--accent-strong); display: block; width: 22px; height: 22px; overflow: visible; }
.k-medal.is-green { --m-face: var(--success-soft); --m-edge: var(--success); --m-ink: color-mix(in srgb, var(--success) 82%, var(--ink)); }
.k-medal.is-gold { --m-face: var(--reward-gold); --m-edge: var(--reward-gold-deep); --m-ink: var(--reward-gold-on); }
.k-medal__edge { fill: var(--m-edge); stroke: var(--m-gap, none); stroke-width: 2.6; stroke-linejoin: round; paint-order: stroke; }
.k-medal__face { fill: var(--m-face); }
.k-medal__mark { fill: none; stroke: var(--m-ink); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.k-medal__mark.is-solid, .k-medal--paper-perfect .k-medal__mark { fill: var(--m-ink); }
.k-medal--paper-perfect .k-medal__mark { stroke-width: 1; }
.k-medal--first-paper .k-medal__mark { stroke-width: 1.6; }
.k-medal__n { fill: var(--m-ink); font-family: var(--font-sans); font-size: 10.6px; font-weight: 700; font-variant-numeric: tabular-nums; }
.k-medal__n.is-long { font-size: 8.8px; letter-spacing: -.05em; }
.k-medal.is-gold .k-medal__n.is-long { font-weight: 800; letter-spacing: -.07em; }

@keyframes k-ripple { 0% { opacity: 1; transform: scale(.25); } 60% { opacity: .7; } 100% { opacity: 0; transform: scale(1); } }
/* 光环先亮着往外走一大半，最后才淡掉（中途那一帧看得清是一圈光） */
@keyframes k-halo { 0% { opacity: .95; inset: 0; } 55% { opacity: .8; } 100% { opacity: 0; inset: calc(var(--k-reach) * -1); } }
@keyframes k-confetti {
  0% { opacity: 0; transform: translate(0, 0) rotate(0deg); }
  8% { opacity: 1; }
  60% { opacity: 1; transform: translate(var(--ca-dx), var(--ca-dy)) rotate(calc(var(--ca-spin) * .6)); }
  100% { opacity: 0; transform: translate(calc(var(--ca-dx) * 1.22), calc(var(--ca-dy) * 1.12)) rotate(var(--ca-spin)); }
}
@keyframes k-aura-glow { 0% { opacity: 0; transform: scale(.3); } 22% { opacity: 1; } 62% { opacity: .7; } 100% { opacity: 0; transform: scale(1); } }
@keyframes k-aura-star {
  0% { opacity: 0; transform: translate(0, 0) scale(0) rotate(0deg); }
  20% { opacity: 1; transform: translate(calc(var(--ca-dx) * .45), calc(var(--ca-dy) * .45)) scale(1.15) rotate(calc(var(--ca-spin) * .3)); }
  60% { opacity: 1; transform: translate(calc(var(--ca-dx) * .85), calc(var(--ca-dy) * .85)) scale(.9) rotate(calc(var(--ca-spin) * .75)); }
  100% { opacity: 0; transform: translate(var(--ca-dx), var(--ca-dy)) scale(.4) rotate(var(--ca-spin)); }
}

/* 减少动态：kaopy.css 已全局关掉动画，这里再写一遍，celebrate.css 单独加载时也成立；基础样式就是终态。
   全屏粒子和局部迸发在减少动态时由脚本直接不生成（这里再兜一层不显示），便签、印章直接出现 */
@media (prefers-reduced-motion: reduce) {
  .ca-live,
  .ca-live *,
  .ca-live *::before,
  .ca-live *::after,
  .ca-live::before,
  .ca-live::after,
  .ca-note { animation: none !important; transition: none !important; }
  .ca-spark,
  .ca-burst,
  .k-fx { display: none !important; }
  .k-press, .k-press:active { transition: none; scale: none; }
}
