  /* ════════════════════════════════════════════════════════════════════════════════════════════
     「다들 어디쯤?」 — 랭킹 (2026-08-31, 오너 지시). 정본 = claudedocs/ranking_spec.md.
     로직 = game/rankScreen.js · 마크업 = index.html #rankView

     ⚠️ **2026-08-31 재설계 (오너: "샤처럼 귀엽고 보고 싶은 디자인이길 원해").**
        첫 판은 「복원 기록」(#stageRecord)의 어두운 장부 문법이었다 — 차분했지만 **보고 싶은 화면이
        아니었고, 무엇보다 샤가 말은 하는데 그림이 없었다.** 지금은 `.reviewModal.shaStoneModal`
        (샤 스톤 셸)로 갈아탔다: 따뜻한 석판 + 샤가 프레임 위로 고개를 내민다.
     ⚠️ **여전히 새 에셋 0 · 새 색 0.** 프레임·딤·등장 애니(`shaStoneInviteIn`·`shaStoneHeadPop`)·
        닫기 버튼은 전부 05-map.css의 셸이 이미 갖고 있다. 이 파일은 **그 안의 내용만** 그린다.
        샤 그림도 이미 있는 것을 쓴다(`assets/gallery/sha/*` · `assets/character/sha_*`).
     ⚠️ **강조를 «내 줄» 하나에만 쓴다.** 옆 줄들을 같이 빛내면 눈이 위아래를 오가며 비교하게 되고,
        그 순간 이 화면은 순위표가 된다(rankScreen.js 머리말 ⑵⑶).
     ⚠️ 애니 길이를 JS에 복제하지 않는다 — 여기에만 있다(`npm run check:anim`이 대조한다).
     ════════════════════════════════════════════════════════════════════════════════════════════ */

  /* ── 상단 점수 줄의 투명 탭 (진입점 ①) ────────────────────────────────────────────────────────
     ⚠️ **아무것도 안 그린다.** 여기에 테두리나 배경을 주면 점수 줄에 「버튼이 하나 생겼다」가 되고,
        그건 부르지 않기로 한 규칙(rankScreen.js ⑷)을 어긴다. 궁금해서 눌러 보는 사람만 찾으면 된다.
     ⚠️ z-index는 콤보 칩(absolute)보다 **아래**다 — 칩은 연출이 지나가는 자리라 덮으면 안 된다.
        칩에는 리스너가 없으므로 덮개가 그 아래에 있어도 탭은 그대로 이 버튼이 받는다. */
  #game .hudScore .hudScoreTap {
    position: absolute; inset: 0; z-index: 0;
    /* ⚠️⚠️ **이 한 줄이 없으면 버튼이 «보이는데 안 눌린다»** (오너 신고 2026-08-31
       *"스코어 숫자를 눌러도 랭킹페이지로 이동하지 않아"*).
       부모 `#game .hudScore`가 `pointer-events: none`이다(10-game-screen.css) — 그 줄은 읽는 표시지
       누르는 물건이 아니라서 보드 위 입력을 가로채지 않으려고 그렇게 돼 있고, 그 이유는 지금도 유효하다.
       그래서 **끄는 쪽을 고치지 말고 이 덮개만 되살린다.**
       ⚠️ **`el.click()`으로 검증하면 이 결함이 안 잡힌다** — 그 호출은 히트테스트를 건너뛰고 이벤트를
          직접 쏘므로 `pointer-events`·`z-index`를 무시한다. 탭 표적은 반드시
          `document.elementFromPoint()`(진짜 히트테스트)로 확인할 것. 내가 그걸로 한 번 놓쳤다. */
    pointer-events: auto;

    /* ── 이 덮개는 «아무것도 그리지 않고 아무것도 움직이지 않는다» ────────────────────────────
       🚨 **`styles/ui.css`의 전역 `button` 규칙이 모든 버튼에 옷을 입힌다** — 그걸 전부 벗겨야 한다.
          안 벗기면 이 덮개는 «투명»이 아니라 **점수 줄 크기의 카드**가 된다.
       실제 사고 (오너 신고 2026-08-31 *"스코어 숫자를 가로지르는 보기싫은 줄이 생겨났어"*):
         · `box-shadow: var(--lift-sm), var(--sheen)` 중 **`--sheen` = `inset 0 1px 0 rgba(255,255,255,.7)`**
           — 덮개가 `inset:0`이라 그 1px 흰 선이 **점수 줄 맨 윗변에 화면 폭 전체로** 그려졌다.
           ⚠️ 랭킹이 웹에서 켜지기 전에는 덮개가 `hidden`이라 안 보였다 → 켜자마자 «생겨난» 것처럼 보인다.
         · `button:active { transform: scaleX(1.06) scaleY(.90) }` — 그대로 두면 누를 때
           **점수 줄 전체가 찌그러진다**(아직 신고 안 된 두 번째 결함. 같이 끈다).
         · `button:hover { filter: brightness(1.03) }` · `min-width: 64px` · `transition`도 같은 출처다.
       ⚠️ 새 속성을 전역 `button`에 더하는 사람이 있으면 여기도 같이 꺼야 한다. `check:ranking`이
          「전역 button이 box-shadow를 주면 이 덮개는 none을 선언한다」를 검사한다. */
    margin: 0; padding: 0; min-width: 0;
    border: 0; border-radius: 0;
    background: none;
    box-shadow: none;
    filter: none;
    transition: none;
    transform: none;
    appearance: none; -webkit-tap-highlight-color: transparent; cursor: pointer;
  }
  #game .hudScore .hudScoreTap[hidden] { display: none; }
  /* 눌리는 감각은 **점수 줄이 살짝 데워지는 것**으로만 낸다 — 덮개 자신은 안 보이므로 자기를 눌러 봐야
     아무 일도 없다. ⚠️ 전역 `button:hover/:active`의 그림자·변형·필터를 여기서 다시 껐다(위 🚨). */
  #game .hudScore .hudScoreTap:hover,
  #game .hudScore .hudScoreTap:active {
    box-shadow: none; filter: none; transform: none;
  }
  #game .hudScore .hudScoreTap:active {
    background: radial-gradient(ellipse at 50% 50%, rgba(255,236,190,.10), transparent 70%);
  }

  /* ── 런 마감 카드의 칩 (진입점 ②) ─────────────────────────────────────────────────────────
     ⚠️ **2026-08-31에 승리 카드에서 여기로 옮겨 왔다.** 옛 칩은 «판 점수»를 말하는 물건이라
        판별 랭킹이 폐지되면서 뜻을 잃었다. 런 최고가 실제로 확정되는 순간은 런이 끊길 때
        하나뿐이라(game/score.js endRun), 이 카드가 숫자와 랭킹이 안 어긋나는 유일한 자리다.
     자리 = 석판과 초록 버튼 «사이». 카드는 세로 flex라 흐름에 그냥 얹힌다(절대좌표 아님) —
     그래서 짧은 화면에서 다른 것을 덮지 않고 기둥만 늘린다.
     ⚠️ **`order`를 주지 마라 (2026-08-31 실측).** DOM 순서대로 놓겠다고 `order: 1`을 줬다가
        기본값(0)인 `.reBtn`보다 «뒤»로 밀려 **칩이 버튼을 덮었다.** 흐름대로 두면 저절로 맞는다.
     ⚠️ **초록 버튼보다 작고 조용해야 한다.** 이 카드의 유일한 출구는 `.reBtn`이고, 칩은 곁가지다.
        둘이 비슷한 무게면 「어느 쪽이 다음이지?」가 된다(modal_design_system §10-1 버튼 위계). */
  #runEndCard > .reRankChip {
    position: relative; z-index: 2;
    min-height: 44px; padding: 10px 18px 11px;   /* 44 = modal_design_system §7 최소 터치 */
    border: 0; border-radius: 999px;
    font-family: 'Jua', system-ui, sans-serif; font-weight: 400;
    font-size: clamp(13px, 3.6vw, 15px); letter-spacing: .01em; white-space: nowrap;
    color: #f2e4bd;
    background: linear-gradient(180deg, #6b4a26 0%, #543718 52%, #3f2711 100%);
    box-shadow: 0 2px 0 rgba(0,0,0,.30), 0 5px 11px rgba(20,10,2,.32),
                inset 0 1px 0 rgba(255,240,200,.24);
    text-shadow: 0 1px 0 rgba(40,20,4,.55);
    -webkit-tap-highlight-color: transparent; cursor: pointer;
    animation: runEndRankChipIn .42s cubic-bezier(.2,.9,.3,1) backwards;
  }
  /* 「눌러 볼 것이 있다」는 귀띔. 글자를 늘리지 않고 꺾쇠 하나로 말한다. */
  #runEndCard > .reRankChip::after { content: " ›"; opacity: .72; }
  #runEndCard > .reRankChip[hidden] { display: none; }
  #runEndCard > .reRankChip:active { transform: scale(.96); transition: transform .06s ease-out; }
  @keyframes runEndRankChipIn {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }
  html[data-motion="reduced"] #runEndCard > .reRankChip { animation: none; }

  /* ── 화면 ─────────────────────────────────────────────────────────────────────────────────
     셸은 `.reviewModal.shaStoneModal`(05-map.css)이 소유한다. 여기서는 **덮어쓸 것 둘**만 적는다:
     ⑴ 층(z-index) — **런 마감 카드(70) 위**에서 열려야 한다. 🚨 2026-08-31 실측 사고: 진입점이
        승리 카드(60)에서 런 마감 카드로 옮겨 갔는데 이 값이 64로 남아 **칩을 눌러도 랭킹이 카드
        뒤에 열렸다**(화면상 아무 일도 안 일어난 것처럼 보였다). 진입점을 옮길 때는 그 표면의 층을
        같이 옮길 것. 위로는 수여식·데일리(72)가 있어 **71이 상한**이다.
     ⑵ 열림 표시 — 이 셸의 형제들은 `style.display`로 여닫는데, 이 화면은 `.show` 클래스를 쓴다
        (`#stageRecord`와 같은 방식 — 애니를 클래스에 걸 수 있고 JS가 인라인 스타일을 안 남긴다). */
  #rankView.rankModal { z-index: 71; }
  #rankView.rankModal.show { display: flex; }

  /* 샤를 조금 크게 세운다(92 → 104px). 이 화면에서 샤는 장식이 아니라 **답을 알려 주는 사람**이라
     제목보다 먼저 눈에 들어와야 한다.
     ⚠️ **`transform`을 여기서 건드리지 않는다** — `.rmSha`의 `translateX(-50%)`가 가운데 정렬이고,
        `shaStoneHeadPop`이 그 위에서 돈다. 폭만 바꾼다(top은 키에 맞춰 함께 올린다). */
  #rankView .rmSha { width: 118px; height: 118px; top: -59px; }

    /* 석판 위 글자는 **진한 잉크 + 밝은 그림자**가 이 저장소의 규칙이다 (05-map.css의 「진한 잉크」
     주석: 옛 #8a7350은 석판 위에서 1.14:1이라 사실상 안 보였다). 그 규칙을 이 화면에도 적용한다. */
  /* ⚠️ **셸 기본(`clamp(18px,5vw,22px)`)보다 «키운» 것이고, 그건 이 셸의 정상 문법이다** —
     형제 `#doodleGrant`도 자기 제목을 14px로 «줄여» 쓴다(모달마다 제목의 역할이 다르기 때문).
     여기서 제목은 화면 이름이자 **질문**이고, 바로 아래 말풍선이 그 **답**이라 답보다 작으면 안 된다.
     ⚠️ 처음엔 `18px` 고정으로 덮어써서 셸보다 «작고 가늘게» 나왔다(굵기 950도 잃었다) —
     그건 실수였고, 지금은 굵기를 셸에 맡긴 채 크기만 한 단 올린다. */
  /* ── 닫기 X — 히트 영역만 44px로 (modal_design_system.md §7) ──────────────────────────────
     🚨 실측(2026-08-31 배포본, 412×915): 이 X는 **38×38**이었다 — §7의 「최소 터치 타깃 44px」 미달.
        원판·테두리는 셸(`.shaStoneModal .rmClose`)이 그리므로 **박스를 키우면 X가 커져** 형제 모달과
        크기가 달라진다(§7이 「시각 크기 유지」라고 못 박은 자리). 그래서 **보이는 크기는 그대로 두고
        히트 영역만** 4px씩 넓힌다(실측: 3px이면 42px로 2px 모자랐다).
     ⚠️ 셸이 `overflow: hidden`이라 밖으로 나가는 `::after`가 잘린다 → 이 모달에서만 visible로 연다.
        배경을 안 그리므로 보이는 것은 여전히 38px 원판이다. */
  #rankView .rmClose { overflow: visible; }
  #rankView .rmClose::after {
    content: ""; position: absolute; inset: -4px; border-radius: 50%;
  }

  #rankView .rmTitle {
    font-size: clamp(21px, 5.6vw, 24px);
    margin-bottom: 11px; color: #2a1c0a;
    text-shadow: 0 1px 0 rgba(255,246,214,.62);
  }

  /* ── 샤의 말풍선 ──────────────────────────────────────────────────────────────────────────
     숫자보다 **먼저** 목소리가 오게 한다. 이 화면이 성적표로 읽히지 않는 이유의 절반이 이 줄이다.
     ⚠️ 꼬리는 «위»를 가리킨다 — 바로 위 제목 너머에 샤의 머리가 있다. 아래를 가리키면 숫자가
        말한 것처럼 읽힌다. */
  .rkSay {
    position: relative;
    margin: 0 2px 12px; padding: 9px 14px 10px;
    border-radius: 14px;
    background: linear-gradient(180deg, #fffaef, #f4e8cd);
    border: 1px solid rgba(138,115,80,.34);
    box-shadow: 0 2px 0 rgba(120,96,58,.16), inset 0 1px 0 rgba(255,255,255,.7);
    font-family: 'Jua', system-ui, sans-serif; font-weight: 400;
    /* ⚠️ **이 셸에는 본문 규격이 «두 벌»이다 — 큰 쪽을 쓴다.**
         · `.shaStoneModal .rmBody`           = 16px  (보조 본문)
         · `#doodleGrant.shaStoneModal .rmBody` = `clamp(20px, 5.7vw, 23px)` ← **샤가 말하는 줄**
       처음에 작은 쪽(16px)을 골랐고 오너 판정은 *"40대 눈 나쁜 사람은 읽기 어려운 크기"*였다.
       이 줄은 화면의 «답»이라 큰 쪽이 맞는다(390px에서 16 → 20.3px, 약 127%). */
    font-size: clamp(19px, 5.2vw, 22px); line-height: 1.45; color: #5f4a2c; word-break: keep-all;
    min-height: 26px;
  }
  .rkSay:empty { display: none; }
  .rkSay::before, .rkSay::after {
    content: ""; position: absolute; left: 50%; margin-left: -8px;
    border: 8px solid transparent; width: 0; height: 0;
  }
  .rkSay::before { top: -17px; border-bottom-color: rgba(138,115,80,.34); }
  .rkSay::after  { top: -15px; border-bottom-color: #fffaef; }

  /* 불러오는 중 · 표본 부족 · 오류 — 셋 다 같은 자리에 한 문장으로.
     ⚠️ 실패를 «빨갛게» 칠하지 않는다. 랭킹이 안 열린 것은 플레이어의 잘못도 손실도 아니다. */
  .rankState {
    margin: 18px 6px; min-height: 44px;
    /* ⚠️ **이 한 문장이 화면의 전부인 상태**(표본 부족·오류)다 — 말풍선과 «같은 단»으로 둔다.
       오너 신고의 그 화면이 바로 이 상태였다. 여기를 작게 두면 랭킹을 못 본 사람이 보는 유일한
       글자가 작아진다. */
    font-size: clamp(19px, 5.2vw, 22px); line-height: 1.5; color: #241905; word-break: keep-all;
    text-shadow: 0 1px 0 rgba(255,246,214,.6);
  }
  .rankState[hidden] { display: none; }
  .rankMain[hidden] { display: none; }

  /* ── 백분위 ──────────────────────────────────────────────────────────────────────────────
     이 화면에서 가장 큰 글자. 금빛은 승리 카드 현수막 숫자와 **같은 그라디언트**다(새 색 0). */
  .rankPct {
    display: block; font-family: 'Jua', system-ui, sans-serif; font-weight: 400;
    font-size: clamp(31px, 8.8vw, 39px); line-height: 1.05;
    background: linear-gradient(180deg, #fff6d8 0%, #ffcf62 48%, #d99022 100%);
    -webkit-background-clip: text; background-clip: text; color: transparent;
    filter: drop-shadow(0 1px 0 rgba(255,252,235,.9)) drop-shadow(0 2px 1px rgba(110,74,16,.35));
  }

  /* ── 「어디쯤」을 그림으로 (ranking_spec §6.2) ────────────────────────────────────────────
     사람들을 작은 점으로 늘어놓고 내 자리에 금빛 이름표를 놓는다. 숫자를 다시 쓰지 않는다.
     ⚠️ **양 끝에 아무 표식도 없다.** 1등 깃발이나 목표선을 놓는 순간 쫓아갈 대상이 생기고,
        그러면 이 기능이 헌법을 통과한 근거(ranking_spec §2)가 무너진다.
     ⚠️ 좌우 여백 20px은 장식이 아니다 — 이름표가 1%·100%에 앉아도 잘리지 않게 하는 자리다. */
  .rkTrack {
    position: relative; height: 22px; margin: 9px 20px 2px;
    background: radial-gradient(circle at 3px 50%, rgba(48,34,12,.42) 1.7px, transparent 2px)
                0 0 / 9px 100% repeat-x;
  }
  .rkYou {
    position: absolute; top: 50%; left: 0; width: 0; height: 0;
    animation: rkYouPulse 2.4s ease-in-out infinite;
  }
  .rkYouLabel {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 22px; height: 20px; box-sizing: border-box; padding: 0 5px; border-radius: 999px;
    background: radial-gradient(circle at 34% 28%, #fff8e2, #ffd06a 56%, #c9871a);
    box-shadow: 0 0 0 3px rgba(255,230,155,.45), 0 2px 4px rgba(70,44,10,.4),
                inset 0 -1px 0 rgba(120,74,10,.4);
    color: #5b3c0b; font: 700 11px/1 'Jua', system-ui, sans-serif; white-space: nowrap;
  }
  /* 숨쉬듯 아주 얕게. 「여기 있어」를 말하는 정도이지 시선을 붙잡는 정도가 아니다. */
  @keyframes rkYouPulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.16); }
  }
  html[data-motion="reduced"] .rkYou { animation: none; }

  /* ── 밝은 양피지 판 ─────────────────────────────────────────────────────────────────────────
     ⚠️ **이 판이 없으면 아래 글자들은 못 읽는다.** 실측(2026-08-31 라이브 렌더): 카드 안 석판은
        srgb(130,118,97)이고, 그 위에서는 **새까만 글자도 3.88:1이 한계**다(작은 글자 기준 4.5 미달).
        첫 판은 이 자리에 크림 카드용 색(#6b5a3c)을 그대로 써서 **이웃 이름이 1.49:1**이었다 —
        화면에 있는데 안 보이는 상태였다. 같은 글자가 이 판 위에서는 10.5:1이 된다.
     ⚠️ 그래서 **작은 글자를 석판 위에 직접 올리지 마라.** 큰 금빛 숫자(상위 N%)만 예외다 —
        그건 큰 글자라 3:1이면 되고, 금빛 자체가 석판과 충분히 갈린다. */
  .rankBoard {
    margin: 12px 0 0; padding: 9px 10px 10px; border-radius: 12px;
    background: linear-gradient(180deg, rgba(255,250,239,.94), rgba(243,231,204,.94));
    border: 1px solid rgba(120,96,58,.36);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.75), 0 1px 0 rgba(90,70,40,.18);
  }
  /* 큰 숫자의 자막. 보조 계열 14px — 12.5px는 이 저장소에서 «상태 칩»(.modalState)의 단이라
     문장에 쓰면 작다. */
  /* 내 점수 — 백분위 «아래», 표본 줄 «위». 순서가 곧 문장이다: 「상위 22% · 내 최고 1,500점 ·
     이 판을 푼 1,204명 중에서」. ⚠️ 백분위보다 작게 둔다 — 이 화면의 주인공은 «어디쯤»이다. */
  .rankMyScore {
    display: block; margin-top: 3px;
    font-family: 'Jua', system-ui, sans-serif; font-weight: 400;
    font-size: clamp(16px, 4.4vw, 18px); color: #4a3520;
    text-shadow: 0 1px 0 rgba(255,246,214,.6);
  }
  .rankMyScore:empty { display: none; }
  /* ── 「한 명만 더」 목표 막대 (오너 결정 2026-09-01) ─────────────────────────────────────────
     내 최고가 채우고, 바로 위 «한 명»의 점수가 목표선이다. 백분위 점줄(`.rkTrack`)과 뜻이 다르므로
     **모양도 다르게** 한다 — 저건 점선 위의 «자리»이고 이건 «채워지는 것»이다.
     ⚠️ 값싼 것만 쓴다: 상자 둘, 애니는 폭 전환 하나(합성 단계). 새 그림자·블러 없음. */
  .rankNext { display: block; margin: 6px 20px 2px; }
  .rankNext[hidden] { display: none; }
  .rnTrack {
    position: relative; height: 9px; border-radius: 999px; overflow: hidden;
    background: rgba(74,53,32,.22);
    box-shadow: inset 0 1px 2px rgba(58,40,18,.35);
  }
  .rnFill {
    display: block; height: 100%; width: 0; border-radius: 999px;
    background: linear-gradient(90deg, #f0b64a, #ffd977);
    box-shadow: 0 0 0 1px rgba(120,80,20,.18) inset;
    transition: width .5s cubic-bezier(.22,.9,.24,1);
  }
  .rnGoal {
    display: block; margin-top: 3px; text-align: right;
    font-family: 'Jua', system-ui, sans-serif; font-weight: 400;
    font-size: clamp(13px, 3.6vw, 15px); color: #6b5335;
  }
  html[data-motion="reduced"] .rnFill { transition: none; }
  .rankSample { display: block; margin: 1px 0 10px; font-size: 15.5px; color: #6b5335; }

  /* ── 내 옆 네 줄 ─────────────────────────────────────────────────────────────────────────── */
  .rankRows {
    list-style: none; margin: 0; padding: 8px 0 0; display: grid; gap: 4px;
    border-top: 1px solid rgba(120,96,58,.26);
  }
  .rankRow {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 9px 12px; border-radius: 8px;
    /* 읽는 줄이다 — 보조 본문(15px)보다 한 단 위. 이름과 점수를 눈으로 훑는 자리라 넉넉해야 한다. */
    background: rgba(126,102,58,.10); font-size: 17px; color: #4a3520;
  }
  .rrName { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .rrScore { font-variant-numeric: tabular-nums; font-weight: 700; color: #6b4e16; }
  /* 내 줄 하나만 데운다 (위 ⚠️). 켜진 탭과 같은 금빛이라 「지금 보고 있는 것」이 한 색으로 묶인다. */
  .rankRow.me {
    color: #4a3208;
    background: linear-gradient(180deg, rgba(255,222,132,.98), rgba(240,180,60,.94));
    box-shadow: inset 0 1px 0 rgba(255,250,228,.7), 0 1px 2px rgba(90,60,10,.24);
  }
  .rankRow.me .rrName { font-weight: 700; }
  .rankRow.me .rrScore { color: #4a3208; }

  /* 내 별명 — **맨 아래 작게.** 이름이 크거나 위에 있으면 「내 이름을 걸고 겨룬다」로 읽혀서,
     자동 별명을 고른 이유(ranking_spec §4)와 정면으로 충돌한다. */
  .rankMine {
    display: block; margin-top: 13px; font-size: 15.5px; font-weight: 700;
    color: #fff3cf;
    text-shadow: 0 1px 2px rgba(28,17,5,.96), 0 0 5px rgba(28,17,5,.78);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* 내용도 카드와 따로 도착한다 — 셸의 형제들과 같은 리듬(05-map.css `shaStonePartIn`).
     ⚠️ 그 규칙의 `:is(...)` 목록에 이 화면의 요소 이름이 없어서 여기서 따로 건다.
     ⚠️ fill-mode는 `backwards`다. `both`면 100% 프레임이 영구히 남아 눌림 피드백이 죽는다. */
  html[data-modal-motion="tiered"] #rankView .rmCard {
    & > :is(.rkSay, .rankState, .rankMain, .rankMine) {
      animation: shaStonePartIn .44s linear backwards;
    }
    & > .rkSay     { animation-delay: .12s; }
    & > .rankState { animation-delay: .18s; }
    & > .rankMain  { animation-delay: .18s; }
    & > .rankMine  { animation-delay: .24s; }
  }

  /* 짧은 화면(320x568 등)에서는 세로를 아낀다 — 카드에 스크롤을 걸지 않는다.
     ⚠️ `.rmCard`에 `overflow`를 주면 **샤의 머리가 잘린다**(그 그림은 카드 위로 나와 있다). */
  @media (max-height: 640px) {
    /* 긴 무작위 대사에서도 샤 머리가 화면 위로 밀리지 않게 모달을 안전 영역부터 배치한다.
       내용이 화면보다 길면 카드가 아니라 바깥 모달이 스크롤되어 돌출된 샤를 자르지 않는다. */
    #rankView.rankModal {
      align-items: flex-start; overflow-y: auto; box-sizing: border-box;
      padding-top: 22px; padding-bottom: 8px;
    }
    /* ⚠️ **세로는 «여백»으로 아끼고 글자는 건드리지 않는다** — 작아서 못 읽는 것이 원래 신고였다.
       실측(360×640, 이웃 5줄): 이 블록 없이는 카드가 608px가 되어 **샤 머리가 2px 잘렸다.** */
    #rankView .rmSha { width: 76px; height: 76px; top: -38px; }
    #rankView .rmCard { padding-top: 46px; padding-bottom: 18px; }
    #rankView .rmTitle { margin-bottom: 8px; }
    .rkSay { margin-bottom: 8px; }
    .rankBoard { margin-top: 9px; padding: 7px 9px 8px; }
    .rankRows { gap: 3px; padding-top: 7px; }
    .rankSample { margin-bottom: 7px; }
    .rankMine { margin-top: 9px; }
    .rkTrack { margin-top: 7px; }
    /* ⚠️ 여기서도 «본문 아래»로는 안 내린다 — 작아서 못 읽는 것이 원래 신고였다.
       세로를 아끼는 것은 여백과 샤 크기로 하고, 글자는 한 단만 내린다. */
    .rkSay { font-size: 18px; padding: 7px 12px 8px; margin-bottom: 10px; }
    .rankPct { font-size: 31px; }
    /* ⚠️ 6px은 눈대중이 아니다 — 360×640에서 5줄이 **정확히 2px 넘쳤다**(실측). 줄당 2px씩 열 줄이면
       10px이 나와 그 초과를 덮고도 남는다. 이웃 줄 수를 3줄로 줄이는 것보다 손해가 작다. */
    .rankRow { padding: 6px 11px; font-size: 15.5px; }
    .rankState { font-size: 18px; margin: 14px 6px; }
    .rankMyScore { font-size: 15px; }
    #rankView .rmTitle { font-size: 20px; }
    /* ⚠️ **버리는 순서가 정해져 있다: ①여백 ②샤 크기 ③이웃 줄 수 ④별명 ⑤점 줄.**
       글자 크기는 끝까지 안 건드린다 —「작아서 못 읽는다」가 원래 신고였다(§6.1-b3).
       ④ 별명은 이 화면에서 가장 덜 중요한 줄이라(§4에서 일부러 작게 둔 자리) 먼저 접는다.
       ⚠️ 터치 타깃 44px은 줄이지 않는다(modal_design_system §7) — 세로는 다른 데서 낸다. */
    .rankMine { display: none; }
  }
  /* 아주 짧은 화면(320×568 등) — 여기서는 ⑤ 점 줄까지 접는다.
     ⚠️ 백분위 숫자가 같은 것을 이미 말하고 있어, 그림이 없어도 «어디쯤»은 남는다.
     실측 근거: 이 블록 없이는 카드가 569~594px가 되어 화면(568)을 넘고 샤 머리가 잘렸다. */
  @media (max-height: 600px) {
    .rkTrack { display: none; }
    .rankMyScore { margin-top: 1px; }
    .rankBoard { margin-top: 7px; }
    .rankMine { font-size: 15px; }
  }
