  /* ════════════════════════════════════════════════════════════════════════════════════════════
     「다들 어디쯤?」 — 랭킹 (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; }
  /* ═══ 상시 표식 — 「이 줄을 누르면 랭킹」 (오너 지시 2026-09-22) ═════════════════════════════════
     자리 = **콤보 칩의 거울**(칩은 오른쪽 68px · 이것은 왼쪽 68px). 숫자 폭 공식이 좌우 72px를 이미
     비우므로(10-game-screen.css의 124px 주석 · `check:score-hud-bands`가 그 관계를 지킨다) 숫자에
     한 픽셀도 영향이 없다. 높이도 그 파일의 띠 규격(88 / 세로 넉넉하면 96 — 2026-09-29 전엔 42 / 50)을 그대로 따른다.
     ⚠️ `pointer-events: none` — 탭은 덮개(`.hudScoreTap`)가 받는다. 여기에 커서를 두면 덮개가 죽는다.
     ⚠️ 흐림(.5)으로 둔다: 숫자가 이 줄의 주인공이고 이건 «눌릴 수 있음»만 말한다(연출 헌법 §4.1). */
  #game .hudScore .rankPeek {
    position: absolute; top: 0; left: 0; width: 68px; height: 88px;   /* 숫자 띠와 같은 높이 — 띠가 2026-09-29에 42 → 88로 커졌다 */
    display: flex; align-items: center; justify-content: flex-start; gap: 2px;
    padding-left: 8px; box-sizing: border-box;
    pointer-events: none; z-index: 1;
    /* 🚨 **투명도로 낮추지 않는다 — 색으로 위계를 만든다.** 1차에 `opacity:.5`로 뒀더니 실측
       스크린샷에서 배경에 묻혔다. 이 저장소가 이미 같은 실패를 한 번 했다(10-game-screen.css
       `.hudBest` 주석: *"9px · opacity .58이었다가 실기기에서 안 읽혔다 … 크기와 대비를 같이 올린다"*).
       그래서 최고 기록 줄과 **같은 크림색**(#efe3c0)을 쓴다 — 가운데 금빛 숫자보다 한 단 차분해서
       주인공은 그대로 숫자다(연출 헌법 §4.1). */
    color: #efe3c0;
  }
  #game .hudScore .rankPeek[hidden] { display: none; }
  #game .hudScore .rankPeekIcon {
    /* 그림·그림자 문법도 왕관(`.hudBestIcon`)과 같은 것을 쓴다 — 같은 띠 위의 형제다. */
    /* 🔁 17 → 32px (오너 2026-09-29 「점수 왼쪽의 아이콘 표시가 작아」 — 같은 날 숫자가 40 → 86px로 커지며 상대적으로 묻혔다).
       자리(왼쪽 68px 칸)는 숫자 크기 식이 이미 비워 둔 칸이라 숫자가 안 밀린다: 8 여백 + 32 + 2 간격 + › 약 12 = 54 ≤ 68. */
    flex: 0 0 32px; width: 32px; height: 32px;
    fill: rgba(255,221,128,.12); stroke: currentColor; stroke-width: 2;
    filter: drop-shadow(0 1px 0 rgba(32,24,12,.9));
  }
  /* 「열린다」는 신호 — 도형 하나로는 «누를 수 있음»이 약했다. 글자가 아니라 부호 하나다. */
  #game .hudScore .rankPeek::after {
    content: "›"; font-family: 'Jua', system-ui, sans-serif; font-size: 26px; line-height: 1;   /* 15 → 26px (아이콘과 함께 키움) */
    text-shadow: 0 1px 0 rgba(32,24,12,.95), 0 0 4px rgba(0,0,0,.5);
  }
  @media (min-height: 640px) { #game .hudScore .rankPeek { height: 96px; } }
  /* 눌리는 감각은 **점수 줄이 살짝 데워지는 것**으로만 낸다 — 덮개 자신은 안 보이므로 자기를 눌러 봐야
     아무 일도 없다. ⚠️ 전역 `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; }

  .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; }

  /* 순위판: 내 등수 → 주변 기록 → 샤의 반응. 기존 석판은 가장자리만 사용한다.
     모든 표면 변경은 #rankView 안으로 한정한다. HUD와 런 마감 카드의 공유 막대는 그대로다. */
  #rankView.rankModal {
    z-index: 71; box-sizing: border-box; padding: 16px 10px;
    background: rgba(28, 25, 18, .80); backdrop-filter: blur(2px);
    overflow-y: auto;
  }
  #rankView.rankModal.show { display: flex; }
  #rankView .rmCard {
    width: min(540px, 100%); padding: 18px 16px 12px;
    border: 16px solid transparent;
    border-image: url('../../assets/common/modal_canvas.webp') 92 / 16px / 0 stretch;
    background: #fff9eb; background-clip: padding-box;
    filter: drop-shadow(0 12px 24px rgba(38,29,15,.24));
    text-align: left; color: #372a1b; margin: auto;
  }
  #rankView .rmTitle {
    margin: 0 46px 16px 0; padding: 0; color: #372a1b;
    font: 400 24px/1.2 'Jua', system-ui, sans-serif; text-shadow: none;
  }
  #rankView .rmTitle::after { content: none; }
  #rankView .rmClose { top: 9px; right: 8px; overflow: visible; }
  #rankView .rmClose::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; }
  #rankView .rankState { margin: 26px 0; font-size: 19px; line-height: 1.5; color: #493b2c; text-align: center; }
  #rankView [hidden] { display: none; }
  #rankView .rankHero { text-align: center; padding: 12px 8px 16px; border-top: 1px solid #dfd3bc; }
  #rankView .rankEyebrow { display: block; color: #665338; font-size: 16px; }
  #rankView .rankPlace {
    display: block; margin: 2px 0 6px; color: #473219;
    font: 400 clamp(40px, 10vw, 52px)/1.12 'Jua', system-ui, sans-serif;
    font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
  }
  #rankView .rankContext { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 12px; font-size: 15px; color: #665338; }
  #rankView .rankPct:not(:empty)::before { content: '·'; margin-right: 12px; }
  #rankView .rankMyScore { display: block; margin-top: 8px; color: #49361e; font-size: 18px; }
  #rankView .rankBoard { --rank-width: 40px; padding: 0; }
  #rankView .rankColumns, #rankView .rankRow {
    display: grid; grid-template-columns: var(--rank-width) minmax(0, 1fr) var(--score-width, 88px);
    gap: 8px; align-items: center; padding: 12px 12px;
  }
  #rankView .rankColumns { padding-top: 0; padding-bottom: 8px; color: #716044; font-size: 14px; }
  #rankView .rankColumns > :last-child { text-align: right; }
  #rankView .rankRows { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; }
  #rankView .rankRow { min-height: 44px; box-sizing: border-box; background: #f2ecdf; border-radius: 6px; font-size: 17px; }
  #rankView .rankRow:nth-child(even) { background: #f8f3e8; }
  #rankView .rrPlace { font-weight: 700; color: #67553b; font-variant-numeric: tabular-nums; white-space: nowrap; }
  /* 별명은 두 줄까지 (오너 2026-10-03 「두 줄로 보여 줘도 UI가 문제없는지 체크」) — 한국어 별명이 「하는 짓 + 사람」(자식자랑 박반장)이 되면서
     한 줄 말줄임이 «사람» 부분을 잘라 먹었다(360폭 400개 중 391개). 띄어쓰기 자리에서만 꺾어(keep-all) 앞말 · 사람으로 나뉜다.
     짧은 이름은 그대로 한 줄이다. 두 줄도 넘치면 마지막에만 말줄임. */
  #rankView .rrName {
    overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
    white-space: normal; word-break: keep-all; overflow-wrap: anywhere;
    font-size: 15px; line-height: 1.12;   /* 두 줄이 옛 한 줄 줄 높이(44px) 근처에 들어오게 — 360×640·360×740에서 17px이면 창이 화면 밖으로 12~18px 나갔다 */
  }
  #rankView .rrScore { text-align: right; color: #49361e; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
  #rankView .rankRow.me {
    background: #ecd6a5; box-shadow: inset 3px 0 #98733a;
    color: #322414; font-weight: 700;
  }
  #rankView .rankRow.me .rrPlace, #rankView .rankRow.me .rrScore { color: #322414; }
  #rankView .rankNext { margin: 16px 4px 0; }
  #rankView .rnGoal { margin: 0 0 6px; text-align: left; font-size: 15px; color: #665338; }
  #rankView .rnTrack { height: 7px; background: #e4dac6; box-shadow: none; }
  #rankView .rnFill { background: #b29158; box-shadow: none; transition: none; }
  #rankView .rankCompanion { display: flex; gap: 8px; align-items: center; margin-top: 16px; padding-top: 10px; border-top: 1px solid #dfd3bc; }
  #rankView .rankShaPortrait { flex: 0 0 62px; width: 62px; height: 70px; object-fit: contain; }
  #rankView .rankCompanion > div { min-width: 0; }
  #rankView .rkSay { margin: 0 0 4px; font-size: 17px; line-height: 1.35; color: #493b2c; word-break: keep-all; }
  #rankView .rkSay:empty { display: none; }
  #rankView .rankMine { display: block; font-size: 14px; color: #716044; overflow-wrap: anywhere; }
  #rankView .chaseTotal { color: #665338; text-shadow: none; margin-top: 6px; }
  /* 궁금해서 연 정보창: 0.24초 짧은 등장 후 정지. 반복 맥동·새 효과음 없음. */
  html[data-modal-motion='tiered'] #rankView .rmCard { animation: shaStoneCalmIn .24s ease-out backwards; }
  html[data-modal-motion='tiered'] #rankView .rmCard > * { animation: none; }
  html[data-motion='reduced'][data-modal-motion='tiered'] #rankView .rmCard { animation: none; }
  @media (max-width: 359px) {
    #rankView .rmCard { border-width: 12px; border-image-width: 12px; padding: 14px 10px 10px; }
    #rankView .rankColumns, #rankView .rankRow { padding-left: 8px; padding-right: 8px; gap: 6px; }
  }
  @media (max-height: 700px) {
    #rankView.rankModal { padding-top: 8px; padding-bottom: 8px; }
    #rankView .rmCard { padding-top: 12px; padding-bottom: 8px; }
    #rankView .rmTitle { margin-bottom: 10px; }
    #rankView .rankHero { padding-top: 8px; padding-bottom: 12px; }
    #rankView .rankPlace { font-size: 40px; }
    #rankView .rankRow { padding-top: 6px; padding-bottom: 6px; }   /* 한 줄 줄은 min-height 44 그대로 · 두 줄 별명 줄만 46px로 — 360×640 독일어가 2px 넘쳤다(2026-10-03) */
    #rankView .rankNext { margin-top: 12px; }
    #rankView .rankCompanion { margin-top: 10px; padding-top: 8px; }
    #rankView .rankShaPortrait { height: 52px; width: 48px; flex-basis: 48px; }
  }
