/* ════════════════════════════════════════════════════════════
   동물 레이스 — 2000년대 플래시게임 스타일
   ════════════════════════════════════════════════════════════ */

:root {
  /* --sky-1/2 는 흙길 하늘색이 :root 에 남아 있던 것인데 참조하는 곳이 없어 뺐습니다.
     하늘색은 테마별 --sky-a/b/c 가 정합니다. */
  --grass:     #74c142;
  --grass-dk:  #4c9327;
  --cream:     #fffaf0;
  --cream-2:   #ffeec9;
  --ink:       #3a2a18;
  --ink-soft:  #6d5943;
  --yellow:    #ffd23f;
  --yellow-dk: #ffa41b;
  --orange:    #ff8a3d;
  --pink:      #ff5d8f;
  --purple:    #9b6cf6;
  --blue:      #3aa3ff;
  --blue-dk:   #1d7fe0;
  --red:       #ff4d4d;
  --panda-ink: #23252d;
  --runner-w:  64px;
  --topbar-h:  46px;

  /* ── 트랙 테마 변수 — 여기에는 '어느 테마의 그림'도 두지 않습니다 ──────
     테마마다 갈아끼우는 값들입니다.
       --track-bg   트랙 바탕색
       --track-tex  트랙 표면 무늬 + 위아래 가장자리 (background 를 여러 겹)
       --dust       달릴 때 뒤로 튀는 입자 색
       --deco-1..3  바닥에 서 있는 장식
       --amb-1/2    떠다니는 것 (+ 어떤 식으로 움직일지)

     아래 값은 **테마가 하나도 안 걸렸을 때만 보이는 중립값**입니다.
     예전에는 여기에 흙길 팔레트가 들어 있었는데, 어떤 이유로든 테마 블록이
     적용되지 않으면 그 테마가 통째로 흙길로 보였습니다 — 실제로 우주·바다에서
     그렇게 됐습니다(팔레트 블록 앞의 주석이 깨져 파서가 블록을 통째로 버렸습니다).
     그래서 특정 테마의 색·무늬·이모지를 기본값으로 쓰지 않습니다. 무채색이면
     "테마가 안 걸렸다"는 게 화면에서 바로 보이고, 다른 테마로 오해할 일도 없습니다.
     실제로는 <body data-track="dirt"> 가 항상 붙어 있어 이 값이 쓰일 일은 없습니다.
     ------------------------------------------------------------------ */
  --track-bg: #c9c9c9;
  --track-tex: none;
  --track-tex-size: auto;
  --track-tex-repeat: repeat;
  --track-tex-pos: 0 0;
  --dust: rgba(255, 255, 255, 0.8);

  --deco-1: ''; --deco-2: ''; --deco-3: '';
  --amb-1: ''; --amb-1-anim: ambDrift; --amb-1-size: 0; --amb-1-op: 0;
  --amb-2: ''; --amb-2-anim: ambRise;  --amb-2-size: 0; --amb-2-op: 0;

  --sky-a: #d8d8d8; --sky-b: #e4e4e4; --sky-c: #eeeeee;
  --hill-1: #bdbdbd; --hill-2: #a8a8a8;
  --ray: rgba(255, 255, 255, 0.08);
}

/* ── 트랙 테마 8종 ──────────────────────────────────────── */
/* ── 잔디 — 화창한 봄날 초원 ──────────────────────────────
   단색 초록으로 두면 평면 카펫처럼 보입니다. 초록을 여러 톤 섞고 잔디결을 아주 옅게 깝니다.
   꽃·클로버는 가장자리에만 두고 가운데는 결만 남깁니다 — 캐릭터가 가장 강해야 합니다.
   연출은 아래 'body[data-track="grass"] …' 규칙들에 이어집니다. */
/* ── 흙길 — 숲속 시골 운동회 (기본 테마) ──────────────────
   기본 테마이므로 나머지 일곱보다 얌전하게 갑니다 —
   애니메이션은 아주 느린 구름 그림자 하나뿐이고, 트랙 위 반짝임 층은 쓰지 않습니다.
   흙길 값은 원래 :root 에 있었는데 :root 는 다른 테마의 폴백이기도 해서,
   거기를 고치면 변수를 안 넣은 테마가 같이 흔들립니다. 그래서 여기 따로 둡니다. */
body[data-track="dirt"],
.map[data-value="dirt"] {
  /* 마른 흙. 밝기를 세 번 재고 고른 값입니다(트랙 평균 휘도 182).
       146 최악 13.0%(갈색 곰이 흙과 붙음) · 168 최악 2.9%(곰) · 182 최악 1.5%  ← 채택
     갈색 캐릭터가 갈색 바닥에 묻히지 않으려면 흙을 곰보다 확실히 밝게 띄워야 합니다. */
  --track-bg: linear-gradient(180deg,
      #e8d3ae 0%, #ddc39a 9%, #d2b689 36%, #cfb283 52%, #dcc196 88%, #e8d3ae 100%);
  --track-line: rgba(255, 250, 236, 0.5);
  /* 층 순서 = 그리는 순서(앞이 위). 잔디는 가장자리에만, 가운데는 흙결만. */
  --track-tex:
    /* 1·2. 가장자리로 삐져나온 잔디 — 타일 폭 21/29px 을 엇갈리게 겹칩니다 */
    radial-gradient(circle 7px at 50% 0, rgba(122, 176, 74, 0.85) 0 100%, transparent 0),
    radial-gradient(circle 5px at 50% 0, rgba(158, 200, 96, 0.7) 0 100%, transparent 0),
    radial-gradient(circle 7px at 50% 100%, rgba(112, 166, 68, 0.85) 0 100%, transparent 0),
    radial-gradient(circle 5px at 50% 100%, rgba(150, 194, 90, 0.7) 0 100%, transparent 0),
    /* 5·6. 잔디 밑 그늘 */
    linear-gradient(180deg, rgba(96, 68, 40, 0.22), rgba(96, 68, 40, 0) 100%),
    linear-gradient(0deg, rgba(96, 68, 40, 0.20), rgba(96, 68, 40, 0) 100%),
    /* 7·8. 작은 돌 — 아주 드물게(no-repeat 타일) */
    radial-gradient(5px 4px at 50% 50%, rgba(190, 184, 172, 0.8),
                    rgba(140, 132, 120, 0.5) 60%, transparent 76%),
    radial-gradient(4px 3px at 50% 50%, rgba(162, 152, 138, 0.7), transparent 78%),
    /* 9~11. 흙 얼룩 — 타일 크기를 서로 배수가 아니게 잡아 격자 반복을 없앱니다 */
    radial-gradient(56px 32px at 32% 42%, rgba(206, 176, 130, 0.30), transparent 72%),
    radial-gradient(36px 22px at 70% 66%, rgba(134, 100, 60, 0.22), transparent 72%),
    radial-gradient(72px 42px at 15% 76%, rgba(198, 168, 124, 0.22), transparent 74%),
    /* 12. 미세한 흙결 */
    repeating-linear-gradient(174deg,
      rgba(96, 68, 40, 0.05) 0 1px, transparent 1px 6px);
  --track-tex-size:
    21px 11px, 29px 8px, 21px 11px, 29px 8px,
    100% 13%, 100% 12%,
    280px 225px, 355px 295px,
    97px 83px, 131px 113px, 179px 149px, auto;
  --track-tex-repeat:
    repeat-x, repeat-x, repeat-x, repeat-x,
    repeat-x, repeat-x,
    repeat, repeat,
    repeat, repeat, repeat, repeat;
  --track-tex-pos:
    0 0, 0 0, 0 100%, 0 100%,
    0 0, 0 100%,
    0 0, 0 0,
    0 0, 0 0, 0 0, 0 0;
  --dust: rgba(168, 128, 84, 0.9);
  --sky-a: #6ecbff; --sky-b: #c9f0ff; --sky-c: #eaf9c9;
  --hill-1: #74c142; --hill-2: #4c9327;
  --ray: rgba(255, 255, 255, 0.24);
  /* 장식은 셋뿐 — 나무 둘, 운동회 깃발 하나 */
  --deco-1: '\1F333'; --deco-2: '\1F6A9'; --deco-3: '\1F333';
  /* 떠다니는 것은 잎사귀 여섯뿐입니다. 기본 테마라 이것 말고는 안 띄웁니다.
     --amb-2 는 안 쓰지만 크기·움직임까지 여기서 못 박습니다 — 하나라도 빠뜨리면
     그 값만 :root 를 타고 들어와서, 테마가 자기 팔레트를 온전히 소유하지 못합니다. */
  --amb-1: '\1F342'; --amb-1-anim: ambDrift; --amb-1-size: 17px; --amb-1-op: 0.45;
  --amb-2: ''; --amb-2-anim: ambRise; --amb-2-size: 14px; --amb-2-op: 0;
}

/* ══════════════════════════════════════════════════════════════
   흙길 테마 전용 연출 (기본 테마 — 가장 얌전하게)
   전부 body[data-track="dirt"] 안에만 있어서 다른 테마는 한 픽셀도 안 바뀝니다.
   다른 테마는 .track-fx 와 .track-glow 를 둘 다 쓰지만 여기는 하나만 씁니다 —
   기본 테마는 첫 화면에서 편안해야 하고, 눈에 띄는 움직임이 적을수록 좋습니다.
   ══════════════════════════════════════════════════════════════ */

/* ── 구름 그림자 ──────────────────────────────────────────
   흙길에서 움직이는 것은 이것 하나뿐입니다. 아주 느리고 아주 옅습니다. */
body[data-track="dirt"] .track-fx {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(300px 150px at 30% 42%, rgba(74, 52, 28, 0.09), transparent 72%),
    radial-gradient(210px 105px at 76% 64%, rgba(74, 52, 28, 0.06), transparent 74%);
  background-size: 760px 100%, 560px 100%;
  animation: dirtShade 58s linear infinite;
}
@keyframes dirtShade {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 760px 0, 1120px 0; }
}
/* .track-glow 는 쓰지 않습니다 — 기본값(display:none)이 그대로 적용됩니다 */

/* ── 나무 울타리 ──────────────────────────────────────────
   .hills 아래쪽에 낮은 울타리 한 줄만 둡니다. 요소를 새로 만들지 않습니다. */
body[data-track="dirt"] .hills::before {
  content: '';
  position: absolute; left: 0; right: 0; bottom: 52%;
  height: 26px;
  background:
    /* 가로 살 두 줄 */
    linear-gradient(rgba(150, 106, 62, 0.55), rgba(150, 106, 62, 0.55)) 0 7px / 100% 4px repeat-x,
    linear-gradient(rgba(150, 106, 62, 0.55), rgba(150, 106, 62, 0.55)) 0 18px / 100% 4px repeat-x,
    /* 세로 기둥 */
    linear-gradient(rgba(132, 92, 52, 0.6), rgba(132, 92, 52, 0.6)) 0 0 / 5px 100% repeat-x;
  background-position: 0 7px, 0 18px, 0 0;
  opacity: 0.5;
}

/* ── 캐릭터 접지 + 흙먼지 ─────────────────────────────────
   입자는 모든 테마가 쓰는 .runner::after 하나뿐입니다.
   다른 테마처럼 .ch-sprite::after 에 두 번째 입자를 두지 않습니다 — 기본 테마니까요. */
body[data-track="dirt"] .ch-sprite::before {
  content: '';
  position: absolute; left: 21%; right: 21%; bottom: 0.5%;
  height: 7%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(72, 48, 24, 0.3), rgba(72, 48, 24, 0));
  pointer-events: none;
}

/* ── 결승선 ───────────────────────────────────────────────
   판정 위치는 engine.js 가 정하고 CSS 를 읽지 않습니다.
   기본 체크무늬는 그대로 두고 나무 기둥 느낌만 살짝 얹습니다 —
   폭도 안 바꾸므로 --gate-extra 가 필요 없습니다. */

