  /* ════════════════════════════════════════════════════════════════════════════════════════════
     「추격 구역」 — 기록 막대 위의 깃발 (2026-09-16, 오너 승인). 로직 = game/chaseZone.js ·
     그리기 = game/chaseHud.js · 깃발 그림 = assets/chase/chase_flag.(webp|svg)

     ⚠️ **새 줄을 만들지 않는다 — 이미 있는 기록 막대(`.scoreRecord`) «위»에 꽂는다.**
        샤가 그 막대를 달려 결승선(`.srGoal`)으로 가므로, 깃발을 거기 두면 「내 앞의 누군가」가 말이
        아니라 **자리**로 읽힌다. 점수 줄의 높이(48px)는 보드 크기와 묶인 예산이라 한 픽셀도 안 쓴다.
     ⚠️ **숫자로 거리를 말하지 않는다** — 「N점 남음」은 금지다(ranking_spec §2-③과 같은 이유).
        차이는 막대 위의 «자리»로만 보인다.
     ⚠️ **플레이 중이다 = 「생각 중이면 조용히」 구간이다.** 보드는 한 칸도 안 가리고, 화면 중앙 연출이
        없고, 입력을 막지 않는다(전부 `pointer-events:none`). 크게 움직이는 것은 «뽑는 순간» 하나뿐이고
        그건 점수가 착지한 뒤 = 생각이 끝난 자리다.
     ⚠️ 이 줄(`#hudScore`)의 `::before/::after`는 콤보 아우라의 것이다 — 의사요소를 쓰지 않는다
        (10-game-screen.css `.scoreRecord` 머리 주석의 실사고). 전부 진짜 요소다. */

  /* ── 깃발 표식 ─────────────────────────────────────────────────────────────────────────────
     자리: 막대와 **같은 좌표계**(`left:14px; right:14px`)에서 `--chase-at` 비율 지점.
     ⚠️ **transform으로 자리를 잡지 않는다** — 가운데 맞춤을 `translateX(-50%)`로 하면 아래 꽂기·뽑기
        키프레임이 그 값을 모든 프레임에 들고 가야 한다(이 저장소에서 여섯 번 재발한 덮어쓰기 함정).
        그래서 깃대 중심 보정은 `margin-left`가 맡고, 움직임은 안쪽 `.chaseFlagBody`만 갖는다.
     모든 표식은 scoreTrail의 같은 눈금을 쓴다. 별도 오른쪽 클램프로 상대 위치를 왜곡하지 않는다.
     z-index 3 = 러너 샤(4) 아래: 샤가 지나가며 뽑는다. */
  #game .chaseFlag {
    --chase-at: 1;
    /* 🚨 세 값은 «그림»에서 잰 것이다 — 그림을 바꾸면 다시 재라 (2026-09-16 오너 그림 `rival_run.webp`
       121×192 실측: 발이 캔버스 바닥에 닿고, 몸 중심이 가로 50%).
       ⚠️ 발이 바닥에 안 닿는 그림을 넣으면 선배가 막대 위에 «떠» 보인다.
       ⚠️ 샤(러너 43px)보다 **작게** 둔다 — 주인공은 나다(연출 헌법 §4.1). */
    /* 🚨 **크기는 상자가 아니라 «그림»으로 맞춘다 (오너 지시 2026-09-16 「선배만 현재 샤 크기로 줄여」).**
       상자만 보면 선배(33.3px)가 샤(42.5px)보다 작아 보이는데 **화면에서는 선배가 더 크다** — 샤의
       스프라이트는 256칸 안에서 잉크가 **71%**(12프레임 중앙값 183/256)뿐이고 선배 그림은 **99%**를 채운다.
       실측: 샤의 보이는 키 30.4px vs 선배 33.0px = **선배가 9% 더 컸다.**
       그래서 30.4 ÷ 0.99 ÷ 1.587 = 19.3px. 그림을 바꾸면 **잉크를 다시 재라**(`magick ... -trim`).
       ⚠️ 상자 수치로 「샤보다 작다」고 판단하지 말 것 — 이 저장소가 아이콘에서 이미 한 번 데인 함정이다. */
    --chase-w: 19.3px;            /* 그림 폭 (보이는 키 ≈30.4px = 샤와 같음) */
    --chase-ratio: 1.587;         /* 높이 ÷ 폭 (192 ÷ 121) */
    --chase-pole: .5;             /* 막대 위에 서는 «몸 중심»의 가로 위치 */
    position: absolute; z-index: 3; bottom: 3px;
    left: calc(var(--top-inset) + (100% - 2 * var(--top-inset)) * var(--chase-at));
    width: var(--chase-w); height: calc(var(--chase-w) * var(--chase-ratio));
    margin-left: calc(var(--chase-w) * var(--chase-pole) * -1);
    pointer-events: none;
    transition: left 1.1s cubic-bezier(.22,.9,.24,1);
  }
  #game .hudScore[data-score-scope="stage"] .chaseFlag { display: none; }
  /* 현재 목표 강조 — 복잡한 유적 배경과 비슷한 청록·보라 옷이 묻히는 문제를 크기 변경 없이 푼다.
     톱니형 노랑 판은 만화의 집중 폭발, 왼쪽 위 `!`는 샤가 발견한 순간의 짧은 감탄이다.
     둘 다 처음 한 번만 뿅 하고 정지한다. 상시 점멸·회전은 풀이 집중과 저사양 프레임을 해친다.
     ⚠️ 실제 노드인 이유: `#hudScore` 의사요소는 콤보 아우라가 소유하며, 장식끼리 덮어쓴 전례가 있다.
     ⚠️ 선배의 몸과 별도 형제라 아래 추월 몸짓의 transform을 덮어쓰지 않는다. */
  #game .chaseTargetBurst {
    position: absolute; z-index: 0; left: 50%; bottom: -4px;
    width: 39px; height: 39px; margin-left: -19.5px;
    background: linear-gradient(145deg, #fff4a8 8%, #ffd55f 54%, #efa83b 100%);
    clip-path: polygon(50% 0, 59% 16%, 74% 7%, 77% 24%, 96% 23%, 84% 39%, 100% 50%, 83% 59%, 94% 76%, 76% 76%, 73% 94%, 58% 84%, 49% 100%, 41% 84%, 25% 94%, 24% 76%, 5% 77%, 17% 60%, 0 50%, 17% 40%, 5% 24%, 24% 24%, 26% 6%, 42% 16%);
    filter: drop-shadow(0 1px 1px rgba(42,24,8,.9));
    opacity: .9;
    animation: chaseTargetPop .48s cubic-bezier(.2,.9,.24,1.15) both;
  }
  #game .chaseTargetBang {
    position: absolute; z-index: 2; right: calc(100% + 2px); top: -7px;
    display: grid; place-items: center;
    width: 16px; height: 16px;
    border: 1.5px solid #593014; border-radius: 55% 48% 52% 45%;
    background: #fff7c8;
    box-shadow: 0 1px 2px rgba(28,15,4,.72);
    color: #7b3511; font: 900 13px/1 system-ui, sans-serif;
    transform: rotate(-9deg);
    animation: chaseTargetBangIn .48s cubic-bezier(.2,.9,.24,1.18) .08s both;
  }
  @keyframes chaseTargetPop {
    0%   { opacity: 0; transform: scale(.35) rotate(-10deg); }
    58%  { opacity: .96; transform: scale(1.14) rotate(3deg); }
    100% { opacity: .9; transform: scale(1) rotate(0); }
  }
  @keyframes chaseTargetBangIn {
    0%   { opacity: 0; transform: translate(5px,4px) rotate(-18deg) scale(.4); }
    62%  { opacity: 1; transform: translate(-1px,-1px) rotate(-6deg) scale(1.16); }
    100% { opacity: 1; transform: rotate(-9deg) scale(1); }
  }
  /* 제친 즉시 «현재 목표» 표식만 걷는다. 사람은 기존 규칙대로 길에 남아 추월 결과를 보여 준다. */
  #game .chaseFlag.chasePassed .chaseTargetBurst,
  #game .chaseFlag.chasePassed .chaseTargetBang {
    animation: none; opacity: 0; transform: scale(.55);
    transition: opacity .16s ease, transform .16s ease;
  }
  /* 소개 카드의 기록이 같은 선배 머리 위에 한 번 이어진다. 반복 알림이 아니며 고정 목표 점수만 말한다.
     오른쪽 가장자리에서 잘리지 않도록 말풍선의 오른끝을 몸에 붙인다. transform·opacity만 움직인다. */
  #game .chaseRivalScore {
    position: absolute; z-index: 5; right: -8px; bottom: calc(100% - 9px);
    display: block; width: max-content; max-width: 76px;
    padding: 2px 6px 3px; box-sizing: border-box;
    border: 1px solid rgba(91,50,18,.9); border-radius: 999px;
    background: rgba(255,247,200,.96);
    box-shadow: 0 1px 2px rgba(28,15,4,.62);
    color: #6d3515; font: 400 11px/1.1 'Jua', system-ui, sans-serif;
    white-space: nowrap; opacity: 0; transform-origin: right bottom;
  }
  #game .chaseRivalScore.on { animation: chaseRivalScore 1.24s cubic-bezier(.2,.86,.24,1) both; }
  @keyframes chaseRivalScore {
    0%   { opacity: 0; transform: translateY(3px) scale(.72); }
    16%  { opacity: 1; transform: translateY(0) scale(1.06); }
    28%  { opacity: 1; transform: translateY(0) scale(1); }
    78%  { opacity: 1; transform: translateY(0) scale(1); }
    100% { opacity: 0; transform: translateY(-2px) scale(.97); }
  }
  /* 📜 깃대 «꽂이»는 지웠다 (2026-09-16 그림 교체) — 사람은 땅에 꽂히지 않는다. 발이 막대를 딛는다. */
  #game .chaseFlagBody {
    position: relative; z-index: 1; display: block; width: 100%; height: 100%;
    transform-origin: calc(var(--chase-pole) * 100%) 100%;   /* 발이 막대를 딛는 점이 축 */
    filter: drop-shadow(0 1px 1px rgba(20,12,4,.85));
  }
  #game .chaseFlagBody img { display: block; width: 100%; height: 100%; }

  /* 등장 — 선배가 «앞쪽에서 달려와 자리를 잡는다». 헌법 §4.6: 닿는 순간 가로로 눌리고(②) 되튐이
     줄어든 뒤(③) **이동 없이** 달린다(④). 한 단계 ≥150ms(⑤).
     📜 옛 이름 `chasePlant`(깃발이 내려와 박힘)를 그대로 쓴다 — 클래스 이름만 역사이고 동작은 착지다. */
  #game .chaseFlagBody.chasePlant { animation: chasePlant .72s ease-out backwards; }
  @keyframes chasePlant {
    0%   { opacity: 0; transform: translate(10px,-6px) scaleX(.9) scaleY(1.12); animation-timing-function: ease-in; }
    28%  { opacity: 1; transform: translateY(0)     scaleX(1.2)  scaleY(.82);  animation-timing-function: ease-in-out; }
    52%  { transform: scaleX(.93) scaleY(1.08); animation-timing-function: ease-in-out; }
    76%  { transform: scaleX(1.03) scaleY(.97); animation-timing-function: ease-in-out; }
    100% { transform: scale(1); }
  }

  /* 지나간 사람 — 뒤에 «남는» 상태. 색을 빼고 한 단 작게 두어 «지금 목표»와 절대 안 헷갈리게 한다.
     ⚠️ 크기를 더 줄이지 마라: 이 사람은 내가 방금 이긴 상대라 알아볼 수 있어야 한다. */
  #game .chaseFlag.chasePassed {
    filter: grayscale(.72) brightness(.82);
    opacity: .66;
    /* 🚨 **왼쪽으로 비켜난다 — 안 그러면 샤가 통째로 덮는다.** 제치는 순간 둘은 «같은 점수 자리»에
       서는데, 샤의 스프라이트는 42.5px이고 선배는 19.3px이라 실측에서 선배가 **한 픽셀도 안 보였다**
       (제친 사람이 안 보이면 「제쳤다」가 또 안 읽힌다 — 이 작업이 고치려던 바로 그 결함).
       ⚠️ 이 12px는 «연출»이지 점수가 아니다. 점수가 더 오르면 눈금이 둘을 갈라놓으므로 이 값은
          곧 무의미해진다 — 겹치는 그 짧은 순간만 구해 준다. 더 키우지 마라(자리를 거짓말하게 된다). */
    transform: translate(-12px, 1px) scale(.88);
    transform-origin: 50% 100%;   /* 발이 길에 붙은 채 작아진다 */
    /* 컷이 라인으로 접혀 들어오는 프레임에 이 클래스가 붙는다(game/chaseHud.js `onFold`) — 그때 바로 바뀐다. */
    transition: opacity .38s ease, filter .38s ease, transform .38s ease;
  }
  /* 몸 애니 클래스가 빠진 뒤에도 선배의 패배 자세가 유지된다. 부모의 점수 좌표는 그대로다. */
  #game .chaseFlag.chasePassed .chaseFlagBody {
    transform: translate(-2px,1px) rotate(-7deg) scale(.97,1);
  }
  html[data-motion="reduced"] #game .chaseFlag.chasePassed { transition: none; }

  /* ── 추적 라인 난입 컷 (오너 기획 2026-09-19 — 그림은 fx/chaseOvertakeFx.js, 순서 ①~⑤는 그 파일 머리말) ──
     📜 같은 날 먼저 들어간 첫 구현(`ca9fa841`)은 기획과 17항목 중 3개만 맞았다 — 정지·라인에서 늘어나기·
        접히기·「슈웅!」·원근이 없었고, 샤 그림 경로가 404라 **샤가 한 번도 안 보였다.** 이 블록은 기획서
        순서 그대로 다시 짰다. 캐릭터는 기존 에셋, SVG는 번개 궤적·속도선·먼지·충격 별만 맡는다.
     ⚠️ 성능 계약(check:chase-zone ⑭): 이 컷의 규칙에는 필터류를 쓰지 않는다 — transform·opacity·
        stroke-dashoffset만 움직여 저사양 앱인토스에서도 합성만으로 끝낸다.
     ⚠️ **길이는 `--comic-t` 하나다** (두 번째부터 3.8초 · 런의 첫 추월 4.2초 — 3차 박자 재설계). 오너 「늘려」(2026-09-19)로
        1.8/2.2초가 됐는데, 기획 순서대로 「추월!」이 ⑤번째(0.83초)에 오게 되면서 온전히 읽히는 시간이
        0.67초로 줄었다(실측) — 그 약속(≈1초 이상)을 지키려고 0.4초씩 더 늘렸다.
        들어오는 동작은 절대 시각(아래 delay 값)으로 고정하고, 나가는 동작만 `--comic-t` 끝에 붙인다 —
        그래서 길이를 바꿔도 등장 리듬은 안 변하고 «머무는 구간»만 늘거나 준다.
        «나감»은 fill을 forwards로만 둔다. backwards면 대기 중에도 0% 값을 입혀 «들어옴»을 덮는다.
     🚨 3차(같은 날, 오너 「내용과 상황을 이해하기 전에 사라져 · 일정한 속도감이라 임팩트와 리듬감이 부족」):
        «박자»로 다시 짰다 — 느림과 빠름을 번갈아 두고, 정점 앞뒤에 멈춤을 둔다. 시간표(초):
          0      정지(0.1초) — 패널이 라인 모양으로 붙잡힌다
          .10    패널이 탄성 있게 늘어남(.3초)
          .30    ① 상황 — 선배가 점수표를 달고 «혼자» 달린다(.8초, 차분). 누가 앞인지 읽는 시간
          .95    ② 예비 — 샤가 왼쪽 끝에 나타나 웅크린다(느림)
          1.15   ③ 돌진 — 번개를 끌고 선배까지 한 번에(.2초, 가속) · 바람 소리
          1.35   ④ 충격 — 별·「슈웅!」·따다당·패널 쿵 · 선배가 «놀람» 그림으로
          1.35   ⑤ 슬로모션 — 샤가 1.3배로 선배 옆을 기어가듯 지난다(.4초) · 「!?」·왕관 비뚤
          1.75   ⑥ 정지 — 한 박자 멈춤(.12초)
          1.87   ⑦ 방출 — 샤가 앞으로 튀어 나가고(.2초, 감속) 선배는 밀려 먼지
          2.05   ⑧ 결과 — 「추월!」 쾅 · 2.35~ 코인이 선배 → 샤 · 샤는 계속 달린다
          T−.32  접힘
        이 컷이 도는 동안 판 시계는 멈추고(game/playClockPause.js `chaseOvertakeComic`) 탭하면 건너뛴다.
     🚨 2차(같은 날): 전용 그림 셋(sha_dash · rival_side_run · rival_shock)으로 갈아탔다. 1차의 「만들다 만
        느낌」은 ⑴ 한 장짜리 그림을 비틀어(skew) 때운 자세 ⑵ 뒤쪽 절반이 멈춘 화면 ⑶ 바닥 없는 갈색 판
        ⑷ 약속한 코인이 장면 밖에서 따로 노는 것이었다 — 넷 다 여기서 고쳤다.
     ⚠️ 칸 안 배치는 `--cw`(칸 폭 px, JS가 잰다)의 비율로 쓴다 — 뷰포트 폭과 무관하게 같은 구도가 나온다. */
  .chaseOvertakeComic {
    position: fixed; inset: 0; z-index: 60; display: grid; place-items: center;
    pointer-events: auto; opacity: 0; background: rgba(18,12,5,.24);   /* 탭 = 건너뛰기(JS) · 그동안 보드 입력은 막는다 */
    contain: layout paint style;
    --comic-t: 3.8s;
    --comic-k: 1;   /* 들어오는 동작의 박자 배율 — 아래 `data-easy`만 줄인다(2026-09-20) */
    --cw: 366px;
    --from-x: 0px; --from-y: -300px; --from-sx: .9; --from-sy: .12;   /* 라인을 못 잰 경우의 기본값(화면 위쪽) */
  }
  .chaseOvertakeComic[data-first="1"] { --comic-t: 4.2s; }
  /* 🚨 이미 넘어 본 점수의 선배 = 같은 컷을 «조금 짧게» (오너 승인 2026-09-20 — game/chaseZone.js `chaseRivalEasy`).
     새로 만들지 않고 들어오는 박자 전체를 .75배로 당긴다(순서·모양은 그대로). 도장(한마디)이 2.05→1.54초에 찍히고
     읽히는 시간 ≈1초를 남겨 끝난다: 3.8초 → 3.05초. 진짜 추월(「추월!」+코인)이 한 단계 더 크게 남는다.
     한마디는 「추월!」보다 길어서(최대 10자) 도장을 가운데에 두고 글자를 조금 줄인다. */
  .chaseOvertakeComic[data-easy="1"] { --comic-k: .75; --comic-t: 3.05s; }
  .chaseOvertakeComic[data-easy="1"] .chaseComicStamp { left: 50%; white-space: nowrap; font-size: clamp(18px, 5.4vw, 24px); }
  /* 🚨 **판 끝 자리 = 짧은 판** (오너 결정 2026-09-21 — 컷이 풀이 중에서 클리어 뒤로 옮겨 왔다).
     끝맺음(독립 단위 9개 · 약 6.8초)과 시간을 다투므로 들어오는 박자를 .62배로 당긴다: 도장이 2.05 →
     1.27초에 찍히고 읽히는 ≈1초를 남겨 끝난다(약 2.35초). ⚠️ **위 두 변형(first 4.2s · easy 3.05s) «뒤»에
     둬야 한다** — 같은 특이도라 순서가 승자를 정한다. 판 끝에서는 셋이 한 길이로 모인다(그게 의도다).
     ⚠️ 도장이 「2명 추월!」처럼 길어지면 한마디와 같은 처리를 한다(가운데 · 줄바꿈 없음 · 한 단 작게). */
  .chaseOvertakeComic[data-quick="1"] { --comic-k: .62; --comic-t: 2.35s; }
  .chaseOvertakeComic[data-many="1"] .chaseComicStamp { left: 50%; white-space: nowrap; font-size: clamp(18px, 5.4vw, 24px); }
  .chaseOvertakeComic.on { animation: chaseComicIn calc(.12s * var(--comic-k)) linear both, chaseComicLife .2s linear calc(var(--comic-t) - .2s) forwards; }
  @keyframes chaseComicIn { from { opacity: 0; } to { opacity: 1; } }
  @keyframes chaseComicLife { from { opacity: 1; } to { opacity: 0; } }
  /* ① 0.1초 정지 — 게임 화면의 애니만 멈춘다(입력·시계는 그대로). 푸는 것은 JS가 패널 애니 시작 프레임에. */
  body.chaseHitstop #game * { animation-play-state: paused !important; }

  .chaseComicPanel {
    position: relative; width: min(366px, calc(100vw - 24px)); aspect-ratio: 390 / 220;
    overflow: hidden; isolation: isolate;
    clip-path: polygon(0 13%, 100% 0, 100% 87%, 0 100%);   /* 대각선 만화 칸 */
    background:
      radial-gradient(ellipse 70% 55% at 62% 78%, var(--cm-glow), transparent 70%),
      linear-gradient(165deg, var(--cm-sky-top), var(--cm-sky-bot)), var(--cm-sky-bot);
    transform-origin: 50% 50%;
  }
  /* 라인 사각형(--from-*)에서 출발해 탄성 있게 늘어나고, 끝에 같은 사각형으로 접혀 들어간다. */
  .chaseOvertakeComic.on .chaseComicPanel {
    animation: chaseComicPanelIn calc(.3s * var(--comic-k)) cubic-bezier(.2,.9,.28,1.18) calc(.1s * var(--comic-k)) both,
               chaseComicPanelThump calc(.2s * var(--comic-k)) ease-out calc(1.35s * var(--comic-k)),
               chaseComicPanelOut .32s cubic-bezier(.5,0,.8,.4) calc(var(--comic-t) - .32s) forwards;
  }
  @keyframes chaseComicPanelIn {
    0% { transform: translate(var(--from-x), var(--from-y)) scale(var(--from-sx), var(--from-sy)) rotate(0); }
    62% { transform: translate(0,0) scaleX(1.04) scaleY(.97) rotate(-2deg); }
    100% { transform: translate(0,0) scale(1) rotate(-2deg); }
  }
  /* ④ 충격 — 패널이 한 번 쿵(부피 보존). fill 없음: 끝나면 «들어옴»의 끝 자세로 돌아간다. */
  @keyframes chaseComicPanelThump {
    0% { transform: translate(0,0) scaleX(1.035) scaleY(.965) rotate(-2deg); }
    55% { transform: translate(0,0) scaleX(.99) scaleY(1.01) rotate(-2deg); }
    100% { transform: translate(0,0) scale(1) rotate(-2deg); }
  }
  @keyframes chaseComicPanelOut {
    0% { transform: translate(0,0) scale(1) rotate(-2deg); opacity: 1; }
    85% { opacity: 1; }
    100% { transform: translate(var(--from-x), var(--from-y)) scale(var(--from-sx), var(--from-sy)) rotate(0); opacity: 0; }
  }
  .chaseComicVector { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; overflow: visible; }
  /* 바닥 길 — 「상단 추적 라인이 확대됐다」가 읽히려면 달리는 땅이 있어야 한다(1차엔 없었다). */
  .chaseComicGround { fill: var(--cm-ground); }
  .chaseComicGroundEdge { fill: none; stroke: var(--cm-edge); stroke-width: 4; stroke-linecap: round; }
  .chaseComicPebbles { fill: var(--cm-pebble); }
  /* 🎨 칸의 색 = **제친 그 상대의 카드 색** (오너 2026-09-19 「추월 카드의 배경이 항상 갈색톤이어서 반복감」).
     카드(`#chaseCard[data-tint]`)가 이미 상대마다 5색 중 하나로 고정돼 있다(game/chaseHud.js `tintIndex`,
     별명·점수 해시). 같은 번호를 받아 하늘·땅·땅 가장자리·빛을 그 색 가족으로 칠한다 — 「그 사람을 제쳤다」가
     색으로도 이어지고, 상대가 바뀌면 장면도 바뀐다. 번호는 카드 쪽 주석의 이름을 따른다.
     ⚠️ 하늘은 **중간~어두운 명도**로 둔다 — 금색 「추월!」·크림 점수표·노란 번개가 모두 밝은 쪽이라
        하늘이 밝으면 글자가 묻힌다. 캐릭터는 진한 갈색 외곽선이라 어느 색에서도 선다.
     🔗 유물상자 만화 칸(`.wfChestStage`, styles/app/14-walk-find.css)도 같은 다섯 색을 쓴다 — 색은 여기 한 곳에만 있다. */
  .chaseOvertakeComic, .wfChestStage {
    --cm-sky-top: rgba(98,66,27,.97); --cm-sky-bot: rgba(38,25,11,.98); --cm-glow: rgba(214,150,70,.18);
    --cm-ground: #5a3b1a; --cm-edge: #c99a52; --cm-pebble: #8a6331;                                /* 0 흙길(기본) */
  }
  .chaseOvertakeComic[data-tint="1"], .wfChestStage[data-tint="1"] {
    --cm-sky-top: rgba(46,104,84,.97); --cm-sky-bot: rgba(12,40,32,.98); --cm-glow: rgba(150,220,150,.16);
    --cm-ground: #3c4a22; --cm-edge: #a8c56c; --cm-pebble: #5f7236;                                /* 1 이끼 낀 돌 */
  }
  .chaseOvertakeComic[data-tint="2"], .wfChestStage[data-tint="2"] {
    --cm-sky-top: rgba(104,56,98,.97); --cm-sky-bot: rgba(32,15,30,.98); --cm-glow: rgba(220,150,230,.16);
    --cm-ground: #3f2442; --cm-edge: #c99bd4; --cm-pebble: #634068;                                /* 2 자수정 그늘 */
  }
  .chaseOvertakeComic[data-tint="3"], .wfChestStage[data-tint="3"] {
    --cm-sky-top: rgba(158,78,38,.97); --cm-sky-bot: rgba(58,20,8,.98); --cm-glow: rgba(255,170,90,.2);
    --cm-ground: #5c2c15; --cm-edge: #eaa262; --cm-pebble: #874523;                                /* 3 녹슨 구리(노을) */
  }
  .chaseOvertakeComic[data-tint="4"], .wfChestStage[data-tint="4"] {
    --cm-sky-top: rgba(48,78,128,.97); --cm-sky-bot: rgba(12,20,38,.98); --cm-glow: rgba(150,190,255,.16);
    --cm-ground: #1f2c42; --cm-edge: #92b6e4; --cm-pebble: #36496a;                                /* 4 밤의 사파이어 */
  }

  /* ② 선배 뒤에만 — 속도선(화면 전체를 흔드는 집중선이 아니다)과 발밑 먼지 */
  .chaseComicSpeed { opacity: 0; }
  .chaseComicSpeed path { fill: none; stroke: rgba(255,240,188,.7); stroke-width: 4; stroke-linecap: round; }
  .chaseOvertakeComic.on .chaseComicSpeed { animation: chaseComicSpeed calc(1.05s * var(--comic-k)) ease-out calc(.3s * var(--comic-k)) both; }
  @keyframes chaseComicSpeed {
    0% { opacity: 0; transform: translateX(10px); }
    12%,88% { opacity: 1; transform: translateX(0); }
    100% { opacity: 0; transform: translateX(-12px); }
  }
  .chaseComicDust { fill: rgba(239,215,165,.75); opacity: 0; }
  .chaseOvertakeComic.on .chaseComicDust { animation: chaseComicDust calc(.5s * var(--comic-k)) ease-out calc(.35s * var(--comic-k)) 2 both, chaseComicDust calc(.45s * var(--comic-k)) ease-out calc(1.87s * var(--comic-k)) both; }
  @keyframes chaseComicDust {
    0% { opacity: 0; transform: translate(172px,184px) scale(.4); }
    30% { opacity: .9; transform: translate(166px,184px) scale(1); }
    100% { opacity: 0; transform: translate(140px,180px) scale(1.25); }
  }

  /* ③ 샤의 노란 번개 궤적 — 샤와 같은 박자로 그려지고, 교차 뒤 잠시 남았다가 걷힌다 */
  .chaseComicTrailBack, .chaseComicTrail {
    fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 1;
  }
  .chaseComicTrailBack { stroke: rgba(89,43,10,.82); stroke-width: 13; }
  .chaseComicTrail { stroke: #ffd45c; stroke-width: 6; }
  .chaseOvertakeComic.on :is(.chaseComicTrailBack, .chaseComicTrail) {
    animation: chaseComicTrailDraw calc(.2s * var(--comic-k)) cubic-bezier(.6,0,1,1) calc(1.15s * var(--comic-k)) both, chaseComicFadeOut calc(.25s * var(--comic-k)) ease-in calc(2.3s * var(--comic-k)) forwards;
  }
  @keyframes chaseComicTrailDraw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

  /* ③ 교차점의 별 충격 — 이 애니의 시작 프레임에 따다당이 울린다(JS가 `animationstart`로 잡는다) */
  .chaseComicHit { fill: #fff5a5; stroke: #8a4312; stroke-width: 4; opacity: 0; transform-box: fill-box; transform-origin: center; }
  .chaseOvertakeComic.on .chaseComicHit { animation: chaseComicHit calc(.6s * var(--comic-k)) ease-out calc(1.35s * var(--comic-k)) both; }
  @keyframes chaseComicHit {
    0% { opacity: 0; transform: scale(.18) rotate(-18deg); animation-timing-function: cubic-bezier(.1,.9,.2,1.3); }
    14% { opacity: 1; transform: scale(1.22) rotate(4deg); }
    30% { opacity: 1; transform: scale(.96) rotate(0); }
    80% { opacity: .95; transform: scale(1) rotate(1deg); }       /* 슬로모션 동안 머문다 */
    100% { opacity: 0; transform: scale(1.1) rotate(3deg); }
  }
  /* ③ 효과음 글자 「슈웅!」 */
  .chaseComicSfx {
    position: absolute; z-index: 4; left: 34%; top: 38%;
    color: #fff6bd; font: 900 clamp(20px, 6vw, 26px)/1 'Jua', system-ui, sans-serif; font-style: normal;
    -webkit-text-stroke: 3px #6d2f0d; paint-order: stroke fill; letter-spacing: .02em;
    opacity: 0; transform-origin: 30% 60%;
  }
  .chaseOvertakeComic.on .chaseComicSfx {
    animation: chaseComicSfxIn calc(.16s * var(--comic-k)) cubic-bezier(.18,.9,.24,1.4) calc(1.35s * var(--comic-k)) both, chaseComicFadeOut calc(.16s * var(--comic-k)) ease-in calc(1.95s * var(--comic-k)) forwards;
  }
  @keyframes chaseComicSfxIn {
    from { opacity: 0; transform: rotate(-16deg) scale(.4); }
    to { opacity: 1; transform: rotate(-10deg) scale(1); }
  }

  /* ② 선배 — 겉(달림·밀려남·멀어짐) > 몸(그림 두 장: 옆모습 달리기 → 놀람). 오른쪽으로 달린다. */
  .chaseComicRival {
    position: absolute; z-index: 2; left: 50%; bottom: 9%;
    width: calc(var(--cw) * .196); aspect-ratio: 390 / 618;   /* 달리기 그림의 비율 */
    transform-origin: 50% 100%;
  }
  .chaseComicRivalBody { position: absolute; inset: 0; display: block; }
  .chaseComicRivalBody img { position: absolute; bottom: 0; left: 50%; display: block; height: 100%; width: auto; transform: translateX(-50%); }
  /* 📜 2026-09-21 — 「105% · 42%」 손보정을 걷었다. 선배가 다섯 명이 되면서 컷의 두 장을
     **한 캔버스에 구워** 몸 크기·자리를 파일에서 맞췄다(`tools/bake-chase-rivals.mjs`).
     그래서 달리기와 같은 값이면 된다 — 사람마다 다른 보정값을 CSS에 박을 수 없다. */
  .chaseComicRivalBody .rvShock { opacity: 0; }
  .chaseOvertakeComic.on .chaseComicRival {
    animation: chaseComicRivalRun calc(1.1s * var(--comic-k)) linear calc(.3s * var(--comic-k)) both,
               chaseComicRivalFall calc(1.4s * var(--comic-k)) linear calc(1.87s * var(--comic-k)) forwards;
  }
  @keyframes chaseComicRivalRun {                                     /* 먼저, 차분히 달린다(충격에서 멈칫) */
    0% { transform: translateX(calc(var(--cw) * -.05)); }
    95% { transform: translateX(calc(var(--cw) * .02)); }
    100% { transform: translateX(calc(var(--cw) * .02)); }
  }
  @keyframes chaseComicRivalFall {                                    /* 방출 — 확 밀리고, 그 뒤 천천히 멀어진다 */
    0% { transform: translateX(calc(var(--cw) * .02)) scale(1); animation-timing-function: cubic-bezier(.1,.8,.3,1); }
    15% { transform: translateX(calc(var(--cw) * -.06)) scale(1.02); }
    100% { transform: translateX(calc(var(--cw) * -.09)) scale(.9); }
  }
  .chaseOvertakeComic.on .chaseComicRivalBody .rvRun {
    animation: chaseComicRivalBob calc(.14s * var(--comic-k)) ease-in-out calc(.3s * var(--comic-k)) 8 alternate both, chaseComicRivalSwap .01s linear calc(1.4s * var(--comic-k)) forwards;
  }
  .chaseOvertakeComic.on .chaseComicRivalBody .rvShock { animation: chaseComicRivalSwap .01s linear calc(1.4s * var(--comic-k)) reverse both; }
  @keyframes chaseComicRivalBob { from { transform: translateX(-50%) translateY(0); } to { transform: translateX(-50%) translateY(-5px); } }
  @keyframes chaseComicRivalSwap { from { opacity: 1; } to { opacity: 0; } }

  /* 점수표 — 선배 것은 머리 위 왕관처럼, 샤 것은 머리 위를 함께 달린다 */
  .chaseComicRivalScore, .chaseComicShaScore {
    position: absolute; left: 50%; bottom: 100%;
    min-width: 54px; padding: 3px 7px 4px; box-sizing: border-box;
    border: 2px solid #6f3813; border-radius: 999px; background: #fff1b8;
    color: #683211; font: 900 13px/1 'Jua', system-ui, sans-serif; text-align: center;
    box-shadow: 0 2px 0 rgba(38,20,6,.62); white-space: nowrap;
  }
  .chaseComicRivalScore { transform: translate(-50%,-4px) rotate(-4deg); transform-origin: 50% 100%; }
  .chaseComicShaScore { transform: translate(-50%,2px); background: #ffd85e; color: #54280b; }
  /* ④ 왕관이 비뚤어진다 */
  .chaseOvertakeComic.on .chaseComicRivalScore { animation: chaseComicCrownTilt calc(.3s * var(--comic-k)) cubic-bezier(.2,.9,.3,1.3) calc(1.45s * var(--comic-k)) both; }
  @keyframes chaseComicCrownTilt {
    0% { transform: translate(-50%,-4px) rotate(-4deg); }
    50% { transform: translate(-62%,-9px) rotate(24deg); }
    100% { transform: translate(-60%,-6px) rotate(17deg); }
  }
  .chaseComicSurprise {
    position: absolute; right: -22px; top: -8px; padding: 3px 5px 4px;
    border: 2px solid #71380f; border-radius: 48% 54% 46% 58%;
    background: #fff4b2; color: #7c310c; font: 900 19px/1 system-ui, sans-serif;
    opacity: 0; transform-origin: 0 100%;
  }
  .chaseOvertakeComic.on .chaseComicSurprise {
    animation: chaseComicSurprise calc(.18s * var(--comic-k)) cubic-bezier(.18,.9,.24,1.3) calc(1.45s * var(--comic-k)) both,
               chaseComicFadeOut .14s ease-in calc(var(--comic-t) - .34s) forwards;
  }
  @keyframes chaseComicSurprise {
    0% { opacity: 0; transform: translate(4px,6px) rotate(10deg) scale(.35); }
    100% { opacity: 1; transform: translate(0,-4px) rotate(-8deg) scale(1); }
  }

  /* ③ 샤 — 대시 그림. 화면 밖에서 난입해 선배 옆에서 잠깐 커지고(원근), 지나친 뒤에도 계속 앞으로 달린다.
     ⚠️ 머무는 구간이 «정지»가 되면 1차의 「만들다 만 느낌」이 돌아온다 — 끝까지 조금씩 움직인다. */
  .chaseComicSha {
    position: absolute; z-index: 3; left: 0; bottom: 6%;
    width: calc(var(--cw) * .28); aspect-ratio: 654 / 686; transform-origin: 50% 100%;
  }
  .chaseComicSha img { display: block; width: 100%; height: 100%; }
  /* 달리기 4프레임(`sha_dash_run.webp`, 가로 한 줄 · 몸통 기준 정렬). 크기·자리는 대시 그림에 맞춰 잰 값이다:
     몸 높이 ≈ 대시의 95% · 몸통이 대시와 같은 x(대시 67.1% / 칸 66%) — 바뀌는 순간 샤가 옆으로 안 튄다. */
  .chaseComicSha .shaRun {
    position: absolute; bottom: 0; left: calc(var(--cw) * .012);
    width: calc(var(--cw) * .267); height: calc(var(--cw) * .318);
    background: url('../../assets/chase/sha_dash_run-q75.webp') 0 0 / 400% 100% no-repeat;
    opacity: 0;
  }
  .chaseOvertakeComic.on .chaseComicSha {
    animation: chaseComicShaPass calc(.8s * var(--comic-k)) linear calc(.95s * var(--comic-k)) both,
               chaseComicShaAhead calc(1.4s * var(--comic-k)) linear calc(1.87s * var(--comic-k)) forwards;
  }
  /* 구간마다 이징이 다르다 — 이것이 「일정한 속도감」을 깨는 장치다. */
  @keyframes chaseComicShaPass {
    0% { transform: translateX(calc(var(--cw) * -.36)) scale(1); animation-timing-function: ease-out; }                /* ② 예비: 나타남 */
    25% { transform: translateX(calc(var(--cw) * -.1)) scaleX(.86) scaleY(1.1); animation-timing-function: cubic-bezier(.7,0,1,1); }  /* 웅크림(몸이 보이게) → ③ 가속 */
    50% { transform: translateX(calc(var(--cw) * .38)) scaleX(1.2) scaleY(.88); animation-timing-function: linear; }    /* ④ 충격(늘어남) → ⑤ 슬로모션 */
    100% { transform: translateX(calc(var(--cw) * .45)) scale(1.3); }                                                  /* 선배 옆 — 가장 가깝다 */
  }
  @keyframes chaseComicShaAhead {                                       /* ⑦ 방출(빠른 감속) → ⑧ 순항 */
    0% { transform: translateX(calc(var(--cw) * .45)) scale(1.3); animation-timing-function: cubic-bezier(.1,.8,.3,1); }
    15% { transform: translateX(calc(var(--cw) * .64)) scale(1.06); }
    100% { transform: translateX(calc(var(--cw) * .69)) scale(1); }
  }
  /* ⑧ 순항 — 대시 그림을 달리기 4프레임으로 바꾼다(방출이 끝나는 2.07초). 옛 «대시 한 장 통통 튀기»는 걷었다. */
  .chaseOvertakeComic.on .chaseComicSha img { animation: chaseComicRivalSwap .01s linear calc(2.07s * var(--comic-k)) forwards; }
  .chaseOvertakeComic.on .chaseComicSha .shaRun {
    animation: chaseComicRivalSwap .01s linear calc(2.07s * var(--comic-k)) reverse both, chaseComicShaRunFrames .36s steps(4) calc(2.07s * var(--comic-k)) infinite;
  }
  @keyframes chaseComicShaRunFrames { from { background-position: 0 0; } to { background-position: 133.333% 0; } }   /* 4/3 → 마지막 칸까지 */

  /* ⑤ 약속한 코인 — 선배 점수표에서 샤 점수표로 튀어 간다(개수 = 실제 지급 `coinChasePull`).
     컷이 접힌 뒤 같은 코인이 상단 라인에서 지갑으로 날아간다(game/hudUpdate.js `flyChasePull`). */
  .chaseComicCoin {
    position: absolute; z-index: 6; left: 47%; top: 24%;   /* 「추월!」(z 5) 위로 — 1차 배치에선 그 밑으로 숨었다 */
    width: calc(var(--cw) * .075); aspect-ratio: 1; opacity: 0;
  }
  .chaseOvertakeComic.on .chaseComicCoin {
    animation: chaseComicCoinHop calc(.46s * var(--comic-k)) cubic-bezier(.3,.5,.4,1) calc((2.35s + var(--i) * .14s) * var(--comic-k)) both;
  }
  @keyframes chaseComicCoinHop {
    0% { opacity: 0; transform: translate(0,0) scale(.5); }
    12% { opacity: 1; transform: translate(0,0) scale(1.1); }
    55% { opacity: 1; transform: translate(calc(var(--cw) * .16), calc(var(--cw) * -.03)) scale(1); }
    90% { opacity: 1; transform: translate(calc(var(--cw) * .32), calc(var(--cw) * .05)) scale(.9); }
    100% { opacity: 0; transform: translate(calc(var(--cw) * .33), calc(var(--cw) * .06)) scale(.6); }
  }
  /* 코인이 닿는 순간 샤 점수표가 한 번 튄다(부피 보존). */
  .chaseOvertakeComic.on .chaseComicShaScore { animation: chaseComicTagGet calc(.3s * var(--comic-k)) ease-in-out calc(2.75s * var(--comic-k)) both; }
  @keyframes chaseComicTagGet {
    0%,100% { transform: translate(-50%,2px) scale(1); }
    40% { transform: translate(-50%,2px) scaleX(1.16) scaleY(.88); }
    70% { transform: translate(-50%,2px) scaleX(.95) scaleY(1.05); }
  }

  /* ⑤ 「추월!」 — 두 점수 사이, 위쪽 */
  .chaseComicStamp {
    position: absolute; z-index: 5; left: 66%; top: 7%;
    padding: 5px 13px 6px; border: 3px solid #6d2f0d; border-radius: 8px;
    background: #fff2a6; color: #7b300d;
    font: 900 clamp(22px, 7vw, 30px)/1 'Jua', system-ui, sans-serif;
    letter-spacing: .02em; opacity: 0; box-shadow: 0 3px 0 rgba(48,24,6,.66);
    transform-origin: center;
  }
  .chaseOvertakeComic.on .chaseComicStamp {
    animation: chaseComicStamp calc(.22s * var(--comic-k)) cubic-bezier(.18,.9,.24,1.3) calc(2.05s * var(--comic-k)) both,
               chaseComicFadeOut .14s ease-in calc(var(--comic-t) - .34s) forwards;
  }
  @keyframes chaseComicStamp {
    0% { opacity: 0; transform: translate(-50%,-6px) rotate(-8deg) scale(1.8); }
    100% { opacity: 1; transform: translate(-50%,0) rotate(-5deg) scale(1); }
  }
  @keyframes chaseComicFadeIn { from { opacity: 0; } to { opacity: 1; } }
  @keyframes chaseComicFadeOut { from { opacity: 1; } to { opacity: 0; } }

  /* 감속: 움직임은 빼고 결과 자리로 곧장. 체류(읽을 시간)는 같다. 정지·접힘 신호(패널 애니 이름)는 살린다. */
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicPanel {
    transform: rotate(-2deg);
    animation: chaseComicPanelInReduced .12s ease-out .1s both, chaseComicPanelOutReduced .2s ease-in calc(var(--comic-t) - .32s) forwards;
  }
  @keyframes chaseComicPanelInReduced { from { opacity: 0; } to { opacity: 1; } }
  @keyframes chaseComicPanelOutReduced { from { opacity: 1; } to { opacity: 0; } }
  html[data-motion="reduced"] .chaseOvertakeComic.on :is(.chaseComicSpeed, .chaseComicTrailBack, .chaseComicTrail, .chaseComicDust) { animation: none; opacity: 0; }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicRival { animation: none; transform: translateX(calc(var(--cw) * -.09)) scale(.9); }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicRivalBody img { animation: none; }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicRivalBody .rvRun { opacity: 0; }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicRivalBody .rvShock { opacity: 1; }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicCoin { animation: none; opacity: 0; }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicShaScore { animation: none; }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicRivalScore { animation: none; transform: translate(-60%,-6px) rotate(17deg); }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicSha { animation: none; transform: translateX(calc(var(--cw) * .68)); }
  html[data-motion="reduced"] .chaseOvertakeComic .chaseComicSha img { animation: none; }
  html[data-motion="reduced"] .chaseOvertakeComic .chaseComicSha .shaRun { animation: none; opacity: 0; }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicHit { animation: chaseComicHitReduced .3s ease-out .12s backwards; }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicSfx { animation: none; opacity: 0; }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicStamp {
    animation: chaseComicStampReduced .12s ease-out .16s both, chaseComicFadeOut .14s ease-in calc(var(--comic-t) - .34s) forwards;
  }
  html[data-motion="reduced"] .chaseOvertakeComic.on .chaseComicSurprise {
    animation: chaseComicFadeIn .1s ease-out .12s both, chaseComicFadeOut .14s ease-in calc(var(--comic-t) - .34s) forwards;
  }
  @keyframes chaseComicHitReduced { 0%,100% { opacity: 0; } 30%,72% { opacity: 1; } }
  @keyframes chaseComicStampReduced { from { opacity: 0; transform: translate(-50%,0) rotate(-5deg); } to { opacity: 1; transform: translate(-50%,0) rotate(-5deg); } }

  /* ── 새 상대 알림 카드 — 저절로 떴다 저절로 사라진다 (오너 지시 2026-09-16) ──────────────────
     마크업 = index.html `#chaseCard` · 그리는 곳 = game/chaseHud.js `showChaseCard()`.

     📜 **여기 있던 「이름표(.chaseTag)」는 오너가 물렸다** — 점수 줄 안에 떠서 ⑴ 지갑 HUD(하트·코인)를
        덮었고 ⑵ 11px 두 줄이라 **신호가 너무 약했다**. 자리를 옮기는 것으로는 못 고친다(짧고 넓은
        화면에서는 그 줄 어디에 놓아도 무언가를 덮는다) — 그래서 **줄 밖으로 나와** 딤 위의 카드가 됐다.

     ⚠️ **이 카드는 모달이 아니다** — 버튼이 없고 포커스를 뺏지 않는다. 다만 2026-09-16 오너 지시로
        **스스로 사라지지 않고 사용자가 닫는다**(옛 1.82초 수명 폐기). 닫는 면은 화면 전체이고 그 사실을
        `.ccTap` 한 줄이 말한다 — 버튼이 없으므로 modal_design_system의 버튼 어휘·닫기 수단 규칙이
        적용될 자리가 없고, 대신 «토스트의 규칙»을 따른다: 손을 멈추게 하지 않는다.
     🚨 **숨김은 반드시 `display:none`이다.** 판 시계 멈춤 판정(`game/playClockPause.js`
        BLOCKING_DIALOG_IDS)이 computed display를 본다 — opacity로만 감추면 카드가 사라진 뒤에도
        시계가 **영영 멈춘다.** 그래서 `.show`/`.ccOut`이 붙은 동안만 `display:flex`다.
     ⚠️ **등수·「N점 남음」·차이를 넣지 마라** (ranking_spec §2 · chase_zone_spec §3.2).
        이 카드가 말하는 것은 «누가 있다»와 «그 사람의 기록» 둘뿐이다.
     ⚠️ **절정은 하나다** — 컨페티·화면 플래시·팡파르 금지(연출 헌법 §3). 승리 카드보다 커지면
        「판을 깼다」와 「누가 왔다」가 같은 크기로 읽힌다. */
  #chaseCard {
    position: fixed; inset: 0; z-index: 58;
    display: none;                       /* 🚨 위 주석 — 시계 멈춤 판정이 이 값을 읽는다 */
    align-items: center; justify-content: center;
    /* 위·아래 여백이 곧 «안 덮는다»는 약속이다: 위는 상단 HUD(하트·점수 줄), 아래는 버튼 줄
       (`.controls`, clamp(64,19.2vw,96) 높이 + 안전영역). 카드는 그 사이에만 앉는다. */
    padding:
      calc(env(safe-area-inset-top, 0px) + clamp(118px, 21vh, 176px)) 18px
      calc(env(safe-area-inset-bottom, 0px) + clamp(118px, 19vh, 168px));
    /* 딤 — 초점을 만들 만큼만(§4.1). 가운데가 조금 더 어둡다: 글자가 놓일 자리다.
       ⚠️ `backdrop-filter`(블러)를 쓰지 마라 — 판 전체를 매 프레임 다시 합성한다(러시 프레임 조사 2026-09-15). */
    background: radial-gradient(circle at 50% 50%, rgba(18,12,5,.82) 0%, rgba(18,12,5,.7) 58%, rgba(18,12,5,.6) 100%);
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;          /* 탭을 삼켜 보드가 안 칠해진다(딤의 두 번째 일) */
  }
  #chaseCard.show, #chaseCard.ccOut { display: flex; }

  /* 카드 = «글자 무리»가 주인공이고 판은 그 받침이다(오너: "글자가 예쁘고 귀엽게 밝게").
     그래서 틀은 **얇은 금 실선 + 반투명 석판**까지만 — 깃발 이름표(`.chaseTag`)가 쓰던 어휘를 그대로
     키운 것이라 문법이 둘로 늘지 않는다. 두껍게 두르면 주인공이 글자에서 액자로 넘어간다.
     ⚠️ 받침이 아예 없으면 밝은 보석 타일 위에서 글자 대비가 무너진다(덱의 타일색은 판마다 다르다). */
  /* 🎨 **상대마다 석판 색이 다르다 (오너 지시 2026-09-16 「경쟁자가 계속 바뀌는데 배경색이 한가지라
     변화의 느낌이 약해」).** 색은 «누구인가»의 표시다 — 같은 상대는 카드가 다시 떠도 늘 같은 색이다
     (`game/chaseHud.js` `tintIndex`가 별명·점수를 해시한다. 샤의 줄을 고르는 방식과 같은 얼개다).
     ⚠️ **기둥 ①(Quiet Canvas)을 깨지 않는다** — 다섯 색 전부 «어둡고 채도 낮은 돌»이다. 보석 타일의
        채도를 넘보면 판보다 카드가 세진다. 금색 글자·금 실선은 그대로 두어 카드의 «가족»이 유지된다.
     ⚠️ 색만으로 정보를 나르지 않는다 — 누구인지는 별명이 말하고, 색은 «바뀌었다»만 거든다. */
  #chaseCard .ccCard {
    --cc-glow: rgba(96,62,22,.42);          /* 안쪽 은은한 빛 */
    --cc-top:  rgba(58,35,15,.82);          /* 석판 위 */
    --cc-bot:  rgba(31,19,9,.86);           /* 석판 아래 */
    display: flex; flex-direction: column; align-items: center; gap: 4px;
    width: min(320px, 100%); padding: 18px 16px 20px;
    /* 집중선은 얼굴을 강조하는 카드 내부 장식이다. 둥근 석판 테두리를 뚫고 나오면 잘림 실수처럼
       보이므로 카드 안에서만 그린다. 카드 자체의 그림자·등장·퇴장 transform에는 영향을 주지 않는다. */
    overflow: hidden;
    text-align: center;
    border-radius: 22px;
    border: 1.5px solid rgba(255,226,150,.5);
    background:
      radial-gradient(ellipse at 50% 46%, var(--cc-glow), rgba(60,38,14,0) 72%),
      linear-gradient(180deg, var(--cc-top), var(--cc-bot));
    box-shadow: 0 8px 22px rgba(12,7,2,.5), inset 0 1px 0 rgba(255,246,205,.2);
    pointer-events: none;                /* 탭은 딤이 받는다 — 카드에는 누를 것이 없다 */
  }
  /* 카드 그림 = 정면 선배(`rival_face.webp`, 256 정사각). 깃발이 아니라 «사람»이라 정사각이다. */
  /* 탭 안내 — 이 카드의 «유일한 출구»를 말한다. 주인공(쫓을 사람·그 기록)보다 한 단 아래로 두어
     읽는 순서가 「누가 있다 → 그 기록 → 닫는 법」이 되게 한다.
     ⚠️ 버튼처럼 보이게 만들지 마라 — 닫는 면은 카드가 아니라 화면 전체다(마크업의 짝 주석). */
  /* ── 🏆 걸린 것 (오너 결정 2026-09-17) ──────────────────────────────────────────────────────
     이 한 줄이 카드를 「안내」에서 «도전장»으로 바꾼다 — 걸린 것이 없으면 도전장이 아니다.
     자리는 **기록 «아래», 닫는 줄 «위»**다. 읽는 순서가 「누가 있다 → 그 기록 → 내가 받을 것 →
     그래서 뭘 하지」가 되어 §2의 「인정 → 고조 → 안착」을 그대로 탄다.
     ⚠️ **기록(.ccScore)보다 작다.** 넘을 대상이 여전히 주인공이고 보상은 그 이유다 — 뒤집히면
        「돈 받으러 가는 화면」이 된다(헌법 §2.1 「본 성취가 먼저」와 같은 결).
     ⚠️ 알약 테두리는 **금 실선 한 겹**까지다. 버튼처럼 만들지 마라 — 이 카드에 누를 것은 없고,
        닫는 면은 화면 전체다(modal_design_system의 버튼 어휘를 여기로 끌어오지 않는다).
     ⚠️ `[hidden]`을 반드시 함께 둔다 — `display`를 선언한 요소는 `hidden` 속성만으로 안 사라진다
        (보상이 꺼졌는데 빈 알약이 남으면 「약속만 남은 자리」가 된다). */
  #chaseCard .ccStake {
    display: inline-flex; align-items: center; gap: 5px;
    margin-top: 9px; padding: 4px 13px 5px;
    border-radius: 999px;
    border: 1px solid rgba(255,226,150,.36);
    background: rgba(255,214,128,.1);
  }
  #chaseCard .ccStake[hidden] { display: none; }
  #chaseCard .ccStakeLabel,
  #chaseCard .ccStakeVal {
    font-family: 'Jua', system-ui, sans-serif; font-weight: 400;
    letter-spacing: 0; text-size-adjust: none; -webkit-text-size-adjust: none;
    paint-order: stroke fill; white-space: nowrap;
  }
  #chaseCard .ccStakeLabel {
    font-size: clamp(13px, 3.7vw, 15px); line-height: 1.3;
    color: #ffe7ae; -webkit-text-fill-color: #ffe7ae;
    -webkit-text-stroke: 1px #3b2a12;
    text-shadow: 0 1px 0 rgba(28,17,6,.6);
  }
  #chaseCard .ccStakeCoin { display: block; width: 18px; height: 18px; flex: 0 0 auto; }
  #chaseCard .ccStakeVal {
    font-size: clamp(17px, 4.8vw, 20px); line-height: 1.2;
    color: #ffd95e; -webkit-text-fill-color: #ffd95e;
    -webkit-text-stroke: 1.5px #5c3114;
    text-shadow: 0 1.5px 0 #3b1d0b, 0 0 10px rgba(255,196,84,.45);
  }
  #chaseCard .ccTap {
    margin-top: 8px;
    font-size: 12px; font-weight: 700; letter-spacing: .02em;
    color: rgba(255,236,186,.62);
    text-shadow: 0 1px 2px rgba(0,0,0,.5);
    animation: ccTapBreathe 2.2s ease-in-out infinite;
  }
  /* ⚠️ 아주 여린 숨쉬기다 — 「눌러야 한다」를 말할 만큼만. 이 카드는 절정이 아니다(연출 헌법 §3). */
  @keyframes ccTapBreathe { 0%, 100% { opacity: .62; } 50% { opacity: 1; } }
  html[data-motion="reduced"] #chaseCard .ccTap { animation: none; opacity: .9; }
  /* 0 = 위 기본값(호박). 1~4만 규칙을 둔다 — 기본을 규칙으로 또 적으면 두 곳이 어긋난다. */
  #chaseCard[data-tint="1"] .ccCard { --cc-glow: rgba(38,84,72,.42); --cc-top: rgba(22,54,47,.82); --cc-bot: rgba(11,30,26,.86); }  /* 이끼 낀 돌 */
  #chaseCard[data-tint="2"] .ccCard { --cc-glow: rgba(86,44,74,.42); --cc-top: rgba(52,27,46,.82); --cc-bot: rgba(28,14,25,.86); }  /* 자수정 그늘 */
  #chaseCard[data-tint="3"] .ccCard { --cc-glow: rgba(100,52,26,.44); --cc-top: rgba(66,31,16,.82); --cc-bot: rgba(34,15,8,.86); }  /* 녹슨 구리 */
  #chaseCard[data-tint="4"] .ccCard { --cc-glow: rgba(40,62,96,.42); --cc-top: rgba(24,38,60,.82); --cc-bot: rgba(12,20,33,.86); }  /* 밤의 사파이어 */
  /* 🥇 **상대가 주인공이다 (오너 결정 2026-09-17).** 얼굴 뒤에 «정적» 집중선을 깔아 눈이 먼저
     사람에게 간다 — 카드가 「누가 있다」는 안내에서 「이 사람이 내 앞에 있다」는 선언으로 바뀐다.
     🚨 **`animation`을 붙이지 마라.** ⑴ 이 카드는 절정이 아니고(헌법 §3) ⑵ 이 화면은 «풀이 중»이라
        반복해 도는 빛이 「생각 중이면 조용히」를 정면으로 깬다 ⑶ 오너 요구가 「FPS가 떨어지지 않을
        수준」이다. 한 번 그려지고 마는 그라데이션이라 매 프레임 비용이 **0**이다.
     ⚠️ 채도를 올리지 마라 — 기둥 ①(Quiet Canvas)에서 진한 색은 보석 타일의 몫이다. 이 선들은
        석판 위의 «옅은 금빛»까지이고, 마스크가 가운데(얼굴)와 바깥(카드 가장자리)을 둘 다 비운다. */
  #chaseCard .ccFace {
    position: relative; display: grid; place-items: center;
  }
  #chaseCard .ccVs { display: contents; }
  #chaseCard .ccMe, #chaseCard .ccBolt { display: none; }
  #chaseCard .ccFace::before {
    content: ""; position: absolute; left: 50%; top: 50%;
    width: 205%; aspect-ratio: 1; transform: translate(-50%, -50%);
    background: repeating-conic-gradient(from 7deg at 50% 50%,
      rgba(255,225,152,.32) 0deg 3.4deg, rgba(255,225,152,0) 3.4deg 15deg);
    -webkit-mask-image: radial-gradient(circle at 50% 50%, rgba(0,0,0,0) 25%, #000 45%, rgba(0,0,0,0) 76%);
            mask-image: radial-gradient(circle at 50% 50%, rgba(0,0,0,0) 25%, #000 45%, rgba(0,0,0,0) 76%);
    pointer-events: none;
  }
  #chaseCard .ccFlag {
    /* ⚠️ 2026-09-17에 한 단 키웠다(21vw → 24vw) — 이 카드의 주인공이 «사람»이 되면서 얼굴이
       별명·기록과 자리를 다투면 안 되기 때문이다. 더 키우면 짧은 화면에서 카드가 버튼 줄을 침범한다. */
    display: block; width: clamp(84px, 24vw, 104px); height: auto;
    aspect-ratio: 1;                     /* 오너 그림 256 정사각(`rival_face.webp`) */
    /* ⚠️ 깃발 시절(52~66px)보다 «넓다» — 그때는 세로로 긴 그림이라 폭이 작아도 키가 섰지만,
       정사각 그림은 같은 폭이면 사람이 손톱만 해진다. 이 카드의 주인공은 «쫓을 사람»이다(§4.1). */
    object-fit: contain;
    filter: drop-shadow(0 2px 3px rgba(18,10,2,.62));
  }

  /* ── 글자 세 줄 = 「인정 → 고조 → 안착」 (§2) ────────────────────────────────────────────────
     샤의 한 줄(작고 따뜻하게) → 별명(중간) → **기록(가장 크고 가장 밝게 — 넘을 대상)**.
     ⚠️ 문법은 승리 제목 블록(`03-modal-win-effects.css`의 `#win h2`)과 **같은 집안**이다:
        Jua + 두꺼운 어두운 외곽선 + 따뜻한 밝은 채움 + 부드러운 글로우 + `paint-order: stroke fill`.
        외곽선 색은 승리 제목의 초록(#3f9e3f) 대신 **점수 HUD의 외곽선**(`--score-stroke: #3e512d`,
        10-game-screen.css `#game .hudScore`)을 쓴다 — ⑴ 이 숫자는 «점수»라 점수의 옷이 맞고
        ⑵ 승리 제목의 서명색을 빌리면 「판을 깼다」로 읽힌다. 같은 어두운 초록 계열이라 집안은 같다.
        (그 토큰들은 `#game .hudScore`에 걸려 있어 이 카드에서는 var()로 못 읽는다 — 값을 옮겨 적되
         바꿀 때는 그쪽과 함께 바꿀 것.)
     ⚠️ **CLAUDE.md의 22px 하한은 「3px 외곽선」에 붙은 값이다** — 그보다 작은 글자에는 두께를
        같이 줄인다(비율로 맞춘다). 지금: 기록 44px/3.5px · 별명 23px/2px · 샤의 줄 16px/1.2px.
        한글이 들어가는 두 줄(샤·별명)의 비율이 승리 제목(3.5/20)보다 **훨씬 얇아** 획이 안 뭉갠다. */
  #chaseCard .ccSay,
  #chaseCard .ccName,
  #chaseCard .ccScore {
    display: block; max-width: 100%;
    font-family: 'Jua', system-ui, sans-serif; font-weight: 400;
    letter-spacing: 0; text-size-adjust: none; -webkit-text-size-adjust: none;
    paint-order: stroke fill;
    /* 긴 별명(「부지런한 나침반」)은 **낱말 단위로 줄바꿈**한다 — 말줄임은 이름의 절반을 지운다. */
    white-space: normal; word-break: keep-all; overflow-wrap: anywhere;
  }

  /* 복귀 알림 — 상대가 샤를 스쳐 앞자리로 나가는 한 장면이다. 카드가 닫힌 뒤에도 같은 상대가
     추적선에 남으므로 「알림 → 실제 목표」가 끊기지 않는다. 이동은 transform/opacity만 써 저사양
     웹뷰에서도 레이아웃·페인트를 반복하지 않는다. */
  #chaseCard .ccScoreLabel { display: none; }
  #chaseCard[data-notice="overtaken"] .ccCard { padding-top: 16px; }
  #chaseCard[data-notice="overtaken"] .ccSay {
    order: -2; margin-bottom: 8px;
    font-size: clamp(23px, 6.5vw, 27px); line-height: 1.2;
    color: #ffad72; -webkit-text-fill-color: #ffad72;
    -webkit-text-stroke: 2px #4a1c0c;
  }
  #chaseCard[data-notice="overtaken"] .ccVs {
    order: -1; display: flex; align-items: center; justify-content: center; gap: 3px;
    width: 100%; min-height: 108px; margin: 1px 0 5px;
  }
  #chaseCard[data-notice="overtaken"] .ccMe,
  #chaseCard[data-notice="overtaken"] .ccFace {
    display: grid; place-items: center; position: relative; flex: 0 0 auto;
    width: clamp(82px, 24vw, 104px); aspect-ratio: 1;
  }
  #chaseCard[data-notice="overtaken"] .ccMe { transform: translateX(-8px) scale(.9); }
  #chaseCard[data-notice="overtaken"] .ccMe img {
    width: 100%; height: 100%; object-fit: contain;
    filter: drop-shadow(0 3px 3px rgba(18,10,2,.55));
  }
  #chaseCard[data-notice="overtaken"] .ccFace { z-index: 2; transform: translateX(12px) scale(1.06); }
  #chaseCard[data-notice="overtaken"] .ccBolt {
    display: block; position: relative; z-index: 1; flex: 0 0 auto;
    width: 42px; height: 18px; margin: 0 -15px;
    border-top: 5px solid #ffd05b; border-radius: 50%;
    transform: rotate(-9deg); filter: drop-shadow(0 2px 0 rgba(65,32,8,.8));
  }
  #chaseCard[data-notice="overtaken"] .ccBolt::after {
    content: ""; position: absolute; right: -1px; top: -8px; width: 11px; height: 11px;
    border-top: 5px solid #ffd05b; border-right: 5px solid #ffd05b; transform: rotate(36deg);
  }
  #chaseCard[data-notice="overtaken"] .ccScoreLabel {
    display: block; margin-top: 5px;
    font-size: clamp(14px, 4vw, 16px); font-weight: 800; color: rgba(255,236,186,.78);
  }
  #chaseCard[data-notice="overtaken"] .ccStake { display: none; }
  #chaseCard[data-notice="overtaken"] .ccTap { margin-top: 12px; }
  #chaseCard[data-notice="overtaken"].show:not(.ccOut) .ccFace { animation: ccReturnPass .76s .08s cubic-bezier(.18,.72,.25,1) both; }
  #chaseCard[data-notice="overtaken"].show:not(.ccOut) .ccMe { animation: ccReturnNotice .6s .38s ease-out both; }
  #chaseCard[data-notice="overtaken"].show:not(.ccOut) .ccBolt { animation: ccReturnTrail .54s .2s ease-out both; }
  #chaseCard[data-notice="overtaken"].show:not(.ccOut) .ccFlag { animation: none; }
  @keyframes ccReturnPass {
    0% { opacity: 0; transform: translateX(-112px) scale(.84); }
    55% { opacity: 1; transform: translateX(0) scaleX(1.12) scaleY(.9); }
    76% { transform: translateX(17px) scaleX(1.03) scaleY(1.07); }
    100% { opacity: 1; transform: translateX(12px) scale(1.06); }
  }
  @keyframes ccReturnNotice {
    0% { transform: translateX(-8px) scale(.9) rotate(0); }
    38% { transform: translateX(-14px) scaleX(.94) scaleY(.87) rotate(-5deg); }
    72% { transform: translateX(-7px) scaleX(.88) scaleY(.93) rotate(2deg); }
    100% { transform: translateX(-8px) scale(.9); }
  }
  @keyframes ccReturnTrail {
    0% { opacity: 0; transform: translateX(-24px) scaleX(.25) rotate(-9deg); }
    55% { opacity: 1; transform: translateX(2px) scaleX(1.12) rotate(-9deg); }
    100% { opacity: .9; transform: rotate(-9deg); }
  }
  html[data-motion="reduced"] #chaseCard[data-notice="overtaken"].show:not(.ccOut) :is(.ccMe, .ccFace, .ccBolt) { animation: none; }
  #chaseCard .ccSay:empty, #chaseCard .ccName:empty, #chaseCard .ccScore:empty { display: none; }
  /* 🚨 **글자 위계 (오너 2026-09-19 「폰트 위계와 색깔도 수정해」)** — 읽는 순서:
       ① 샤의 한마디(도전) → ② 상대 이름(누구) → ③ 기록(넘을 목표, 가장 큰 숫자) → ④ 걸린 것 → ⑤ 닫는 법.
     옛 배치는 ①이 16px로 **가장 작고** ②가 23px로 그보다 컸으며, ①②③이 모두 같은 크림·금빛이라
     「도전」이 어디에도 안 읽혔다. 이제 ①은 **경쟁의 색(산호 주황)**으로 따로 서고, ②는 한 단 낮춘
     조용한 크림, ③은 종전 그대로 금색 주인공이다. 금색(기록)과 산호(도전)가 서로 다른 뜻을 맡는다.
     제친 직후의 짧은 한마디(「다음 상대는?」, `data-say="pass"`)는 한 단 더 크게 — 도전장의 제목이다.
     외곽선 두께는 글자 크기에 비례(22px 이하 ≈ 크기의 8%, 아래 ⚠️ 문단의 규칙). */
  #chaseCard .ccSay {
    font-size: clamp(18px, 5.2vw, 20px); line-height: 1.3;
    color: #ff9a62; -webkit-text-fill-color: #ff9a62;
    -webkit-text-stroke: 1.6px #4a1c0c;
    text-shadow: 0 2px 0 #2e1206, 0 0 10px rgba(255,128,72,.38);
  }
  #chaseCard[data-say="pass"] .ccSay {
    font-size: clamp(24px, 7vw, 27px); line-height: 1.2;
    -webkit-text-stroke: 2.2px #4a1c0c;
    text-shadow: 0 2.5px 0 #2e1206, 0 0 14px rgba(255,128,72,.45);
  }
  #chaseCard .ccName {
    margin-top: 3px;
    font-size: clamp(16px, 4.6vw, 18px); line-height: 1.26;
    /* 🚨 외곽선을 **깃발 그림과 같은 진한 갈색**으로 쓴다 (오너 2026-09-16: 「예쁘고 귀엽게 밝게」).
       초록 외곽선(승리 제목의 서명색)을 쓰면 따뜻한 카드 위에서 글자가 초록빛으로 «옅게» 읽혔다 —
       실측 화면에서 확인. 깃발·코인이 이미 갈색 외곽선이라 같은 가족으로 묶인다. */
    color: #f1dfbd; -webkit-text-fill-color: #f1dfbd;   /* 조용한 크림 — 도전(산호)·기록(금) 사이의 받침 */
    -webkit-text-stroke: 1.4px #5c3114;
    text-shadow: 0 1.5px 0 #3b1d0b;
  }
  #chaseCard .ccScore {
    margin-top: 4px;
    font-size: clamp(34px, 9.8vw, 44px); line-height: 1.2;
    /* 🚨 **`background-clip: text`(승리 제목의 금빛 그라데이션)를 여기 그대로 베끼면 색이 뒤집힌다.**
       크롬은 그 배경을 **외곽선까지 포함한 글자 모양**에 물리는데, 채움이 투명하면 그 위에 외곽선이
       덧칠돼 «가운데가 초록, 테두리만 금색»이 된다(실측 38px/3.5px: 금색이 1.75px 띠로만 남았다).
       승리 제목은 20px라 그 결과가 곧 그 블록의 «초록 서명»이지만, 여기서는 **기록이 금색이어야** 한다
       (점수 HUD와 같은 뜻의 숫자다). 그래서 그라데이션 대신 **밝은 금색 단색 + 초록 외곽선**을 쓴다 —
       `paint-order: stroke fill`이 채움을 외곽선 위에 얹어 금색이 주인공이 된다. */
    color: #ffd95e; -webkit-text-fill-color: #ffd95e;
    -webkit-text-stroke: 3.4px #5c3114;
    text-shadow: 0 3px 0 #3b1d0b, 0 5px 11px rgba(40,18,4,.45), 0 0 20px rgba(255,196,84,.6);
  }

  /* ── 수명 — 나타남·머묾·사라짐이 **한 애니**다 (길이는 CSS만 안다, fx/animClass.js) ────────────
     1.82초 = 7% 스며듦(127ms) + 머묾 + 220ms 퇴장. 기록이 다 도착한 뒤(≈1.06s)에도 0.5초 이상
     가만히 있어 읽힌다(§7 「최종 결과 확인 최소 1초」). */
  /* ⚠️ **길이는 «체류»가 정한다** (오너 2026-09-16: *"모달이 너무 짧게 닫혀"*). 1.82s → 2.9s.
     등장·퇴장 시간은 그대로 두고 가운데 머무는 구간만 늘렸다(§4.6-5 「머무는 시간이 곧 쫀득함」).
     🔑 그동안 판 시계가 멈추므로(playClockPause의 `chaseCard`) 길어져도 플레이어가 시간을 잃지 않는다.
     길이를 바꾸면 `game/chaseHud.js`는 안 고쳐도 된다 — 거기서는 `playClass`가 끝을 «듣는다». */
  /* 🚨 **카드는 스스로 사라지지 않는다 — 사용자가 닫는다 (오너 지시 2026-09-16).**
     옛 `chaseCardLife 2.9s`는 «등장 + 체류 + 퇴장»을 한 애니로 갖고 있었고, 그 끝이 곧 자동 닫힘이었다
     (`playClass`의 `onDone`). 지금은 **등장만** 한다 — 남는 일은 탭이 한다.
     ⚠️ `forwards`가 필수다: 끝 상태(불투명)로 «머물러야» 카드가 남는다.
     ⚠️ 판 시계는 이 카드가 떠 있는 동안 멈춘다(`game/playClockPause.js`의 목록) — 수동으로 바뀌어도
        **시간을 물리지 않는다.** 그 결합이 이 변경을 안전하게 만든 전제다.
     📜 되살리려면 git 이력에 `chaseCardLife`가 있다. */
  #chaseCard.show { animation: chaseCardIn .13s linear forwards; }
  @keyframes chaseCardIn {
    0%   { opacity: 0; }
    100% { opacity: 1; }
  }
  /* 🏹 **퇴장 = 화살 (오너 지시 2026-09-16 「용수철처럼 등장해서 화살처럼 사라지는건 어때?」).**
     옛 퇴장은 `opacity`만 0으로 갔다 — 방향이 없어 «사라졌다»로만 읽혔다. 화살은 ⑴ 잠깐 뒤로 당겼다가
     ⑵ 쏘아 나가고 ⑶ 그 방향으로 «늘어난다»(§4.6-2 「빠를 때 늘어난다」).
     ⚠️ **`transform`과 `opacity`만 쓴다** — 오너 요구가 「FPS가 떨어지지 않을 수준」이다. 이 둘은
        합성기가 맡으므로 레이아웃·페인트가 없다. 여기에 `filter`·`box-shadow`를 얹으면 그 약속이 깨진다
        (클리어 개화에서 이미 데인 축이다).
     ⚠️ 딤은 카드보다 «늦게» 걷힌다(.22s vs .16s) — 카드가 먼저 떠나야 「쏘아 나갔다」로 읽힌다. */
  #chaseCard.ccOut { animation: chaseCardOut .22s ease-in forwards; }
  @keyframes chaseCardOut { 0% { opacity: 1; } 55% { opacity: .55; } 100% { opacity: 0; } }
  #chaseCard.ccOut .ccCard { animation: ccCardShoot .22s cubic-bezier(.5,0,.85,.3) forwards; }
  @keyframes ccCardShoot {
    0%   { transform: translate(0,0) scaleX(1) scaleY(1); opacity: 1; animation-timing-function: ease-out; }
    18%  { transform: translate(0,6px) scaleX(1.06) scaleY(.92); opacity: 1; animation-timing-function: ease-in; }  /* 당김 */
    100% { transform: translate(34px,-120px) scaleX(.72) scaleY(1.34); opacity: 0; }                                /* 쏘아 나감 */
  }
  html[data-motion="reduced"] #chaseCard.ccOut .ccCard { animation: none; }

  /* 착지 — §4.6 부피 보존 스쿼시&스트레치. 올라오며 세로로 늘어나고(①②), 닿는 순간 가로로 눌리고,
     되튐이 두 단으로 줄어든 뒤(③) **이동 없이** 선다(④). 한 단 ≥124ms(⑤ — 진폭 말고 체류).
     ⚠️ 자식들의 등장은 `.show`에 «걸어» 둔다 — 그래야 `.ccOut`으로 갈아탈 때 다시 돌지 않는다
        (display:none ↔ flex는 애니를 재시작시킨다). */
  /* ⚠️ **`:not(.ccOut)`가 필수다 — 특이도 동점 함정.** 퇴장(`#chaseCard.ccOut .ccCard`)과 이 규칙은
     특이도가 «같아서» 순서가 승부를 가른다. 착지가 뒤에 있으면 화살 퇴장이 **한 프레임도 안 걸린다**
     (실측: 퇴장 중간 프레임의 transform이 `none`이었다). 순서에 기대지 말고 뜻으로 가른다 —
     «들어오는 중»과 «나가는 중»은 배타적이다. 이 저장소가 별 등급에서 이미 같은 동점에 당했다. */
  #chaseCard.show:not(.ccOut) .ccCard { animation: ccCardLand .62s backwards; }
  @keyframes ccCardLand {
    0%   { opacity: 0; transform: translateY(18px) scaleX(.86) scaleY(1.16); animation-timing-function: ease-out; }
    28%  { opacity: 1; transform: translateY(0)    scaleX(1.13) scaleY(.88); animation-timing-function: ease-in-out; }
    56%  { transform: scaleX(.96) scaleY(1.05); animation-timing-function: ease-in-out; }
    80%  { transform: scaleX(1.02) scaleY(.985); animation-timing-function: ease-in-out; }
    100% { transform: scale(1); }
  }
  /* 내용은 틀과 **따로** 도착한다(§7 「이전 요소가 읽힌 뒤 다음을 시작한다」) — 깃발 → 샤의 줄 →
     별명 → 기록 순으로 한 박자씩. 마지막(기록)이 가장 크게 튄다. */
  #chaseCard.show:not(.ccOut) .ccFlag  { animation: ccPopSoft .44s .10s backwards; }
  #chaseCard.show:not(.ccOut) .ccSay   { animation: ccPopSoft .44s .22s backwards; }
  #chaseCard.show:not(.ccOut) .ccName  { animation: ccPop     .5s  .34s backwards; }
  #chaseCard.show:not(.ccOut) .ccScore { animation: ccPopBig  .58s .48s backwards; }
  @keyframes ccPopSoft {
    0%   { opacity: 0; transform: translateY(6px) scaleX(.94) scaleY(1.06); animation-timing-function: ease-out; }
    38%  { opacity: 1; transform: translateY(0)   scaleX(1.05) scaleY(.95); animation-timing-function: ease-in-out; }
    72%  { transform: scaleX(.99) scaleY(1.01); animation-timing-function: ease-in-out; }
    100% { transform: scale(1); }
  }
  @keyframes ccPop {
    0%   { opacity: 0; transform: translateY(9px) scaleX(.9)  scaleY(1.1);  animation-timing-function: ease-out; }
    34%  { opacity: 1; transform: translateY(0)   scaleX(1.09) scaleY(.92); animation-timing-function: ease-in-out; }
    64%  { transform: scaleX(.98) scaleY(1.03); animation-timing-function: ease-in-out; }
    100% { transform: scale(1); }
  }
  @keyframes ccPopBig {
    0%   { opacity: 0; transform: translateY(12px) scaleX(.82) scaleY(1.2);  animation-timing-function: ease-out; }
    28%  { opacity: 1; transform: translateY(0)    scaleX(1.16) scaleY(.86); animation-timing-function: ease-in-out; }
    56%  { transform: scaleX(.96) scaleY(1.045); animation-timing-function: ease-in-out; }
    80%  { transform: scaleX(1.02) scaleY(.985); animation-timing-function: ease-in-out; }
    100% { transform: scale(1); }
  }

  /* ── 감속 모드 (헌법 §8) — 이동·기울임·낙하는 걷고 **뜻은 남긴다**: 150ms 스며듦 + 밝기 하이라이트. ── */
  html[data-motion="reduced"] #game .chaseFlag { transition: none; }
  html[data-motion="reduced"] #game .chaseTargetBurst,
  html[data-motion="reduced"] #game .chaseTargetBang { animation: none; }
  html[data-motion="reduced"] #game .chaseFlagBody.chasePlant { animation: chaseFadeIn .15s ease-out backwards; }
  html[data-motion="reduced"] #game .chaseRivalScore.on { animation: chaseRivalScoreReduced 1.1s linear both; }
  @keyframes chaseFadeIn  { 0% { opacity: 0; filter: brightness(2); } 100% { opacity: 1; filter: brightness(1); } }
  @keyframes chaseFadeOut { 0% { opacity: 1; filter: brightness(2.2); } 100% { opacity: 0; filter: brightness(1); } }
  @keyframes chaseRivalScoreReduced { 0%,82% { opacity: 1; } 100% { opacity: 0; } }
  /* 알림 카드 — **총 시간은 그대로 1.82초**(수명 애니를 안 건드린다). 걷는 것은 «움직임»뿐이다:
     낙하·스쿼시를 빼고 스며듦만 남기되, 도착 순서(깃발 → 샤 → 별명 → 기록)는 지킨다 —
     그게 「인정 → 고조 → 안착」을 전하는 장치라 이동이 아니라 **뜻**이다(§4.5 감속 규칙).
     ⚠️ `animation-name`/`-duration`만 덮어쓴다 — 위 규칙의 **딜레이가 그대로 살아** 순서가 보존된다. */
  html[data-motion="reduced"] #chaseCard.show .ccCard,
  html[data-motion="reduced"] #chaseCard.show .ccFlag,
  html[data-motion="reduced"] #chaseCard.show .ccSay,
  html[data-motion="reduced"] #chaseCard.show .ccName,
  html[data-motion="reduced"] #chaseCard.show .ccScore {
    animation-name: ccFadeIn; animation-duration: .16s; animation-timing-function: ease-out;
  }
  @keyframes ccFadeIn { 0% { opacity: 0; } 100% { opacity: 1; } }

  /* ── 구역 끝 토스트의 깃발 그림 — 공용 알약(#badgeClaimToast) 안에 글자와 한 줄로 ── */
  #badgeClaimToast .chaseToastFlag {
    display: inline-block; width: 16px; height: 17px; margin: -4px 6px -3px 0; vertical-align: middle;
    filter: drop-shadow(0 1px 1px rgba(20,8,2,.7));
  }

  /* ── 랭킹 창의 누적 한 줄 — `.rankMine`과 같은 결(석판 위 밝은 글자)로 조용히 ──
     ⚠️ 밝은 양피지(`.rankBoard`) «안»에 넣으면 대비가 무너진다 — 석판 카드(`.rmCard`) 직속에 둘 것. */
  .chaseTotal {
    display: flex; align-items: center; justify-content: center; gap: 5px;
    margin-top: 8px; font-size: 14px; font-weight: 700; color: #fff3cf;
    text-shadow: 0 1px 2px rgba(28,17,5,.96), 0 0 5px rgba(28,17,5,.78);
  }
  .chaseTotal[hidden] { display: none; }
  .chaseTotal img { width: 15px; height: 16px; filter: drop-shadow(0 1px 1px rgba(20,8,2,.7)); }