body[data-track="dirt"] .pack-track::after {
  background:
    repeating-conic-gradient(#fff 0 25%, #4a3626 0 50%) 0 0 / 12px 12px,
    linear-gradient(180deg, #a97748, #8a5f36);
  border-left: 3px solid #6f4a29;
  box-shadow: -7px 0 12px -8px rgba(90, 62, 34, 0.4);
}

body[data-track="grass"],
.map[data-value="grass"] {
  /* 밝기를 다섯 번 바꿔 가며 재고 고른 값입니다(트랙 평균 휘도 202).
     이 테마는 캐릭터 넷이 좁은 구간에 몰려 있어 어느 밝기로 가도 하나가 손해입니다 —
     곰 110 · 용 169 · 오리 172 · 토끼 223.
       142(어둡게) 최악 15.1%(곰) · 176(원래) 10.3%(용) · 191 7.4%(용) · 181(민트) 8.4%(용)
       202 최악 4.2%(오리)  ← 채택
     특히 초록 용은 잔디와 색·밝기가 둘 다 비슷해서, 잔디를 용보다 확실히 밝게 띄워야 삽니다. */
  --track-bg: linear-gradient(180deg,
      #c9edac 0%, #b6e394 9%, #a6d982 36%, #a2d67e 52%, #b2e08f 88%, #c9edac 100%);
  --track-line: rgba(255, 255, 255, 0.7);
  /* 층 순서 = 그리는 순서(앞이 위). 꽃은 가장자리에만, 가운데는 잔디결만. */
  --track-tex:
    /* 1·2. 위아래 가장자리 풀숲 — 타일 폭 23/31px 을 엇갈리게 겹쳐 되풀이 티를 없앱니다 */
    radial-gradient(circle 9px at 50% 0, rgba(86, 158, 60, 0.85) 0 100%, transparent 0),
    radial-gradient(circle 6px at 50% 0, rgba(122, 190, 78, 0.8) 0 100%, transparent 0),
    radial-gradient(circle 9px at 50% 100%, rgba(80, 150, 56, 0.85) 0 100%, transparent 0),
    radial-gradient(circle 6px at 50% 100%, rgba(116, 184, 74, 0.8) 0 100%, transparent 0),
    /* 5~7. 꽃과 클로버 — 아주 드물게(no-repeat 타일), 서로 배수가 아닌 크기 */
    radial-gradient(4px 4px at 50% 50%, #fff6b0, rgba(255, 226, 122, 0.7) 58%, transparent 74%),
    radial-gradient(4px 4px at 50% 50%, #ffe2ee, rgba(246, 176, 208, 0.65) 58%, transparent 76%),
    radial-gradient(4px 3px at 50% 50%, rgba(150, 206, 108, 0.9), transparent 76%),
    /* 8~10. 초록 얼룩 — 톤이 다른 초록을 겹쳐 단색 카펫을 피합니다 */
    radial-gradient(58px 34px at 32% 42%, rgba(168, 218, 118, 0.34), transparent 72%),
    radial-gradient(38px 24px at 70% 66%, rgba(88, 156, 62, 0.24), transparent 72%),
    radial-gradient(74px 44px at 15% 76%, rgba(186, 226, 130, 0.26), transparent 74%),
    /* 11·12. 잔디결 — 두 방향을 아주 옅게 겹쳐 '풀이 누운 결'로 보이게 합니다 */
    repeating-linear-gradient(84deg,
      rgba(255, 255, 255, 0.10) 0 1px, transparent 1px 5px),
    repeating-linear-gradient(96deg,
      rgba(58, 116, 40, 0.07) 0 1px, transparent 1px 7px);
  --track-tex-size:
    23px 13px, 31px 9px, 23px 13px, 31px 9px,
    270px 215px, 350px 290px, 195px 245px,
    97px 83px, 131px 113px, 179px 149px,
    auto, auto;
  --track-tex-repeat:
    repeat-x, repeat-x, repeat-x, repeat-x,
    repeat, repeat, repeat,
    repeat, repeat, repeat,
    repeat, repeat;
  --track-tex-pos:
    0 0, 0 0, 0 100%, 0 100%,
    0 0, 0 0, 0 0,
    0 0, 0 0, 0 0,
    0 0, 0 0;
  /* 달릴 때 뒤로 튀는 것 — 풀잎입니다 */
  --dust: rgba(126, 196, 84, 0.95);
  --sky-a: #62c2f0; --sky-b: #b4e6fb; --sky-c: #e6f7dd;
  --hill-1: #a6dc7c; --hill-2: #7cc253;
  --ray: rgba(255, 252, 210, 0.16);
  /* 장식은 셋뿐 — 꽃, 나무, 클로버 */
  --deco-1: '\1F33C'; --deco-2: '\1F333'; --deco-3: '\1F340';
  /* 날리는 잎사귀 여섯, 나비 넷(아주 가끔) */
  --amb-1: ''; --amb-1-anim: ambFlutter; --amb-1-size: 8px;  --amb-1-op: 0.5;
  --amb-2: '\1F98B'; --amb-2-anim: ambFlutter; --amb-2-size: 17px; --amb-2-op: 0.75;
}
/* ── 모래 — 여름 해변 ─────────────────────────────────────
   바다 테마는 '해저'(물속·젖은 회색 모래·산호), 여기는 '해변'(물 밖·마른 금빛 모래·파도)입니다.
   둘이 헷갈리지 않게 모래 색부터 다르게 잡습니다 —
   해저는 채도가 낮고 푸른 쪽(R-B 약 30), 해변은 따뜻하고 금빛(R-B 약 60)입니다.
   연출은 아래 'body[data-track="sand"] …' 규칙들에 이어집니다. */
body[data-track="sand"],
.map[data-value="sand"] {
  /* 마른 금빛 모래. 가운데가 살짝 진해 캐릭터가 뜹니다.
     너무 밝게 두면 노란 오리와 흰 토끼가 묻힙니다(사탕 테마에서 실측으로 배운 것). */
  --track-bg: linear-gradient(180deg,
      #f3dcaa 0%, #e6c78c 9%, #dcb877 36%, #d8b271 52%, #e4c68d 88%, #f3dcaa 100%);
  --track-line: rgba(255, 250, 232, 0.7);
  /* 층 순서 = 그리는 순서(앞이 위). 조개·돌은 가장자리에, 가운데는 모래결만. */
  --track-tex:
    /* 1·2. 위아래 젖은 모래 자국 — 파도가 닿았다 물러난 자리 */
    linear-gradient(180deg, rgba(150, 116, 66, 0.28), rgba(150, 116, 66, 0) 100%),
    linear-gradient(0deg, rgba(150, 116, 66, 0.24), rgba(150, 116, 66, 0) 100%),
    /* 3~5. 가장자리 조개·불가사리·작은 돌 — 드물게(no-repeat) */
    radial-gradient(7px 5px at 50% 50%, rgba(255, 246, 236, 0.95),
                    rgba(232, 186, 176, 0.6) 58%, transparent 74%),
    radial-gradient(6px 6px at 50% 50%, rgba(246, 154, 118, 0.85),
                    rgba(214, 112, 84, 0.5) 60%, transparent 76%),
    radial-gradient(5px 4px at 50% 50%, rgba(186, 176, 160, 0.75),
                    rgba(142, 130, 116, 0.5) 60%, transparent 78%),
    /* 6~8. 모래 굴곡 — 타일 크기를 서로 배수가 아니게 잡아 격자 반복을 없앱니다 */
    radial-gradient(56px 32px at 32% 42%, rgba(255, 244, 214, 0.34), transparent 72%),
    radial-gradient(36px 22px at 70% 66%, rgba(168, 132, 78, 0.20), transparent 72%),
    radial-gradient(72px 42px at 15% 76%, rgba(255, 240, 206, 0.24), transparent 74%),
    /* 9. 미세한 모래결 */
    repeating-linear-gradient(176deg,
      rgba(255, 250, 232, 0.20) 0 1px, transparent 1px 5px);
  --track-tex-size:
    100% 15%, 100% 14%,
    290px 230px, 370px 310px, 205px 265px,
    97px 83px, 131px 113px, 179px 149px, auto;
  --track-tex-repeat:
    repeat-x, repeat-x,
    repeat, repeat, repeat,
    repeat, repeat, repeat, repeat;
  --track-tex-pos:
    0 0, 0 100%,
    0 0, 0 0, 0 0,
    0 0, 0 0, 0 0, 0 0;
  /* 달릴 때 뒤로 튀는 것 — 모래알입니다 */
  --dust: rgba(226, 190, 128, 0.95);
  /* 여름 한낮 하늘. 해저(어두운 청록)와 확실히 다르게 밝은 파랑입니다. */
  --sky-a: #3ab0ee; --sky-b: #8fd8f7; --sky-c: #d8f0fb;
  --hill-1: #4fbfe6; --hill-2: #2e9fd0;
  --ray: rgba(255, 250, 220, 0.16);
  /* 장식은 셋뿐 — 야자수, 소라, 꽃게 */
  --deco-1: '\1F334'; --deco-2: '\1F41A'; --deco-3: '\1F980';
  /* 갈매기 넷, 반짝이는 모래 여섯 */
  --amb-1: ''; --amb-1-anim: ambTwinkle; --amb-1-size: 3px; --amb-1-op: 0.8;
  --amb-2: '\1F426'; --amb-2-anim: ambDrift; --amb-2-size: 13px; --amb-2-op: 0.4;
}
/* ── 눈길 — 눈 쌓인 설원 ──────────────────────────────────
   순백으로 두면 흰 토끼가 통째로 묻힙니다. 그래서 바닥을 흰색이 아니라
   '푸른 그림자가 깔린 눈'으로 둡니다 — 가운데가 가장 어둡고 가장자리 눈두둑만 밝습니다.
   연출은 아래 'body[data-track="snow"] …' 규칙들에 이어집니다. */
body[data-track="snow"],
.map[data-value="snow"] {
  /* 가운데가 가장 푸르고 어둡습니다. 여기가 흰색이면 캐릭터가 사라집니다. */
  --track-bg: linear-gradient(180deg,
      #eef6fd 0%, #d2e2f0 10%, #b3cade 40%, #adc5da 58%, #cfe0ee 90%, #eef6fd 100%);
  --track-line: rgba(120, 165, 205, 0.6);
  /* 층 순서 = 그리는 순서(앞이 위). 밝은 것은 가장자리에만 둡니다. */
  --track-tex:
    /* 1~4. 위아래 쌓인 눈 — 타일 폭 29/41px 을 엇갈리게 겹쳐 되풀이 티를 없앱니다 */
    radial-gradient(circle 14px at 50% 0, #fdfeff 0 100%, transparent 0),
    radial-gradient(circle 10px at 50% 0, #f2f8fe 0 100%, transparent 0),
    radial-gradient(circle 14px at 50% 100%, #fdfeff 0 100%, transparent 0),
    radial-gradient(circle 10px at 50% 100%, #f2f8fe 0 100%, transparent 0),
    /* 5·6. 눈두둑 아래로 지는 푸른 그림자 */
    linear-gradient(180deg, rgba(112, 154, 196, 0.40), rgba(112, 154, 196, 0) 100%),
    linear-gradient(0deg, rgba(112, 154, 196, 0.36), rgba(112, 154, 196, 0) 100%),
    /* 7. 아주 드물게 반짝이는 눈 결정 */
    radial-gradient(circle 1.4px at 50% 50%, rgba(255, 255, 255, 0.95) 0 100%, transparent 0),
    /* 8~10. 부드러운 눈 굴곡 — 타일 크기를 서로 배수가 아니게 잡아 격자 반복을 없앱니다 */
    radial-gradient(58px 34px at 30% 40%, rgba(255, 255, 255, 0.34), transparent 72%),
    radial-gradient(38px 24px at 72% 66%, rgba(120, 158, 196, 0.30), transparent 72%),
    radial-gradient(74px 44px at 14% 78%, rgba(255, 255, 255, 0.24), transparent 74%),
    /* 11. 미세한 눈결 */
    repeating-linear-gradient(168deg,
      rgba(255, 255, 255, 0.14) 0 1px, transparent 1px 7px);
  --track-tex-size:
    29px 16px, 41px 11px, 29px 16px, 41px 11px,
    100% 22%, 100% 20%,
    167px 139px,
    97px 83px, 131px 113px, 179px 149px, auto;
  --track-tex-repeat:
    repeat-x, repeat-x, repeat-x, repeat-x,
    repeat-x, repeat-x,
    repeat,
    repeat, repeat, repeat, repeat;
  --track-tex-pos:
    0 0, 0 0, 0 100%, 0 100%,
    0 0, 0 100%,
    0 0,
    0 0, 0 0, 0 0, 0 0;
  --dust: rgba(255, 255, 255, 0.95);
  --sky-a: #8ec6e8; --sky-b: #cbe6f7; --sky-c: #eef7fd;
  --hill-1: #e6f2fb; --hill-2: #c8dcec;
  --ray: rgba(255, 255, 255, 0.10);
  /* 주변 장식은 제한적으로 — 눈사람 하나, 눈 덮인 소나무 둘 */
  --deco-1: '\26C4'; --deco-2: '\1F332'; --deco-3: '\1F332';
  /* 눈송이 — 크기와 속도를 아래 개별 규칙에서 흩어 놓습니다 */
  --amb-1: ''; --amb-1-anim: ambFall; --amb-1-size: 7px; --amb-1-op: 0.85;
  --amb-2: ''; --amb-2-anim: ambFall; --amb-2-size: 4px; --amb-2-op: 0.6;
}
/* ── 우주 — 정거장 플랫폼 ────────────────────────────────
   팔레트(변수)만 여기 두고, 별·유성·네온 같은 연출은 아래
   'body[data-track="space"] …' 규칙들에 이어집니다. */
body[data-track="space"],
.map[data-value="space"] {
  /* 금속 판 — 가운데가 살짝 밝아 캐릭터가 뜹니다. 순검정으로 두면 가독성이 떨어집니다. */
  --track-bg:
    linear-gradient(102deg, rgba(120, 150, 210, 0.07) 0 18%, transparent 34% 66%, rgba(120, 150, 210, 0.05) 82%),
    linear-gradient(180deg, #0d1322 0%, #161f36 11%, #1d2740 42%, #1d2740 58%, #161f36 89%, #0d1322 100%);
  --track-line: rgba(150, 200, 255, 0.5);
  /* 층 순서 = 그리는 순서(앞이 위).
     네온은 가장자리에만 두고, 가운데는 아주 약한 패널 라인만 남깁니다. */
  --track-tex:
    /* 1·2. 위아래 가장자리 금속 띠 */
    linear-gradient(#0a0f1c, #0a0f1c),
    linear-gradient(#0a0f1c, #0a0f1c),
    /* 3·4. 그 안쪽 네온 에너지 라인 (청록 / 자주) */
    linear-gradient(90deg,
      rgba(90, 226, 255, 0.30) 0 8%, rgba(150, 244, 255, 0.85) 14% 22%,
      rgba(90, 226, 255, 0.35) 30% 44%, rgba(160, 246, 255, 0.8) 52% 60%,
      rgba(90, 226, 255, 0.3) 70% 84%, rgba(150, 244, 255, 0.75) 90% 100%),
    linear-gradient(90deg,
      rgba(196, 130, 255, 0.28) 0 10%, rgba(226, 176, 255, 0.8) 18% 26%,
      rgba(196, 130, 255, 0.3) 36% 50%, rgba(230, 184, 255, 0.75) 58% 68%,
      rgba(196, 130, 255, 0.26) 78% 92%),
    /* 5·6. 패널 이음매 — 아주 약하게. 여기가 밝아지면 캐릭터가 묻힙니다. */
    repeating-linear-gradient(90deg,
      rgba(150, 190, 240, 0.055) 0 1px, transparent 1px 118px),
    repeating-linear-gradient(0deg,
      rgba(150, 190, 240, 0.04) 0 1px, transparent 1px 74px),
    /* 7. 패널 모서리 리벳 */
    radial-gradient(circle 1.2px at 6px 6px, rgba(176, 208, 245, 0.16) 0 100%, transparent 0),
    /* 8. 금속 결 */
    repeating-linear-gradient(97deg,
      rgba(170, 200, 240, 0.018) 0 1px, transparent 1px 5px);
  --track-tex-size:
    100% 9%, 100% 9%,
    /* 네온 두 줄은 90deg 그라디언트라 폭이 곧 무늬 주기입니다. 100% 로 두면
       월드가 길어질수록 무늬가 통째로 늘어나 버려서, px 주기로 고정해 되풀이시킵니다.
       두 줄의 주기를 다르게 잡아 위아래가 같은 자리에서 만나지 않게 했습니다. */
    433px 2.5px, 571px 2.5px,
    auto, auto, 118px 74px, auto;
  --track-tex-repeat:
    no-repeat, no-repeat,
    repeat-x, repeat-x,
    repeat, repeat, repeat, repeat;
  --track-tex-pos:
    0 0, 0 100%,
    0 9%, 0 91%,
    0 0, 0 0, 0 0, 0 0;
  /* 달릴 때 뒤로 남는 것 — 먼지가 아니라 별가루입니다 */
  --dust: rgba(190, 232, 255, 0.95);
  --sky-a: #05070f; --sky-b: #0b1024; --sky-c: #131a35;
  --hill-1: #1a2242; --hill-2: #0c1122;
  --ray: rgba(140, 180, 255, 0.05);
  /* 바닥 장식은 쓰지 않습니다 — 행성 하나만 배경에 둡니다(아래 .hills 규칙) */
  --deco-1: ''; --deco-2: ''; --deco-3: '';
  /* 가까운 별 여섯(반짝임) + 유성 넷(아주 가끔) */
  --amb-1: ''; --amb-1-anim: ambTwinkle; --amb-1-size: 3px; --amb-1-op: 0.9;
  --amb-2: ''; --amb-2-anim: ambDrift;   --amb-2-size: 3px; --amb-2-op: 0.9;
}
/* ── 바다 — 수중 레이스 ─────────────────────────────────
   설계 원칙 세 가지입니다.
   ① **트랙 안이 곧 물이어야 합니다.** 예전에는 가운데가 밝은 모래(#e2dfcd)라
      바깥 파란 배경과 따로 놀아서 '수영장 레인'처럼 보였습니다. 지금은 위에서
      아래로 밝은 청록 → aqua blue → 조금 깊은 청록으로 내려가고, 맨 아래
      7% 만 얕은 모래바닥입니다. 수심이 세로로 읽히는 게 핵심입니다.
   ② **장식은 위아래 가장자리에만.** 캐릭터가 달리는 가운데는 물빛만 둡니다.
      위쪽은 수면과 햇빛, 아래쪽은 모래바닥에 산호·해초·조개·자갈.
      예전에 위아래에 같이 깔려 있던 분홍 원(산호)은 지웠습니다 — 천장에
      산호가 매달린 꼴이라 바다로 안 읽히고 사탕처럼 보였습니다.
   ③ 캐릭터 위에는 아무 것도 얹지 않습니다. 연출은 전부 러너보다 뒤에 있습니다.
   자세한 연출은 아래 'body[data-track="ocean"] …' 규칙들에 이어집니다. */
body[data-track="ocean"],
.map[data-value="ocean"] {
  /* 수면 바로 아래(밝은 청록) → aqua blue → 깊은 청록 → 얕은 모래바닥.
     93~96% 사이의 탁한 색은 물과 모래가 섞이는 자리입니다 — 여기가 없으면
     모래가 띠처럼 뚝 끊겨 보입니다. */
  --track-bg: linear-gradient(180deg,
      #b6f2ec 0%, #79dfe2 5%, #45c4da 16%, #2ba7c9 34%,
      #1e8bb2 50%, #17749d 66%, #14638a 79%, #196279 85%,
      #4d7a7c 89.5%, #9d8a6b 93.5%, #b09b78 97%, #8e7a58 100%);
  --track-line: rgba(214, 248, 255, 0.55);
  /* 층 순서 = 그리는 순서(앞이 위).
     타일 높이를 전부 100% 로 두고 '타일 안에서의 Y' 로 높이를 정합니다 —
     그래야 트랙이 얇든(레인 26px) 두껍든(팩 400px) 바닥에 그대로 붙습니다.
     타일 폭은 서로 배수가 아닌 값으로 잡아 되풀이가 눈에 안 띄게 했습니다. */
  --track-tex:
    /* 1. 조개 — 모래 위에 아주 드물게 */
    radial-gradient(9px 6px at 44% 95.4%, rgba(255, 246, 250, 0.92), rgba(236, 180, 202, 0.6) 56%, transparent 70%),
    /* 2. 자갈 */
    radial-gradient(8px 5px at 71% 97%, rgba(196, 214, 220, 0.72), rgba(120, 148, 158, 0.48) 58%, transparent 74%),
    /* 3~5. 산호 한 그루 — 폭이 같은 세 층이라 늘 같은 자리에 모여 가지가 갈라진 꼴이 됩니다.
       반원 하나로 두면 산호가 아니라 물방울이나 사탕으로 보입니다.
       뿌리를 99% 에 두어 아래 테두리에 잘리지 않고 모래 위에 서 있게 합니다. */
    radial-gradient(9px 40px at 14% 99%, rgba(206, 78, 106, 0.75) 0 100%, transparent 0),
    radial-gradient(7px 27px at 17.8% 99%, rgba(226, 116, 84, 0.7) 0 100%, transparent 0),
    radial-gradient(6px 17px at 11% 99%, rgba(188, 66, 98, 0.72) 0 100%, transparent 0),
    /* 6. 청록 산호 — 폭이 달라 붉은 그루와 엇갈려 섭니다 */
    radial-gradient(8px 29px at 62% 99%, rgba(44, 152, 146, 0.7) 0 100%, transparent 0),
    /* 6b~6d. 두 번째 산호 무리 — **주기를 다르게 한 변주**입니다.
       월드가 화면 여섯 개까지 길어지면서, 장식 하나가 한 가지 주기로만 되풀이되면
       '같은 배경을 복사해 붙인' 티가 납니다. 같은 종류를 서로 배수가 아닌 주기로
       한 벌 더 깔아 두면 두 리듬이 어긋나면서 되풀이가 눈에 안 띕니다. */
    radial-gradient(7px 34px at 41% 99%, rgba(178, 74, 122, 0.62) 0 100%, transparent 0),
    radial-gradient(5px 21px at 45% 99%, rgba(214, 108, 96, 0.58) 0 100%, transparent 0),
    radial-gradient(6px 24px at 77% 99%, rgba(62, 170, 158, 0.55) 0 100%, transparent 0),
    /* 6e. 아주 넓은 바닥 명암 — 한 화면에 다 안 들어오는 주기라 '리듬' 으로 안 읽히고
       월드를 지나가는 동안 밝기만 느리게 오르내립니다. */
    radial-gradient(420px 40px at 50% 100%, rgba(58, 44, 20, 0.16), transparent 76%),
    /* 7. 해초 한 포기 — 타일을 아주 넓게 잡아 화면에 한두 포기만 보입니다.
       가로 반지름만 px 로 두고 세로는 타일 높이에 맡겨서 끝이 뾰족한 잎이 됩니다.
       가장자리에서 흔들리는 해초는 .track-glow 가 따로 맡습니다. */
    radial-gradient(7px 100% at 31% 100%, rgba(24, 108, 84, 0.5) 0 100%, transparent 0),
    /* 8~9. 모래 얼룩 — 가로줄이 생기지 않게 넓고 낮은 타원으로만 깝니다 */
    radial-gradient(96px 16px at 38% 96%, rgba(122, 100, 68, 0.26), transparent 76%),
    radial-gradient(128px 13px at 74% 98.6%, rgba(168, 146, 104, 0.24), transparent 78%),
    /* 10. 마린 스노우 — 물속에 떠 있는 아주 작은 부유물 */
    radial-gradient(circle 1.5px at 50% 50%, rgba(228, 252, 255, 0.28), transparent 70%),
    /* 11. 수면 잔물결 — 위 가장자리에 매달린 밝은 물결 */
    radial-gradient(46px 10px at 50% 0, rgba(226, 255, 255, 0.5), transparent 74%);
  /* 주기는 서로 배수가 아닌 값(소수에 가깝게)으로 잡습니다 —
     두 층이 같은 자리에서 다시 만나는 지점이 월드보다 훨씬 멀어야 되풀이가 안 보입니다. */
  --track-tex-size:
    431px 100%, 337px 100%,
    683px 100%, 683px 100%, 683px 100%,
    521px 100%,
    1013px 100%, 1013px 100%, 877px 100%,
    2311px 100%,
    757px 26%,
    293px 100%, 419px 100%,
    137px 119px, 92px 100%;
  --track-tex-repeat:
    repeat-x, repeat-x,
    repeat-x, repeat-x, repeat-x,
    repeat-x,
    repeat-x, repeat-x, repeat-x,
    repeat-x,
    repeat-x,
    repeat-x, repeat-x,
    repeat, repeat-x;
  --track-tex-pos:
    0 0, 0 0,
    0 0, 0 0, 0 0,
    0 0,
    0 0, 0 0, 0 0,
    0 0,
    0 100%,
    0 0, 0 0,
    0 0, 0 0;
  /* 달릴 때 뒤로 남는 것 — 먼지가 아니라 기포입니다 */
  --dust: rgba(236, 252, 255, 0.95);
  --sky-a: #073d63; --sky-b: #12719b; --sky-c: #3ea8bf;
  --hill-1: #12617e; --hill-2: #084a66;
  /* 화면 전체에 내리는 햇빛 — 물속이라는 인상이 트랙 밖까지 이어지게 조금 올렸습니다 */
  --ray: rgba(190, 240, 255, 0.11);
  --deco-1: '\1FAB8'; --deco-2: '\1F41A'; --deco-3: '\1F33F';
  /* 배경에 떠다니는 것 — 기포 여섯, 멀리 지나가는 물고기 넷 */
  --amb-1: ''; --amb-1-anim: ambRise;  --amb-1-size: 7px;  --amb-1-op: 0.5;
  --amb-2: '\1F41F'; --amb-2-anim: ambDrift; --amb-2-size: 15px; --amb-2-op: 0.32;
}
/* ── 용암 — 화산 지대 ────────────────────────────────────
   설계 원칙 두 가지입니다.
   ① 캐릭터가 달리는 트랙 중앙은 거의 검게 두고 단순하게 유지합니다.
      빛은 전부 위아래 가장자리(암반이 갈라진 이음매)로 몰아 둡니다.
      예전에는 --track-bg 한가운데(47%)에 #ff7a2f 밝은 띠가 있어서 캐릭터가 묻혔습니다.
   ② 화면 전체를 빨갛게 칠하지 않습니다. 붉은 것은 하늘 위쪽·분화구·균열뿐이고
      나머지는 검은 현무암입니다.
   자세한 연출은 아래 'body[data-track="lava"] …' 규칙들에 이어집니다. */
body[data-track="lava"],
.map[data-value="lava"] {
  /* 가운데가 가장 어둡고 위아래로 갈수록 조금 따뜻해집니다 */
  --track-bg:
    radial-gradient(140% 60% at 50% 50%, #0f0a09 0 46%, rgba(15, 10, 9, 0) 78%),
    linear-gradient(180deg, #2a1410 0%, #191110 12%, #16100e 42%, #16100e 60%, #191110 88%, #2a1410 100%);
  --track-line: rgba(255, 170, 90, 0.55);
  /* 층 순서 = 그리는 순서(앞이 위).
     검은 암반(1~4)을 위아래 가장자리에 얹고, 그 '밑에' 발광층(5~6)을 깔았습니다.
     그래서 빛이 암반 덩어리 사이사이로 새어 나옵니다 — 이게 '균열에서 빛나는' 모양입니다.
     발광층만 lavaFlow 로 옆으로 흘러서 용암이 천천히 흐르는 것처럼 보입니다. */
  --track-tex:
    /* 1·2. 위 암반 — 판판한 띠 + 아래로 불룩한 자갈 */
    radial-gradient(circle at 50% 0, #0b0706 0 11px, transparent 12px),
    /* 1b·3b. 타일 폭이 다른 두 번째 자갈 — 26px 하나만 쓰면 같은 반원이 26px 마다
       정확히 되풀이돼 눈에 띕니다(FFT 진폭 63.6 실측). 37px 을 겹쳐 주기를 깨뜨립니다. */
    radial-gradient(circle at 50% 0, #0d0908 0 8px, transparent 9px),
    linear-gradient(#0b0706, #0b0706),
    /* 3·4. 아래 암반 */
    radial-gradient(circle at 50% 100%, #0b0706 0 11px, transparent 12px),
    radial-gradient(circle at 50% 100%, #0d0908 0 8px, transparent 9px),
    linear-gradient(#0b0706, #0b0706),
    /* 5·6. 암반 밑에 깔린 용암 — 심지는 거의 흰색이고 아래로 빠르게 죽습니다.
       세로로 빨리 죽여야 빛이 트랙 가운데로 번지지 않습니다(캐릭터가 묻히는 걸 막습니다). */
    radial-gradient(54% 96% at 50% 0, #fff2c8 0 12%, #ffc44a 30%,
                    rgba(255, 108, 18, 0.75) 56%, rgba(150, 30, 6, 0) 84%),
    radial-gradient(58% 96% at 50% 100%, #ffeab4 0 10%, #ffab34 28%,
                    rgba(236, 82, 14, 0.7) 54%, rgba(150, 30, 6, 0) 82%),
    /* 7~11. 균열 다섯 조각 — 상자 하나에 사선 띠 하나씩 그려 no-repeat 로 흩어 놓습니다.
       repeating-linear-gradient 로 그리면 트랙을 끝에서 끝까지 가로지르는 무한 직선이 되어
       균열이 아니라 레이저처럼 보이고, 무엇보다 캐릭터보다 눈에 띕니다.
       심지는 축 길이의 1%(약 2px)뿐이고 양옆에 어두운 어깨를 둡니다 —
       어깨가 없으면 통짜 막대기처럼 보이고 '갈라진 틈'으로 안 읽힙니다. */
    linear-gradient(104deg,
      transparent 0 46%, rgba(6,3,2,0.42) 46% 49%,
      rgba(176,30,6,0.80) 49% 49.6%,
      rgba(255,118,22,0.90) 49.6% 50%,
      rgba(255,220,126,0.95) 50% 50.4%,
      rgba(255,108,18,0.88) 50.4% 50.8%,
      rgba(168,26,5,0.75) 50.8% 51.4%,
      rgba(6,3,2,0.4) 51.4% 54%, transparent 54%),
    linear-gradient(74deg,
      transparent 0 46%, rgba(6,3,2,0.42) 46% 49%,
      rgba(176,30,6,0.66) 49% 49.6%,
      rgba(255,118,22,0.74) 49.6% 50%,
      rgba(255,220,126,0.78) 50% 50.4%,
      rgba(255,108,18,0.72) 50.4% 50.8%,
      rgba(168,26,5,0.61) 50.8% 51.4%,
      rgba(6,3,2,0.4) 51.4% 54%, transparent 54%),
    linear-gradient(96deg,
      transparent 0 46%, rgba(6,3,2,0.42) 46% 49%,
      rgba(176,30,6,0.74) 49% 49.6%,
      rgba(255,118,22,0.83) 49.6% 50%,
      rgba(255,220,126,0.87) 50% 50.4%,
      rgba(255,108,18,0.81) 50.4% 50.8%,
      rgba(168,26,5,0.69) 50.8% 51.4%,
      rgba(6,3,2,0.4) 51.4% 54%, transparent 54%),
    linear-gradient(118deg,
      transparent 0 46%, rgba(6,3,2,0.42) 46% 49%,
      rgba(176,30,6,0.59) 49% 49.6%,
      rgba(255,118,22,0.67) 49.6% 50%,
      rgba(255,220,126,0.70) 50% 50.4%,
      rgba(255,108,18,0.65) 50.4% 50.8%,
      rgba(168,26,5,0.55) 50.8% 51.4%,
      rgba(6,3,2,0.4) 51.4% 54%, transparent 54%),
    linear-gradient(84deg,
      transparent 0 46%, rgba(6,3,2,0.42) 46% 49%,
      rgba(176,30,6,0.69) 49% 49.6%,
      rgba(255,118,22,0.77) 49.6% 50%,
      rgba(255,220,126,0.82) 50% 50.4%,
      rgba(255,108,18,0.76) 50.4% 50.8%,
      rgba(168,26,5,0.65) 50.8% 51.4%,
      rgba(6,3,2,0.4) 51.4% 54%, transparent 54%),
    /* 10~11. 암반 얼룩 — 타일 크기를 서로 배수가 아니게 잡아야 물방울무늬 격자가 안 보입니다. */
    radial-gradient(46px 27px at 34% 40%, rgba(150, 118, 104, 0.14), transparent 72%),
    radial-gradient(29px 19px at 71% 67%, rgba(112, 84, 74, 0.16), transparent 72%),
    radial-gradient(64px 38px at 18% 74%, rgba(96, 62, 50, 0.13), transparent 74%),
    /* 12. 고운 결 — 주기를 아주 짧게 잡아 '무늬'가 아니라 '거친 질감'으로 읽히게 합니다. */
    repeating-linear-gradient(116deg,
      rgba(255, 158, 96, 0.014) 0 1px, transparent 1px 5px);
  --track-tex-size:
    26px 16px, 37px 11px, 100% 10%,
    26px 16px, 37px 11px, 100% 10%,
    120px 17%, 132px 17%,
    /* 균열 다섯은 원래 no-repeat 이라 트랙에 한 번씩만 그려졌습니다. 트랙이 화면 하나였을
       때는 그게 맞았지만, 월드가 화면 여섯 개로 길어지면 왼쪽 끝에만 남고 나머지는
       민무늬 현무암이 됩니다. 서로 배수가 아닌 넓은 주기로 되풀이시켜 월드 전체에 흩습니다. */
    709px 52px, 941px 44px, 1153px 38px, 827px 58px, 1289px 34px,
    97px 83px, 131px 113px, 179px 149px, auto;
  --track-tex-repeat:
    repeat-x, repeat-x, no-repeat,
    repeat-x, repeat-x, no-repeat,
    repeat-x, repeat-x,
    repeat-x, repeat-x, repeat-x, repeat-x, repeat-x,
    repeat, repeat, repeat, repeat;
  /* 균열은 이제 되풀이되므로 가로 값은 '위상' 입니다 — 서로 다른 px 로 어긋나게 둡니다.
     세로 값(34~78%)은 그대로라 균열이 트랙 아래위 여러 높이에 흩어집니다. */
  --track-tex-pos:
    0 10%, 0 10%, 0 0,
    0 90%, 0 90%, 0 100%,
    0 0, 0 100%,
    37px 34%, 211px 62%, 463px 28%, 89px 58%, 617px 78%,
    0 0, 0 0, 0 0, 0 0;
  /* 달릴 때 뒤로 튀는 것 — 재가 아니라 불티입니다 */
  --dust: rgba(255, 168, 62, 0.95);
  /* 검붉은 하늘. 위쪽만 어둡고 지평선 근처가 달아오릅니다 */
  --sky-a: #150913; --sky-b: #451613; --sky-c: #1d0d0b;
  --hill-1: #2a1310; --hill-2: #170c0a;
  --ray: rgba(255, 120, 50, 0.10);
  /* 장식은 이모지 대신 연기 기둥으로 그립니다(아래 .deco 규칙) */
  --deco-1: ''; --deco-2: ''; --deco-3: '';
  /* 떠다니는 것도 이모지가 아니라 발광하는 불티 점입니다. 개수는 기존 10개 그대로. */
  /* .amb 여섯은 위로 오르는 불티, .amb2 넷은 천천히 내려앉는 화산재입니다.
     개수는 다른 테마와 같은 10개 그대로이고 늘리지 않습니다. */
  --amb-1: ''; --amb-1-anim: ambRise; --amb-1-size: 5px; --amb-1-op: 0.75;
  --amb-2: ''; --amb-2-anim: ambFall; --amb-2-size: 4px; --amb-2-op: 0.4;
}
/* ── 사탕 — Candy Land ────────────────────────────────────
   여덟 테마 중 가장 화려한 쪽이지만, 화려한 것은 전부 '가장자리와 배경'에만 둡니다.
   달리는 바닥은 분홍이 아니라 웨하스/쿠키 길입니다 —
   분홍 바탕에 노란 오리·흰 토끼를 올리면 캐릭터가 묻힙니다.
   연출은 아래 'body[data-track="candy"] …' 규칙들에 이어집니다. */
body[data-track="candy"],
.map[data-value="candy"] {
  /* 웨하스 — 밝기를 세 번 바꿔 가며 재고 고른 값입니다(트랙 평균 휘도 190).
     더 밝게(201) 하면 흰 토끼·노란 오리가 묻히고(최악 5.6%),
     더 어둡게(166) 하면 갈색 곰이 묻힙니다(최악 7.8%).
     190 이 세 캐릭터를 동시에 살리는 지점입니다(최악 2.7%). */
  --track-bg: linear-gradient(180deg,
      #5c3620 0%, #e8caa4 8%, #dbb68c 34%, #d7b087 52%, #e3c39c 88%, #5c3620 100%);
  --track-line: rgba(255, 246, 232, 0.7);
  /* 층 순서 = 그리는 순서(앞이 위). 사탕은 가장자리에만, 가운데는 웨하스 결만. */
  --track-tex:
    /* 1·2. 위아래 가장자리 사탕 — 빨강·흰 소용돌이(페퍼민트) */
    repeating-radial-gradient(circle at 50% 0,
      #ef5f77 0 3px, #fff3f6 3px 6px, #ef5f77 6px 9px, transparent 9px),
    repeating-radial-gradient(circle at 50% 100%,
      #6fc9a8 0 3px, #fff8ee 3px 6px, #f2a13f 6px 9px, transparent 9px),
    /* 3·4. 그 안쪽 초콜릿 테두리 */
    linear-gradient(#5d3620, #5d3620),
    linear-gradient(#5d3620, #5d3620),
    /* 5·6. 초콜릿 밑에서 배어 나오는 크림 */
    linear-gradient(180deg, rgba(255, 246, 226, 0.55), rgba(255, 246, 226, 0) 100%),
    linear-gradient(0deg, rgba(255, 246, 226, 0.5), rgba(255, 246, 226, 0) 100%),
    /* 7~9. 초코칩과 쿠키 부스러기 — 아주 드물게, 타일을 서로 배수가 아니게 */
    radial-gradient(5px 4px at 50% 50%, rgba(92, 54, 30, 0.55), transparent 74%),
    radial-gradient(3px 3px at 50% 50%, rgba(120, 74, 42, 0.45), transparent 76%),
    radial-gradient(4px 3px at 50% 50%, rgba(255, 246, 226, 0.5), transparent 76%),
    /* 10·11. 웨하스 격자 — 아주 옅게. 여기가 진해지면 캐릭터가 묻힙니다. */
    repeating-linear-gradient(90deg, rgba(120, 76, 44, 0.09) 0 1px, transparent 1px 26px),
    repeating-linear-gradient(0deg, rgba(120, 76, 44, 0.07) 0 1px, transparent 1px 26px),
    /* 12. 구운 결 */
    repeating-linear-gradient(102deg, rgba(120, 76, 44, 0.05) 0 1px, transparent 1px 6px);
  --track-tex-size:
    18px 11px, 18px 11px,
    100% 7%, 100% 7%,
    100% 17%, 100% 15%,
    233px 197px, 151px 127px, 311px 263px,
    auto, auto, auto;
  --track-tex-repeat:
    repeat-x, repeat-x,
    no-repeat, no-repeat,
    repeat-x, repeat-x,
    repeat, repeat, repeat,
    repeat, repeat, repeat;
  --track-tex-pos:
    0 0, 0 100%,
    0 0, 0 100%,
    0 7%, 0 93%,
    0 0, 0 0, 0 0,
    0 0, 0 0, 0 0;
  /* 달릴 때 뒤로 남는 것 — 먼지가 아니라 설탕가루입니다 */
  --dust: rgba(255, 246, 252, 0.98);
  --sky-a: #ffc2e2; --sky-b: #ffe6f4; --sky-c: #fff3dd;
  --hill-1: #ffd8ec; --hill-2: #f6b0d4;
  --ray: rgba(255, 255, 255, 0.14);
  /* 장식은 셋뿐입니다 — 막대사탕·컵케이크·젤리 */
  --deco-1: '\1F36D'; --deco-2: '\1F9C1'; --deco-3: '\1F36C';
  /* 설탕가루 반짝임 — 크기를 아래 개별 규칙에서 흩어 놓습니다 */
  --amb-1: ''; --amb-1-anim: ambTwinkle; --amb-1-size: 5px; --amb-1-op: 0.9;
  --amb-2: ''; --amb-2-anim: ambTwinkle; --amb-2-size: 3px; --amb-2-op: 0.75;
}
/* 어두운 하늘 위에 놓이는 카드 밖 텍스트는 밝게 뒤집습니다 */
body[data-track="space"] .race-unit,
body[data-track="space"] .race-target { color: #cfc2ff; }
body[data-track="lava"] .race-unit,
body[data-track="lava"] .race-target { color: #ffcda8; }

* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }

/* hidden 속성은 브라우저 기본 스타일의 [hidden]{display:none} 으로 동작하는데,
   클래스 선택자가 우선순위에서 이깁니다. 실제로 .countdown{display:grid} 때문에
   GO! 오버레이가 영원히 안 사라지는 버그가 있었습니다. 전역으로 못을 박습니다. */
[hidden] { display: none !important; }

body {
  background: linear-gradient(180deg, var(--sky-a) 0%, var(--sky-b) 55%, var(--sky-c) 100%);
  background-attachment: fixed;
  transition: background 0.4s ease;
  color: var(--ink);
  font-family: 'Pretendard', 'Pretendard Variable', -apple-system, BlinkMacSystemFont,
               'Segoe UI', 'Malgun Gothic', 'Apple SD Gothic Neo', system-ui, sans-serif;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}
button { font-family: inherit; }

/* ── 배경 ───────────────────────────────────────────────── */
.sky { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
.sunburst {
  position: absolute;
  top: -40vh; left: 50%;
  width: 160vmax; height: 160vmax;
  transform: translateX(-50%);
  background: repeating-conic-gradient(
    from 0deg,
    var(--ray) 0deg 7deg,
    rgba(255, 255, 255, 0) 7deg 14deg);
  animation: spin 90s linear infinite;
}
@keyframes spin { to { transform: translateX(-50%) rotate(360deg); } }

.cloud {
  position: absolute;
  width: 150px; height: 46px;
  background: #fff;
  border-radius: 999px;
  opacity: 0.9;
  filter: drop-shadow(0 6px 0 rgba(58, 42, 24, 0.08));
  animation: float 46s linear infinite;
}
.cloud::before, .cloud::after {
  content: ''; position: absolute; background: #fff; border-radius: 50%;
}
.cloud::before { width: 66px; height: 66px; top: -28px; left: 22px; }
.cloud::after  { width: 46px; height: 46px; top: -18px; left: 78px; }
.c1 { top: 12%; left: -20%; animation-duration: 52s; }
.c2 { top: 28%; left: -30%; transform: scale(0.7); animation-duration: 70s; animation-delay: -20s; }
.c3 { top: 6%;  left: -40%; transform: scale(1.2); animation-duration: 88s; animation-delay: -45s; opacity: 0.7; }
@keyframes float { to { left: 130%; } }

/* ── 테마 장식 (바닥에 서 있는 것) ────────────────────────
   내용은 CSS 변수로만 들어옵니다. 테마를 바꾸면 나무 → 선인장 → 산호 → 화산으로 바뀝니다. */
.deco {
  position: absolute; bottom: 2%;
  font-size: 90px; opacity: 0.55;
  line-height: 1;
  animation: sway 5s ease-in-out infinite;
}
.deco::before { content: var(--deco); }
.d1 { left: 2%;  --deco: var(--deco-1); animation-delay: -1s; }
.d2 { left: 91%; --deco: var(--deco-2); animation-delay: -3s; font-size: 118px; }
.d3 { left: 77%; --deco: var(--deco-3); animation-delay: -2s; font-size: 64px; opacity: 0.4; }
@keyframes sway { 0%, 100% { transform: rotate(-5deg); } 50% { transform: rotate(6deg); } }

/* ── 떠다니는 배경 ────────────────────────────────────────
   바다에서는 물고기가 지나가고 기포가 오르고, 우주에서는 별이 흐르고,
   눈길에서는 눈이 내리고, 용암에서는 기포가 뽁 하고 터집니다.
   .amb  = 테마 주역(--amb-1), .amb2 = 보조(--amb-2)
   움직이는 방식도 테마가 정합니다(--amb-*-anim). */
.ambient { position: absolute; inset: 0; overflow: hidden; }
.amb, .amb2 {
  position: absolute;
  font-style: normal; line-height: 1;
  will-change: transform, opacity;
}
.amb::before  { content: var(--amb-1); }
.amb2::before { content: var(--amb-2); }
.amb {
  font-size: var(--amb-1-size, 20px);
  opacity: var(--amb-1-op, 0.6);
  animation: var(--amb-1-anim, ambDrift) 26s linear infinite;
}
.amb2 {
  font-size: var(--amb-2-size, 14px);
  opacity: var(--amb-2-op, 0.5);
  animation: var(--amb-2-anim, ambRise) 14s linear infinite;
}
/* 같은 것들이 한 줄로 몰려 보이지 않게 출발 위치와 시간을 흩습니다 */
.a1 { top: 14%; animation-duration: 24s; animation-delay:  -2s; }
.a2 { top: 31%; animation-duration: 33s; animation-delay: -11s; }
.a3 { top: 48%; animation-duration: 28s; animation-delay: -19s; }
.a4 { top: 62%; animation-duration: 38s; animation-delay:  -6s; }
.a5 { top: 74%; animation-duration: 30s; animation-delay: -24s; }
.a6 { top: 22%; animation-duration: 43s; animation-delay: -15s; }
.amb2.b1 { left: 12%; animation-duration: 11s; animation-delay: -3s; }
.amb2.b2 { left: 38%; animation-duration: 15s; animation-delay: -8s; }
.amb2.b3 { left: 63%; animation-duration: 13s; animation-delay: -1s; }
.amb2.b4 { left: 86%; animation-duration: 17s; animation-delay: -6s; }

/* 오른쪽 → 왼쪽으로 흘러갑니다 (물고기·별·낙엽·사탕) */
@keyframes ambDrift {
  from { transform: translateX(102vw); }
  to   { transform: translateX(-12vw); }
}
/* 팔랑팔랑 (나비) */
@keyframes ambFlutter {
  0%   { transform: translate(102vw, 0) rotate(-6deg); }
  25%  { transform: translate(76vw, -26px) rotate(8deg); }
  50%  { transform: translate(50vw, 10px) rotate(-8deg); }
  75%  { transform: translate(24vw, -20px) rotate(6deg); }
  100% { transform: translate(-12vw, 4px) rotate(-6deg); }
}
/* 위에서 아래로 (눈) — 옆으로도 조금 흘립니다 */
@keyframes ambFall {
  from { transform: translate(0, -12vh) rotate(0deg); }
  to   { transform: translate(-14vw, 106vh) rotate(220deg); }
}
/* 아래에서 위로 (기포·연기) */
@keyframes ambRise {
  from { transform: translate(0, 104vh) scale(0.7); opacity: 0; }
  15%  { opacity: inherit; }
  85%  { opacity: inherit; }
  to   { transform: translate(18px, -10vh) scale(1.15); opacity: 0; }
}
/* 뽁 하고 터집니다 (용암 기포) */
@keyframes ambPop {
  0%   { transform: translate(0, 96vh) scale(0.25); opacity: 0; }
  55%  { transform: translate(4px, 62vh) scale(1); opacity: 0.9; }
  70%  { transform: translate(4px, 58vh) scale(1.5); opacity: 0.55; }
  75%, 100% { transform: translate(4px, 58vh) scale(1.9); opacity: 0; }
}
/* 제자리에서 반짝 (사탕) */
@keyframes ambTwinkle {
  0%, 100% { transform: translateY(70vh) scale(0.4); opacity: 0; }
  35%      { transform: translateY(52vh) scale(1);   opacity: 0.9; }
  70%      { transform: translateY(34vh) scale(0.5); opacity: 0; }
}

.hills {
  position: absolute; left: -5%; right: -5%; bottom: -60px; height: 220px;
  background:
    radial-gradient(70% 100% at 20% 100%, var(--hill-1) 60%, transparent 61%),
    radial-gradient(60% 100% at 70% 100%, var(--hill-2) 60%, transparent 61%),
    radial-gradient(80% 100% at 100% 100%, var(--hill-1) 60%, transparent 61%);
  opacity: 0.55;
}

/* ── 상단바 ─────────────────────────────────────────────── */
/* 로고를 뺀 뒤로는 소리 버튼만 남아서, 여기가 두꺼우면 본문이 통째로 아래로 밀립니다.
   높이를 고정하고 main 의 min-height 계산에도 같은 값을 씁니다. */
.topbar {
  position: relative;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  height: var(--topbar-h);
  max-width: 1360px; margin: 0 auto;
  padding: 0 clamp(14px, 4vw, 36px);
}
.topbar-left, .topbar-right { display: flex; align-items: center; gap: 8px; }
.topbar-left { min-width: 0; flex-wrap: wrap; }
/* 배속·중단은 레이스 화면에서만. 레이스 머리(.race-head)에 두면 상단바 바로 아래에
   버튼 줄이 하나 더 생겨 두 줄로 보입니다 — 상단바 오른쪽에 붙여 한 줄로 만듭니다. */
.topbar-race { display: none; align-items: center; gap: 8px; }
body[data-screen="screen-race"] .topbar-race { display: inline-flex; }

/* ── 미니게임 홈으로 돌아가기 ────────────────────────────
   레이스 중에는 감춥니다. 실수로 누르면 한 판이 통째로 날아가는데,
   그 자리에는 이미 [중단] 이 있습니다 — 그만두려면 그쪽을 쓰는 게 맞습니다.
   .btn 이 <button> 기준으로 쓰여 있어서 <a> 로 쓸 때 필요한 것만 여기서 맞춥니다. */
.btn-home { display: inline-flex; align-items: center; gap: 4px; }
body[data-screen="screen-race"] .btn-home { display: none; }
/* 좁은 화면에서는 화살표만 남깁니다 — 상단바 한 줄에 전적·배속·중단이 같이 섭니다 */
@media (max-width: 560px) {
  .btn-home .home-t { display: none; }
  .btn-home { padding-left: 12px; padding-right: 12px; }
}

/* ── 브금 플레이어 ──────────────────────────────────────
   곡은 파일이 아니라 app.js 의 Bgm 이 WebAudio 로 연주합니다. */
/* ── 소리 ────────────────────────────────────────────────
   상단바에는 단추 두 개(🎵 🔊)만 두고, 나머지는 눌렀을 때 열리는 패널에 담습니다.
   예전처럼 여섯 칸을 늘 펴 두면 레이스 중 배속·중단 단추와 자리를 다툽니다. */
.sound { position: relative; display: flex; align-items: center; gap: 6px; }
.sound .icon-btn.is-on { background: var(--yellow); }
/* 브금이 나오는 동안에는 음표가 살짝 흔들려 '지금 나온다'를 알립니다 */
.sound.is-playing #btn-sound { animation: bgmBob 0.9s ease-in-out infinite alternate; }
.sound-panel {
  position: absolute; z-index: 40; top: calc(100% + 8px); right: 0;
  width: 208px; padding: 10px;
  border: 3px solid var(--ink); border-radius: 14px;
  background: linear-gradient(180deg, #fff, var(--cream-2));
  box-shadow: 0 5px 0 var(--ink);
  animation: dropIn 0.16s ease;
}
.sound-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.sound-sep { margin-top: 10px; padding-top: 9px; border-top: 2px dotted rgba(58, 42, 24, 0.25); }
.sound-label { font-size: 12.5px; font-weight: 900; color: var(--ink); }
.sound-toggle {
  flex: none; padding: 2px 10px;
  border: 2px solid var(--ink); border-radius: 999px;
  background: var(--cream); color: var(--ink-soft);
  font: inherit; font-size: 11px; font-weight: 900; cursor: pointer;
}
.sound-toggle.is-on { background: var(--yellow); color: var(--ink); }
.sound-toggle:active { transform: translateY(1px); }
.sound-track { display: flex; align-items: center; gap: 6px; margin: 7px 0 5px; }
.sound-panel .player-vol { width: 100%; margin: 0; }

.player-btn {
  flex: none; width: 24px; height: 24px; padding: 0;
  display: grid; place-items: center;
  border: 2px solid var(--ink); border-radius: 50%;
  background: var(--cream);
  font-size: 11px; line-height: 1; color: var(--ink);
  cursor: pointer;
}
.player-btn:hover { background: var(--yellow); }
.player-btn:active { transform: translateY(1px); }
.player-title {
  flex: 1 1 auto; min-width: 0; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11.5px; font-weight: 900; color: var(--ink-soft);
}
.sound.is-playing .player-title { color: var(--ink); }
@keyframes bgmBob { from { transform: translateY(0); } to { transform: translateY(-2px); } }
.player-vol {
  flex: none; width: 54px; height: 4px; margin: 0 2px 0 0; padding: 0;
  -webkit-appearance: none; appearance: none;
  border-radius: 999px; background: rgba(58, 42, 24, 0.28); cursor: pointer;
}
.player-vol::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--ink); background: var(--yellow);
}
.player-vol::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--ink); background: var(--yellow);
}
/* 좁은 화면에서는 상단바 버튼이 46px 높이를 넘겨 삐져나옵니다 */
@media (max-width: 620px) {
  .topbar { gap: 6px; }
  .topbar .btn-sm { padding: 5px 10px; font-size: 11.5px; box-shadow: 0 3px 0 var(--ink); }
  .topbar .icon-btn { width: 32px; height: 32px; font-size: 16px; box-shadow: 0 3px 0 var(--ink); }
  /* 상단바는 단추 두 개뿐이라 더 접을 게 없습니다.
     패널만 화면 폭에 맞춰 줄이고, 오른쪽으로 넘치지 않게 붙여 둡니다. */
  .sound-panel { width: min(208px, calc(100vw - 24px)); }
}

.icon-btn {
  width: 36px; height: 36px;
  border: 3px solid var(--ink);
  border-radius: 50%;
  background: linear-gradient(180deg, #fff, #ffe9b8);
  box-shadow: 0 4px 0 var(--ink);
  font-size: 18px; cursor: pointer;
}
.icon-btn:active { transform: translateY(4px); box-shadow: 0 0 0 var(--ink); }

/* ── 레이아웃 ───────────────────────────────────────────── */
main { max-width: 1360px; margin: 0 auto; padding: 4px clamp(14px, 4vw, 36px) 10px; }
.screen { display: none; animation: dropIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); }
.screen.is-active { display: block; }
@keyframes dropIn { from { opacity: 0; transform: translateY(-16px) scale(0.98); } to { opacity: 1; transform: none; } }


/* ── 카드 ───────────────────────────────────────────────── */
.card {
  position: relative;
  background: var(--cream);
  border: 4px solid var(--ink);
  border-radius: 22px;
  padding: clamp(16px, 2.2vw, 24px);
  box-shadow: 0 8px 0 var(--ink), 0 20px 34px rgba(58, 42, 24, 0.25);
}
.card-title {
  margin: 0 0 4px;
  font-size: 19px; font-weight: 900;
  letter-spacing: -0.01em;
}
.card-title.mt { margin-top: 26px; }
.card-title.center { text-align: center; font-size: 24px; }
.card-title .count {
  font-style: normal; font-size: 12px; font-weight: 900;
  color: #fff; background: var(--grass-dk);
  border: 2px solid var(--ink); border-radius: 999px;
  padding: 1px 9px; margin-left: 6px; vertical-align: 2px;
}
.card-title .count:empty { display: none; }
/* 제목 줄 오른쪽 끝에 버튼을 붙입니다 (비우기) */
.card-title.title-row { display: flex; align-items: center; gap: 4px; }
.card-title.title-row .count { margin-left: 2px; }
.card-title.title-row > .btn { margin-left: auto; }
.card-desc { margin: 0 0 14px; font-size: 13.5px; color: var(--ink-soft); font-weight: 700; }

/* 버전 뱃지 — 상단바 소리 버튼 왼쪽. 정보일 뿐이라 눈에 띄지 않게 둡니다. */
.ver-badge {
  flex: none;
  display: inline-grid; place-items: center;
  padding: 3px 9px;
  border: 2px solid var(--ink); border-radius: 999px;
  background: var(--cream);
  font-size: 11px; font-weight: 900; color: var(--ink-soft);
  letter-spacing: 0.02em;
  box-shadow: 0 2px 0 var(--ink);
  user-select: none;
}

textarea {
  width: 100%;
  padding: 12px 14px;
  border: 3px solid var(--ink);
  border-radius: 14px;
  background: #fff;
  color: var(--ink);
  font-family: inherit; font-size: 15px; font-weight: 700; line-height: 1.8;
  resize: vertical;
  box-shadow: inset 0 3px 0 rgba(58, 42, 24, 0.08);
}
textarea:focus { outline: 4px solid var(--yellow); outline-offset: 1px; }

/* ── 버튼 ───────────────────────────────────────────────── */
.btn {
  position: relative;
  /* 404 페이지의 '출발선으로 돌아가기' 는 <button> 이 아니라 <a> 입니다.
     이 시트에는 a 셀렉터가 하나도 없어서, 그 하나만 브라우저 기본 밑줄이 남고
     display:inline 이라 알약 모양이 줄 흐름을 벗어났습니다.
     <button> 은 원래 inline-block 이고 flex 자식이면 어차피 블록화되므로
     여기 두 줄은 버튼 쪽 모양을 바꾸지 않습니다. */
  display: inline-block; text-decoration: none;
  border: 3px solid var(--ink);
  border-radius: 999px;
  padding: 11px 22px;
  font-size: 15px; font-weight: 900;
  color: var(--ink);
  cursor: pointer;
  box-shadow: 0 5px 0 var(--ink), 0 9px 14px rgba(58, 42, 24, 0.22);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
  transition: transform 0.08s, box-shadow 0.08s, filter 0.15s;
  overflow: hidden;
}
.btn::after {
  content: ''; position: absolute; inset: 2px 2px 55% 2px;
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0));
  pointer-events: none;
}
.btn:not(:disabled):hover { filter: brightness(1.06); }
.btn:not(:disabled):active { transform: translateY(5px); box-shadow: 0 0 0 var(--ink); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: 0 5px 0 rgba(58, 42, 24, 0.4); }
.btn-lg { padding: 14px 30px; font-size: 17px; }
.btn-sm { padding: 8px 16px; font-size: 13px; box-shadow: 0 4px 0 var(--ink); }
.btn-sm:not(:disabled):active { transform: translateY(4px); }
/* 제목 줄에 얹는 크기. 반드시 .btn 뒤에 와야 합니다 — 앞에 두면 특이도가 같아
   .btn 의 padding(11px 22px)에 밀려 버튼이 47px 로 부풉니다. */
.btn-xs { padding: 7px 16px; font-size: 13px; box-shadow: 0 4px 0 var(--ink); }
.btn-xs:not(:disabled):active { transform: translateY(4px); box-shadow: 0 0 0 var(--ink); }

.btn-yellow { background: linear-gradient(180deg, #ffe680, var(--yellow) 55%, var(--yellow-dk)); }
.btn-green  { background: linear-gradient(180deg, #b6f07a, var(--grass) 55%, var(--grass-dk)); }
.btn-sky    { background: linear-gradient(180deg, #a9dcff, var(--blue) 55%, var(--blue-dk)); color: #fff; text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); }
.btn-gray   { background: linear-gradient(180deg, #fff, #ddd2c2 60%, #c3b6a4); }
.blink { animation: blinkGlow 1.1s ease-in-out infinite; }
@keyframes blinkGlow {
  0%, 100% { box-shadow: 0 5px 0 var(--ink), 0 9px 14px rgba(58, 42, 24, 0.22), 0 0 0 0 rgba(255, 210, 63, 0.9); }
  50%      { box-shadow: 0 5px 0 var(--ink), 0 9px 14px rgba(58, 42, 24, 0.22), 0 0 0 10px rgba(255, 210, 63, 0); }
}

.btn-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; align-items: center; }
.btn-row-main { margin-top: 16px; }
.center-row { justify-content: center; }
.hint { margin: 10px 0 0; font-size: 13.5px; font-weight: 800; color: var(--red); min-height: 20px; }
/* 비어 있을 때 자리를 차지하면 설정 화면이 한 화면을 넘습니다 */
.hint:empty { display: none; }

/* ══ 로비(설정 화면) ══════════════════════════════════════
   위에서 아래로 ① 참가 선수 등록 → ② 레이스 맵 → ③ 경기 규칙 → START.
   PC 에서만 ②③ 이 나란히 서고, 모바일은 그대로 이어집니다.
   설정 항목은 하나도 빼지 않았습니다 — 자리만 옮겼습니다. */
.lobby { display: flex; flex-direction: column; gap: 16px; }
/* 스티커가 인원 표시(6 / 30) 위에 겹칩니다 — 로비에서만 바깥으로 물립니다 */
.lobby-sec { min-width: 0; }
.lobby-title {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 10px; font-size: 17px; font-weight: 900; color: var(--ink);
}
/* 단계 번호. 배지 자체가 동그라미라 안에는 **맨 숫자**를 넣습니다.
   예전에는 ①②③(동그라미 친 숫자)을 넣어서 원 안에 원이 겹치고 글자가 뭉갰습니다. */
.lobby-no {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  border: 3px solid var(--ink); border-radius: 50%;
  background: var(--yellow);
  font-size: 14px; font-weight: 900; line-height: 1;
  /* 숫자는 글꼴에 따라 좌우 여백이 달라 살짝 왼쪽으로 치우쳐 보입니다 */
  text-indent: 0.03em;
  box-shadow: 0 2px 0 var(--ink);
}
/* 인원은 제목 오른쪽 끝에 붙여 언제든 보이게 합니다 */
.lobby-count {
  margin-left: auto; padding: 2px 10px;
  border: 2px solid var(--ink); border-radius: 999px;
  background: var(--cream-2);
  font-style: normal; font-size: 12.5px; font-weight: 900;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.lobby-count.is-full { background: var(--red); color: #fff; }
/* ── ① 참가 선수 등록 ───────────────────────────────────
   이 칸이 곧 명단입니다. '추가' 버튼이 없어서 적는 대로 아래 칩이 따라옵니다 —
   한 명이든 서른 명 붙여넣기든 같은 칸으로 받습니다. */
.entry-box {
  display: block; width: 100%; box-sizing: border-box;
  /* 캐릭터 고르는 칸과 참가자 넣는 칸은 하는 일이 다릅니다.
     붙여 두면 한 덩어리로 읽혀서 넉넉히 띄웁니다. 카드 상자에 이미 테두리와 그림자가
     있어서 여기에 선을 하나 더 그으면 오히려 어수선해집니다 — 여백으로만 가릅니다. */
  margin-top: 24px;
  min-height: 74px; resize: vertical;
  padding: 10px 14px;
  border: 3px solid var(--ink); border-radius: 18px;
  background: #fff;
  font: inherit; font-size: 15px; font-weight: 800; line-height: 1.55; color: var(--ink);
  box-shadow: inset 0 2px 0 rgba(58, 42, 24, 0.08);
}
.entry-box::placeholder { color: var(--ink-soft); font-weight: 700; opacity: 0.7; }
.entry-box:focus { outline: none; border-color: var(--yellow-dk); }
.entry-foot { display: flex; align-items: center; gap: 14px; margin-top: 8px; }
/* display:flex 가 hidden 을 이깁니다 — 빈 명단에서 줄이 접히도록 다시 눌러 둡니다 */
.entry-foot[hidden] { display: none; }
/* 안내는 왼쪽, [전체 비우기] 는 오른쪽 끝. 안내가 길어져도 버튼을 밀지 않습니다. */
.entry-tip {
  flex: 1 1 auto; min-width: 0;
  font-size: 11.5px; font-weight: 800; color: var(--ink-soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 30명을 넘겨 적었을 때만. 평소 안내와 같은 회색이면 '빠진다' 는 말을 놓칩니다. */
.entry-tip.is-warn { color: var(--red); }
.entry-clear { flex: none; }
/* 좁은 화면 — 안내가 길면 버튼 아래로 접어 내립니다. */
@media (max-width: 620px) {
  .entry-foot { flex-wrap: wrap; gap: 6px 8px; }
  .entry-tip { flex: 1 1 100%; order: 2; white-space: normal; }
  .entry-clear { order: 1; margin-left: auto; }
}
/* 제목 바로 아래 첫 줄이라 위 여백은 거의 두지 않습니다 */
.charline {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 0 0 6px;
}
.charline-label { font-size: 12.5px; font-weight: 900; color: var(--ink-soft); }
.charline-hint { font-size: 11.5px; font-weight: 800; color: var(--ink-soft); opacity: 0.8; }
/* 🎲 랜덤 카드 — 아홉 마리 끝에 붙습니다. 동물이 아니라 '방식' 이라
   테두리를 점선으로 두어 한눈에 다른 성격임을 알립니다. */
.ch-dice { border-style: dashed; border-color: var(--ink-soft); }
/* 예전에는 여기 `.ch-dice .ch-emoji { font-size: 34px; line-height: 50px }` 이 있었습니다.
   기본 .ch-emoji 와 값이 **글자 하나까지 똑같아서** 평상시에는 아무 일도 안 했는데,
   특이도가 (0,2,0) 이라 반응형에서 줄여 놓은 line-height(42px·34px)를 이겼습니다.
   그래서 아이콘 칸만 34px 로 줄고 글줄 상자는 50px 로 남아 이모지가 '랜덤' 글자를 덮었습니다.
   동물 아홉 장은 이미지(.ch-face)라 멀쩡하고 🎲 한 장만 어긋났습니다. 규칙을 지웁니다. */
.ch-dice.is-on { border-style: solid; }
/* ── ②③ 2단 (PC 전용) ────────────────────────────────── */
.lobby-cols { display: flex; flex-direction: column; gap: 16px; }
@media (min-width: 900px) {
  .lobby-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 26px; align-items: start; }
  /* 기본 .charpick 규칙이 이 아래에 있어서 특이도를 올려야 이깁니다.
     아홉 마리 + 🎲 = 열 칸을 한 줄에 세웁니다(1006px / 10 = 100px). */
  #sec-roster .charpick { grid-template-columns: repeat(10, minmax(0, 1fr)); }
}

/* ── ② 맵 카드 + 미리보기 ─────────────────────────────── */
/* 썸네일은 그 테마의 --track-bg / --track-tex 를 그대로 씁니다.
   팔레트 블록 선택자에 .map[data-value="…"] 를 얹어 뒀기 때문에
   새로 만든 이미지가 한 장도 없습니다. */
.maps {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px;
  padding: 8px;
  border: 3px solid var(--ink); border-radius: 16px;
  background: var(--cream-2);
  box-shadow: 0 4px 0 var(--ink);
}
.map {
  display: flex; flex-direction: column; align-items: stretch; gap: 4px;
  padding: 5px 5px 4px;
  border: 2px solid transparent; border-radius: 13px;
  background: rgba(255, 255, 255, 0.5);
  font: inherit; cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.08s;
}
.map span {
  font-size: 12px; line-height: 1.2; font-weight: 900; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.map-thumb {
  display: block; height: 40px;
  border: 2px solid var(--ink); border-radius: 8px;
  background: var(--track-bg);
  background-size: cover;
  overflow: hidden; position: relative;
}
/* 무늬는 의사요소에 얹습니다 — 바탕색과 겹쳐야 실제 트랙처럼 보입니다 */
.map-thumb::before {
  content: ''; position: absolute; inset: 0;
  background: var(--track-tex);
  background-size: var(--track-tex-size, auto);
  background-repeat: var(--track-tex-repeat, repeat);
  background-position: var(--track-tex-pos, 0 0);
  opacity: 0.9;
}
.map:hover { background: #fff; }
.map.is-on {
  border-color: var(--ink); background: var(--yellow);
  transform: translateY(-2px);
  box-shadow: 0 3px 0 var(--ink);
}
.map.is-on span { color: var(--ink); }

.preview {
  position: relative;
  margin-bottom: 10px;
  border: 3px solid var(--ink); border-radius: 14px;
  background: var(--cream);
  box-shadow: 0 4px 0 var(--ink);
  overflow: hidden;
}
.preview-tag {
  position: absolute; z-index: 2; left: 8px; top: 7px;
  padding: 1px 8px;
  border: 2px solid var(--ink); border-radius: 999px;
  background: var(--cream);
  font-size: 10px; font-weight: 900; letter-spacing: 0.02em; color: var(--ink);
}
/* 트랙 자체는 레이스 화면의 .pack-track 과 같은 재료를 씁니다 */
.preview-track {
  position: relative; overflow: hidden;
  height: 96px;
  background: var(--track-bg);
}
.preview-track::before {
  content: ''; position: absolute; inset: 0;
  background: var(--track-tex);
  background-size: var(--track-tex-size, auto);
  background-repeat: var(--track-tex-repeat, repeat);
  background-position: var(--track-tex-pos, 0 0);
  opacity: 0.9; pointer-events: none;
}
/* 결승 게이트 — 레이스 화면과 같은 흑백 체크 */
.preview-track::after {
  content: ''; position: absolute; right: 0; top: 0; bottom: 0; width: 10px;
  background: repeating-conic-gradient(#fff 0 25%, #2b2b2b 0 50%) 0 0 / 10px 10px;
  border-left: 3px solid var(--ink);
}
.preview .runner { position: absolute; bottom: 5px; left: 8px; width: 42px; }
.preview .runner.pv2 { bottom: 31px; left: 30px; }
.preview .runner.pv3 { bottom: 18px; left: 52px; }
/* 3초만 달리고 멈춥니다. is-playing 이 빠지면 출발 자리로 돌아가 idle 로 섭니다.
   셋의 속도를 조금씩 다르게 줘야 한 덩어리로 미끄러지는 티가 안 납니다. */
.preview.is-playing .runner { animation: pvRun 3s linear 1 both; }
.preview.is-playing .runner.pv2 { animation-duration: 3.4s; }
.preview.is-playing .runner.pv3 { animation-duration: 2.7s; }
@keyframes pvRun { to { left: calc(100% - 58px); } }

/* 좁은 화면 — 동물은 가로로 훑고, START 는 아래에 붙어 따라옵니다 */
@media (max-width: 899px) {
  .lobby { gap: 14px; }
  /* 한 줄로 세워 그림과 이름을 크게 두고 옆으로 넘깁니다.
     기본 .charpick 규칙이 이 아래에 있어서 특이도를 올려야 이깁니다. */
  #sec-roster .charpick {
    display: flex; flex-wrap: nowrap;
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
  }
  #sec-roster .charpick .ch { flex: 0 0 92px; scroll-snap-align: center; }
  #sec-roster .ch-icon { width: 56px; height: 56px; }
  #sec-roster .ch-name { font-size: 12.5px; }
  /* 칩 이름이 길어도 줄이 밀려나지 않게 */
  .preview-track { height: 88px; }
  .lobby-start {
    position: sticky; bottom: 0; z-index: 6;
    margin: 0 -14px -14px; padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
    /* 위 10px 만 부드럽게 흐리고 그 아래는 불투명입니다.
       예전에는 38% 까지 투명이라 그 구간에 놓인 요약 줄('오리 · 30명 · 바다 …') 뒤로
       아래를 지나가는 참가자 칩이 비쳐 글자가 겹쳐 읽혔습니다. 세로가 짧고 인원이 많을수록
       심했습니다 — 스크롤로 지나가는 내용을 가려 주는 것이 이 띠의 일입니다. */
    background: linear-gradient(180deg, rgba(255, 250, 240, 0), var(--cream) 10px);
  }
  .btn-start { width: 100%; padding: 15px 20px; font-size: 20px; }
  /* 방문자 배지는 position:fixed(z-index 30) 라 sticky RACE 바(z-index 6) 위로 올라탑니다.
     좁은 화면에서는 화면 아래 같은 자리를 다퉈 RACE 글자를 가립니다(실측 375px: 196x34px 겹침).
     로비에서만 내립니다 — 레이스·결과 화면에는 그대로 보입니다.
     로컬 file:// 에서는 fetch 가 실패해 배지 자체가 안 뜨므로 배포본에서만 보이는 문제였습니다. */
  body[data-screen="screen-setup"] .visits { display: none; }
}

/* ── ③ 레이스 시간 더보기 ─────────────────────────────── */
.dur-wrap { display: flex; gap: 8px; align-items: flex-start; }
.dur-wrap .seg { flex: 1 1 auto; min-width: 0; }
.dur-toggle { flex: none; align-self: stretch; white-space: nowrap; }
/* 아주 좁은 화면에서는 [⋯ 더보기] 를 아랫줄로 내립니다.
   한 줄에 같이 두면 다섯 칸이 각각 29.5px 까지 눌려 '30초'(25.3px) 가 옆 칸을 넘고,
   마지막 '5분' 이 더보기와 11px 겹쳐 그 자리를 누르면 더보기가 눌립니다.
   아랫줄로 내리면 다섯 칸이 50px 씩 확보돼 320px 에서도 글자가 제자리에 들어갑니다. */
@media (max-width: 420px) {
  .dur-wrap { flex-wrap: wrap; }
  .dur-wrap .seg { flex: 1 1 100%; }
  .dur-toggle { width: 100%; align-self: auto; }
}
/* 접혀 있을 때는 자주 쓰는 넷만. 단 '고른 값' 은 접혀 있어도 남깁니다. */
.dur-extra { display: none; }
.dur-wrap.is-open .dur-extra { display: inline-flex; }
.dur-extra.is-on { display: inline-flex; }

/* ── ④ START ──────────────────────────────────────────── */
.lobby-start { display: flex; flex-direction: column; align-items: center; gap: 6px; }

/* ── 현재 설정 한 줄 ──────────────────────────────────────
   RACE 버튼 바로 위에서 마지막으로 확인만 시켜 줍니다. 설정 영역이 아니므로
   테두리도 배경도 크게 주지 않고, 글자도 본문보다 작게 둡니다.
   좁은 화면에서는 항목이 줄바꿈되도록 두어 잘리지 않게 합니다. */
.lobby-summary {
  margin: 0 0 2px;
  padding: 0 10px;
  max-width: 100%;
  font-size: 13px; font-weight: 800; line-height: 1.5;
  color: var(--ink-soft);
  text-align: center;
  word-break: keep-all;
}
.lobby-summary.is-empty { color: var(--ink-soft); opacity: 0.75; font-weight: 700; }
/* 요약 줄의 동물 얼굴 — 글자 높이에 맞춥니다. em 으로 잡아야 폰트 크기를 바꿔도 같이 따라옵니다.
   .ch-face 는 원래 부모 폭을 꽉 채우므로(width:100%) 여기서 크기를 직접 정해 줍니다. */
.lobby-summary .sum-face {
  display: inline-block; width: 1.55em; height: 1.3em;
  vertical-align: -0.38em; margin-right: 1px;
}
.lobby-summary .sum-face .ch-face { width: 100%; height: 100%; aspect-ratio: auto; }
.btn-start {
  width: min(320px, 100%);
  padding: 16px 24px;
  border-radius: 999px;
  font-size: 21px; letter-spacing: 0.06em;
  animation: startPulse 1.8s ease-in-out infinite;
}
@keyframes startPulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.035); }
}
.btn-start:active { animation: none; }

/* ── 옵션 세그먼트 ──────────────────────────────────────── */

.opt-grid { display: flex; flex-wrap: wrap; gap: 22px; margin-top: 10px; }
.opt { display: flex; flex-direction: column; gap: 7px; }
/* 설정 칸이 연달아 오면 사이를 띄웁니다. 안쪽 gap(7px)과 같으면 '레이스 시간' 과
   '돌발 이벤트' 가 한 덩어리로 붙어 보여서, 칸 사이만 두 배 넘게 벌립니다. */
.lobby-sec .opt + .opt { margin-top: 16px; }
.opt-wide { flex: 1 1 100%; }
/* 한 줄에 항목 두 개 — 세로 공간을 아끼려고 나란히 둡니다.
   wrap 을 켜두면 폭이 조금만 모자라도 아래로 떨어져 한 줄만큼(약 70px) 더 먹습니다.
   nowrap 이면 대신 .seg 안쪽 버튼이 줄바꿈돼서 훨씬 얌전하게 줄어듭니다. */
.opt-row2 { flex-direction: row; align-items: flex-start; gap: 12px; flex-wrap: nowrap; }
.opt-sub { display: flex; flex-direction: column; gap: 7px; min-width: 0; flex: 0 1 auto; }
/* 이모지만 있는 세그먼트 (그림체). 반드시 .seg button 뒤 — 앞에 두면 padding 이 밀립니다. */
.seg.seg-emoji button { padding: 5px 9px; font-size: 18px; line-height: 1.1; min-width: 34px; }
.opt-label { font-size: 12.5px; font-weight: 900; color: var(--ink-soft); }
.opt-note { font-style: normal; font-weight: 700; opacity: 0.75; margin-left: 4px; }
.opt-help { margin: 2px 0 0; font-size: 12px; font-weight: 700; color: var(--ink-soft); }
.opt-help b { color: var(--ink); }

.chaos-row { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.chaos-row .event-details { flex: 1 1 240px; min-width: 0; }

/* 이벤트 목록은 늘 펴 둡니다 — 어떤 사건이 터지는지가 이 게임의 재미 자체라
   접어 두면 처음 온 사람이 무슨 게임인지 모르고 지나갑니다. */
.event-details { margin-top: 2px; }

.event-legend {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1px 12px;
  margin: 4px 0 0; padding: 8px 12px;
  border: 3px dashed rgba(58, 42, 24, 0.28);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.5);
  font-size: 11.5px; font-weight: 700; line-height: 1.5; color: var(--ink-soft);
}
.event-legend b { font-size: 13px; margin-right: 3px; }

.seg {
  display: inline-flex; flex-wrap: wrap; gap: 5px; padding: 5px;
  border: 3px solid var(--ink); border-radius: 999px;
  background: var(--cream-2);
  box-shadow: 0 4px 0 var(--ink);
}
/* 선택지가 많으면 알약 모양보다 사각 카드가 줄바꿈에 안정적입니다.
   .seg button 과 특이도가 같으므로 반드시 그 뒤에 와야 적용됩니다. */
/* 줄바꿈이 생기는 선택지(테마·시간)는 flex-wrap 이면 마지막 줄만 짧게 남아
   들쭉날쭉해 보입니다. 격자로 깔아 칸을 고르게 맞춥니다. */
.seg.seg-multi {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
  border-radius: 16px;
}
.seg.seg-multi button { border-radius: 11px; padding: 7px 10px; justify-content: center; }
#opt-theme { grid-template-columns: repeat(4, 1fr); }
#opt-duration { grid-template-columns: repeat(5, 1fr); }
/* ── 캐릭터 고르기 (라디오 카드) ─────────────────────────
   칸마다 라디오 · 실제 게임 얼굴 · 이름을 세로로 쌓습니다.
   아이콘은 이모지가 아니라 characterBadge() 가 그린 그 동물 자신입니다. */
.charpick {
  display: grid;
  /* auto-fill 이면 남는 칸을 빈 트랙으로 잡아 두어서 동물이 몇 마리 없으면
     오른쪽이 통째로 비어 보입니다. auto-fit 은 빈 트랙을 접어 가로를 꽉 채웁니다.
     칸 최소폭은 한 줄에 다섯 마리가 들어가게 잡았습니다 — 84px 이면 일곱 마리가
     들어가서 아홉 마리일 때 둘째 줄에 두 칸만 남아 허전합니다(9 = 5 + 4). */
  grid-template-columns: repeat(auto-fit, minmax(106px, 1fr));
  gap: 6px;
  padding: 8px;
  /* 아홉 마리가 5+4 로 갈라지면 둘째 줄이 허전합니다. 넓은 화면에서는 한 줄로 세웁니다. */
  border: 3px solid var(--ink); border-radius: 16px;
  background: var(--cream-2);
  box-shadow: 0 4px 0 var(--ink);
}
.ch {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 7px 4px 6px;
  border: 2px solid transparent; border-radius: 13px;
  background: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.08s;
}
.ch input { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.ch-dot {
  flex: none; width: 12px; height: 12px;
  border: 2px solid var(--ink-soft); border-radius: 50%;
  background: #fff;
  transition: background 0.12s, border-color 0.12s, box-shadow 0.12s;
}
/* span 은 인라인이라 display 를 안 주면 width/height 가 무시되고
   안쪽 SVG 높이에 끌려다닙니다 */
.ch-icon { display: block; flex: none; width: 50px; height: 50px; }
.ch-icon .ch-face { width: 100%; height: 100%; aspect-ratio: auto; }
/* 배지 아래쪽 색 띠는 레이스에서 등번호 색을 알리는 것이라 여기선 군더더기입니다 */
.ch-emoji { font-size: 34px; line-height: 50px; }
.ch-name {
  /* line-height 를 안 주면 body 의 1.6 을 물려받아 칸마다 5px 씩 헛돕니다 */
  font-size: 12px; line-height: 1.2; font-weight: 900; color: var(--ink-soft);
  white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
/* 십이지 순서를 알려 주는 모서리 표시 (자축인묘…) */
.ch:hover { background: rgba(255, 255, 255, 0.75); }
.ch:active { transform: translateY(1px); }
/* :has() 를 못 쓰는 브라우저를 위해 app.js 가 .is-on 도 같이 걸어 둡니다 */
.ch.is-on {
  background: linear-gradient(180deg, #b6f07a, var(--grass));
  border-color: var(--ink);
}
.ch.is-on .ch-name { color: var(--ink); }
.ch.is-on .ch-dot {
  border-color: var(--ink);
  background: var(--ink);
  box-shadow: inset 0 0 0 2.5px #fff;
}
.ch input:focus-visible ~ .ch-dot { outline: 3px solid var(--ink); outline-offset: 2px; }
.seg button {
  border: 2px solid transparent; background: transparent;
  color: var(--ink-soft);
  padding: 6px 15px; border-radius: 999px;
  font-size: 13px; font-weight: 900; cursor: pointer;
  transition: background 0.12s, color 0.12s, transform 0.08s;
}
.seg button.is-on {
  background: linear-gradient(180deg, #b6f07a, var(--grass));
  border-color: var(--ink);
  color: var(--ink);
}
.seg button:not(.is-on):hover { background: rgba(255, 255, 255, 0.7); color: var(--ink); }

/* ── 예측 카드 ──────────────────────────────────────────── */

/* ── 레이스 화면 ────────────────────────────────────────── */
/* ── 레이스 화면은 '넓은 필드 하나' 입니다 ─────────────────
   순위·실황 패널과 진행바를 걷어내고 트랙이 화면을 꽉 채웁니다.
   시간은 가운데 큰 알약으로 띄웁니다 — 옆에 순위표가 없으니 가장 중요한 정보입니다.

   예전에는 이 값들이 body[data-mode="pack"] 안에 따로 있었습니다. 레이스 방식
   (한 트랙 / 개인 레인) 선택을 없애면서 늘 이 배치가 되어 기본 규칙에 합쳤습니다.
   .side / .progress 마크업은 남아 있지만 여기서 감춰지므로 화면에는 안 나옵니다. */
.race-head {
  display: flex; align-items: center; justify-content: center;
  gap: 12px; margin-bottom: 6px; flex-wrap: wrap;
}
.race-meta {
  display: flex; align-items: baseline; gap: 6px;
  padding: 4px 22px;
  border: 4px solid var(--ink); border-radius: 999px;
  background: var(--cream);
  box-shadow: 0 6px 0 var(--ink), 0 12px 20px rgba(58, 42, 24, 0.22);
}
.race-time {
  font-size: 46px; font-weight: 900; font-variant-numeric: tabular-nums;
  color: #fff;
  -webkit-text-stroke: 0.13em var(--ink); paint-order: stroke fill;
  text-shadow: 0 4px 0 rgba(58, 42, 24, 0.35);
}
.race-unit { font-size: 17px; font-weight: 900; }
.race-target { font-size: 13px; font-weight: 800; color: var(--ink-soft); margin-left: 6px; }

@keyframes stripe { to { background-position: 34px 0; } }

.race-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; align-items: start; }

.track-wrap {
  background: var(--cream);
  border: 4px solid var(--ink); border-radius: 20px;
  padding: 12px;
  box-shadow: 0 8px 0 var(--ink), 0 18px 30px rgba(58, 42, 24, 0.22);
}
/* ── 선두 진행 HUD ────────────────────────────────────────
   월드가 화면보다 길어지면서 '지금 어디쯤인가' 를 화면만 보고는 알 수 없게 됐습니다.
   이 한 줄이 그걸 대신합니다.

     🚩 START ──────●┈┈┈┊┈┈┈▓▓ 🏁 FINISH   47%

   **카메라와 무관합니다.** --lead 는 선두의 실제 코스 진행률(pos/length)이고,
   카메라 위치(--cam-x)와는 아무 관계가 없습니다.

   높이는 22px + 아래 여백 12px = 34px 로, 예전 두 층짜리 HUD 와 **똑같은 세로 자리**를
   씁니다. fitPack() 이 남은 높이로 트랙과 캐릭터 크기를 정하기 때문에, 여기서 자리를
   줄이면 캐릭터 크기가 같이 바뀝니다 — 이번 작업은 HUD 만 바꾸는 것이라 그대로 뒀습니다. */
.track-ruler {
  display: flex; align-items: center; gap: 8px;
  height: 22px; margin-bottom: 12px; padding: 0 4px;
  font-size: 11px; font-weight: 900; color: var(--ink-soft);
}
.ruler-end { flex: none; white-space: nowrap; }
.ruler-bar {
  position: relative; flex: 1 1 auto; min-width: 60px;
  height: 9px;
  border: 2px solid var(--ink); border-radius: 999px;
  background:
    /* ① 선두가 지나온 만큼 채웁니다 */
    linear-gradient(90deg, var(--yellow), var(--yellow-dk)) left / calc(var(--lead, 0) * 100%) 100% no-repeat,
    /* ② 마지막 20% — 아주 옅게만 구분합니다. 막판 연출은 이미 스퍼트·음악이 하고 있어서
          HUD 가 시선을 빼앗으면 안 됩니다. */
    linear-gradient(90deg, transparent 0 80%, rgba(255, 138, 61, 0.13) 80% 100%),
    var(--cream-2);
}
/* 50% 눈금 — START·50%·FINISH 세 지점을 짚어 주되 작게만 */
.ruler-bar::before {
  content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 2px;
  margin-left: -1px;
  background: rgba(58, 42, 24, 0.34);
}
/* 결승선 — 트랙 끝의 체크무늬를 그대로 씁니다. 남은 거리가 곧 결승까지의 거리로 읽힙니다. */
.ruler-bar::after {
  content: ''; position: absolute; right: 0; top: 0; bottom: 0; width: 11px;
  background: repeating-conic-gradient(#fff 0 25%, var(--ink) 0 50%) 0 0 / 5px 5px;
  border-left: 2px solid var(--ink);
  border-radius: 0 999px 999px 0;
}
/* 선두 마커. 좌우 6px 을 물려서 양 끝에서도 동그라미가 막대 밖으로 안 삐져나갑니다. */
.ruler-dot {
  position: absolute; top: 50%; left: calc(6px + var(--lead, 0) * (100% - 12px));
  width: 13px; height: 13px; margin: -7px 0 0 -7px;
  border-radius: 50%;
  background: var(--cream); border: 3px solid var(--ink);
  box-shadow: 0 2px 0 rgba(58, 42, 24, 0.28);
  z-index: 2;
  transition: box-shadow 0.3s ease;
}
.ruler-pct {
  flex: none; min-width: 32px; text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  transition: color 0.3s ease;
}
/* 선두가 80% 를 지난 뒤 — 마커와 숫자에만 가벼운 강조를 줍니다(색을 바꾸지 않고 테를 두름) */
.track-ruler.is-final .ruler-dot { box-shadow: 0 2px 0 rgba(58, 42, 24, 0.28), 0 0 0 3px rgba(255, 138, 61, 0.42); }
.track-ruler.is-final .ruler-pct { color: #c2571a; }

/* 좁은 화면 — 글자를 빼고 깃발만 남겨 한 줄을 지킵니다(막대에 폭을 더 줍니다) */
@media (max-width: 620px) {
  .track-ruler { gap: 6px; }
  .ruler-end .t { display: none; }
  .ruler-pct { min-width: 28px; }
}
/* padding-top 이 없으면 스크롤포트 위쪽 경계가 트랙 상단과 딱 붙어,
   트랙 위로 떠오르는 이벤트 이모지 뱃지(.fx, top:-12px)가 잘립니다.
   margin 으로 되돌려 트랙 위치는 그대로 두고 클립 여유만 만듭니다. */
.lanes {
  max-height: 64vh; overflow-y: auto; overflow-x: hidden;
  padding: 15px 4px 0 0; margin-top: -15px;
}
.lanes::-webkit-scrollbar { width: 10px; }
.lanes::-webkit-scrollbar-track { background: rgba(58, 42, 24, 0.08); border-radius: 5px; }
.lanes::-webkit-scrollbar-thumb { background: var(--ink); border-radius: 5px; }

.runner {
  position: absolute; bottom: 1px;
  /* 12px = 결승선 폭. 테마가 게이트를 넓히면 --gate-extra 로 그만큼 더 물려야
     캐릭터가 게이트 안으로 파고들지 않습니다(기본 테마는 0 이라 그대로입니다). */
  left: calc(var(--p, 0) * (100% - var(--runner-w) - 12px - var(--gate-extra, 0px)));
  width: var(--runner-w);
  /* 맵 미리보기(3마리, 3초)에서만 쓰는 경로입니다. 레이스 트랙 쪽은 아래
     .pack-track .runner 가 transform 으로 덮어씁니다. */
  will-change: left;
}
.runner .fx {
  position: absolute; top: -12px; left: 50%;
  transform: translateX(-50%);
  font-size: 18px; opacity: 0; pointer-events: none;
  filter: drop-shadow(0 2px 0 rgba(58, 42, 24, 0.4));
}
.runner.has-fx .fx { opacity: 1; animation: fxpop 0.5s ease; }
@keyframes fxpop {
  0%   { transform: translateX(-50%) scale(0.3) translateY(6px); }
  60%  { transform: translateX(-50%) scale(1.3) translateY(-3px); }
  100% { transform: translateX(-50%) scale(1) translateY(0); }
}

/* ── 캐릭터 (PNG 스프라이트) ───────────────────────────────
   달리는 자세는 프레임 교체만으로 만듭니다.
   scale / rotate / translateY 로 흔들어서 달리는 척하지 않습니다.
   좌우 이동은 .runner 의 --p 가 담당합니다(아래 .runner 규칙).

   스트립은 프레임 개수만큼 가로로 늘여 깔고 background-position-x 를
   steps() 로 끊어 넘깁니다. 끝값 --sprite-end 는 100% * n/(n-1) 이고
   panda.js 가 인라인으로 넣어 줍니다 (6프레임이면 120%). */
.ch-sprite {
  position: relative;
  width: 100%;
  aspect-ratio: var(--cell-w) / var(--cell-h);
  font-size: calc(var(--runner-w, 64px) * 0.19);   /* 등번호 글자 크기 기준 */
}
.ch-sprite > i { position: absolute; inset: 0; background-repeat: no-repeat; background-position: 0 0; }
.ch-idle { background-size: 100% 100%; }
.ch-run  { background-size: calc(100% * var(--frames)) 100%; display: none; }

/* 달릴 때만 스트립으로 바꾸고 프레임을 넘깁니다.
   완주(is-finished)·설정 화면에는 is-running 이 없으므로 자동으로 대기 그림이 나옵니다. */
.runner.is-running .ch-idle { display: none; }
.runner.is-running .ch-run  {
  display: block;
  animation: chRun var(--run-dur, 0.5s) var(--steps, steps(6)) infinite;
}
@keyframes chRun {
  from { background-position-x: 0; }
  to   { background-position-x: var(--sprite-end, 120%); }
}
/* 배속 — 프레임 넘기는 속도만 바뀝니다.
   고정값이 아니라 캐릭터 기본값(--run-dur)에 곱합니다.
   프레임 수가 캐릭터마다 달라서(오리·판다 6, 돼지 9) 고정하면 돼지만 훨씬 빨라집니다. */
.runner.is-boost .ch-run { animation-duration: calc(var(--run-dur, 0.5s) * 0.58); }
.runner.is-slow  .ch-run { animation-duration: calc(var(--run-dur, 0.5s) * 1.8); }
.runner.is-sleep .ch-run { animation-play-state: paused; }

/* ── 눈길 테마: 스키 ──────────────────────────────────────
   눈 위에서는 달리는 게 아니라 미끄러집니다. 그래서
     · 달리기 프레임을 1번에서 멈춥니다 (다리를 젓지 않습니다)
     · 발밑에 스키 두 짝을 깔고
     · 몸만 살짝 앞으로 기울입니다 (스키 자세. 움직이는 게 아니라 고정 자세입니다)
     · 뒤로 눈보라가 더 크게 흩날립니다
   스키는 바닥에 평평하게 두고 오리만 기울여야 자세가 자연스럽습니다. */
body[data-track="snow"] .runner.is-running .ch-run {
  animation: none;              /* 미끄러지는 중 — 다리 프레임을 넘기지 않습니다 */
  background-position-x: 0;
}
body[data-track="snow"] .ch-run,
body[data-track="snow"] .ch-idle {
  transform: rotate(-7deg);
  transform-origin: 52% 96%;
}
/* 스키 두 짝. 오리가 오른쪽을 보므로 앞(오른쪽) 끝이 더 둥글게 들립니다. */
body[data-track="snow"] .ch-sprite::before,
body[data-track="snow"] .ch-sprite::after {
  content: '';
  position: absolute;
  height: 6.5%;
  min-height: 3px;
  border-radius: 2px 999px 999px 2px;
  box-shadow: 0 1px 0 rgba(30, 60, 90, 0.35);
  pointer-events: none;
}
body[data-track="snow"] .ch-sprite::before {   /* 뒤쪽(멀리 있는) 스키 */
  left: 1%; right: 5%; bottom: 4.2%;
  background: linear-gradient(180deg, #5c9fe6, #2a6fbe);
}
body[data-track="snow"] .ch-sprite::after {    /* 앞쪽 스키 */
  left: 6%; right: 0%; bottom: 0.8%;
  background: linear-gradient(180deg, #ff7c69, #e03a24);
}
/* 뒤로 흩날리는 눈보라 — 기본 먼지보다 크고 멀리 갑니다 */
body[data-track="snow"] .runner.is-running::after {
  width: 17px; height: 17px;
  animation-name: snowSpray;
}
@keyframes snowSpray {
  0%   { opacity: 0.85; transform: translate(2px, 2px) scale(0.3); }
  100% { opacity: 0;    transform: translate(-30px, -14px) scale(1.7); }
}

/* ══════════════════════════════════════════════════════════════
   잔디 테마 전용 연출 — 화창한 봄날 초원
   전부 body[data-track="grass"] 안에만 있어서 다른 테마는 한 픽셀도 안 바뀝니다.
   .track-fx / .track-glow 는 buildPack() 이 테마와 무관하게 만들어 두고
   기본이 display:none 인 훅이라, 빌려 쓰는 데 js 변경이 없습니다.
   ══════════════════════════════════════════════════════════════ */

/* ── 구름 그림자 ──────────────────────────────────────────
   초원이 통째로 균일하면 카펫처럼 보입니다.
   아주 옅은 그림자가 천천히 지나가며 하늘에 구름이 있다는 것만 알려 줍니다. */
body[data-track="grass"] .track-fx {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(280px 140px at 30% 40%, rgba(46, 98, 34, 0.11), transparent 72%),
    radial-gradient(200px 100px at 74% 66%, rgba(46, 98, 34, 0.08), transparent 74%);
  background-size: 700px 100%, 520px 100%;
  animation: grassShade 46s linear infinite;
}
@keyframes grassShade {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 700px 0, 1040px 0; }
}

/* ── 바람에 눕는 잔디 ─────────────────────────────────────
   결이 통째로 정지해 있으면 인조 잔디처럼 보입니다.
   아주 옅은 밝은 결이 천천히 지나가며 바람이 스치는 것만 냅니다. */
body[data-track="grass"] .track-glow {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background: repeating-linear-gradient(88deg,
    transparent 0 26px, rgba(255, 255, 255, 0.07) 30px 38px, transparent 42px 78px);
  background-size: 78px 100%;
  animation: grassWind 15s linear infinite;
}
@keyframes grassWind {
  from { background-position: 0 0; }
  to   { background-position: 156px 0; }
}

/* ── 날리는 잎사귀와 나비 ─────────────────────────────────
   .amb 여섯은 이모지 대신 작은 잎사귀, .amb2 넷은 나비입니다.
   개수는 다른 테마와 같고 늘리지 않습니다. */
body[data-track="grass"] .amb {
  width: var(--amb-1-size); height: calc(var(--amb-1-size) * 0.62);
  border-radius: 60% 0 60% 0;
  background: #86c95c;
}
body[data-track="grass"] .a1 { left: 8%;  animation-duration: 27s; }
body[data-track="grass"] .a2 { left: 25%; animation-duration: 35s; opacity: 0.35; background: #a8db82; }
body[data-track="grass"] .a3 { left: 42%; animation-duration: 23s; }
body[data-track="grass"] .a4 { left: 59%; animation-duration: 39s; opacity: 0.4;  background: #f0d98a; }
body[data-track="grass"] .a5 { left: 76%; animation-duration: 30s; }
body[data-track="grass"] .a6 { left: 92%; animation-duration: 33s; opacity: 0.35; background: #f6c6dd; }
/* 나비는 '아주 가끔' 지나가야 합니다 — 다른 테마(11~17초)보다 훨씬 느리게 둡니다 */
body[data-track="grass"] .amb2 { animation-duration: 58s; }
body[data-track="grass"] .amb2.b1 { animation-delay: -5s; }
body[data-track="grass"] .amb2.b2 { animation-delay: -24s; }
body[data-track="grass"] .amb2.b3 { animation-delay: -41s; }
body[data-track="grass"] .amb2.b4 { animation-delay: -53s; }

/* ── 초원 언덕 ────────────────────────────────────────────*/
body[data-track="grass"] .hills {
  opacity: 0.85;
  background-image:
    radial-gradient(74% 100% at 18% 100%, var(--hill-1) 62%, transparent 63%),
    radial-gradient(58% 100% at 62% 100%, var(--hill-2) 62%, transparent 63%),
    radial-gradient(86% 100% at 100% 100%, var(--hill-1) 62%, transparent 63%);
}

/* ── 캐릭터 접지 + 튀는 풀잎 ──────────────────────────────*/
body[data-track="grass"] .ch-sprite::before {
  content: '';
  position: absolute; left: 21%; right: 21%; bottom: 0.5%;
  height: 7%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(40, 84, 28, 0.32), rgba(40, 84, 28, 0));
  pointer-events: none;
}
/* 풀잎은 동그란 먼지가 아니라 길쭉한 잎 모양입니다 */
body[data-track="grass"] .runner::after {
  width: 9px; height: 5px;
  border-radius: 60% 0 60% 0;
}
body[data-track="grass"] .runner.is-running::after { animation: grassKick 1.2s ease-out infinite; }
body[data-track="grass"] .runner.is-boost::after   { animation-duration: 0.6s; }
body[data-track="grass"] .runner.is-running .ch-sprite::after {
  content: '';
  position: absolute; left: 4%; bottom: 6%;
  width: 7%; height: 4%;
  border-radius: 60% 0 60% 0;
  background: rgba(160, 214, 112, 0.95);
  opacity: 0;
  pointer-events: none;
  animation: grassKick 1.9s ease-out infinite 0.55s;
}
/* 풀잎은 튀었다가 빙글 돌며 떨어집니다 — 앞 45% 에서만 보입니다 */
@keyframes grassKick {
  0%   { opacity: 0.85; transform: translate(0, 0) scale(0.4) rotate(0deg); }
  45%  { opacity: 0;    transform: translate(-21px, -9px) scale(1.1) rotate(150deg); }
  100% { opacity: 0;    transform: translate(-21px, -9px) scale(1.1) rotate(150deg); }
}

/* ── 자연스러운 결승 게이트 ───────────────────────────────
   판정 위치는 engine.js 가 정하고 CSS 를 읽지 않습니다. 보이는 것만 바꿉니다.
   덩굴 기둥에 잎과 꽃이 달린 모양입니다. */
body[data-track="grass"] .pack-track { --gate-extra: 6px; }

body[data-track="grass"] .pack-track::after {
  width: 20px;
  background:
    /* 기둥에 달린 꽃 */
    radial-gradient(4px 4px at 30% 50%, #fff6b0, rgba(255, 220, 120, 0.7) 58%, transparent 74%) 0 0 / 20px 54px repeat-y,
    radial-gradient(4px 4px at 72% 50%, #ffe2ee, rgba(246, 176, 208, 0.7) 58%, transparent 76%) 0 27px / 20px 66px repeat-y,
    /* 잎 */
    radial-gradient(6px 4px at 50% 50%, rgba(122, 190, 78, 0.95), transparent 74%) 0 13px / 20px 31px repeat-y,
    /* 덩굴 기둥 */
    repeating-linear-gradient(90deg,
      #5f9c41 0 5px, #4e8535 5px 9px, #6cab4c 9px 14px, #467a30 14px 20px),
    linear-gradient(180deg, #6cab4c, #46782f);
  border-left: 3px solid #3f6d2b;
  box-shadow:
    -9px 0 15px -8px rgba(60, 110, 40, 0.4),
    inset 2px 0 0 rgba(226, 246, 200, 0.35);
}

/* 모바일 — 가장 비싼 두 가지(구름 그림자·바람)를 정지 그림으로 둡니다 */
@media (max-width: 620px) {
  body[data-track="grass"] .track-fx { animation: none; }
  body[data-track="grass"] .track-glow { animation: none; }
  body[data-track="grass"] .runner.is-running .ch-sprite::after { display: none; }
}

/* ══════════════════════════════════════════════════════════════
   모래 테마 전용 연출 — 여름 해변
   전부 body[data-track="sand"] 안에만 있어서 다른 테마는 한 픽셀도 안 바뀝니다.
   .track-fx / .track-glow 는 buildPack() 이 테마와 무관하게 만들어 두고
   기본이 display:none 인 훅이라, 빌려 쓰는 데 js 변경이 없습니다.
   ══════════════════════════════════════════════════════════════ */

/* ── 모래 위 햇빛 ─────────────────────────────────────────
   한낮 해변은 모래가 균일하지 않고 얼룩덜룩 빛납니다.
   아주 옅은 밝은 얼룩이 천천히 지나가며 그걸 냅니다. */
body[data-track="sand"] .track-fx {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(260px 130px at 28% 38%, rgba(255, 250, 224, 0.16), transparent 72%),
    radial-gradient(190px 96px at 72% 64%, rgba(255, 248, 216, 0.12), transparent 74%);
  background-size: 660px 100%, 500px 100%;
  animation: sandShimmer 41s linear infinite;
}
@keyframes sandShimmer {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 660px 0, 1000px 0; }
}

/* ── 반짝이는 모래알 ──────────────────────────────────────
   젖은 모래알이 햇빛을 튕겨 냅니다. 아주 옅게, 아주 느리게. */
body[data-track="sand"] .track-glow {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  pointer-events: none;
  background:
    radial-gradient(circle 1.3px at 50% 50%, rgba(255, 255, 244, 0.9), transparent 72%),
    radial-gradient(circle 0.9px at 50% 50%, rgba(255, 250, 226, 0.8), transparent 74%);
  background-size: 197px 163px, 131px 107px;
  animation: sandGlint 9s ease-in-out infinite alternate;
}
@keyframes sandGlint {
  from { opacity: 0.2; }
  to   { opacity: 0.75; }
}

/* ── 바다와 파도 ──────────────────────────────────────────
   레이스 화면은 카드가 대부분을 덮습니다 — 배경이 보이는 곳은 위쪽 띠와 좌우 여백뿐입니다.
   그래서 수평선을 그 띠에 걸치게 올립니다. 하늘 아래 바다, 그 앞이 백사장(카드)입니다.
   .hills 를 바다 띠로 씁니다 — 요소를 새로 만들지 않습니다. */
body[data-track="sand"] .hills {
  top: 0; bottom: auto; height: 100%;
  opacity: 1;
  background:
    /* 밀려오는 파도 — 축을 세로(177/183deg)로 둬야 띠가 '가로 물결' 이 됩니다.
       92deg 로 두면 축이 가로여서 띠가 세로 막대가 됩니다(한 번 그렇게 나왔습니다).
       두 줄이 서로 다른 주기·속도로 해안 쪽(아래)으로 흘러 되풀이가 안 보입니다. */
    repeating-linear-gradient(177deg,
      transparent 0 13px, rgba(255, 255, 255, 0.5) 15px 17px, transparent 19px 34px),
    repeating-linear-gradient(183deg,
      transparent 0 19px, rgba(255, 255, 255, 0.32) 22px 24px, transparent 27px 47px),
    /* 바다 — 수평선에서 멀수록 진합니다. 위 6% 는 하늘로 남깁니다. */
    linear-gradient(180deg,
      transparent 0 6%, #1a79b2 6% 6.6%, #2a95c8 6.6% 9%,
      #47b2da 9% 13%, #7fd0e8 13% 16.5%, transparent 16.5%);
  background-size: 100% 10.5%, 100% 10.5%, 100% 100%;
  background-repeat: repeat-x, repeat-x, no-repeat;
  background-position: 0 6.6%, 0 6.6%, 0 0;
  animation: sandWaves 19s linear infinite;
}
/* 파도만 아래로 흐릅니다(해안 쪽). 타일 높이의 정수배라 이음매 없이 되돌아옵니다. */
@keyframes sandWaves {
  from { background-position: 0 6.6%, 0 6.6%, 0 0; }
  to   { background-position: 0 calc(6.6% + 34px), 0 calc(6.6% + 47px), 0 0; }
}
/* 물가 — 바다와 백사장이 만나는 젖은 모래 */
body[data-track="sand"] .hills::before {
  content: '';
  position: absolute; left: 0; right: 0; top: 16.5%;
  height: 26px;
  background: linear-gradient(180deg, rgba(226, 206, 168, 0.9), rgba(226, 206, 168, 0));
}

/* ── 반짝이는 모래(배경) + 갈매기 ─────────────────────────
   .amb 여섯은 모래알 반짝임, .amb2 넷은 멀리 나는 갈매기입니다. */
body[data-track="sand"] .amb {
  width: var(--amb-1-size); height: var(--amb-1-size);
  border-radius: 50%;
  background: #fffdf0;
  box-shadow: 0 0 4px 1px rgba(255, 244, 200, 0.8);
}
body[data-track="sand"] .a1 { left: 10%; } body[data-track="sand"] .a2 { left: 27%; }
body[data-track="sand"] .a3 { left: 44%; } body[data-track="sand"] .a4 { left: 61%; }
body[data-track="sand"] .a5 { left: 78%; } body[data-track="sand"] .a6 { left: 93%; }
/* 갈매기는 아주 가끔 지나갑니다 */
body[data-track="sand"] .amb2 { animation-duration: 66s; filter: saturate(0.2) brightness(0.7); }
body[data-track="sand"] .amb2.b1 { top: 5%;  animation-delay: -4s; }
body[data-track="sand"] .amb2.b2 { top: 9%;  animation-delay: -22s; }
body[data-track="sand"] .amb2.b3 { top: 3%;  animation-delay: -41s; }
body[data-track="sand"] .amb2.b4 { top: 12%; animation-delay: -58s; }

/* ── 발자국 ───────────────────────────────────────────────
   .runner::before 는 부스트 잔상 자리인데 평소에는 opacity 0 이라 비어 있습니다.
   여기에 뒤로 갈수록 옅어지는 발자국 두 줄을 그립니다 — **DOM 을 새로 만들지 않습니다.**
   자국은 러너를 따라다니는 짧은 꼬리입니다. 지나간 자리마다 영구히 남기려면
   자국마다 요소나 캔버스가 필요해서, 눈길과 같은 이유로 이렇게 했습니다. */
body[data-track="sand"] .runner.is-running::before {
  right: 58%;
  top: auto; bottom: 3%;
  width: 74px; height: 9px;
  border-radius: 0;
  opacity: 0.45;
  background:
    radial-gradient(4px 3px at 50% 50%, rgba(150, 116, 66, 0.9), transparent 72%) 0 0 / 19px 5px repeat-x,
    radial-gradient(4px 3px at 50% 50%, rgba(150, 116, 66, 0.75), transparent 72%) 9px 100% / 19px 5px repeat-x;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 78%);
  mask-image: linear-gradient(90deg, transparent, #000 78%);
  animation: none;
}
/* 부스트 때는 원래 잔상으로 돌아갑니다 — 속도감 신호라 살립니다. */
body[data-track="sand"] .runner.is-boost::before {
  right: 82%;
  top: 34%; bottom: auto;
  width: 54px; height: 26%;
  border-radius: 999px;
  opacity: 0.7;
  background: linear-gradient(90deg, transparent, var(--racer, #fff));
  -webkit-mask-image: none; mask-image: none;
  animation: trail 0.32s linear infinite;
}

/* ── 캐릭터 접지 + 튀는 모래 ──────────────────────────────*/
body[data-track="sand"] .ch-sprite::before {
  content: '';
  position: absolute; left: 21%; right: 21%; bottom: 0.5%;
  height: 7%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(122, 92, 48, 0.34), rgba(122, 92, 48, 0));
  pointer-events: none;
}
body[data-track="sand"] .runner.is-running::after { animation: sandKick 1.1s ease-out infinite; }
body[data-track="sand"] .runner.is-boost::after   { animation-duration: 0.6s; }
body[data-track="sand"] .runner::after { width: 8px; height: 8px; }
body[data-track="sand"] .runner.is-running .ch-sprite::after {
  content: '';
  position: absolute; left: 4%; bottom: 5%;
  width: 6%; aspect-ratio: 1;
  border-radius: 50%;
  background: var(--dust);
  opacity: 0;
  pointer-events: none;
  animation: sandKick 1.7s ease-out infinite 0.5s;
}
/* 모래는 가볍게 튀었다 금방 떨어집니다 — 앞 45% 에서만 보입니다 */
@keyframes sandKick {
  0%   { opacity: 0.8; transform: translate(0, 0) scale(0.3); }
  45%  { opacity: 0;   transform: translate(-19px, -7px) scale(1.2); }
  100% { opacity: 0;   transform: translate(-19px, -7px) scale(1.2); }
}

/* ── 해변 결승 표지 ───────────────────────────────────────
   판정 위치는 engine.js 가 정하고 CSS 를 읽지 않습니다. 보이는 것만 바꿉니다.
   나무 기둥에 깃발 천이 걸린 모양입니다. */
body[data-track="sand"] .pack-track { --gate-extra: 7px; }

body[data-track="sand"] .pack-track::after {
  width: 21px;
  background:
    /* 기둥에 감긴 깃발 천 — 빨강·흰 */
    repeating-linear-gradient(0deg,
      rgba(226, 84, 74, 0.9) 0 14px, rgba(255, 250, 240, 0.95) 14px 28px),
    /* 나무 결 */
    repeating-linear-gradient(90deg,
      #a97748 0 4px, #94663c 4px 7px, #b7834f 7px 11px),
    linear-gradient(180deg, #b7834f, #8a5f36);
  background-size: 100% 100%, auto, auto;
  border-left: 3px solid #6f4a29;
  box-shadow:
    -9px 0 15px -8px rgba(120, 80, 40, 0.45),
    inset 2px 0 0 rgba(255, 246, 230, 0.35);
}

/* 모바일 — 가장 비싼 두 가지(햇빛 얼룩·파도)를 정지 그림으로 둡니다 */
@media (max-width: 620px) {
  body[data-track="sand"] .track-fx { animation: none; }
  body[data-track="sand"] .hills { animation: none; }
  body[data-track="sand"] .runner.is-running .ch-sprite::after { display: none; }
}

/* ══════════════════════════════════════════════════════════════
   사탕 테마 전용 연출
   전부 body[data-track="candy"] 안에만 있어서 다른 테마는 한 픽셀도 안 바뀝니다.
   .track-fx / .track-glow 는 buildPack() 이 테마와 무관하게 만들어 두고
   기본이 display:none 인 훅이라, 빌려 쓰는 데 js 변경이 없습니다.

   원칙: 화려한 것은 전부 가장자리와 배경에 둡니다.
   달리는 가운데는 웨하스 결만 남겨 캐릭터가 가장 강하게 보이게 합니다.
   ══════════════════════════════════════════════════════════════ */

/* ── 설탕가루 반짝임 ──────────────────────────────────────
   천천히 나타났다 사라집니다. 두 층의 주기가 달라 겹치는 자리가 계속 바뀝니다. */
body[data-track="candy"] .track-fx {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(circle 1.7px at 50% 50%, rgba(255, 255, 255, 0.95), transparent 72%),
    radial-gradient(circle 1.1px at 50% 50%, rgba(255, 232, 246, 0.9), transparent 74%);
  background-size: 167px 139px, 109px 97px;
  animation: candySparkle 6s ease-in-out infinite alternate;
}
@keyframes candySparkle {
  from { opacity: 0.2; }
  to   { opacity: 0.9; }
}
/* 두 번째 반짝임 층 — 주기가 달라 '천천히 나타났다 사라지는' 느낌이 균일해지지 않습니다 */
body[data-track="candy"] .track-glow {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(circle 2.2px at 50% 50%, rgba(255, 244, 214, 0.9), transparent 70%);
  background-size: 251px 211px;
  animation: candySparkle2 8.5s ease-in-out infinite alternate 1.6s;
}
@keyframes candySparkle2 {
  from { opacity: 0.85; }
  to   { opacity: 0.15; }
}

/* ── 솜사탕 구름 ──────────────────────────────────────────
   흰 구름을 파스텔 솜사탕으로. 셋의 색을 다르게 줍니다. */
body[data-track="candy"] .cloud,
body[data-track="candy"] .cloud::before,
body[data-track="candy"] .cloud::after { background: #ffc9e4; }
body[data-track="candy"] .cloud { opacity: 0.75; filter: blur(1px) drop-shadow(0 6px 0 rgba(214, 130, 176, 0.22)); }
body[data-track="candy"] .c2,
body[data-track="candy"] .c2::before,
body[data-track="candy"] .c2::after { background: #d9c9ff; }
body[data-track="candy"] .c3,
body[data-track="candy"] .c3::before,
body[data-track="candy"] .c3::after { background: #c9f0e4; }

/* ── 사탕 언덕 ────────────────────────────────────────────
   .hills 를 파스텔 줄무늬 언덕으로 씁니다. 요소를 새로 만들지 않습니다. */
body[data-track="candy"] .hills {
  opacity: 0.85;
  background-image:
    repeating-linear-gradient(64deg,
      rgba(255, 255, 255, 0.5) 0 9px, rgba(255, 255, 255, 0) 9px 18px),
    radial-gradient(74% 100% at 18% 100%, var(--hill-1) 62%, transparent 63%),
    radial-gradient(58% 100% at 62% 100%, var(--hill-2) 62%, transparent 63%),
    radial-gradient(86% 100% at 100% 100%, var(--hill-1) 62%, transparent 63%);
}

/* ── 반짝이는 설탕가루(배경) ──────────────────────────────
   .amb / .amb2 열 개를 이모지 대신 네 갈래 반짝임으로 씁니다.
   개수는 다른 테마와 같고 늘리지 않습니다. */
body[data-track="candy"] .amb,
body[data-track="candy"] .amb2 {
  background:
    linear-gradient(0deg, transparent 42%, #fff 42% 58%, transparent 58%),
    linear-gradient(90deg, transparent 42%, #fff 42% 58%, transparent 58%);
  border-radius: 0;
  filter: drop-shadow(0 0 4px rgba(255, 214, 240, 0.9));
}
body[data-track="candy"] .a1 { left: 9%;  width: 9px;  height: 9px; }
body[data-track="candy"] .a2 { left: 26%; width: 5px;  height: 5px; opacity: 0.7; }
body[data-track="candy"] .a3 { left: 43%; width: 12px; height: 12px; }
body[data-track="candy"] .a4 { left: 60%; width: 6px;  height: 6px; opacity: 0.75; }
body[data-track="candy"] .a5 { left: 77%; width: 8px;  height: 8px; }
body[data-track="candy"] .a6 { left: 93%; width: 5px;  height: 5px; opacity: 0.65; }
body[data-track="candy"] .amb2.b1 { width: 4px; height: 4px; }
body[data-track="candy"] .amb2.b2 { width: 7px; height: 7px; }
body[data-track="candy"] .amb2.b3 { width: 3px; height: 3px; }
body[data-track="candy"] .amb2.b4 { width: 6px; height: 6px; }

/* ── 캐릭터 접지 + 달릴 때 설탕가루 ───────────────────────
   웨하스 위에서도 그림자가 없으면 캐릭터가 떠 보입니다. 아주 옅게만 깝니다. */
body[data-track="candy"] .ch-sprite::before {
  content: '';
  position: absolute; left: 21%; right: 21%; bottom: 0.5%;
  height: 7%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(104, 60, 34, 0.32), rgba(104, 60, 34, 0));
  pointer-events: none;
}
body[data-track="candy"] .runner::after {
  width: 7px; height: 7px;
  border-radius: 0;
  background:
    linear-gradient(0deg, transparent 40%, var(--dust) 40% 60%, transparent 60%),
    linear-gradient(90deg, transparent 40%, var(--dust) 40% 60%, transparent 60%);
  filter: drop-shadow(0 0 4px rgba(255, 200, 236, 0.95));
}
body[data-track="candy"] .runner.is-running::after { animation: candyTrail 1.4s ease-out infinite; }
body[data-track="candy"] .runner.is-boost::after   { animation-duration: 0.7s; }
body[data-track="candy"] .runner.is-running .ch-sprite::after {
  content: '';
  position: absolute; left: 5%; bottom: 6%;
  width: 6%; aspect-ratio: 1;
  background:
    linear-gradient(0deg, transparent 40%, #fff6fb 40% 60%, transparent 60%),
    linear-gradient(90deg, transparent 40%, #fff6fb 40% 60%, transparent 60%);
  filter: drop-shadow(0 0 4px rgba(255, 210, 240, 0.95));
  opacity: 0;
  pointer-events: none;
  animation: candyTrail 2.2s ease-out infinite 0.7s;
}
/* 앞 35% 구간에서만 보이고 나머지는 쉽니다 — 계속 흩날리면 캐릭터보다 눈에 띕니다 */
@keyframes candyTrail {
  0%   { opacity: 0.9; transform: translate(0, 0) scale(0.3) rotate(0deg); }
  35%  { opacity: 0;   transform: translate(-22px, -12px) scale(1.1) rotate(120deg); }
  100% { opacity: 0;   transform: translate(-22px, -12px) scale(1.1) rotate(120deg); }
}

/* ── 막대사탕 결승 아치 ───────────────────────────────────
   판정 위치는 engine.js 가 정하고 CSS 를 읽지 않습니다. 보이는 것만 바꿉니다.
   빨강·흰 사선 줄무늬(캔디케인)에 광택을 넣고, 위아래 끝을 둥글려 아치처럼 보이게 합니다. */
body[data-track="candy"] .pack-track { --gate-extra: 10px; }

body[data-track="candy"] .pack-track::after {
  width: 24px;
  border-radius: 0 11px 11px 0;
  background:
    /* 광택 — 막대사탕 표면에 비치는 빛 */
    linear-gradient(90deg, rgba(255, 255, 255, 0.55) 0 4px, transparent 4px 9px,
      rgba(255, 255, 255, 0.25) 9px 12px, transparent 12px),
    /* 캔디케인 사선 줄무늬 */
    repeating-linear-gradient(52deg,
      #ec4258 0 11px, #fff6f8 11px 22px),
    linear-gradient(180deg, #ffd9e4, #ffb8cc);
  border-left: 3px solid #fff6f8;
  box-shadow:
    -10px 0 18px -9px rgba(236, 66, 88, 0.5),
    inset 0 6px 10px -6px rgba(255, 255, 255, 0.9),
    inset 0 -6px 10px -6px rgba(196, 62, 88, 0.45);
}

/* 모바일 — 반짝임 두 층을 정지 그림으로 둡니다 */
@media (max-width: 620px) {
  body[data-track="candy"] .track-fx { animation: none; opacity: 0.5; }
  body[data-track="candy"] .track-glow { animation: none; opacity: 0.4; }
  body[data-track="candy"] .runner.is-running .ch-sprite::after { display: none; }
}

/* ══════════════════════════════════════════════════════════════
   눈길 테마 전용 연출
   전부 body[data-track="snow"] 안에만 있어서 다른 테마는 한 픽셀도 안 바뀝니다.
   .track-fx / .track-glow 는 buildPack() 이 테마와 무관하게 만들어 두고
   기본이 display:none 인 훅이라, 빌려 쓰는 데 js 변경이 없습니다.

   주의: 눈길은 이미 .ch-sprite::before/::after 를 스키에 쓰고 있습니다.
   그래서 접지 그림자와 스키 자국은 다른 자리를 씁니다.
   ══════════════════════════════════════════════════════════════ */

/* ── 바닥에 드리운 구름 그림자 ────────────────────────────
   설원이 통째로 균일하면 종이처럼 보입니다.
   아주 옅은 푸른 얼룩이 천천히 지나가며 눈밭에 굴곡이 있다는 것만 알려 줍니다. */
body[data-track="snow"] .track-fx {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(240px 120px at 30% 40%, rgba(120, 160, 200, 0.13), transparent 72%),
    radial-gradient(180px 90px at 74% 62%, rgba(120, 160, 200, 0.10), transparent 74%);
  background-size: 620px 100%, 470px 100%;
  animation: snowShade 34s linear infinite;
}
@keyframes snowShade {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 620px 0, 940px 0; }
}

/* ── 반짝이는 눈 ──────────────────────────────────────────
   햇빛에 눈 결정이 반짝이는 정도입니다. 아주 옅게, 아주 느리게. */
body[data-track="snow"] .track-glow {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  pointer-events: none;
  background:
    radial-gradient(circle 1.6px at 50% 50%, rgba(255, 255, 255, 0.9), transparent 72%),
    radial-gradient(circle 1.1px at 50% 50%, rgba(226, 244, 255, 0.8), transparent 74%);
  background-size: 211px 173px, 139px 109px;
  animation: snowSparkle 7s ease-in-out infinite alternate;
}
@keyframes snowSparkle {
  from { opacity: 0.25; }
  to   { opacity: 0.85; }
}

/* ── 눈송이 ───────────────────────────────────────────────
   .amb 여섯 + .amb2 넷을 이모지 대신 동그란 눈송이로 씁니다.
   크기와 낙하 속도를 하나하나 다르게 줘야 '내리는 눈'으로 보입니다.
   (전부 같은 크기·속도면 커튼처럼 보입니다) */
body[data-track="snow"] .amb,
body[data-track="snow"] .amb2 {
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 0 4px 1px rgba(214, 236, 252, 0.75);
}
body[data-track="snow"] .a1 { left: 7%;  width: 8px;   height: 8px;   animation-duration: 21s; }
body[data-track="snow"] .a2 { left: 23%; width: 5px;   height: 5px;   animation-duration: 29s; opacity: 0.7; }
body[data-track="snow"] .a3 { left: 39%; width: 10px;  height: 10px;  animation-duration: 17s; }
body[data-track="snow"] .a4 { left: 55%; width: 4px;   height: 4px;   animation-duration: 34s; opacity: 0.6; }
body[data-track="snow"] .a5 { left: 71%; width: 7px;   height: 7px;   animation-duration: 24s; }
body[data-track="snow"] .a6 { left: 88%; width: 5.5px; height: 5.5px; animation-duration: 31s; opacity: 0.75; }
body[data-track="snow"] .amb2.b1 { width: 3px;   height: 3px;   animation-duration: 39s; }
body[data-track="snow"] .amb2.b2 { width: 4.5px; height: 4.5px; animation-duration: 27s; }
body[data-track="snow"] .amb2.b3 { width: 2.5px; height: 2.5px; animation-duration: 44s; }
body[data-track="snow"] .amb2.b4 { width: 6px;   height: 6px;   animation-duration: 23s; }

/* ── 주변 — 눈 언덕과 눈 덮인 소나무 ──────────────────────
   .hills 를 눈 언덕으로 씁니다. 요소를 새로 만들지 않습니다. */
body[data-track="snow"] .hills {
  opacity: 0.9;
  background-position: 0 100%;
  background-repeat: no-repeat;
  background-size: 100% 220px;
  background-image:
    radial-gradient(74% 100% at 18% 100%, #f2f9ff 62%, transparent 63%),
    radial-gradient(58% 100% at 62% 100%, #dbeaf7 62%, transparent 63%),
    radial-gradient(86% 100% at 100% 100%, #eaf4fd 62%, transparent 63%);
}
/* 소나무 실루엣 — 언덕 위에 몇 그루만. 큰 장식을 여러 개 두면 시선이 분산됩니다. */
/* 레이스 화면은 카드가 대부분을 덮습니다 — 배경이 보이는 곳은 위쪽 띠와 좌우 여백뿐입니다.
   소나무를 언덕 위(아래쪽)에 두면 영영 안 보이므로 그 띠에 걸치도록 올립니다. */
body[data-track="snow"] .hills { top: 0; bottom: auto; height: 100%; }
body[data-track="snow"] .hills::before,
body[data-track="snow"] .hills::after {
  content: '';
  position: absolute; top: 4%;
  background:
    linear-gradient(180deg, transparent 0 8%, #7fa8c4 8% 100%);
  clip-path: polygon(50% 0, 62% 34%, 56% 34%, 70% 66%, 62% 66%, 78% 100%, 22% 100%, 38% 66%, 30% 66%, 44% 34%, 38% 34%);
  opacity: 0.55;
}
body[data-track="snow"] .hills::before { left: 7%;  width: 62px; height: 96px; }
body[data-track="snow"] .hills::after  { left: 82%; width: 48px; height: 74px; top: 7%; opacity: 0.42; }

/* ── 스키 자국 ────────────────────────────────────────────
   눈길 캐릭터는 스키를 신고 있으므로 '발자국' 이 아니라 스키 자국이 맞습니다.
   .runner::before 는 부스트 잔상 자리인데 평소에는 opacity 0 이라 비어 있습니다.
   여기에 뒤로 뻗는 두 줄 홈을 그립니다 — **DOM 을 새로 만들지 않습니다.**
   자국은 러너를 따라다니는 짧은 꼬리입니다. 바닥에 영구히 남기려면 자국마다
   요소나 캔버스가 필요해서, 요청하신 'DOM 과도 생성 금지' 와 맞바꿨습니다. */
body[data-track="snow"] .runner.is-running::before {
  right: 62%;
  top: auto; bottom: 4%;
  width: 62px; height: 6px;
  border-radius: 0;
  opacity: 0.75;
  background:
    linear-gradient(90deg, transparent, rgba(96, 136, 176, 0.85)) 0 0 / 100% 2px no-repeat,
    linear-gradient(90deg, transparent, rgba(96, 136, 176, 0.7)) 0 100% / 100% 2px no-repeat;
  animation: none;
}
/* 부스트 때는 원래 잔상으로 돌아갑니다 — 속도감을 알려 주는 신호라 살립니다. */
body[data-track="snow"] .runner.is-boost::before {
  right: 82%;
  top: 34%; bottom: auto;
  width: 54px; height: 26%;
  border-radius: 999px;
  opacity: 0.7;
  background: linear-gradient(90deg, transparent, var(--racer, #fff));
  animation: trail 0.32s linear infinite;
}

/* ── 겨울 결승선 ──────────────────────────────────────────
   판정 위치는 engine.js 가 정하고 CSS 를 읽지 않습니다. 보이는 것만 바꿉니다.
   흑백 체크는 그대로 두되(결승선인 걸 알아봐야 합니다) 위에 눈을 얹고 고드름을 답니다. */
body[data-track="snow"] .pack-track { --gate-extra: 6px; }

body[data-track="snow"] .pack-track::after {
  width: 20px;
  background:
    /* 기둥 위에 얹힌 눈 */
    radial-gradient(circle 11px at 50% 100%, #ffffff 0 100%, transparent 0) 0 0 / 20px 13px repeat-x,
    /* 아래로 자란 고드름 */
    linear-gradient(180deg, rgba(198, 230, 250, 0.95) 0 60%, transparent 60%) 3px 100% / 5px 16px repeat-x,
    /* 얼음 낀 체크무늬 */
    repeating-conic-gradient(#eef8ff 0 25%, #6f93ad 0 50%) 0 0 / 10px 10px,
    linear-gradient(180deg, #dceefb, #b9d6ea);
  border-left: 3px solid #7fa8c4;
  box-shadow: -8px 0 14px -8px rgba(120, 165, 205, 0.55);
}

/* 모바일 — 가장 비싼 두 가지(구름 그림자·반짝임)를 끄고 정지 그림으로 둡니다 */
@media (max-width: 620px) {
  body[data-track="snow"] .track-fx { animation: none; }
  body[data-track="snow"] .track-glow { animation: none; opacity: 0.5; }
}

/* ══════════════════════════════════════════════════════════════
   우주 테마 전용 연출
   전부 body[data-track="space"] 안에만 있어서 다른 테마는 한 픽셀도 안 바뀝니다.
   .track-fx / .track-glow 는 buildPack() 이 테마와 무관하게 만들어 두고
   기본이 display:none 인 훅이라, 빌려 쓰는 데 js 변경이 없습니다.
   ══════════════════════════════════════════════════════════════ */

/* ── 플랫폼 위를 지나가는 에너지 스캔 ─────────────────────
   금속판이 통째로 정지 그림이면 죽은 바닥처럼 보입니다.
   아주 옅은 띠 하나가 천천히 지나가며 판이 살아 있다는 것만 알려 줍니다. */
body[data-track="space"] .track-fx {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background: linear-gradient(100deg,
    transparent 0 40%, rgba(140, 214, 255, 0.055) 47% 53%, transparent 60%);
  background-size: 240% 100%;
  animation: spaceScan 13s linear infinite;
}
@keyframes spaceScan {
  from { background-position: -60% 0; }
  to   { background-position: 160% 0; }
}

/* ── 가장자리 네온 맥동 ───────────────────────────────────
   배경 레이어 하나하나에 따로 애니메이션을 걸 방법이 CSS 에 없어서,
   맥동시킬 네온만 별도 요소에 담아 opacity 를 흔듭니다. 9초 왕복이라 번쩍이지 않습니다. */
body[data-track="space"] .track-glow {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  pointer-events: none;
  box-shadow:
    inset 0 5px 12px -6px rgba(90, 226, 255, 0.55),
    inset 0 -5px 12px -6px rgba(206, 150, 255, 0.5);
  animation: spaceNeon 4.5s ease-in-out infinite alternate;
}
@keyframes spaceNeon {
  from { opacity: 0.45; }
  to   { opacity: 1; }
}

/* ── 별 ───────────────────────────────────────────────────
   .sunburst 는 원래 회전하는 햇살입니다. 우주에서는 '먼 별밭'으로 씁니다 —
   요소를 새로 만들지 않으려는 것이고, 크기·밝기가 다른 층 넷을 겹쳐
   같은 별이 격자로 반복되지 않게 합니다. 아주 느리게 흘러 깊이감을 냅니다. */
body[data-track="space"] .sunburst {
  top: -20vh; left: -20vw;
  width: 140vw; height: 140vh;
  transform: none;
  background:
    radial-gradient(circle 1.6px at 50% 50%, rgba(255, 255, 255, 0.95) 0 100%, transparent 0),
    radial-gradient(circle 1.1px at 50% 50%, rgba(198, 226, 255, 0.75) 0 100%, transparent 0),
    radial-gradient(circle 2.3px at 50% 50%, rgba(255, 244, 214, 0.9) 0 100%, transparent 0),
    radial-gradient(circle 0.8px at 50% 50%, rgba(226, 236, 255, 0.5) 0 100%, transparent 0);
  background-size: 137px 113px, 89px 79px, 331px 277px, 61px 53px;
  animation: spaceDrift 210s linear infinite;
}
/* 아주 느립니다. 층마다 속도가 달라 시차가 생기고, 그게 깊이감이 됩니다.
   타일 크기의 정수배만큼 움직여 이음매 없이 되돌아옵니다. */
@keyframes spaceDrift {
  from { background-position: 0 0, 0 0, 0 0, 0 0; }
  to   { background-position: -274px 0, -178px 0, -331px 0, -122px 0; }
}

/* 성운 — 흰 구름이 뜨면 우주가 아닙니다. 아주 옅은 색 얼룩으로 바꿉니다. */
body[data-track="space"] .cloud,
body[data-track="space"] .cloud::before,
body[data-track="space"] .cloud::after { background: rgba(96, 84, 196, 0.28); }
body[data-track="space"] .cloud { opacity: 0.5; filter: blur(22px); }
body[data-track="space"] .c2 { background: rgba(40, 118, 176, 0.26); }
body[data-track="space"] .c3 { background: rgba(150, 70, 150, 0.22); }

/* ── 멀리 있는 큰 행성 ────────────────────────────────────
   .hills 를 언덕 대신 행성 하나로 씁니다(요소를 새로 만들지 않으려고).
   레이스 화면은 카드가 대부분을 덮으므로 위쪽 띠에 걸치도록 올립니다.
   캐릭터보다 눈에 띄면 안 되므로 채도와 밝기를 낮게 둡니다. */
body[data-track="space"] .hills {
  top: 0; bottom: auto; height: 100%;
  opacity: 1;
  background: none;
}
body[data-track="space"] .hills::before {
  content: '';
  position: absolute;
  right: 6%; top: -9%;
  width: 260px; height: 260px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 32% 28%, rgba(120, 146, 200, 0.5), rgba(52, 62, 104, 0.5) 52%, rgba(20, 24, 44, 0.55) 100%),
    repeating-linear-gradient(96deg,
      rgba(88, 106, 158, 0.16) 0 7px, transparent 7px 19px);
  box-shadow: inset -22px -14px 40px rgba(6, 8, 18, 0.75);
  opacity: 0.5;
}
/* 고리 — 행성만 있으면 밋밋합니다. 아주 옅게만 겁니다. */
body[data-track="space"] .hills::after {
  content: '';
  position: absolute;
  right: -2%; top: 4%;
  width: 340px; height: 78px;
  border-radius: 50%;
  border: 3px solid rgba(150, 176, 226, 0.16);
  transform: rotate(-14deg);
}

/* ── 가까운 별 ────────────────────────────────────────────
   .amb 여섯을 이모지 대신 반짝이는 점으로 씁니다. 크기를 서로 다르게 줍니다. */
body[data-track="space"] .amb {
  width: var(--amb-1-size); height: var(--amb-1-size);
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 0 5px 1px rgba(190, 224, 255, 0.8);
}
body[data-track="space"] .a1 { left: 8%;  width: 4px;   height: 4px; }
body[data-track="space"] .a2 { left: 24%; width: 2px;   height: 2px; opacity: 0.6; }
body[data-track="space"] .a3 { left: 41%; width: 5px;   height: 5px; }
body[data-track="space"] .a4 { left: 58%; width: 2.5px; height: 2.5px; opacity: 0.7; }
body[data-track="space"] .a5 { left: 74%; width: 3.5px; height: 3.5px; }
body[data-track="space"] .a6 { left: 91%; width: 2px;   height: 2px; opacity: 0.55; }

/* ── 유성 ─────────────────────────────────────────────────
   .amb2 넷을 유성으로 씁니다. '가끔' 이어야 하므로 한 주기의 앞 6% 에서만 보이고
   나머지는 투명합니다. 넷의 시작 시각을 흩어 평균 11초에 하나씩 지나갑니다. */
body[data-track="space"] .amb2 {
  width: 2px; height: 2px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 0 6px 2px rgba(200, 230, 255, 0.9);
  animation-name: spaceMeteor;
  animation-duration: 44s;
  animation-timing-function: linear;
}
/* 꼬리 — 진행 방향 뒤로 뻗습니다 */
body[data-track="space"] .amb2::after {
  content: '';
  position: absolute; right: 0; top: 50%;
  width: 74px; height: 1.5px;
  transform: translateY(-50%) rotate(-27deg);
  transform-origin: right center;
  background: linear-gradient(270deg, rgba(214, 238, 255, 0.85), transparent);
}
body[data-track="space"] .amb2.b1 { top: 6%;  animation-delay: -3s; }
body[data-track="space"] .amb2.b2 { top: 21%; animation-delay: -14s; }
body[data-track="space"] .amb2.b3 { top: 12%; animation-delay: -25s; }
body[data-track="space"] .amb2.b4 { top: 30%; animation-delay: -36s; }
@keyframes spaceMeteor {
  0%   { opacity: 0;    transform: translate(14vw, -6vh); }
  1%   { opacity: 0.95; }
  6%   { opacity: 0;    transform: translate(-46vw, 26vh); }
  100% { opacity: 0;    transform: translate(-46vw, 26vh); }
}

/* ── 캐릭터 접지 + 달릴 때 별가루 ─────────────────────────
   금속 판 위에 그림자가 없으면 캐릭터가 떠 보입니다. 아주 옅게만 깝니다.
   입자는 뒤쪽 발밑에서 납니다 — 얼굴을 가리지 않는 자리입니다. */
body[data-track="space"] .ch-sprite::before {
  content: '';
  position: absolute; left: 22%; right: 22%; bottom: 0.5%;
  height: 7%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0));
  pointer-events: none;
}
body[data-track="space"] .runner::after {
  width: 6px; height: 6px;
  background: var(--dust);
  box-shadow: 0 0 7px 2px rgba(150, 214, 255, 0.85);
}
body[data-track="space"] .runner.is-running::after { animation: spaceTrail 1.4s ease-out infinite; }
body[data-track="space"] .runner.is-boost::after   { animation-duration: 0.7s; }
body[data-track="space"] .runner.is-running .ch-sprite::after {
  content: '';
  position: absolute; left: 5%; bottom: 5%;
  width: 5%; aspect-ratio: 1;
  border-radius: 50%;
  background: rgba(226, 244, 255, 0.95);
  box-shadow: 0 0 6px 2px rgba(160, 220, 255, 0.8);
  opacity: 0;
  pointer-events: none;
  animation: spaceTrail 2.2s ease-out infinite 0.7s;
}
/* 앞 35% 구간에서만 보이고 나머지는 쉽니다 — 계속 흩날리면 캐릭터보다 눈에 띕니다 */
@keyframes spaceTrail {
  0%   { opacity: 0.9; transform: translate(0, 0) scale(0.3); }
  35%  { opacity: 0;   transform: translate(-22px, -10px) scale(1.1); }
  100% { opacity: 0;   transform: translate(-22px, -10px) scale(1.1); }
}

/* ── 네온 에너지 결승 게이트 ──────────────────────────────
   판정 위치는 engine.js 가 정하고 CSS 를 읽지 않습니다. 여기서는 보이는 것만 바꿉니다.
   폭을 넓힌 만큼 --gate-extra 로 러너를 물려 1등이 게이트에 잘리지 않게 합니다. */
body[data-track="space"] .pack-track { --gate-extra: 9px; }

body[data-track="space"] .pack-track::after {
  width: 23px;
  background:
    /* 게이트 안을 흐르는 에너지 */
    repeating-linear-gradient(90deg,
      rgba(120, 236, 255, 0.9) 0 2px, transparent 2px 6px,
      rgba(206, 150, 255, 0.85) 6px 8px, transparent 8px 13px),
    linear-gradient(180deg, rgba(24, 40, 72, 0.9), rgba(12, 20, 40, 0.95));
  border-left: 3px solid rgba(140, 240, 255, 0.9);
  box-shadow:
    -14px 0 22px -10px rgba(110, 220, 255, 0.6),
    inset 0 0 14px rgba(120, 220, 255, 0.35);
}

/* 모바일 — 가장 비싼 두 가지(스캔·별 흐름)를 끄고 정지 그림으로 둡니다 */
@media (max-width: 620px) {
  body[data-track="space"] .track-fx { animation: none; }
  body[data-track="space"] .sunburst { animation: none; }
  body[data-track="space"] .runner.is-running .ch-sprite::after { display: none; }
}

/* ══════════════════════════════════════════════════════════════
   바다 테마 전용 연출
   변수만으로는 안 되는 것들(caustics·기포·해초·해저 결승 게이트)을 여기 모읍니다.
   전부 body[data-track="ocean"] 안에만 있어서 다른 테마는 한 픽셀도 안 바뀝니다.

   **공통 코드를 안 건드립니다.** .track-fx / .track-glow 는 용암 작업 때 만든 훅인데
   buildPack() 이 테마와 무관하게 트랙마다 하나씩 만들어 두고 기본은 display:none 입니다.
   바다는 그 두 개를 그대로 빌려 쓰기 때문에 js 변경이 없습니다.
   ══════════════════════════════════════════════════════════════ */

/* ── caustics — 물 위에서 굴절된 햇빛 ─────────────────────
   서로 다른 각도·주기의 옅은 밝은 띠 셋을 겹쳐 각각 다른 속도로 흘립니다.
   겹치는 자리가 계속 바뀌어 되풀이가 안 보입니다.
   캐릭터에 filter 를 걸지 않습니다 — 그러면 캐릭터가 흐려집니다.
   .track-fx 는 러너보다 뒤에 그려지므로 캐릭터 위에 얹히지 않습니다. */
body[data-track="ocean"] .track-fx {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  /* 굵은 띠로 만들면 나뭇결처럼 보입니다. 잘게 그은 두 방향을 겹쳐 그물눈을 만들고,
     서로 다른 속도로 흘려서 겹치는 자리가 계속 바뀌게 합니다.
     알파를 0.055 → 0.10 으로 올렸습니다. 예전 배경(밝은 모래) 위에서는 이만큼만
     올려도 눈이 아팠는데, 지금은 바탕이 짙은 물빛이라 이 정도가 되어야 보입니다. */
  background:
    repeating-linear-gradient(104deg,
      rgba(255, 255, 255, 0) 0 11px, rgba(255, 255, 255, 0.10) 20px, rgba(255, 255, 255, 0) 30px 44px),
    repeating-linear-gradient(-68deg,
      rgba(226, 250, 255, 0) 0 10px, rgba(226, 250, 255, 0.10) 18px, rgba(226, 250, 255, 0) 27px 41px),
    /* 셋째 층은 직선 대신 옅은 빛무리로 둡니다 — 직선만 겹치면 물결빛이 아니라
       비 내리는 것처럼 보입니다. 이 층이 교차점을 둥글게 뭉쳐 줍니다. */
    radial-gradient(44px 17px at 50% 50%, rgba(255, 255, 255, 0.075), transparent 72%);
  background-size: auto, auto, 197px 154px;
  /* 빛은 수면에서 들어옵니다 — 아래로 갈수록 옅어져야 깊이가 읽힙니다.
     이 마스크가 '위가 밝고 아래가 어둡다'를 한 번 더 못박아 줍니다. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.55) 46%, rgba(0, 0, 0, 0.12) 78%, transparent 92%);
  mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.55) 46%, rgba(0, 0, 0, 0.12) 78%, transparent 92%);
  animation: seaCaustics 19s linear infinite;
}
@keyframes seaCaustics {
  from { background-position: 0 0, 0 0, 0 0; }
  to   { background-position: 40px 15px, -37px 8px, 197px -154px; }
}

/* ── 수면 + 햇빛 광선 ─────────────────────────────────────
   트랙 위쪽 절반에만 겁니다. 아래로 갈수록 사라지므로 캐릭터가 달리는
   가운데는 거의 건드리지 않습니다. 광선은 폭이 아주 느리게 벌어졌다 좁아집니다. */
body[data-track="ocean"] .track-fx::before {
  content: '';
  position: absolute; left: -10%; right: -10%; top: 0;
  height: 62%;
  pointer-events: none;
  background:
    /* 수면 바로 아래의 밝은 띠 */
    linear-gradient(180deg, rgba(224, 255, 255, 0.42) 0, rgba(224, 255, 255, 0.10) 14%, transparent 34%),
    /* 비스듬히 내리꽂히는 빛기둥 — 굵기를 서로 다르게 두 겹 */
    repeating-linear-gradient(96deg,
      transparent 0 46px, rgba(216, 252, 255, 0.13) 54px 78px, transparent 88px 132px),
    repeating-linear-gradient(93deg,
      transparent 0 78px, rgba(236, 255, 255, 0.08) 92px 112px, transparent 126px 214px);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.6) 40%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.6) 40%, transparent 100%);
  transform-origin: 50% 0;
  animation: seaRays 26s ease-in-out infinite alternate;
}
@keyframes seaRays {
  from { transform: scaleX(1)     translateX(-6px); opacity: 0.85; }
  to   { transform: scaleX(1.06)  translateX(10px); opacity: 1; }
}

/* ── 지나가는 물고기 실루엣 ───────────────────────────────
   **순수 배경입니다.** 게임 오브젝트가 아니고 클릭도 충돌도 레이스 계산도 없습니다 —
   .track-fx 는 pointer-events:none 이고 러너보다 뒤에 그려집니다.
   DOM 을 만들지 않으려고 배경 이미지 두 장(큰 물고기·작은 물고기)을 한 요소에 얹고
   background-position 만 옮깁니다. 이동 구간을 화면 밖까지 아주 넓게 잡아서
   대부분의 시간에는 보이지 않습니다 — '가끔 한 마리 지나간다'가 목적입니다. */
body[data-track="ocean"] .track-fx::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 68 30'%3E%3Cg fill='%23052b3c'%3E%3Cpath d='M22 8 L29 1 L34 9 Z'/%3E%3Cpath d='M30 22 L36 29 L24 27 Z'/%3E%3Cellipse cx='25' cy='15' rx='21' ry='9'/%3E%3Cpath d='M44 15 L67 3 L59 15 L67 27 Z'/%3E%3C/g%3E%3Ccircle cx='11' cy='12' r='1.8' fill='%23d6f2fb'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 68 30'%3E%3Cg fill='%2306364a'%3E%3Cpath d='M24 9 L30 3 L34 10 Z'/%3E%3Cellipse cx='26' cy='15' rx='20' ry='8'/%3E%3Cpath d='M45 15 L67 5 L60 15 L67 25 Z'/%3E%3C/g%3E%3Ccircle cx='13' cy='12.5' r='1.6' fill='%23d6f2fb'/%3E%3C/svg%3E");
  background-repeat: no-repeat, no-repeat;
  background-size: 62px 27px, 36px 16px;
  opacity: 0.4;
  animation: seaFish 96s linear infinite;
}
@keyframes seaFish {
  from { background-position: 250% 27%, 420% 63%; }
  to   { background-position: -70% 34%, -120% 57%; }
}

/* ── 트랙 안 기포 + 해초 ──────────────────────────────────
   기포는 배경 무늬를 위로 밀어 올려서 냅니다 — 방울마다 DOM 을 만들지 않습니다.
   해초는 아래 가장자리에 뿌리를 두고 아주 천천히 좌우로 흔듭니다. */
body[data-track="ocean"] .track-glow {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  /* 방울을 조금 키우고 진하게 했습니다 — 짙은 물빛 위에서 예전 값은 거의 안 보였습니다. */
  background:
    radial-gradient(circle 4px at 50% 50%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.16) 66%, transparent 74%),
    radial-gradient(circle 2.5px at 50% 50%, rgba(226, 250, 255, 0.72), transparent 76%),
    radial-gradient(circle 5px at 50% 50%, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.1) 70%, transparent 77%);
  background-size: 143px 118px, 97px 83px, 211px 173px;
  background-repeat: repeat, repeat, repeat;
  animation: seaBubbles 21s linear infinite;
}
/* 위로만 흐릅니다. 타일 높이의 정수배라 이음매 없이 되돌아옵니다. */
@keyframes seaBubbles {
  from { background-position: 0 0, 0 0, 0 0; }
  to   { background-position: 0 -236px, 0 -166px, 0 -346px; }
}
/* 해초 — 모래바닥에 뿌리를 두고 자랍니다. transform-origin 이 바닥이라
   skew 를 걸면 끝(위쪽)이 더 크게 흔들려 물살에 흔들리는 것처럼 보입니다.
   좌우 가장자리에만 둡니다. 잎을 늘리고 키우고(34% → 46%) 진하게 해서
   흔들림이 눈에 들어오게 했습니다. */
body[data-track="ocean"] .track-glow::before,
body[data-track="ocean"] .track-glow::after {
  content: '';
  position: absolute; bottom: 0;
  height: 42%;
  transform-origin: 50% 100%;
  opacity: 0.58;
  animation: seaSway 11s ease-in-out infinite alternate;
}
/* 잎 하나 = radial-gradient 하나입니다. 가로 반지름은 px, 세로 반지름은 100%(칸 높이)로
   두면 뿌리는 굵고 끝은 뾰족한 잎이 됩니다.
   예전에는 linear-gradient(2deg, 초록 0 6px, transparent 6px) 였는데, 이건 '기울기 2도인
   축을 따라 바닥에서 6px 까지만 초록'이라는 뜻이라 실제로는 바닥에 붙은 6px 짜리
   조각만 그려졌습니다 — 그래서 해초가 안 보였고 seaSway 도 알아볼 수 없었습니다. */
/* 예전에는 왼쪽 1.5% · 오른쪽 2.5% 에 한 포기씩 박아 두었습니다. 트랙이 화면 하나였을
   때는 '양쪽 가장자리 장식' 이었지만, 월드가 화면 여섯 개로 길어지자 **월드의 양 끝에만**
   남고 가운데는 텅 비었습니다. 이제 월드 전체에 되풀이해 깝니다.

   주기를 349 / 557 / 431 / 683 / 911px 로 서로 어긋나게 두어, 같은 배치가 다시 만나는
   지점이 월드보다 훨씬 멀어지게 했습니다.
   skewX 는 **세로 거리에 비례**해 밀리므로(가로와 무관) 폭이 아무리 넓어져도
   잎 전체가 같은 각도로 함께 눕습니다 — 해초밭이 물살에 쓸리는 그림 그대로입니다. */
body[data-track="ocean"] .track-glow::before {
  left: 0; right: 0;
  background:
    radial-gradient(5px 100% at 22% 100%, rgba(34, 150, 104, 0.95) 0 100%, transparent 0) 0 100% / 787px 100% repeat-x,
    radial-gradient(4px 100% at 61% 100%, rgba(52, 178, 122, 0.85) 0 100%, transparent 0) 0 100% / 941px 72% repeat-x,
    radial-gradient(4px 100% at 12% 100%, rgba(24, 126, 92, 0.9) 0 100%, transparent 0) 0 100% / 1279px 54% repeat-x;
}
body[data-track="ocean"] .track-glow::after {
  left: 0; right: 0;
  animation-duration: 14s; animation-delay: -4s;
  background:
    radial-gradient(5px 100% at 44% 100%, rgba(40, 164, 112, 0.95) 0 100%, transparent 0) 0 100% / 1103px 90% repeat-x,
    radial-gradient(4px 100% at 78% 100%, rgba(26, 132, 96, 0.85) 0 100%, transparent 0) 0 100% / 1451px 60% repeat-x,
    radial-gradient(3px 100% at 8% 100%, rgba(32, 144, 104, 0.75) 0 100%, transparent 0) 0 100% / 1783px 46% repeat-x;
}
@keyframes seaSway {
  from { transform: skewX(-5.5deg); }
  to   { transform: skewX(5.5deg); }
}

/* 물속이라 흰 구름이 뜨면 '바다 위'로 읽힙니다. 수면에서 어른거리는 빛무리로 바꿉니다. */
body[data-track="ocean"] .cloud,
body[data-track="ocean"] .cloud::before,
body[data-track="ocean"] .cloud::after { background: rgba(180, 236, 250, 0.5); }
body[data-track="ocean"] .cloud { opacity: 0.35; filter: blur(3px); }

/* ── 배경 기포와 물고기 ───────────────────────────────────
   .amb 여섯은 이모지 대신 작은 기포로 씁니다(개수는 다른 테마와 같습니다).
   물고기(.amb2)는 이모지 그대로이고 아주 느리게 지나갑니다. */
body[data-track="ocean"] .amb {
  width: var(--amb-1-size); height: var(--amb-1-size);
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.15) 62%, transparent 72%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
}
/* .amb 은 원래 left 가 없어 전부 왼쪽 끝(static position)에 붙습니다 */
body[data-track="ocean"] .a1 { left: 11%; }
body[data-track="ocean"] .a2 { left: 29%; }
body[data-track="ocean"] .a3 { left: 46%; }
body[data-track="ocean"] .a4 { left: 63%; }
body[data-track="ocean"] .a5 { left: 80%; }
body[data-track="ocean"] .a6 { left: 94%; }
/* 물고기는 '아주 가끔' 지나가야 합니다 — 다른 테마(24~43초)보다 훨씬 느리게 둡니다.
   색을 빼고 어둡게 눌러 실루엣으로 만듭니다. 멀리 있는 물고기라는 인상이 나야
   트랙 안쪽(.track-fx::after)의 물고기와 거리 차이가 읽힙니다. */
body[data-track="ocean"] .amb2 { animation-duration: 78s; filter: saturate(0.15) brightness(0.42); opacity: 0.4; }
body[data-track="ocean"] .amb2.b1 { animation-delay: -6s; }
body[data-track="ocean"] .amb2.b2 { animation-delay: -32s; }
body[data-track="ocean"] .amb2.b3 { animation-delay: -55s; }
body[data-track="ocean"] .amb2.b4 { animation-delay: -71s; }

/* ── 캐릭터 접지 + 달릴 때 기포 ───────────────────────────
   그림자가 없으면 캐릭터가 물에 떠 보입니다. 바탕이 짙어진 만큼 조금 진하게 깔되
   푸른 기가 도는 색으로 둡니다 — 검은 그림자는 물속에서 이물감이 납니다.
   기포는 뒤쪽 발밑에서 납니다 — 얼굴을 가리지 않는 자리입니다. */
body[data-track="ocean"] .ch-sprite::before {
  content: '';
  position: absolute; left: 21%; right: 21%; bottom: 0.5%;
  height: 7%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(6, 48, 68, 0.38), rgba(6, 48, 68, 0));
  pointer-events: none;
}
body[data-track="ocean"] .runner::after {
  width: 9px; height: 9px;
  background: transparent;
  border: 2px solid var(--dust);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
}
body[data-track="ocean"] .runner.is-running::after { animation: seaTrail 1.3s ease-out infinite; }
body[data-track="ocean"] .runner.is-boost::after   { animation-duration: 0.7s; }
body[data-track="ocean"] .runner.is-running .ch-sprite::after {
  content: '';
  position: absolute; left: 5%; bottom: 6%;
  width: 7%; aspect-ratio: 1;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.9);
  opacity: 0;
  pointer-events: none;
  animation: seaTrail 2.1s ease-out infinite 0.6s;
}
/* 뒤로 흐르면서 위로 떠오릅니다. 앞 40% 구간에서만 보이고 나머지는 쉽니다. */
@keyframes seaTrail {
  0%   { opacity: 0.85; transform: translate(0, 0) scale(0.35); }
  40%  { opacity: 0;    transform: translate(-20px, -16px) scale(1); }
  100% { opacity: 0;    transform: translate(-20px, -16px) scale(1); }
}

/* ── 해저 결승 게이트 ─────────────────────────────────────
   산호 기둥에 조개가 박힌 모양입니다. 결승선이라는 건 알아봐야 하므로
   세로 줄무늬 리듬은 유지합니다. 폭을 넓힌 만큼 --gate-extra 로 러너를 물려
   1등 캐릭터가 게이트에 잘리지 않게 합니다(용암에서 겪은 문제입니다). */
body[data-track="ocean"] .pack-track { --gate-extra: 8px; }

body[data-track="ocean"] .pack-track::after {
  width: 22px;
  background:
    radial-gradient(5px 4px at 50% 22%, rgba(255, 246, 232, 0.95), rgba(240, 200, 214, 0.6) 62%, transparent 74%),
    radial-gradient(4px 3px at 50% 68%, rgba(255, 238, 246, 0.9), rgba(226, 182, 200, 0.55) 62%, transparent 76%),
    repeating-linear-gradient(90deg,
      rgba(198, 76, 92, 0.95) 0 6px, rgba(38, 122, 112, 0.92) 6px 11px,
      rgba(214, 116, 78, 0.92) 11px 16px, rgba(28, 100, 100, 0.92) 16px 22px),
    linear-gradient(180deg, rgba(22, 104, 130, 0.6), rgba(10, 64, 86, 0.5));
  background-size: 22px 46px, 22px 62px, auto, auto;
  background-repeat: repeat-y, repeat-y, repeat, no-repeat;
  border-left: 3px solid rgba(236, 250, 255, 0.85);
  box-shadow: -10px 0 16px -9px rgba(210, 246, 255, 0.5);
}

/* 모바일 — 가장 비싼 것들(caustics·기포 흐름·광선·물고기)을 끄고 정지 그림으로 둡니다.
   물고기는 아예 지웁니다 — 좁은 화면에서는 캐릭터와 겹칠 확률이 커집니다. */
@media (max-width: 620px) {
  body[data-track="ocean"] .track-fx { animation: none; }
  body[data-track="ocean"] .track-fx::before { animation: none; }
  body[data-track="ocean"] .track-fx::after { display: none; }
  body[data-track="ocean"] .track-glow { animation: none; }
  body[data-track="ocean"] .runner.is-running .ch-sprite::after { display: none; }
}

/* ══════════════════════════════════════════════════════════════════
   용암 테마 전용 연출
   변수만으로는 안 되는 것들(화산 실루엣·기포·아지랑이·현무암 결승선)을 여기 모읍니다.
   전부 body[data-track="lava"] 안에만 있어서 다른 테마는 한 픽셀도 안 바뀝니다.

   성능 원칙 — DOM 파티클을 만들지 않습니다. 여기서 움직이는 것은
   기존 요소(.amb 10개 · .deco 3개 · .track-fx 1개)의 의사요소뿐이고
   레이스 중에 생성·삭제되지 않습니다.
   ══════════════════════════════════════════════════════════════════ */

/* ── 흐르는 용암 ──────────────────────────────────────────
   트랙 표면(::before)의 이음매 층만 옆으로 아주 천천히 흘립니다.
   background-position 애니메이션이라 레이아웃을 건드리지 않습니다. */
/* 트랙 하나에만 겁니다. background-position 애니메이션이라 레이아웃을 안 건드립니다. */
body[data-track="lava"] .pack-track::before {
  animation: lavaFlow 26s linear infinite;
}
/* 발광층(5·6)만 옆으로 움직입니다. 타일 폭의 정수배라 이음매 없이 되돌아옵니다. */
@keyframes lavaFlow {
  from { background-position: 0 10%, 0 10%, 0 0, 0 90%, 0 90%, 0 100%,    0 0,     0 100%, 16% 34%, 43% 62%, 68% 28%, 86% 58%, 31% 78%, 0 0, 0 0, 0 0, 0 0; }
  to   { background-position: 0 10%, 0 10%, 0 0, 0 90%, 0 90%, 0 100%, 240px 0, -264px 100%, 16% 34%, 43% 62%, 68% 28%, 86% 58%, 31% 78%, 0 0, 0 0, 0 0, 0 0; }
}

/* ── 트랙 위 연출 레이어 ──────────────────────────────────
   .track-fx 자체 = 열기 아지랑이, 의사요소 둘 = 가끔 터지는 기포.
   러너보다 뒤(z-index 0)라 캐릭터를 가리지 않습니다.
   아지랑이에 filter/backdrop-filter 를 쓰면 캐릭터까지 흐려지므로 쓰지 않습니다 —
   대신 따뜻한 반투명 띠가 천천히 지나가게 해서 아른거리는 느낌만 냅니다. */
.track-fx { display: none; }
body[data-track="lava"] .track-fx {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background:
    linear-gradient(100deg,
      transparent 0 18%, rgba(255, 150, 60, 0.07) 26%, transparent 34% 62%,
      rgba(255, 120, 40, 0.05) 70%, transparent 78%);
  background-size: 220% 100%;
  animation: heatHaze 9s ease-in-out infinite alternate;
}
@keyframes heatHaze {
  from { background-position: 0 0; }
  to   { background-position: 100% 0; }
}

/* ── 맥동하는 균열 ────────────────────────────────────────
   배경 레이어 하나하나에 따로 애니메이션을 걸 방법이 CSS 에 없어서,
   '천천히 밝아졌다 어두워지는' 균열만 별도 요소에 담아 opacity 를 흔듭니다.
   요소는 트랙당 하나뿐이고 레이스 중 생성·삭제되지 않습니다.
   13초 왕복이라 번쩍이지 않습니다. */
.track-glow { display: none; }
body[data-track="lava"] .track-glow {
  display: block;
  position: absolute; inset: 0;
  z-index: 0;
  border-radius: 11px;
  overflow: hidden;
  pointer-events: none;
  background:
    linear-gradient(112deg,
      transparent 0 46%, rgba(6,3,2,0.38) 46% 49.1%,
      rgba(178,32,6,0.72) 49.1% 49.7%,
      rgba(255,124,26,0.85) 49.7% 50.1%,
      rgba(255,226,140,0.92) 50.1% 50.5%,
      rgba(255,112,20,0.8) 50.5% 50.9%,
      rgba(170,28,5,0.68) 50.9% 51.5%,
      rgba(6,3,2,0.36) 51.5% 54%, transparent 54%),
    linear-gradient(68deg,
      transparent 0 46%, rgba(6,3,2,0.36) 46% 49.2%,
      rgba(170,28,5,0.6) 49.2% 49.8%,
      rgba(255,116,22,0.78) 49.8% 50.2%,
      rgba(255,214,124,0.86) 50.2% 50.5%,
      rgba(255,104,16,0.72) 50.5% 50.9%,
      rgba(160,24,4,0.58) 50.9% 51.4%,
      rgba(6,3,2,0.34) 51.4% 54%, transparent 54%);
  background-size: 194px 46px, 156px 62px;
  background-repeat: no-repeat, no-repeat;
  background-position: 57% 44%, 12% 71%;
  animation: lavaPulse 6.5s ease-in-out infinite alternate;
}
@keyframes lavaPulse {
  from { opacity: 0.3; }
  to   { opacity: 1; }
}

/* 용암 기포 — 부풀었다 터집니다. 주기를 길게(7~9초) 잡아 '가끔'이 되게 합니다. */
body[data-track="lava"] .track-fx::before,
body[data-track="lava"] .track-fx::after {
  content: '';
  position: absolute;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 34%, #fff2c0, #ffa32c 45%, #d8380a 100%);
  box-shadow: 0 0 12px rgba(255, 140, 40, 0.85);
  opacity: 0;
  will-change: transform, opacity;
}
body[data-track="lava"] .track-fx::before {
  left: 22%; top: 6%;
  animation: lavaBubble 7.5s ease-out infinite;
}
body[data-track="lava"] .track-fx::after {
  left: 68%; bottom: 5%;
  animation: lavaBubble 9.2s ease-out infinite 3.4s;
}
@keyframes lavaBubble {
  0%      { opacity: 0;    transform: translateY(6px) scale(0.2); }
  12%     { opacity: 0.95; transform: translateY(2px) scale(0.7); }
  22%     { opacity: 1;    transform: translateY(-2px) scale(1.15); }
  27%     { opacity: 0.5;  transform: translateY(-6px) scale(1.9); }   /* 터짐 */
  32%,
  100%    { opacity: 0;    transform: translateY(-9px) scale(2.2); }
}

/* ── 하늘 ─────────────────────────────────────────────────
   회전하는 햇살(.sunburst)을 갈색으로 두면 화면 전체가 나무결처럼 보입니다.
   용암에서는 아예 끄고, 검붉은 하늘 + 지평선 발광만 씁니다. */
body[data-track="lava"] .sunburst { display: none; }
/* 구름은 화산재 구름으로. 흰 구름이 떠 있으면 화산 지대로 안 읽힙니다. */
body[data-track="lava"] .cloud,
body[data-track="lava"] .cloud::before,
body[data-track="lava"] .cloud::after {
  background: #2e2320;
}
body[data-track="lava"] .cloud {
  opacity: 0.55;
  filter: drop-shadow(0 6px 0 rgba(0, 0, 0, 0.18));
}

/* ── 배경 화산 ────────────────────────────────────────────
   .hills 를 언덕 대신 원뿔 실루엣 둘로 씁니다(요소를 새로 만들지 않으려고). */
/* 카드(트랙·설정)가 화면의 대부분을 덮습니다 — 레이스 화면 기준으로 배경이 실제로
   보이는 곳은 위쪽 약 150px 띠와 좌우 36px 여백뿐입니다.
   그래서 화산을 아래(지평선)에 두면 영영 안 보입니다. 봉우리가 그 띠에 걸치도록 올리고,
   산기슭은 카드에 가려지게 둡니다 — 멀리 있는 화산 뒤로 경기장이 놓인 모양이 됩니다. */
body[data-track="lava"] .hills {
  top: 0; bottom: auto; height: 100%;
  opacity: 1;
  /* 아래쪽 굳은 용암 지대. 데스크톱에서는 카드에 가려 안 보이고,
     카드가 작은 모바일에서 화면 아래가 휑하게 붉은 것을 막아 줍니다. */
  background: linear-gradient(180deg, transparent 0 42%, #1a0f0c 42%);
}
body[data-track="lava"] .hills::before,
body[data-track="lava"] .hills::after {
  content: '';
  position: absolute;
  background:
    /* 분화구에서 흘러내리는 용암 줄기 */
    linear-gradient(183deg, rgba(255, 140, 45, 0.9) 0 3px, transparent 3px) 47% 0 / 7px 46% no-repeat,
    linear-gradient(172deg, rgba(255, 100, 22, 0.65) 0 2px, transparent 2px) 57% 0 / 6px 34% no-repeat,
    linear-gradient(180deg, #4a231b 0%, #26120e 40%, #150b09 100%);
}
body[data-track="lava"] .hills::before {          /* 큰 화산 */
  left: 3%; top: 6%; width: 34%; height: 62%;
  clip-path: polygon(45% 0, 59% 0, 100% 100%, 0 100%);
}
body[data-track="lava"] .hills::after {           /* 작은 화산 */
  right: 4%; top: 11%; width: 25%; height: 52%;
  clip-path: polygon(42% 0, 62% 0, 100% 100%, 0 100%);
  opacity: 0.9;
}
/* 분화구 불빛 — 봉우리 바로 위가 달아오릅니다 */
body[data-track="lava"] .sky::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(90px 46px at 20% 7%, rgba(255, 128, 36, 0.55), transparent 70%),
    radial-gradient(64px 34px at 84% 12%, rgba(255, 108, 26, 0.42), transparent 70%);
}

/* ── 연기 기둥 ────────────────────────────────────────────
   .deco 는 원래 바닥에 서 있는 이모지 장식입니다. 용암에서는 이모지를 비우고(--deco-*: '')
   화산 위로 피어오르는 연기로 씁니다. */
body[data-track="lava"] .deco {
  bottom: auto;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(96, 76, 70, 0.5), rgba(60, 44, 40, 0) 70%);
  animation: lavaSmoke 13s ease-in infinite;
  opacity: 0;
}
body[data-track="lava"] .d1 { left: 15%; top: 4%;  width: 120px; height: 120px; animation-delay: -2s; }
body[data-track="lava"] .d2 { left: 79%; top: 9%;  width: 84px;  height: 84px;  animation-delay: -7s; }
body[data-track="lava"] .d3 { left: 19%; top: 6%;  width: 96px;  height: 96px;  animation-delay: -11s; }
@keyframes lavaSmoke {
  0%   { opacity: 0;    transform: translate(0, 34px) scale(0.35); }
  22%  { opacity: 0.5; }
  70%  { opacity: 0.3; }
  100% { opacity: 0;    transform: translate(52px, -96px) scale(1.6); }
}

/* ── 불티와 재 ────────────────────────────────────────────
   .amb / .amb2 는 원래 이모지를 띄우는 요소입니다. 용암에서는 이모지를 비우고
   발광하는 점으로 바꿉니다. 개수는 기존 그대로 10개이고 늘리지 않습니다. */
body[data-track="lava"] .amb,
body[data-track="lava"] .amb2 {
  width: var(--amb-1-size); height: var(--amb-1-size);
  border-radius: 50%;
  background: #ffc24a;
  box-shadow: 0 0 7px 1px rgba(255, 140, 40, 0.9);
}
/* 화산재 — 빛나지 않고 천천히 내려앉는 회색 부스러기입니다 */
body[data-track="lava"] .amb2 {
  width: var(--amb-2-size); height: var(--amb-2-size);
  border-radius: 40%;
  background: #6b5d57;
  box-shadow: none;
  animation-duration: 34s;
}
/* .amb(a1~a6)는 원래 left 가 없어 전부 왼쪽 끝(static position)에 붙습니다.
   이모지 하나가 떠갈 때는 티가 안 났지만 불티는 흩어져 있어야 해서 여기서 벌려 둡니다. */
body[data-track="lava"] .a1 { left: 9%; }
body[data-track="lava"] .a2 { left: 27%; }
body[data-track="lava"] .a3 { left: 44%; }
body[data-track="lava"] .a4 { left: 61%; }
body[data-track="lava"] .a5 { left: 78%; }
body[data-track="lava"] .a6 { left: 92%; }

/* ── 결승선 — 검은 현무암 기둥 + 붉은 빛 ──────────────────
   다른 테마의 흑백 체크 대신, 세로로 쪼개진 주상절리에 용암이 비치는 모양입니다.
   결승선이라는 건 알아봐야 하므로 세로 줄무늬 리듬은 유지합니다. */
/* 게이트를 넓힌 만큼 러너를 더 물려야 캐릭터가 안 잘립니다.
   30 + 테두리 3 = 33 → 기본 17 대비 +16. */
body[data-track="lava"] .pack-track { --gate-extra: 16px; }

body[data-track="lava"] .pack-track::after {
  width: 30px;
  background:
    /* 기둥 사이 좁은 틈에서 새는 빛 — 30px 안에 세 줄. 굵거나 밝으면 '주황 세로줄'이 됩니다. */
    repeating-linear-gradient(90deg,
      transparent 0 9px, rgba(255, 142, 50, 0.5) 9px 10px, transparent 10px 11px),
    /* 가로로 갈라진 결 — 없으면 기둥이 밋밋한 세로 줄무늬로만 보입니다 */
    repeating-linear-gradient(0deg,
      transparent 0 21px, rgba(255, 108, 30, 0.13) 21px 22px, transparent 22px 23px),
    /* 기둥 표면 — 주상절리로 보이려면 명암 대비가 있어야 합니다(이전 값은 차이가 거의 없었습니다) */
    repeating-linear-gradient(90deg,
      #1e1310 0 6px, #090504 6px 8px, #261813 8px 13px, #040202 13px 15px),
    /* 위아래 끝만 달아오릅니다 — 게이트 안쪽 깊은 곳의 용암 */
    linear-gradient(180deg, rgba(255, 96, 24, 0.4) 0 4%, #120a08 22%,
                    #070404 50%, #120a08 78%, rgba(255, 96, 24, 0.35) 96% 100%);
  border-left: 3px solid #060303;
  box-shadow:
    -18px 0 30px -10px rgba(255, 84, 20, 0.4),   /* 게이트 앞이 은은하게 달아오릅니다 */
    inset 0 0 20px rgba(255, 80, 16, 0.5);
}

@keyframes lavaLaneFlow {
  from { background-position: 0 14%, 0 14%, 0 0, 0 86%, 0 86%, 0 100%,    0 0,    0 100%, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0; }
  to   { background-position: 0 14%, 0 14%, 0 0, 0 86%, 0 86%, 0 100%, 152px 0, -168px 100%, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0; }
}
/* 결승선 바로 앞이 붉게 달아오릅니다 */
/* ── 트랙 가장자리 ────────────────────────────────────────
   기본 테두리(3px 단색)는 '검은 바닥에 두른 테' 처럼 보입니다.
   두께를 키워 굳은 용암 덩어리처럼 만들고, 아래쪽으로만 약한 빛이 새게 합니다.
   위쪽까지 빛나게 하면 화면이 붉게 떠서 캐릭터가 묻힙니다. */
/* 테두리는 .pack-track(월드)이 아니라 .track-view(창) 에 있습니다.
   여기에 border-width/color 만 적어 두면 border-style 이 none 이라 아예 안 그려졌고,
   설령 그려졌더라도 월드는 화면보다 길어서 좌우 변이 화면 밖에 있고 카메라를 따라
   흘러갔을 것입니다. 창 쪽으로 옮깁니다(.track-view 는 이미 solid 3px 입니다). */
body[data-track="lava"] .track-view {
  border-width: 5px;
  border-color: #0a0605;
}
body[data-track="lava"] .pack-track {
  box-shadow:
    inset 0 3px 0 rgba(255, 255, 255, 0.05),
    inset -34px 0 40px -28px rgba(255, 90, 20, 0.75),   /* 결승 게이트 앞 */
    inset 0 -14px 18px -14px rgba(255, 96, 20, 0.45),   /* 바닥 균열 쪽 반사광 */
    0 3px 0 #1a0f0c,                                     /* 바깥 암반 단차 */
    0 10px 20px -8px rgba(255, 78, 16, 0.35);            /* 가장자리 아래로 새는 빛 */
}

/* ── 접지 그림자 ──────────────────────────────────────────
   가장 어두운 트랙에서는 캐릭터가 바닥에 닿아 있다는 신호가 없어서 허공에 뜬 것처럼 보입니다.
   .ch-sprite::before 는 눈길 테마의 스키가 쓰는 자리인데 서로 다른 테마라 겹치지 않습니다.
   ::before 는 스프라이트 <i> 보다 먼저 그려지므로 캐릭터 뒤에 깔립니다. */
body[data-track="lava"] .ch-sprite::before {
  content: '';
  position: absolute; left: 19%; right: 19%; bottom: 0.5%;
  height: 8.5%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0));
  pointer-events: none;
}

/* ── 달릴 때 뒤로 튀는 불티 ───────────────────────────────
   입자는 모든 테마가 쓰는 .runner::after 와, 용암에서만 쓰는 .ch-sprite::after 둘뿐입니다.
   주기를 다르게(1.5s / 2.3s) 잡아 서로 어긋나게 터지므로 '간헐적'으로 보입니다.
   DOM 을 새로 만들지 않으므로 참가자가 30명이어도 입자는 60개를 넘지 않습니다. */
body[data-track="lava"] .runner::after {
  width: 7px; height: 7px;
  box-shadow: 0 0 7px 1px rgba(255, 130, 30, 0.9);
}
body[data-track="lava"] .runner.is-running::after { animation: lavaSpark 1.5s ease-out infinite; }
body[data-track="lava"] .runner.is-boost::after   { animation-duration: 0.8s; }
body[data-track="lava"] .runner.is-running .ch-sprite::after {
  content: '';
  position: absolute; left: 4%; bottom: 5%;
  width: 8%; aspect-ratio: 1;
  border-radius: 50%;
  background: #ffbe5c;
  box-shadow: 0 0 6px 1px rgba(255, 120, 26, 0.9);
  opacity: 0;
  pointer-events: none;
  animation: lavaSpark 2.3s ease-out infinite 0.7s;
}
/* 앞 30% 구간에서만 보이고 나머지는 쉽니다 — 계속 흩날리면 캐릭터보다 눈에 띕니다 */
@keyframes lavaSpark {
  0%   { opacity: 0.85; transform: translate(0, 0) scale(0.3); }
  30%  { opacity: 0;    transform: translate(-24px, -12px) scale(1); }
  100% { opacity: 0;    transform: translate(-24px, -12px) scale(1); }
}

/* 모바일 — 가장 비싼 두 가지(아지랑이·기포 하나)를 끕니다.
   트랙 표면·화산·결승선은 정지 그림에 가까워 그대로 둡니다. */
@media (max-width: 620px) {
  body[data-track="lava"] .track-fx { animation: none; background: none; }
  /* 가장 비싼 두 가지 — 표면 전체를 다시 칠하는 흐름과 맥동을 끕니다 */

  body[data-track="lava"] .pack-track::before { animation: none; }
  body[data-track="lava"] .track-glow { animation: none; opacity: 0.55; }
  body[data-track="lava"] .track-fx::after { display: none; }
  body[data-track="lava"] .d3 { display: none; }
  /* 좁은 화면은 카드가 작아 배경이 다 보입니다 — 원뿔을 낮춰야 '먼 화산'으로 읽힙니다.
     (데스크톱 비율 그대로 두면 화면 절반짜리 검은 판때기가 됩니다) */
  body[data-track="lava"] .hills::before { left: -5%; top: 13%; width: 52%; height: 30%; }
  body[data-track="lava"] .hills::after  { right: -4%; top: 17%; width: 40%; height: 25%; }
  body[data-track="lava"] .sky::before {
    background:
      radial-gradient(62px 30px at 20% 14%, rgba(255, 128, 36, 0.5), transparent 70%),
      radial-gradient(46px 24px at 82% 18%, rgba(255, 108, 26, 0.4), transparent 70%);
  }
  body[data-track="lava"] .d1 { left: 12%; top: 9%;  width: 84px; height: 84px; }
  body[data-track="lava"] .d2 { left: 76%; top: 13%; width: 64px; height: 64px; }
  /* 캐릭터당 입자를 둘에서 하나로 — 30명이면 60개가 됩니다 */
  body[data-track="lava"] .runner.is-running .ch-sprite::after { display: none; }
}

/* 등번호 — 이미지에 합성하지 않고 캐릭터 위에 얹습니다 */
.ch-bib {
  position: absolute;
  left: 52%; bottom: 6%;
  transform: translateX(-50%);
  min-width: 1.55em; height: 1.55em; line-height: 1.45em;
  padding: 0 0.2em;
  box-sizing: border-box;
  border: 0.12em solid var(--ink);
  border-radius: 999px;
  font-weight: 900; font-size: 1em; color: var(--ink);
  text-align: center; font-variant-numeric: tabular-nums;
  pointer-events: none;
}

/* 참가자 칩 · 시상대 · 동물 고르는 칸에 쓰는 정지 그림 */
.ch-face {
  display: block; width: 100%; aspect-ratio: 325 / 271;
  background-repeat: no-repeat; background-position: center bottom;
  background-size: contain;
}

@keyframes logIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: none; } }

/* ── 카운트다운 / 사진판정 ──────────────────────────────── */
.countdown {
  position: fixed; inset: 0; z-index: 50;
  display: grid; place-items: center;
  background: rgba(58, 42, 24, 0.42);
  backdrop-filter: blur(2px);
}
.countdown span {
  font-size: clamp(100px, 20vw, 230px); font-weight: 900;
  color: var(--yellow);
  -webkit-text-stroke: 0.09em var(--ink); paint-order: stroke fill;
  text-shadow: 0 10px 0 rgba(58, 42, 24, 0.5);
  animation: cdpop 0.85s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes cdpop { 0% { transform: scale(0.2) rotate(-14deg); opacity: 0; } 45% { transform: scale(1.2) rotate(4deg); opacity: 1; } 100% { transform: scale(1) rotate(0); opacity: 1; } }

.photo-finish {
  position: fixed; top: 16%; left: 50%; z-index: 45;
  transform: translateX(-50%);
  padding: 10px 30px;
  border: 4px solid var(--ink); border-radius: 999px;
  background: linear-gradient(180deg, #fff59a, var(--yellow), var(--orange));
  box-shadow: 0 7px 0 var(--ink), 0 16px 30px rgba(58, 42, 24, 0.35);
  font-size: clamp(17px, 3vw, 27px); font-weight: 900;
  animation: pfPulse 0.55s ease-in-out infinite alternate;
}
@keyframes pfPulse { from { transform: translateX(-50%) scale(1) rotate(-1.5deg); } to { transform: translateX(-50%) scale(1.07) rotate(1.5deg); } }

/* ── 결과 ───────────────────────────────────────────────── */
/* 시상대와 순위표를 세로로 쌓으면 결과 화면이 한 화면을 260px 넘습니다.
   넓은 화면에서는 나란히 놓아 그만큼을 통째로 없앱니다. */
.result-grid { display: grid; gap: 18px; align-items: start; }
@media (min-width: 900px) {
  .result-grid { grid-template-columns: minmax(0, 400px) minmax(0, 1fr); gap: 26px; }
  /* 시상대만 두면 순위표(400px 넘음) 옆이 휑해서 요약을 2x2 로 아래에 붙입니다 */
  .result-grid .result-stats { grid-template-columns: repeat(2, 1fr); }
}
.result-list, .result-side { min-width: 0; }

/* ── 요약 띠 ────────────────────────────────────────────
   레이스 전체를 숫자 넷으로 요약합니다(중계 스코어보드의 지표 칸). */
.result-stats {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px;
  max-width: 620px; margin: 14px auto 0;
}
.rstat {
  min-width: 0; text-align: center; padding: 5px 3px 4px;
  background: var(--cream); border: 3px solid var(--ink); border-radius: 12px;
  box-shadow: 0 3px 0 var(--ink);
}
.rstat-k { display: block; font-size: 10.5px; font-weight: 900; color: var(--ink-soft); letter-spacing: -0.02em; }
.rstat-v { display: block; font-size: 15px; font-weight: 900; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── 경기 하이라이트 ──────────────────────────────────────
   그 판에서 실제로 일어난 일 하나만 보여 줍니다(app.js 의 raceHighlight).
   내세울 게 없는 판에서는 아예 안 나옵니다. */
/* 하이라이트는 최대 두 줄입니다 — 폭은 목록이 잡고, 알약은 그 폭을 그대로 씁니다.
   (알약마다 따로 auto 여백을 주면 줄 수에 따라 위아래 간격이 어긋납니다) */
.rhl-list {
  display: flex; flex-direction: column; gap: 6px;
  max-width: 620px; margin: 10px auto 0;
}
.rhighlight {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 6px 12px;
  border: 3px solid var(--ink); border-radius: 999px;
  background: var(--cream);
  box-shadow: 0 3px 0 var(--ink);
}
.rhl-icon { flex: none; font-size: 17px; line-height: 1.25; }
.rhl-label {
  flex: none; margin-top: 1px; padding: 1px 9px;
  border: 2px solid var(--ink); border-radius: 999px;
  background: var(--yellow);
  font-size: 11px; font-weight: 900; letter-spacing: 0.04em;
}
/* 좁은 화면(폭 375px면 글자 자리가 190px 남습니다)에서 말줄임으로 자르면
   하필 문장 끝의 숫자 — 기록·격차·배율 — 가 먼저 날아갑니다. 두 줄까지 접습니다.
   word-break 기본값이면 '2.6' 과 '초' 사이에서도 끊기므로 keep-all 로 두어
   띄어쓴 자리(곧 ' · ' 구분자)에서만 넘어가게 합니다. */
.rhl-text {
  min-width: 0; overflow: hidden;
  white-space: normal; word-break: keep-all;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  font-size: 13px; line-height: 1.35; font-weight: 900; color: var(--ink);
}
/* 종류마다 이름표 색만 다릅니다 — 무슨 하이라이트인지 색으로 먼저 읽히게 */
.rhighlight.is-photo { background: linear-gradient(90deg, #fff1c6, var(--cream)); }
.rhighlight.is-photo .rhl-label { background: #d64500; color: #fff; }
/* BEST SPRINT 는 담는 값이 넷(이름·순위·격차·배율)이라 글자만 한 호 작게 잡습니다 */
.rhighlight.is-sprint .rhl-label {
  background: #ff7a18; color: #fff; letter-spacing: 0.06em;
}
.rhighlight.is-sprint .rhl-text { font-size: 12px; }

/* ── 시상대 ────────────────────────────────────────────
   카드 세 장이 아니라 **진짜 시상대**입니다. 캐릭터가 받침대 위에 올라서고,
   이름·기록은 받침대 앞면의 명패에 새깁니다.
   받침대 높이(--pod-lift)와 캐릭터 크기(--pod-ch)만으로 등수가 읽혀야 합니다. */
/* 3칸 그리드로 두면 참가자가 2명일 때 세 번째 칸이 빈 채로 남아 시상대가
   왼쪽으로 치우칩니다(2명은 validateSetup 이 허용하는 최소 인원이라 실제로 나옵니다).
   flex 로 두면 올라선 인원만큼만 자리를 잡고 가운데로 모입니다. */
.podium {
  --pod-ch: 60px;                    /* 2·3위 캐릭터 폭 — 1위만 여기에 1.35 를 곱합니다 */
  position: relative;
  display: flex; justify-content: center;
  align-items: flex-end; gap: 5px;   /* 셋이 한 덩어리로 읽히도록 바짝 붙입니다 */
  max-width: 620px; margin: 18px auto 0;
  padding-bottom: 5px;               /* 아래 바닥선 자리 */
}
/* 바닥선 — 이게 없으면 받침대 셋이 허공에 뜬 것처럼 보입니다.
   시상대는 '바닥에 놓인 단' 이라는 게 읽혀야 높이 차이가 등수로 읽힙니다. */
.podium::after {
  content: ''; position: absolute; left: 2%; right: 2%; bottom: 0;
  height: 5px; border-radius: 3px;
  background: var(--ink);
  box-shadow: 0 2px 0 rgba(58, 42, 24, 0.18);
}
.pod {
  flex: 1 1 0; min-width: 0; max-width: 190px;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
  /* 등장은 3위 → 2위 → 1위. 마지막(1위)이 0.70+0.50 = 1.20초에 자리를 잡습니다. */
  animation: podUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
}
/* --pod-lift = 명패 위에 더 얹는 높이. 받침대 높이를 고정값으로 두면 이름·기록이
   차지하는 높이가 그보다 커질 때(좁은 화면·긴 이름) 2위와 3위가 같은 높이로 뭉개집니다.
   '내용 높이 + 등수별 여유' 로 쌓으면 어떤 화면에서도 계단이 무너지지 않습니다. */
.pod-1 { order: 2; animation-delay: 0.70s; --pod-lift: 42px; --pod-a: #fff5cc; --pod-b: #f5c132; }
.pod-2 { order: 1; animation-delay: 0.40s; --pod-lift: 20px; --pod-a: #fbfdff; --pod-b: #c4cfda; }
.pod-3 { order: 3; animation-delay: 0.10s; --pod-lift: 0px;  --pod-a: #ffe4c8; --pod-b: #d2955f; }
@keyframes podUp { from { opacity: 0; transform: translateY(34px); } to { opacity: 1; transform: none; } }

/* 캐릭터가 서는 자리. 받침대 위로 5px 물려 발이 윗면을 딛고 선 것처럼 보이게 합니다.
   스프라이트마다 아래쪽에 투명 여백이 2~3% 있어서(발끝이 PNG 맨 아래가 아닙니다)
   0 으로 두면 캐릭터가 받침대에서 살짝 떠 보입니다. */
.pod-figure {
  position: relative; z-index: 1;
  width: var(--pod-ch); margin-bottom: -5px;
}
/* 1위만 1.35배 — 크기 하나로 우승자가 먼저 눈에 들어옵니다 */
.pod-1 .pod-figure { width: calc(var(--pod-ch) * 1.35); }
.pod-char { display: block; width: 100%; }
.pod-char .ch-face { filter: drop-shadow(0 2px 0 rgba(58, 42, 24, 0.25)); }

/* 금빛 후광 — 캐릭터 **뒤**에 깔립니다(스프라이트는 그대로 두고 빛만 더합니다) */
.pod-1 .pod-figure::before {
  content: ''; position: absolute; z-index: -1;
  left: -22%; right: -22%; top: -14%; bottom: -6%;
  border-radius: 50%;
  background: radial-gradient(closest-side,
    rgba(255, 222, 96, 0.95), rgba(255, 186, 30, 0.55) 48%, rgba(255, 186, 30, 0) 76%);
  animation: podGlow 2.4s ease-in-out 1.25s infinite backwards;
}
@keyframes podGlow {
  0%, 100% { opacity: 0.6; transform: scale(0.92); }
  50%      { opacity: 1;   transform: scale(1.08); }
}
.pod-1 .pod-char .ch-face {
  filter: drop-shadow(0 2px 0 rgba(58, 42, 24, 0.25)) drop-shadow(0 0 7px rgba(255, 196, 46, 0.9));
}
.pod-crown {
  position: absolute; left: 50%; top: -20px;
  font-size: 25px; line-height: 1;
  filter: drop-shadow(0 2px 1px rgba(0, 0, 0, 0.28));
  transform: translateX(-50%);
  animation: podCrown 0.45s cubic-bezier(0.34, 1.7, 0.64, 1) 1.10s backwards;
}
@keyframes podCrown {
  from { opacity: 0; transform: translate(-50%, 14px) scale(0.5) rotate(-18deg); }
  to   { opacity: 1; transform: translate(-50%, 0) scale(1) rotate(0); }
}

/* 받침대 = 명패. 높이 = 내용(메달·이름·기록·격차) + 등수별 여유(--pod-lift).
   이름이 길거나 글꼴이 커져도 글자가 잘리지 않고, 등수 간 높이 차이도 유지됩니다. */
.pod-base {
  position: relative; overflow: hidden;
  width: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: calc(8px + var(--pod-lift, 0px)) 6px 9px;
  border: 3px solid var(--ink); border-bottom: none; border-radius: 12px 12px 0 0;
  background: linear-gradient(180deg, var(--pod-a), var(--pod-b));
  box-shadow: inset 0 -7px 0 rgba(58, 42, 24, 0.10);
}
/* 큰 등수는 받침대에 새긴 것처럼 뒤에 깝니다 — 읽는 건 메달과 이름입니다 */
.pod-rank {
  position: absolute; left: 0; right: 0; bottom: -6px;
  font-size: calc(38px + var(--pod-lift, 0px) * 0.45); font-weight: 900; line-height: 1;
  text-align: center; color: rgba(58, 42, 24, 0.13);
  text-shadow: 0 2px 0 rgba(255, 255, 255, 0.5);
  pointer-events: none;
}
.pod-medal { font-size: 19px; line-height: 1; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.25)); }
.pod-1 .pod-medal { font-size: 23px; }
.pod-name {
  max-width: 100%; margin-top: 1px;
  font-size: 13px; font-weight: 900; line-height: 1.25; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pod-1 .pod-name { font-size: 14.5px; }
.pod-time { font-size: 14px; font-weight: 900; line-height: 1.2; font-variant-numeric: tabular-nums; }
.pod-1 .pod-time { font-size: 16px; }
.pod-time small { font-size: 10px; font-weight: 800; color: var(--ink-soft); margin-left: 1px; }
.pod-gap {
  font-size: 10.5px; font-weight: 900; line-height: 1.3;
  color: rgba(58, 42, 24, 0.62); font-variant-numeric: tabular-nums;
}
/* WINNER 는 글자가 아니라 도장처럼 찍힙니다 */
.pod-1 .pod-gap {
  margin-top: 2px; padding: 1px 8px;
  border: 2px solid var(--ink); border-radius: 999px;
  background: #d64500; color: #fff;
  font-size: 10px; letter-spacing: 0.1em;
  animation: podWinner 0.4s cubic-bezier(0.34, 1.7, 0.64, 1) 1.20s backwards;
}
@keyframes podWinner {
  from { opacity: 0; transform: scale(0.4); }
  to   { opacity: 1; transform: scale(1); }
}

/* 요약 띠와 하이라이트, 오른쪽 순위표도 시상대에 이어 차례로 들어옵니다.
   가장 늦은 것(하이라이트)이 1.30+0.40 = 1.70초에 끝납니다. */
.result-stats { animation: podFade 0.4s ease 0.55s backwards; }
.result-list.is-reveal { animation: podFade 0.45s ease 0.85s backwards; }
#result-highlight:not([hidden]) { animation: podFade 0.4s ease 1.30s backwards; }
@keyframes podFade { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* 좁은 화면·낮은 창의 시상대 축소는 아래 반응형 구간에 모아 두었습니다
   (`@media (max-width: 560px)` · `(max-height: 680px)` 등) — 여기서 또 줄이면
   같은 값이 두 군데 흩어져 어느 쪽이 이겼는지 알기 어려워집니다. */

/* overflow 를 쓰면 표 아래 그림자(0 4px 0)가 잘려서 여백을 조금 둡니다 */
.table-wrap { overflow-x: auto; padding-bottom: 6px; }
.pager {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  margin-top: 12px;
}
.pager-info {
  font-size: 13px; font-weight: 900; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; min-width: 96px; text-align: center;
}
.result-table { width: 100%; border-collapse: collapse; font-size: 14px; min-width: 380px; }
.result-table th {
  text-align: left; font-size: 11.5px; font-weight: 900; color: var(--ink-soft);
  padding: 8px 10px; border-bottom: 3px solid var(--ink);
}
.result-table td { padding: 9px 10px; border-bottom: 2px dotted rgba(58, 42, 24, 0.22); font-weight: 800; font-variant-numeric: tabular-nums; }
.result-table tr.top1 td { color: #d64500; font-weight: 900; }
.result-table .cell-name { display: flex; align-items: center; gap: 8px; }
.result-table .cell-name .dot { width: 13px; height: 13px; border-radius: 50%; border: 2px solid var(--ink); }

/* ── 순위표(결과 화면 전용) ─────────────────────────────
   역대 전적 모달도 .result-table 을 쓰므로, 여기 장식은 .result-board 에만 겁니다.
   둥근 모서리를 주려면 border-collapse 를 separate 로 둬야 합니다. */
.result-board {
  border-collapse: separate; border-spacing: 0; min-width: 420px;
  background: var(--cream); border: 3px solid var(--ink); border-radius: 14px;
  box-shadow: 0 4px 0 var(--ink); overflow: hidden;
}
.result-board thead th {
  background: var(--ink); color: var(--cream);
  border-bottom: none; padding: 7px 10px; letter-spacing: 0.02em;
}
.result-board th.c-rank { text-align: center; }
.result-board th.c-time { text-align: right; }
.result-board td { padding: 6px 10px; border-bottom: 2px solid rgba(58, 42, 24, 0.10); }
.result-board tbody tr:nth-child(even) td { background: rgba(58, 42, 24, 0.045); }
.result-board tbody tr:last-child td { border-bottom: none; }
/* 1~3위는 줄 전체에 메달 색을 옅게 깔아 표 안에서도 바로 눈에 띄게 합니다 */
.result-board tbody tr.rank-1 td { background: rgba(255, 210, 63, 0.34); }
.result-board tbody tr.rank-2 td { background: rgba(178, 190, 203, 0.34); }
.result-board tbody tr.rank-3 td { background: rgba(224, 168, 119, 0.30); }

/* 열 너비 — 이름 칸에 max-width:0 을 주면 남는 폭만 차지해 말줄임이 동작합니다 */
.result-board .c-rank { width: 46px; text-align: center; }
.result-board .c-name { width: 100%; max-width: 0; }
.result-board .c-time { width: 88px; text-align: right; white-space: nowrap; }
.result-board .c-gap  { width: 148px; }

.rank-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  border: 2.5px solid var(--ink); background: var(--cream);
  box-shadow: 0 2px 0 var(--ink);
  font-size: 12.5px; font-weight: 900; font-variant-numeric: tabular-nums;
}
tr.rank-1 .rank-badge { background: linear-gradient(180deg, #fff3bf, var(--yellow)); }
tr.rank-2 .rank-badge { background: linear-gradient(180deg, #fff, #cfd6de); }
tr.rank-3 .rank-badge { background: linear-gradient(180deg, #ffe0c0, #dda06d); }

.result-board .cell-name { gap: 7px; min-width: 0; }
.cell-face {
  width: 28px; height: 28px; flex: none; overflow: hidden;
  border-radius: 50%; border: 2.5px solid var(--ink);
  /* 흰색으로 두면 흰 토끼가 배지 안에서 안 보입니다 */
  background: var(--cream-2);
}
.cell-face .ch-face {
  width: 100%; height: 100%; aspect-ratio: auto;
  background-size: 122%; background-position: center 86%;
}
.cell-dot { width: 9px; height: 9px; flex: none; border-radius: 50%; border: 2px solid var(--ink); }
.cell-nick { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.result-board .c-time b { font-size: 14.5px; }
.result-board .c-time small { font-size: 10.5px; font-weight: 800; color: var(--ink-soft); margin-left: 1px; }

/* 1위와의 차이 — 숫자만 보면 감이 안 와서 꼴찌 격차를 100%로 한 막대를 같이 그립니다.
   td 의 width 는 표 알고리즘이 무시할 수 있어서, 안쪽 내용에 min-width 를 걸어
   차이 칸이 확실히 자리를 잡게 합니다(안 그러면 막대가 30px 로 찌그러집니다). */
.gap-wrap { display: flex; align-items: center; gap: 7px; min-width: 150px; }
.gap-bar {
  flex: 1 1 auto; min-width: 28px; height: 9px; overflow: hidden;
  border: 2px solid var(--ink); border-radius: 6px; background: rgba(255, 255, 255, 0.65);
}
.gap-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--yellow), var(--orange)); }
.gap-num { flex: none; font-size: 12px; font-weight: 900; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.gap-lead {
  display: inline-block; padding: 1px 9px;
  background: #d64500; color: #fff; border: 2px solid var(--ink); border-radius: 999px;
  font-size: 11px; font-weight: 900; letter-spacing: 0.06em;
}

/* ── 폭죽 ───────────────────────────────────────────────── */
.confetti { position: fixed; inset: 0; pointer-events: none; z-index: 40; overflow: hidden; }
.confetti i {
  position: absolute; top: -20px;
  width: 10px; height: 16px;
  border: 2px solid rgba(58, 42, 24, 0.5);
  animation: fall linear forwards;
}
@keyframes fall {
  0%   { transform: translateY(-10vh) rotate(0deg); opacity: 1; }
  100% { transform: translateY(108vh) rotate(720deg); opacity: 0.9; }
}

/* ── 한 트랙에 다 같이 (pack 모드) ──────────────────────── */
.lanes.is-pack { overflow: visible; padding-right: 0; }
/* ── 뷰포트와 월드 ────────────────────────────────────────
   레이스는 화면보다 긴 '월드' 에서 벌어지고, 화면은 그 일부를 보여 주는 창입니다.

     .track-view   창(뷰포트). 테두리·바탕색·높이는 여기 있습니다. 화면 폭 그대로.
     .pack-track   월드. 폭이 --world-w 이고 카메라(--cam-x)만큼 옆으로 밀립니다.
                   캐릭터·결승선·바닥 장식·물결 연출이 전부 이 안에 있습니다.

   **가로만 잘라야 합니다.** 이름표와 이벤트 이모지는 트랙 위로 떠오르므로
   overflow:hidden 으로 자르면 같이 잘립니다. CSS 는 한 축만 hidden 으로 둘 수 없어서
   clip-path 로 좌우만 자릅니다(위아래는 9999px 열어 둡니다). */
.track-view {
  position: relative;
  isolation: isolate;                      /* 러너 z-index(최대 700)를 이 안에 가둡니다 */
  height: var(--pack-h, 400px);
  border: 3px solid var(--ink); border-radius: 14px;
  background: var(--track-bg);
  box-shadow: inset 0 3px 0 rgba(255, 255, 255, 0.35);
  clip-path: inset(-9999px 0 -9999px 0);
}
.pack-track {
  position: absolute; left: 0; top: 0;
  width: var(--world-w, 100%); height: 100%;
  /* 카메라. translate3d 로 써야 합성 레이어로 올라가 매 프레임 재배치가 안 생깁니다.
     transform 만 움직이므로 캐릭터의 경기 위치(--p)는 전혀 건드리지 않습니다. */
  transform: translate3d(calc(-1 * var(--cam-x, 0px)), 0, 0);
  will-change: transform;
  /* 월드 맨 왼쪽의 출발선 한 줄. 카메라가 처음에 여기서 시작하므로
     '어디서 떠났는지' 가 보이고, 멀어질수록 실제로 나아간 느낌이 납니다. */
  background: linear-gradient(90deg, rgba(255, 255, 255, 0.6) 0 3px, transparent 3px) no-repeat;
}
/* 트랙 표면 — 테마마다 다릅니다(흙+돌 / 잔디결+꽃 / 모래결+발자국 / 눈두둑 / …).
   가장자리 장식(눈 두둑, 산호, 검은 암석, 초콜릿 테두리)도 여기 background 에 같이 들어갑니다. */
.pack-track::before {
  content: ''; position: absolute; inset: 0;
  border-radius: 11px;
  background: var(--track-tex);
  background-size: var(--track-tex-size, auto);
  background-repeat: var(--track-tex-repeat, repeat);
  background-position: var(--track-tex-pos, 0 0);
  opacity: 0.9;
  pointer-events: none;
}
.pack-track::after {                       /* 결승선 — 월드 오른쪽 끝 */
  content: ''; position: absolute; right: 0; top: 0; bottom: 0; width: 14px;
  background: repeating-conic-gradient(#fff 0 25%, #2b2b2b 0 50%) 0 0 / 14px 14px;
  border-left: 3px solid var(--ink);
  border-radius: 0 11px 11px 0;
}
/* 월드 모서리는 창의 모서리가 아니므로 무늬의 둥근 모서리를 없앱니다 */
.pack-track::before { border-radius: 0; }
.pack-track .runner {
  bottom: auto;
  /* --slot  = 출발 세로 위치. 0..1 비율이고 app.js packSlots() 가 흩어서 정합니다.
     --dy    = 달리는 중 세로 흔들림 + 겹침 회피 (app.js packOffsets).
     --travel = 러너가 실제로 지나는 거리(px). app.js 의 fitWorld() 가 정합니다.
     고정된 칸이 없습니다. 화면 위치만 건드리고 순위에는 영향이 없습니다.

     **left/top 이 아니라 transform 으로 옮깁니다.** 매 프레임 30마리의 left·top 을
     쓰면 그때마다 레이아웃이 다시 돌아, 프레임 비용의 대부분을 여기서 씁니다
     (실측 30명 3.73ms 중 약 85%). transform 은 합성 단계라 레이아웃을 건드리지 않습니다.
     데스크톱은 원래도 60fps 였지만 저사양 폰에서 차이가 납니다. */
  top: 7px;
  left: 0;
  transform: translate3d(
    calc(var(--p, 0) * var(--travel, 0px)),
    calc(var(--slot, 0) * var(--slot-span, 0px) + var(--dy, 0px)),
    0);
  /* **전환(transition)을 걸지 않습니다.** 예전 top 에는 0.08초가 걸려 있었는데,
     x·y 가 한 속성(transform)으로 합쳐지면 그 시간이 가로 진행에도 붙습니다.
     그러면 화면 속 캐릭터가 실제 위치보다 최대 80ms 뒤처져, 결승선 근처에서
     보이는 순서가 실제 순위와 어긋납니다(실측: p=0.4979 인데 그려진 곳은 0.457 자리).
     세로 흔들림(--dy)은 원래 프레임마다 이어지는 값이라 전환 없이도 부드럽습니다. */
  will-change: transform;
}
.pack-track .runner.is-lead { filter: drop-shadow(0 0 6px var(--yellow)); }
/* 이름표 — 전원분이 늘 DOM 에 있고 .is-on 으로 켜고 끕니다.
   레이스 중에 넣었다 뺐다 하면 상위 3명이 바뀔 때마다 화면이 튑니다.
   기본은 꺼진 상태(투명 + 살짝 작게)이고, 켜질 때 0.18초에 걸쳐 떠오릅니다. */
.pack-tag {
  position: absolute; top: -13px; left: 50%;
  padding: 0 6px;
  border: 2px solid var(--ink); border-radius: 999px;
  font-size: 10px; font-weight: 900; color: var(--ink);
  white-space: nowrap; pointer-events: none;
  opacity: 0;
  transform: translateX(-50%) translateY(3px) scale(0.9);
  transform-origin: 50% 100%;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.pack-tag.is-on {
  opacity: 1;
  transform: translateX(-50%) translateY(0) scale(1);
}
/* 순위 숫자는 이름 앞에 붙습니다. 이름과 붙어 보이지 않게 살짝 띄우고
   배경을 한 겹 깔아 '2위 · 이름' 처럼 두 덩어리로 읽히게 합니다. */
.pack-tag .tag-rank {
  display: inline-block;
  margin: 0 3px 0 -2px; padding: 0 3px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.72);
  font-variant-numeric: tabular-nums;
}
.pack-tag .tag-rank:empty { display: none; }
/* 돌발 이벤트 이모지(.fx)도 머리 위 같은 자리를 씁니다(둘 다 top:-12/-13px).
   이벤트가 떠 있는 동안에는 이름표를 한 칸 위로 올려 서로 가리지 않게 합니다.
   transition 이 그대로 걸려 있어 툭 튀지 않고 미끄러져 올라갔다 내려옵니다. */
.runner.has-fx .pack-tag.is-on { transform: translateX(-50%) translateY(-17px); }

/* ── 방문자 카운터 ──────────────────────────────────────── */
/* pointer-events:none — 화면 위에 떠 있는 요소가 클릭을 먹는 사고를 원천 차단합니다 */
.visits {
  position: fixed; right: 14px; bottom: 12px; z-index: 30;
  display: flex; align-items: center; gap: 10px;
  padding: 5px 14px;
  border: 3px solid var(--ink); border-radius: 999px;
  background: linear-gradient(180deg, #fff, var(--cream-2));
  box-shadow: 0 4px 0 var(--ink);
  font-size: 12px; font-weight: 800; color: var(--ink-soft);
  pointer-events: none;
}
.visits-title { color: var(--ink); }
.visits b { color: #d64500; font-size: 14px; font-variant-numeric: tabular-nums; }

/* ── 반응형 ─────────────────────────────────────────────── */
@media (max-width: 900px) {
  .setup-grid { grid-template-columns: minmax(0, 1fr); gap: 18px; }
}
/* 카드가 화면 아래 남는 공간까지 채우고, 그 여유를 왼쪽 칸(입력창 + 참가자 칩)이 가져갑니다.
   오른쪽 설정 칸은 항목이 고정이라 더 늘어날 게 없으므로 위로 정렬해 둡니다.
   카드 최소 높이는 오른쪽 칸 내용이 정하므로, 화면이 짧으면 예전처럼 딱 맞게 줄어듭니다. */
@media (min-width: 901px) {
  main { min-height: calc(100vh - var(--topbar-h)); display: flex; flex-direction: column; }
}
/* 안전망 — fitLanes()/fitSide() 가 이미 한 화면에 맞추지만, 창 크기를 바꾼 직후처럼
   측정 타이밍이 어긋나는 순간에도 페이지 스크롤바가 생기지 않게 막습니다.
   문턱을 961px 로 둔 것은 좁은 화면에서 트랙 높이를 더 조이는 구간과 겹치지 않게 하기 위해서입니다. */
@media (min-width: 961px) {
  #screen-race.is-active { max-height: calc(100vh - var(--topbar-h) - 16px); overflow: hidden; }
  .screen.is-active { flex: 1 1 auto; }
  #screen-setup.is-active { display: flex; flex-direction: column; }
  #screen-setup > .card { flex: 1 1 auto; display: flex; flex-direction: column; }
  #screen-setup .setup-grid { flex: 1 1 auto; }
  #screen-setup .setup-col { display: flex; flex-direction: column; min-height: 0; }
  /* 입력창과 칩 목록은 같은 정보를 두 번 보여줍니다. 둘 다 늘리면 둘 다 잘리므로,
     칩(전원 확인용)에 먼저 자리를 주고 남는 만큼을 입력창이 가져갑니다. */
  /* 상한이 없으면 초대형 모니터에서 입력창만 계속 커지고
     오른쪽 설정 칸에 커다란 빈 구멍이 남습니다. */
  /* **칩 목록이 필요한 만큼 먼저 가져가고, 남는 것을 입력창이 씁니다.**
     예전에는 칩에 max-height:300px 상한이 걸려 있어서 30명일 때 칩이 잘리고
     그 공간을 입력창이 다 먹었습니다 — 짧은 이름 몇 줄만 든 커다란 빈 상자(사용자 지적).
     상한을 풀면 칩이 자기 높이(30명 ≈ 308px)를 먼저 차지하고, 참가자가 적을 때만
     입력창이 늘어나 빈 자리가 안 남습니다. */
  /* 상한을 두면 참가자가 적을 때 칸 아래가 통째로 빕니다(1080p·6명에서 257px).
     오른쪽 설정 칸의 빈 구멍은 항목 수가 고정이라 어차피 못 메우므로,
     왼쪽만이라도 남는 자리를 입력창이 가져가게 둡니다. */
  #screen-setup .entry-box { flex: 0 0 auto; min-height: 88px; }
  /* 상한을 아예 풀면 이름이 긴 30명에서 칩이 8줄까지 늘어나 카드를 밀어냅니다.
     화면 비례로 걸어 두면 짧은 이름 30명(약 308px)은 다 보이고, 그보다 길어지면
     칩 목록 안에서만 스크롤됩니다. */
}

/* 세로가 짧은 노트북과 태블릿 폭(561~900px)에서도 여백을 조입니다.
   폭 조건을 min-width:901px 로 묶어두면 태블릿에서 설정 화면이 크게 넘칩니다.
   문턱을 900px 로 둔 이유: 780px 로 두면 781~900px 구간이 아무 보정도 못 받아
   그 구간에서 설정 화면이 한꺼번에 넘칩니다. */
@media (max-height: 900px), (min-width: 561px) and (max-width: 900px) {
  /* 칩은 flex:1 로 남는 만큼만 쓰므로 상한을 따로 안 걸어도 됩니다.
     대신 짧은 화면에서는 더 줄어들 수 있게 하한만 낮춥니다. */
  /* 칩은 필요한 만큼만 쓰고 나머지는 입력창이 가져갑니다. 여기서 입력창에 상한을 걸면
     참가자가 적을 때 칸 아래가 통째로 비어 버립니다 — min 만 낮추고 max 는 건드리지 않습니다. */
  .setup-col .opt-grid { gap: 7px; }
  .opt { gap: 4px; }
  .lobby-sec .opt + .opt { margin-top: 10px; }
  /* 버튼 여백을 조여 한 줄에 들어가게 합니다.
     `.seg-multi button` 으로 쓰면 base 의 `.seg.seg-multi button`(특이도 0,2,1)에 져서
     padding 만 조용히 버려집니다 — 반드시 `.seg.seg-multi` 형태로 쓸 것. */
  .seg-multi { gap: 3px; padding: 4px; }
  .seg.seg-multi button { padding: 4px 7px; font-size: 11.5px; }
  .charpick { gap: 5px; padding: 6px; grid-template-columns: repeat(auto-fit, minmax(94px, 1fr)); }
  #opt-theme { grid-template-columns: repeat(4, 1fr); }
  #opt-duration { grid-template-columns: repeat(5, 1fr); }
  .ch { padding: 3px 1px 2px; }
  /* #sec-roster 로 한 번 더 적어 주는 이유: 폭 899px 이하 블록의
     `#sec-roster .ch-icon`(특이도 1,1,0) 이 클래스만 쓴 이 규칙(0,1,0)을 이깁니다.
     같은 형태로 맞추지 않으면 폰에서는 이 축소가 한 번도 적용되지 않습니다. */
  .ch-icon, #sec-roster .ch-icon { width: 42px; height: 42px; }
  .ch-emoji { font-size: 28px; line-height: 42px; }
  .ch-name, #sec-roster .ch-name { font-size: 11px; }
  /* 결과 순위표 10행 + 시상대가 한 화면에 들어오게 조입니다 */
  .result-table td, .result-table th { padding: 5px 10px; }
  /* 받침대는 명패를 담으므로 이보다 더 낮출 수 없습니다 — 캐릭터를 먼저 줄입니다 */
  .podium { --pod-ch: 52px; }
  .pod-1 { --pod-lift: 34px; } .pod-2 { --pod-lift: 16px; } .pod-3 { --pod-lift: 0px; }
  .podium { margin-top: 20px; }
  .result-stats { margin-top: 10px; }
  .rstat-v { font-size: 14px; }
  .event-legend {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0 10px; padding: 6px 10px; line-height: 1.4; font-size: 11px;
  }
  .opt-help { font-size: 11.5px; }
  .btn-row-main { margin-top: 12px; }
  .card-desc { margin-bottom: 10px; }
}
@media (max-width: 960px) {
  .race-body { grid-template-columns: minmax(0, 1fr); }
  
  
  .lanes { max-height: 46vh; }
}
/* 세로가 아주 짧은 창(≈640px). 세 화면 모두 한 단계 더 조여 스크롤을 없앱니다. */
@media (max-height: 680px) {
  /* 설정 화면 — 참가자 칩 목록과 입력창이 가장 크게 양보할 수 있는 곳입니다.
     min-width:901px 블록이 ID 특이도로 이기므로, 여기서도 같은 형태로 써야 먹힙니다. */
  /* 세로 680px 이하에서는 30명 칩(약 260px)이 들어갈 자리가 없습니다.
     여기서만 상한을 걸어 칩 목록 안에서 스크롤되게 둡니다. */
  #screen-setup .entry-box { min-height: 62px; }
  .entry-foot { margin-top: 6px; }
  .setup-col .opt-grid { gap: 5px; }
  .opt { gap: 3px; }
  .lobby-sec .opt + .opt { margin-top: 7px; }
  .opt-label { font-size: 11.5px; }
  .seg { padding: 3px; }
  /* 시간 10칸 · 테마 8칸이 각각 한 줄에 들어가야 세로가 두 줄(약 58px) 절약됩니다 */
  #opt-duration button,
  #opt-theme button { padding: 4px 4px; }
  .charpick { gap: 4px; padding: 5px; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); }
  #opt-theme { grid-template-columns: repeat(4, 1fr); }
  #opt-duration { grid-template-columns: repeat(5, 1fr); }
  .ch { padding: 2px 1px 1px; }
  .ch-dot { width: 10px; height: 10px; }
  /* 위 블록과 같은 이유로 #sec-roster 를 같이 적습니다.
     가로 모드 폰은 max-height:680 과 max-width:899 를 동시에 만족하므로,
     ID 를 붙여 두지 않으면 카드가 56px 그대로 남아 '한 단계 더 조인다' 는 목적이
     정작 폰에서만 달성되지 않습니다. */
  .ch-icon, #sec-roster .ch-icon { width: 34px; height: 34px; }
  .ch-emoji { font-size: 23px; line-height: 34px; }
  .ch-name, #sec-roster .ch-name { font-size: 10px; }
  #screen-setup .card { padding: 14px; }
  .card-title { font-size: 16px; }
  /* 세로가 아주 짧은 창 — 시상대가 자리를 제일 많이 먹으므로 여기서 가장 크게 조입니다 */
  .podium { --pod-ch: 40px; }
  .pod-1 { --pod-lift: 22px; } .pod-2 { --pod-lift: 10px; } .pod-3 { --pod-lift: 0px; }
  .pod-medal { font-size: 16px; } .pod-1 .pod-medal { font-size: 19px; }
  .pod-crown { font-size: 20px; top: -16px; }
  .pod-name { font-size: 11.5px; } .pod-1 .pod-name { font-size: 12.5px; }
  .pod-time { font-size: 12px; } .pod-1 .pod-time { font-size: 13.5px; }
  .podium { margin-top: 18px; }
  .result-stats { margin-top: 8px; gap: 5px; }
  .rstat { padding: 3px 3px 2px; }
  .rstat-v { font-size: 13px; }
  .result-table td, .result-table th { padding: 3px 10px; font-size: 12.5px; }
  .result-board td { padding: 3px 8px; }
  .rank-badge { width: 23px; height: 23px; font-size: 11.5px; }
  .cell-face { width: 24px; height: 24px; }
  #screen-result .card { padding: 14px; }
  #screen-result .card-title { margin-bottom: 6px; }
}
@media (max-width: 560px) {
  
  
  
  .race-time { font-size: 32px; }
  .visits { right: 8px; bottom: 8px; font-size: 11px; padding: 4px 10px; gap: 7px; }
  /* 결과 화면(시상대 + 10행 순위표)도 한 화면에 담습니다 */
  #screen-result .card { padding: 14px; }
  .result-table td, .result-table th { padding: 5px 8px; font-size: 13px; }
  .podium { --pod-ch: 44px; gap: 6px; }
  .pod-1 { --pod-lift: 26px; } .pod-2 { --pod-lift: 12px; } .pod-3 { --pod-lift: 0px; }
  .pod-medal { font-size: 17px; } .pod-1 .pod-medal { font-size: 20px; }
  .pod-crown { font-size: 21px; top: -17px; }
  /* 1위가 가장 커야 합니다. 예전에는 여기서 11.5/12.5 로 잡아 놓고 같은 블록 아래에서
     `.pod-name { 13px }` 을 한 번 더 써서, 특이도가 낮은 2·3위만 13px 로 커지고
     `.pod-1 .pod-name` 은 12.5px 에 남아 **1위 이름이 제일 작아졌습니다.**
     값은 한곳에서만 정합니다. */
  .pod-name { font-size: 13px; } .pod-1 .pod-name { font-size: 14px; }
  .pod-time { font-size: 12.5px; } .pod-1 .pod-time { font-size: 14px; }
  /* 좁은 화면에서는 요약 띠를 두 줄로 접습니다 */
  .result-stats { grid-template-columns: repeat(2, 1fr); max-width: 320px; }
  /* 표가 가로로 밀려 스크롤 되지 않도록 차이 칸을 숫자만 남기고 줄입니다 */
  .result-board { min-width: 300px; }
  .result-board .c-gap { width: 76px; }
  .gap-wrap { min-width: 0; }
  .gap-bar { display: none; }
  .result-board td { padding: 5px 7px; }
  .cell-face { width: 24px; height: 24px; }
}
/* 좁고 '동시에' 짧은 화면. 위 두 블록(max-height:680 / max-width:560)이 서로 덮어써서
   더 조이려던 값이 지므로, 여기서 최종값을 한 번 더 못박습니다. */
@media (max-width: 560px) and (max-height: 680px) {
  .pod-1 { --pod-lift: 18px; } .pod-2 { --pod-lift: 8px; } .pod-3 { --pod-lift: 0px; }
}

/* ── 이펙트 강화 ────────────────────────────────────────── */
/* 부스트 잔상 — 캐릭터 색(--racer)으로 뒤로 뻗는 꼬리 */
.runner::before {
  content: ''; position: absolute; right: 82%; top: 34%;
  width: 54px; height: 26%;
  border-radius: 999px;
  background: linear-gradient(90deg, transparent, var(--racer, #fff));
  opacity: 0; pointer-events: none;
  transition: opacity 0.15s;
}
.runner.is-boost::before { opacity: 0.7; animation: trail 0.32s linear infinite; }
@keyframes trail {
  0%   { transform: translateX(6px) scaleX(0.7); opacity: 0.75; }
  100% { transform: translateX(-14px) scaleX(1.25); opacity: 0; }
}

/* 달릴 때 뒤로 튀는 입자 — 테마마다 색이 다릅니다
   (흙먼지 / 잎사귀 / 모래먼지 / 눈가루 / 별가루 / 기포 / 불티 / 설탕가루) */
.runner::after {
  content: ''; position: absolute; left: -4px; bottom: 1px;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--dust, rgba(255, 255, 255, 0.8));
  opacity: 0; pointer-events: none;
}
.runner.is-running::after { animation: dust 0.5s ease-out infinite; }
.runner.is-boost::after   { animation-duration: 0.26s; }
.runner.is-sleep::after,
.runner.is-finished::after { animation: none; opacity: 0; }
@keyframes dust {
  0%   { opacity: 0.65; transform: translate(0, 0) scale(0.35); }
  100% { opacity: 0;    transform: translate(-20px, -9px) scale(1.5); }
}

/* 부스트 중에는 캐릭터 자체가 빛납니다 */
.runner.is-boost .ch-sprite {
  filter: drop-shadow(0 0 7px var(--racer, #fff)) drop-shadow(0 0 2px #fff);
}

/* 이벤트 뱃지가 터질 때 링이 퍼집니다 */
.runner.has-fx .fx::before {
  content: ''; position: absolute; inset: -7px;
  border: 3px solid var(--racer, #fff); border-radius: 50%;
  opacity: 0;                    /* 없으면 애니메이션이 끝난 뒤 불투명한 원이 그대로 남습니다 */
  animation: burst 0.55s ease-out;
}
@keyframes burst {
  0%   { transform: scale(0.3); opacity: 0.95; }
  100% { transform: scale(2.4); opacity: 0; }
}

@keyframes leadPulse {
  0%   { box-shadow: inset 0 0 0 3px var(--yellow), 0 0 0 0 rgba(255, 210, 63, 0.75); }
  100% { box-shadow: inset 0 0 0 3px var(--yellow), 0 0 0 9px rgba(255, 210, 63, 0); }
}

/* 로켓 부스터가 터지면 트랙 전체가 흔들립니다 */
.track-wrap.is-shake { animation: shake 0.45s ease-in-out; }
@keyframes shake {
  0%, 100% { transform: translate(0, 0) rotate(0); }
  15% { transform: translate(-7px, 3px) rotate(-0.5deg); }
  30% { transform: translate(6px, -4px) rotate(0.5deg); }
  45% { transform: translate(-5px, 2px) rotate(-0.3deg); }
  60% { transform: translate(4px, -2px) rotate(0.3deg); }
  80% { transform: translate(-2px, 1px) rotate(0); }
}

/* 사진 판정 구간에는 트랙이 붉게 달아오릅니다 */
.track-wrap.is-photo {
  box-shadow: 0 8px 0 var(--ink), 0 0 0 6px rgba(255, 93, 143, 0.55), 0 18px 40px rgba(255, 93, 143, 0.4);
  animation: photoGlow 0.5s ease-in-out infinite alternate;
}
@keyframes photoGlow {
  from { box-shadow: 0 8px 0 var(--ink), 0 0 0 4px rgba(255, 93, 143, 0.4), 0 18px 30px rgba(255, 93, 143, 0.3); }
  to   { box-shadow: 0 8px 0 var(--ink), 0 0 0 10px rgba(255, 93, 143, 0.7), 0 18px 46px rgba(255, 93, 143, 0.5); }
}

/* 완주 순간 반짝 */
.runner.is-finished .ch-sprite { filter: drop-shadow(0 0 8px var(--yellow)); }

/* ── 역대 전적 모달 ─────────────────────────────────────── */
.records {
  position: fixed; inset: 0; z-index: 60;
  display: grid; place-items: center;
  padding: 20px;
  background: rgba(58, 42, 24, 0.55);
  backdrop-filter: blur(3px);
  animation: fadeIn 0.2s ease;
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.records[hidden] { display: none !important; }
.records-box {
  position: relative;
  width: min(680px, 100%); max-height: 82vh;
  display: flex; flex-direction: column;
  background: var(--cream);
  border: 4px solid var(--ink); border-radius: 22px;
  padding: 22px 22px 18px;
  box-shadow: 0 8px 0 var(--ink), 0 22px 40px rgba(58, 42, 24, 0.4);
  animation: dropIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.records-close {
  position: absolute; top: -14px; right: -12px;
  width: 40px; height: 40px;
  border: 3px solid var(--ink); border-radius: 50%;
  background: var(--red); color: #fff;
  font-size: 17px; font-weight: 900; cursor: pointer;
  box-shadow: 0 4px 0 var(--ink);
}
.records-close:active { transform: translateY(4px); box-shadow: 0 0 0 var(--ink); }
/* ── 전적 팝업의 탭 ─────────────────────────────────────
   같은 기록을 두 각도로 봅니다. 세그먼트 버튼(맵·시간)과 같은 어휘를 씁니다 —
   고른 칸만 크림색으로 떠오르고 나머지는 눌린 것처럼 가라앉습니다. */
.rec-tabs {
  display: flex; gap: 4px;
  margin: 0 auto 12px;
  padding: 4px;
  width: fit-content; max-width: 100%;
  background: rgba(58, 42, 24, 0.09);
  border-radius: 999px;
}
.rec-tab {
  border: 0; background: none;
  padding: 7px 18px;
  border-radius: 999px;
  font: inherit; font-size: 13.5px; font-weight: 900;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.rec-tab:hover { color: var(--ink); }
.rec-tab.is-on {
  background: var(--cream);
  color: var(--ink);
  box-shadow: 0 2px 0 rgba(58, 42, 24, 0.25);
}
.rec-tab:focus-visible { outline: 3px solid var(--yellow-dk); outline-offset: 2px; }
/* 캐릭터 칸의 얼굴 — 이름 앞에 붙습니다. 결과표의 .cell-face 와 같은 크기입니다. */
/* .cell-face 는 원래 flex 자식(.cell-name 안)이라 크기가 먹었습니다. 여기서는 <td> 바로
   아래라 inline 이 되어 width/height 가 무시되고 가는 선만 남았습니다. */
#chars-body .cell-face { display: inline-block; margin-right: 7px; vertical-align: middle; }
#chars-body .char-name { vertical-align: middle; }

.records-scroll { overflow: auto; margin-top: 6px; }
.records .card-desc.center { text-align: center; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; }
}
