  /* ── Daily / event intro card (opens from the Daily nav button before the puzzle) ── */
  #dailyCard { position: fixed; inset: 0; z-index: 72; display: none; align-items: center; justify-content: center;
    padding: 24px;
    /* no backdrop blur: the modal_canvas frame edge clashes with a blurred backdrop (same as #win/#noDrags) */
    background: radial-gradient(circle at 50% 42%, rgba(255,231,112,.16), transparent 44%), rgba(26,33,21,.6); }
  #dailyCard.show { display: flex; animation: dcFade .28s ease both; }
  #dailyCard .dcCard {
    position: relative; width: min(86vw, 340px);
    display: flex; flex-direction: column; align-items: center; gap: 9px;
    padding: 22px 24px 22px;
    /* premium modal frame + corner gems via 9-slice — same asset/sizing as #noDrags/#ask (was the plainer map_card_bg) */
    border-style: solid; border-width: 44px;
    border-image: url('../../assets/common/modal_canvas.webp') 120 fill / 44px / 0 stretch;
    border-radius: 0; background: transparent;
    box-shadow: 0 0 34px rgba(255,218,76,.34), 0 22px 46px rgba(45,23,7,.42);
    color: #6b3912; text-align: center; overflow: visible;
    animation: dcPop .60s linear both;
  }
  /* warm light aura behind the stone frame — same magic glow as #win/#noDrags so the daily reads as premium */
  #dailyCard .dcCard::before {
    content: ""; position: absolute; inset: -72px -64px;
    background: url('../../assets/win/light_burst.webp') center / contain no-repeat;
    opacity: .24; mix-blend-mode: screen; pointer-events: none; z-index: -1;
    animation: winAuraSpin 12s linear infinite;
  }
  /* carved amber-stone knob — echoes the frame's gem corners + uses the game's SVG icon language
     (was a flat dark CSS lozenge with a font ✕ that clashed with the painted frame) */
  #dailyCard .dcClose {
    position: absolute; top: 6px; right: 6px; width: 36px; height: 36px; min-width: 0; box-sizing: border-box;
    border-radius: 50%; display: grid; place-items: center; padding: 0; cursor: pointer; z-index: 3;
    color: #5e3410;                                   /* icon stroke = the card's warm brown */
    border: 1px solid #d9a866;
    background: radial-gradient(120% 120% at 50% 28%, #fbe6bd, #e9c489 46%, #c98f4e 100%);
    box-shadow: inset 0 1px 1px rgba(255,255,255,.78),  /* top sheen */
                inset 0 -2px 4px rgba(120,70,20,.42),   /* carved bottom shade */
                0 3px 6px rgba(40,20,6,.4);             /* soft drop */
  }
  #dailyCard .dcClose .ico { width: 18px; height: 18px; stroke-width: 2.4; }
  #dailyCard .dcClose:active { transform: translateY(1px);
    box-shadow: inset 0 2px 5px rgba(120,70,20,.5), 0 1px 2px rgba(40,20,6,.4); }
  #dailyCard .dcKicker { font-size: 12px; font-weight: 900; letter-spacing: .12em; text-transform: uppercase;
    color: #c8862f; text-shadow: 0 1px 0 rgba(255,246,205,.6); }
  #dailyCard .dcTitle { margin: 0; font-size: clamp(22px, 6vw, 28px); font-weight: 950; color: #5e3410;
    text-shadow: 0 1px 0 rgba(255,246,205,.7); }
  /* puzzle silhouette preview — shows today's shape (crystal blue) without spoiling the regions */
  #dailyCard .dcPreview { width: 62%; max-width: 172px; display: flex; align-items: center; justify-content: center; margin: 2px 0; }
  #dailyCard .dcSil { display: grid; gap: 3px; width: 100%; filter: drop-shadow(0 4px 8px rgba(40,30,10,.3)); }
  #dailyCard .dcSil i { aspect-ratio: 1; }
  #dailyCard .dcSil i.f { background: linear-gradient(145deg, #7ad0ff, #2f7fce 70%, #1e5ea0); border-radius: 3px;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.45); }
  #dailyCard .dcSil i.v { background: transparent; }
  #dailyCard .dcMeta { display: flex; align-items: center; gap: 14px; font-weight: 900; }
  #dailyCard .dcReward { display: inline-flex; align-items: center; gap: 4px; font-size: 18px; color: #6b3912; }
  /* 보상 앞의 화폐 표식 = **둥근 금화** (오너 2026-08-08, 상점 가격과 같은 지시). `dcReward`가 보여
     주는 값은 `dailyReward`(코인)이므로 화폐가 맞고, 텍스트 별로 그리면 평점 별(★ 1~3개)과 겹쳐
     화폐 이름을 「별빛」→「코인」으로 바꿔 없앤 이중 의미 함정이 그림으로 되살아난다.
     ⚠️ 이 카드는 config 주석상 옛 데일리(v1에서 비활성)라 지금 안 보일 수 있다. 그래도 고쳐 두는
        이유는 되살아나는 날 **혼자 옛 문법으로 남아 있지 않게** 하기 위해서다 — 이 저장소가 여러 번
        겪은 「값은 남고 근거는 죽은 자리」의 예방이다. */
  #dailyCard .dcReward::before {
    content: ""; width: 1.05em; height: 1.05em; flex: 0 0 auto;
    background: url('../../assets/common/star_coin.webp') center / contain no-repeat;
    filter: drop-shadow(0 1px 1.5px rgba(60,30,2,.45));
  }
  #dailyCard .dcDay { font-size: 13px; font-weight: 800; color: #a06e3c; }
  #dailyCard .dcDone { margin: 0; font-size: 13px; font-weight: 700; color: #a06e3c; display: none; }
  #dailyCard.is-done .dcDone { display: block; }
  #dailyCard #dailyStart {
    width: 100%; max-width: 240px; aspect-ratio: 467 / 146; margin-top: 4px; padding: 0 8% 3%;
    border: none; border-radius: 0; background: url('../../assets/common/btn_green.png') center / 100% 100% no-repeat;
    display: flex; align-items: center; justify-content: center;
    color: #fff8e9; font-size: clamp(17px, 5vw, 21px); font-weight: 900;
    text-shadow: 0 2px 0 #2c7a1f, 0 3px 5px rgba(18,55,8,.5); cursor: pointer; transition: transform .08s, filter .12s;
  }
  #dailyCard #dailyStart:active { transition: transform .06s ease-out; transform: scaleX(1.06) scaleY(.90); filter: brightness(.95); }
  @keyframes dcFade { from { opacity: 0; } to { opacity: 1; } }
  @keyframes dcPop {
    0%   { opacity: 0; transform: scaleX(.66) scaleY(.74) translateY(16px); animation-timing-function: cubic-bezier(.2,.72,.3,1); }
    58%  { opacity: 1; transform: scaleX(1.075) scaleY(.945); animation-timing-function: ease-in-out; }
    84%  { opacity: 1; transform: scaleX(.982) scaleY(1.018); animation-timing-function: ease-in-out; }
    100% { opacity: 1; transform: scale(1); }
  }
  html[data-motion="reduced"] { #dailyCard.show, #dailyCard .dcCard, #dailyCard .dcCard::before { animation: none; } }

  /* tablet/wide (≥640px) — same breakpoint as the shared rule in 10-game-screen.css (which already
     widens #shop .mapBar/.mapScroll below); #dailyCard isn't covered by that shared block, so it gets
     its own local rule here. Ratios match 10-game-screen.css's own scaling (~1.5x). */
  @media (min-width: 640px) {
    #dailyCard .dcPreview  { max-width: 260px; }
    #dailyCard #dailyStart { max-width: 360px; }
  }

  /* badges list */
  /* ── badges: jungle bg + stone-bar rows, painted icon left + text right ── */
  #achievements { position: fixed; inset: 0; background: url('../../assets/stage/bg.webp') center / cover no-repeat; overflow: hidden; isolation: isolate; }
  /* 2026-07-28 오너: 배지 상단 바(.mapBar)를 통째로 걷어냈다 — 뒤로가기는 하단 내비로, 「Badges」 제목
     석판과 「1/25」 진행도 탭은 제거. 목록 자체가 어디인지·무엇을 얻었는지 보여주므로 셋 다 중복이었다.
     지갑만 타이틀·맵과 같은 두 모서리 배치로 남는다(.titleTopLeft/.titleTop + .hudSolo). */
  /* 상단 furniture가 [제목바+2단 지갑+기어]에서 [1단 지갑+기어]로 줄었다 → 기어 아래끝(≈109) 기준으로 낮춤 */
  #achList { padding-top: calc(env(safe-area-inset-top,0px) + 118px) !important;
             padding-bottom: calc(env(safe-area-inset-bottom,0px) + 132px) !important; }  /* clear taller nav */
  .achSectionHead {
    position: relative; z-index: 1; margin: 0 8px 12px; font-size: clamp(15px, 3.7vw, 15px);
    font-weight: 900; letter-spacing: .05em; text-transform: uppercase;
    /* light cream + dark outline so the label reads on the dark stage bg (matches .mapTitle) */
    color: #fdeec6;
    text-shadow: 0 1px 0 #3a2710, -1px 0 0 #3a2710, 1px 0 0 #3a2710, 0 2px 5px rgba(0,0,0,.55);
  }
  .achSectionHead:not(:first-child) { margin-top: clamp(18px, 4.5vw, 26px); }
  .badge {
    position: relative; display: flex; align-items: center; gap: 12px;
    min-height: 82px; margin-bottom: clamp(14px, 3vw, 22px); padding: 8px 18px 8px 92px;
    background: none; border: none; border-radius: 0; box-shadow: none;
  }
  .badge::before {
    content: ""; position: absolute; inset: 0; z-index: 0;
    background: url('../../assets/badge/row_bar.png') center / 100% 100% no-repeat;
  }
  .badge.locked::before { filter: brightness(.66) saturate(.18) contrast(.96); }   /* greyed-out = clearly "not yet" */
  .badge > * { position: relative; z-index: 1; }
  .badge .bIcon {
    position: absolute; left: 4px; top: 50%; transform: translateY(-50%); z-index: 1;
    width: 80px; height: 80px;
    background-position: center; background-size: contain; background-repeat: no-repeat;
    filter: drop-shadow(0 2px 3px rgba(30,16,4,.5));
  }
  .badge .bIcon svg { display: none; }
  .badge.locked .bIcon { background-image: url('../../assets/badge/padlock.png') !important; }
  .badge .bText b { display: block; font-size: clamp(16px, 4.8vw, 20px); font-weight: 900; }
  .badge .bText span { font-size: clamp(15px, 3.4vw, 14px); font-weight: 700; }
  /* Clear earned/locked color coding:
     EARNED = warm GOLD (the reward color — stars/coins) with a dark edge so it stays crisp on the
     lit stone bar → obviously "got it". LOCKED = flat desaturated grey on a greyed-out bar (below)
     → obviously "not yet". The two differ in HUE (warm gold vs cool grey), so the state reads instantly. */
  .badge.got .bText b {
    color: #ffd56a; -webkit-text-stroke: .8px #6b4309; paint-order: stroke fill;
    text-shadow: 0 1px 1px rgba(45,27,4,.5);
  }
  .badge.got .bText span {
    color: #f1d295; -webkit-text-stroke: .5px #6b4309; paint-order: stroke fill;
    text-shadow: 0 1px 1px rgba(45,27,4,.45);
  }
  .badge.locked .bText b { color: #a9a395; text-shadow: 0 1px 2px rgba(0,0,0,.45); }
  .badge.locked .bText span { color: #837d70; text-shadow: 0 1px 2px rgba(0,0,0,.4); }
  .badge.nextGoal::before {
    filter: brightness(.86) saturate(.62) contrast(1.02);
  }
  .badge.locked.nextGoal::before {
    filter: brightness(.76) saturate(.46) contrast(1.04);
  }
  .badge.nextGoal .bIcon {
    filter: drop-shadow(0 2px 3px rgba(30,16,4,.5)) brightness(1.08);
  }
  .badge.nextGoal .bText b {
    color: #efe0bd;
    text-shadow: 0 1px 0 #3a2710, 0 2px 4px rgba(0,0,0,.5);
  }
  .badge.nextGoal .bText span {
    color: #cfc1a0;
    text-shadow: 0 1px 2px rgba(0,0,0,.45);
  }
  .badge .nextGoalTag {
    position: absolute; right: 20px; top: -7px; z-index: 2;
    max-width: 42%;
    padding: 3px 9px 4px;
    border-radius: 999px;
    border: 1px solid rgba(255,230,154,.74);
    background: linear-gradient(180deg, #dca44e, #825223);
    color: #fff5cf;
    font-size: 12px; font-weight: 900; line-height: 1;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    text-shadow: 0 1px 1px rgba(53,29,7,.7);
    box-shadow: inset 0 1px 0 rgba(255,246,205,.48), 0 2px 4px rgba(20,10,2,.36);
  }
  /* reward claim: an emerald "받기" button on an earned-but-unclaimed badge → collect the tiered reward.
     Subtle glow draws the eye (the 2-step funnel's second beat); turns into a ✓ marker once claimed. */
  .badge .claimBtn {
    margin-left: auto; flex: 0 0 auto; z-index: 2;
    display: flex; flex-direction: column; align-items: center; gap: 1px;
    padding: 6px 14px; border-radius: 12px; border: 1px solid rgba(255,236,170,.85);
    background: linear-gradient(180deg, #4bbf6a, #2e8f4c); color: #fff; cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.35), 0 3px 6px rgba(10,40,20,.4);
    animation: claimBtnGlow 1.8s ease-in-out infinite;
  }
  .badge .claimBtn:active { transition: transform .06s ease-out; transform: scaleX(1.06) scaleY(.90); filter: brightness(.96); }
  .badge.claiming .claimBtn,
  .badge .claimBtn:disabled {
    cursor: default; animation: none;
    background: linear-gradient(180deg, #d7a94d, #8f5d25);
    border-color: rgba(255,238,178,.96);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.4), 0 0 12px rgba(255,190,78,.55), 0 3px 6px rgba(40,20,8,.42);
  }
  .badge .claimBtn .claimLabel { font-size: 15px; font-weight: 900; line-height: 1; }
  .badge .claimBtn .claimReward {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 12px; font-weight: 800; line-height: 1; color: #eafff0; white-space: nowrap;
  }
  @keyframes claimBtnGlow {
    0%,100% { box-shadow: inset 0 1px 0 rgba(255,255,255,.35), 0 3px 6px rgba(10,40,20,.4); }
    50%     { box-shadow: inset 0 1px 0 rgba(255,255,255,.5), 0 0 12px rgba(96,232,142,.85), 0 3px 6px rgba(10,40,20,.4); }
  }
  .badge .claimedMark {
    margin-left: auto; flex: 0 0 auto; z-index: 2;
    font-size: 12px; font-weight: 900; color: #bfe9c8; white-space: nowrap;
    text-shadow: 0 1px 2px rgba(0,0,0,.4);
  }
  .badge.claimed::before { filter: brightness(.92) saturate(.92); }   /* gently settled vs an unclaimed "got" */
  .badge.claimBurst::after {
    content: ""; position: absolute; inset: 3px 14px 3px 80px; z-index: 1; pointer-events: none;
    border-radius: 14px;
    background: linear-gradient(105deg, transparent 0 28%, rgba(255,246,186,.9) 45%, rgba(255,214,95,.36) 56%, transparent 74% 100%);
    mix-blend-mode: screen;
    animation: claimSweep .78s ease-out both;
  }
  @keyframes claimSweep {
    0% { opacity: 0; transform: translateX(-34%) skewX(-9deg); }
    22% { opacity: 1; }
    100% { opacity: 0; transform: translateX(42%) skewX(-9deg); }
  }
  .badge .claimFly {
    position: absolute; right: clamp(80px, 26vw, 118px); top: 50%; z-index: 5; pointer-events: none;
    transform: translateY(-50%); white-space: nowrap;
    padding: 4px 11px 5px; border-radius: 999px; border: 1.5px solid rgba(255,247,210,.92);
    color: #fff3c4; font-weight: 950; font-size: clamp(17px, 5.2vw, 24px);
    background: radial-gradient(circle at 35% 25%, rgba(255,255,220,.65), transparent 32%), linear-gradient(180deg, #b47421, #6a3a10);
    box-shadow: 0 0 13px rgba(255,205,82,.72), 0 3px 7px rgba(30,12,2,.48);
    text-shadow: 0 2px 0 #5a3a08, 0 2px 6px rgba(0,0,0,.5);
    animation: claimFly 1.05s ease-out both;
  }
  @keyframes claimFly {
    0%   { opacity: 0; transform: translateY(-50%) scale(.7); }
    18%  { opacity: 1; transform: translateY(-70%) scale(1.08); }
    64%  { opacity: 1; transform: translateY(-112%) scale(1); }
    100% { opacity: 0; transform: translateY(-160%) scale(.96); }
  }
  html[data-motion="reduced"] {
    .badge .claimBtn { animation: none; }
    .badge.claimBurst::after { animation: none; opacity: .35; }
    .badge .claimFly { animation-duration: .01s; }
  }

  /* shop rows */
  #shop {
    position: fixed;
    inset: 0;
    background:
      linear-gradient(180deg, rgba(14,26,14,.14), rgba(13,28,16,.36)),
      url('../../assets/shop/bg.webp') center / cover no-repeat;
    overflow: hidden;
  }
  #shop::after {
    content: "";
    position: absolute;
    inset: auto 0 0;
    height: 36%;
    pointer-events: none;
    background: linear-gradient(180deg, transparent, rgba(13,42,22,.34));
  }
  /* Wallets own the first row. Back + title use a second top row, matching Settings instead of leaving
     a lone floating control at the bottom. Shop has no settings shortcut, so both sides use the same inset. */
  /* 상점 헤더(.shopTopHud = 뒤로가기 + 「상점」 안내판)는 2026-08-08 오너 지시로 삭제됐다. 그 전용
     규칙 둘도 함께 지운다 — 대상이 없는 규칙은 다음 사람에게 "그런 요소가 있다"는 거짓 정보가 된다. */
  /* coins + lives = MAP-style combined stone HUD (.levelHudCombo) for cross-screen unity */
  #shop .mapScroll {
    position: relative;
    z-index: 1;
    max-width: 430px;
    /* ⚠️ 아래 여백 132px은 **하단 내비 바를 피하는 값**이다(맵 `#map .mapScroll`·배지 `#achList`와 같은
       집안 표준). 줄이지 말 것 — 2026-08-05 오너 신고 "점프권 10개 밑의 상품까지 스크롤되지 않아
       「구매 복원」을 볼 수 없다"가 정확히 이 값이 작아서 났다.
       경위: 여기 있던 30px은 2026-07-30(db0c57c)에 정해졌고 **그때 상점에는 하단 바가 없었다.** 오늘
       784275b가 하단 내비에 상점 탭을 추가하며 `body[data-screen="shop"] .titleNav`를 켰는데, 바가
       `position:fixed; bottom:0; z-index:6`이라 이 스크롤 영역 위를 덮는데도 여백은 그대로 남았다.
       바 높이 = padding 20 + 아이콘 + padding 20(라벨은 display:none). 아이콘은 `width:100%`라 칸 너비를
       따르고 원본이 148×159(세로 = 가로 × 1.074) → **폭 360~430px에서 101~113px, 태블릿 상한 121px**.
       30px이면 **76px이 바 밑에 깔려**(상품 한 줄 64px + 간격보다 크다) 마지막 줄이 통째로 잠겼다.
       132px이면 어느 폭에서도 11~31px 남는다.
       ⚠️ 이 숫자가 지금 세 파일에 각각 하드코딩돼 있다(여기·05-map.css:115·이 파일 91행). 바 높이를
       바꾸면 **셋 다** 고쳐야 한다 — 토큰 하나로 묶는 정리는 아직 안 했다. */
    /* ⚠️ 위 여백 148 → 104 (2026-08-08): 헤더가 사라져 그만큼 빈 띠가 남았다. 104인 근거는 **지갑 칩**이다
       — 칩은 `top: safe + 8~18px`에 서고 높이가 폭×81/246(390px 화면에서 ≈44px)이라 아래끝이 safe+~54다.
       104면 그 아래로 50px이 남아 칩과 부딪히지 않는다. 더 올리려면 그 54px이 바닥이다.
       ⚠️ 아래 여백 132는 **하단 내비 바를 피하는 값**이라 그대로 둔다(맵·배지와 같은 집안 표준). */
    padding: calc(env(safe-area-inset-top,0px) + 104px) 14px
             calc(env(safe-area-inset-bottom,0px) + 132px);
    justify-content: flex-start;
    gap: clamp(14px, 3.6vw, 20px);
    /* top fade so rows DISSOLVE as they scroll UNDER the floating (background-less) HUD instead of hard-
       overlapping it — same technique as #map .mapScroll. Bar bottom ≈ safe-area + 96px; a soft bottom
       fade mirrors it. */
    -webkit-mask-image: linear-gradient(to bottom,
      transparent 0,
      transparent calc(env(safe-area-inset-top,0px) + 106px),
      #000 calc(env(safe-area-inset-top,0px) + 142px),
      #000 calc(100% - 40px),
      transparent 100%);
    mask-image: linear-gradient(to bottom,
      transparent 0,
      transparent calc(env(safe-area-inset-top,0px) + 106px),
      #000 calc(env(safe-area-inset-top,0px) + 142px),
      #000 calc(100% - 40px),
      transparent 100%);
  }

  /* Screen-transition hooks: the controller owns the wallet flight; this is only the quiet arrival cue on the
     real header counter. No particles or new background treatment are used here. */
  /* 쫀득 물리 적용(2026-08-02, §4.6): 순수 fade+slide+균일scale → 착지 전까지만 이동 + 부피보존
     스쿼시/스트레치 감쇠(접근 스트레치 → 착지 스쿼시 → 되튐 → rest), .24s→.34s(×1.42). */
  #shop .hudSolo.walletLanding { animation: shopWalletLanding .34s linear both; }
  #shop .hudSolo.walletLanding.walletHeart { border-color: rgba(255,178,170,.72); }
  #shop .hudSolo.walletLanding.walletStarlight { border-color: rgba(255,224,140,.78); }
  @keyframes shopWalletLanding {
    0%   { opacity: .76; transform: translateY(3px) scaleX(.95) scaleY(1.03); animation-timing-function: ease-in-out; }
    55%  { opacity: 1; transform: translateY(0) scaleX(1.06) scaleY(.97); animation-timing-function: ease-in-out; }
    80%  { transform: translateY(0) scaleX(.985) scaleY(1.015); animation-timing-function: ease-in-out; }
    100% { opacity: 1; transform: translateY(0) scale(1); }
  }
  html[data-motion="reduced"] #shop .hudSolo.walletLanding { animation: none; }

  /* shop row = 9-slice card (no stretch warp): [icon+qty | name+desc | star price] */
  .shopRow {
    position: relative;
    display: flex; align-items: center; gap: 15px;
    width: 100%; min-height: 108px;
    border-style: solid; border-width: 26px;
    border-image: url('../../assets/map/card_bg.webp') 96 fill / 26px / 0 stretch;
    border-radius: 0; background: transparent; box-shadow: none !important;
    color: #6b3912; text-align: left;
  }
  .shopIcon {
    position: relative; flex-shrink: 0;
    width: 56px; height: 56px;
    filter: drop-shadow(0 3px 3px rgba(72,30,4,.25));
    /* base background removed — every .shopIcon is under #shop or #settings, which set their own bg */
  }
  .shopIconLife { background: none; display: flex; align-items: center; justify-content: center; }

  /* 방패 상품 아이콘 — 하트 칸과 같은 자리·같은 크기(두 상품이 같은 무게로 읽혀야 한다).
     그림 에셋이라 색을 물려받지 않으므로 `currentColor` 계열 규칙은 쓰지 않는다. */
  #shop .shopIconShield { display: grid; place-items: center; position: relative; }
  #shop .shopIconShield img { width: 78%; height: auto; object-fit: contain; }
  /* 점프 패스 — 방패와 같은 처리(그림 한 장). 다만 **가로로 넓은 그림**이라(1024×893, 날개가 좌우로
     퍼진다) 방패의 78%를 그대로 쓰면 56px 칸에서 위아래가 남고 작아 보인다. 폭을 꽉 채우고 세로는
     비율에 맡긴다. ⚠️ `height:auto`를 고정값으로 바꾸지 말 것 — 그러면 날개가 눌린다. */
  #shop .shopIconPass { display: grid; place-items: center; position: relative; }
  #shop .shopIconPass img { width: 100%; height: auto; object-fit: contain; }
  .lifeHeart { width: 52px; height: 52px; color: #e8453c; filter: drop-shadow(0 3px 3px rgba(72,30,4,.28)); }
  .shopQty {
    position: absolute; right: -9px; bottom: -6px;
    background: #e8453c; color: #fff;
    font-size: 12px; font-weight: 900; line-height: 1;
    padding: 3px 7px; border-radius: 10px; border: 2px solid #fff7ea;
    box-shadow: 0 2px 4px rgba(80,20,10,.3);
  }
  .shopInfo { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
  .shopName { font-size: 19px; font-weight: 950; color: #6b3912; text-shadow: 0 1px 0 rgba(255,246,205,.7); white-space: nowrap; }
  .shopDesc { font-size: 15px; font-weight: 700; color: #a06e3c; }
  .shopPrice {
    flex-shrink: 0; min-width: 84px; height: 40px;
    padding: 0 15px 0 34px;
    display: inline-flex; align-items: center; justify-content: flex-end;
    background: url('../../assets/stage/hud_star.webp') left center / contain no-repeat;
    color: #5a3510; font-size: 17px; font-weight: 950; text-shadow: none;
  }
  /* value-steering tag. 2026-07-22: was flat web pills (orange/violet/teal/green + white border)
     — off-palette per the Quiet Canvas rule (saturated color belongs to jewels only). Retinted to the
     jewel tokens (--s2..--s5, theme-adaptive) with a gold rim + inset sheen so each ribbon reads as a
     painted gem tag in the shop's own material kit, not a modern notification pill. Hue still steers the
     eye by intent (variants below); only the material/palette changed. */
  .shopRibbon {
    position: absolute; top: -12px; right: 18px; z-index: 4;
    --rib: var(--s2);                                  /* default (popular/인기) = amber jewel */
    background: linear-gradient(180deg, rgba(255,255,255,.30), rgba(255,255,255,0) 46%), var(--rib);
    color: #fff; font-size: 12px; font-weight: 900; letter-spacing: .5px;
    padding: 3px 13px; border-radius: 11px; border: 1.5px solid rgba(255,224,122,.92);
    text-shadow: 0 1px 2px rgba(20,10,4,.55);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.42), inset 0 -4px 6px rgba(22,11,4,.32), 0 3px 7px rgba(30,14,4,.42);
  }
  /* coming-soon: dimmed, neutral price chip */
  .shopRow.soon { filter: saturate(.72); }
  .shopPrice.soon {
    background: rgba(78,50,24,.16); border-radius: 13px;
    min-width: 92px; height: 34px; padding: 0 14px; justify-content: center;
    color: rgba(107,57,18,.66); font-size: 13px;
  }
  .shopRow:disabled:not(.soon) { opacity: .55; filter: grayscale(.35) saturate(.8); }
  .shopRow:not(:disabled):active { transform: scale(.985); }

  /* ── shop reskin: jungle-temple stone rows (shared settings stone bar) + green buy buttons over bg_shop ── */
  #shop .shopRow {
    position: relative; display: flex; align-items: center; gap: 10px;
    width: 100%; flex-shrink: 0; aspect-ratio: 610 / 138; min-height: clamp(76px, 21vw, 88px);
    border: none !important; border-image: none !important; border-radius: 0;
    background: transparent; box-shadow: none !important;
    padding: 0 4% 0 22%; color: #f3ead2; text-align: left;
  }
  #shop .shopRow::before {
    content: ""; position: absolute; inset: 0; z-index: -1;
    background: url('../../assets/setting/row.png') center / 100% 100% no-repeat;
    filter: drop-shadow(0 3px 4px rgba(18,28,12,.4));
  }
  /* icon sits in the gold frame on the left; make it feel like a small relic pedestal */
  #shop .shopIcon {
    position: absolute; left: 11%; top: 50%; transform: translate(-50%, -50%);
    width: 13.5%; height: auto; aspect-ratio: 1; flex-shrink: 0;
    border: 2px solid rgba(255,219,143,.52);
    border-radius: 13px;
    background:
      radial-gradient(circle at 34% 25%, rgba(255,247,198,.74) 0 9%, transparent 19%),
      radial-gradient(circle at 50% 56%, #715238 0 42%, #46311f 68%, #21170f 100%) !important;
    display: flex; align-items: center; justify-content: center; filter: none;
    box-shadow:
      inset 0 2px 3px rgba(255,237,190,.32),
      inset 0 -4px 7px rgba(24,13,6,.5),
      0 2px 4px rgba(18,10,4,.36);
    overflow: visible;
  }
  #shop .lifeHeart {
    width: 70%; height: 70%;
    color: #ff6860;
    filter: drop-shadow(0 1px 0 #5a1410) drop-shadow(0 2px 3px rgba(20,6,2,.5));
  }
  #shop .shopIcon .starIco {
    width: 73%; height: 73%;
    filter: drop-shadow(0 1px 0 #6c4510) drop-shadow(0 2px 3px rgba(20,12,2,.5));
  }   /* painted 별빛 star sits inside the relic slot */
  #shop .shopSvg {
    width: 64%; height: 64%; color: #e8c465; fill: none; stroke: currentColor; stroke-width: 2.1; stroke-linecap: round; stroke-linejoin: round;
    filter: drop-shadow(0 1px 0 #5a3a12) drop-shadow(0 2px 3px rgba(18,10,2,.5));
  }
  #shop .shopSvg path[fill="currentColor"] { fill: currentColor; }
  #shop .shopQty {
    right: -9px; bottom: -7px; font-size: 12px;
    border-color: #fff0c4;
    background: linear-gradient(180deg, #cf5d4b, #8d2d25);
    box-shadow: 0 2px 4px rgba(30,10,4,.35), inset 0 1px 0 rgba(255,255,255,.28);
  }
  #shop .shopInfo { flex: 1; min-width: 0; gap: 1px; }
  #shop .shopName { font-size: clamp(15px, 4.2vw, 18px); font-weight: 900; color: #f7eed3; white-space: nowrap;
    text-shadow: 0 2px 0 #3a2810, -1px 0 0 #3a2810, 1px 0 0 #3a2810, 0 2px 4px rgba(0,0,0,.55); }
  #shop .shopDesc { font-size: clamp(15px, 3vw, 12.5px); font-weight: 700; color: #cdbe98; text-shadow: 0 1px 2px rgba(0,0,0,.5); }
  /* 보유 수 — 설명 아래 한 줄. 설명(무엇에 쓰는가)보다 **작고 조용하게**: 이 줄은 사기 전에 참고하는
     숫자이지 상품을 고르는 근거가 아니다. 값이 없으면 줄 자체를 만들지 않는다(빈 여백 방지). */
  #shop .shopStock { display: block; margin-top: 2px; font-size: clamp(13px, 2.6vw, 11.5px); font-weight: 700; color: #9ec8a6; }
  #shop .shopStock:empty { display: none; }
  /* price = carved gold plate, not a modern green CTA */
  #shop .shopPrice {
    flex-shrink: 0; min-width: clamp(84px, 24vw, 104px); height: clamp(34px, 9.5vw, 40px);
    display: inline-flex; align-items: center; justify-content: center; gap: 4px; padding: 0 9px 2px;
    border: 2px solid rgba(255,228,157,.9);
    border-radius: 14px;
    background:
      linear-gradient(180deg, rgba(255,255,224,.34), transparent 45%),
      linear-gradient(180deg, #b78a3f 0%, #8a5b25 55%, #5b3517 100%);
    color: #fff7e6; font-size: clamp(14px, 4vw, 17px); font-weight: 900;
    text-shadow: 0 2px 0 #3d2411, 0 1px 2px rgba(0,0,0,.45);
    box-shadow:
      inset 0 1px 0 rgba(255,249,210,.5),
      inset 0 -3px 5px rgba(44,23,8,.48),
      0 3px 6px rgba(17,9,3,.35);
  }
  /* 가격 앞의 화폐 표식 = **둥근 금화**(`.starIco`와 같은 그림). 오너 2026-08-08 지시로 텍스트 별(★)에서
     바꿨다 — 이 게임에는 평점 별(★ 1~3개)이 따로 있어서, 화폐를 별로 그리면 화폐 이름을 「별빛」에서
     「코인」으로 바꿔 없앤 **이중 의미 함정이 그림으로 되살아난다**(CLAUDE.md 화폐 절). 지금 이 표식이
     붙는 줄은 코인으로 사는 둘뿐이다 — 하트 +1(90) · 하트 +15(900). 현금(.cash)·광고(.adWatch)·
     준비중(.soon)은 아래에서 각자 이 표식을 끈다.
     ⚠️ 새 화폐 표시를 만들 때 그림을 복제하지 말고 이 경로(`assets/common/star_coin.webp`)를 쓸 것 —
        2026-08-05까지 보상 연출 5곳이 테두리 없는 반짝임을 써서 **반짝이가 동전 지갑으로 날아가고**
        있었다. 모양(동전 vs 5각 별)이 두 별을 가르는 유일한 장치다. */
  #shop .shopPrice::before {
    content: ""; width: 1.05em; height: 1.05em; flex: 0 0 auto;
    background: url('../../assets/common/star_coin.webp') center / contain no-repeat;
    filter: drop-shadow(0 1px 1.5px rgba(60,30,2,.55));
  }
  /* ad→코인 row: same plate, but a ▶ play glyph instead of a price (it's an EARN action, not a cost) */
  #shop .shopPrice.adWatch::before { content: none; }
  /* coming-soon price: neutral chip, no green button */
  #shop .shopPrice.soon { background: rgba(20,30,16,.56) !important; border: 1px solid rgba(255,240,190,.25); border-radius: 12px;
    min-width: 92px; height: 32px; padding: 0 12px; color: #d9cba6; font-size: 12.5px; text-shadow: 0 1px 2px rgba(0,0,0,.5); }
  #shop .shopPrice.soon::before { content: none; }
  #shop .shopRibbon { top: -10px; }
  /* ribbon hue steers the eye by intent — now via jewel tokens (on-palette, theme-adaptive):
     best=emerald(--s3) · first-buy=amethyst(--s5, the game's own purple gem, not web violet) · beginner=sapphire(--s4) */
  #shop .shopRibbon.best  { --rib: var(--s3); right: auto; left: 18px; }
  #shop .shopRibbon.first { --rib: var(--s5); }
  #shop .shopRibbon.new   { --rib: var(--s4); right: auto; left: 18px; }

  /* section dividers between shop groups */
  .shopSectionHead {
    align-self: flex-start; flex-shrink: 0; margin: 14px 4px 6px 6px; padding: 0;
    font-size: clamp(15px, 3.6vw, 15px); font-weight: 900; letter-spacing: .4px;
    color: #ffe7bd; text-shadow: 0 1px 3px rgba(0,0,0,.6); text-transform: uppercase;
  }
  .shopSectionHead:first-child { margin-top: 2px; }

  /* real-money (IAP) price pill: emerald "buy" plate, currency text — no ★ prefix */
  #shop .shopPrice.cash {
    border-color: rgba(180,255,205,.85);
    background:
      linear-gradient(180deg, rgba(230,255,236,.32), transparent 45%),
      linear-gradient(180deg, #46b86b 0%, #2c8a4c 55%, #176235 100%);
    letter-spacing: .2px;
  }
  #shop .shopPrice.cash::before { content: none; }

  /* dummy rows: very slight desaturation so they read as "preview", still tappable */
  #shop .shopRow.mock { filter: saturate(.95); }
  /* lead remove-ads offer: soft gold glow draws the eye (alignment-tolerant; the row keeps its baked frame) */
  #shop .shopRow.removeAdsLead { filter: none; }
  #shop .shopRow.removeAdsLead::after {
    content: ""; position: absolute; inset: 0; z-index: -1; border-radius: 16px;
    box-shadow: 0 0 16px 2px rgba(255,200,80,.42); pointer-events: none;
  }
  /* pack rows carry a 2-line description (bundle contents) → give them extra height */
  #shop .shopRow.pack { aspect-ratio: auto; min-height: clamp(86px, 24vw, 100px); }
  #shop .shopRow.pack .shopDesc { white-space: normal; line-height: 1.25; }
  /* one quiet caption flagging the cash lineup as a non-wired design mockup */
  .shopMockNote {
    margin: 14px 8px calc(8px + env(safe-area-inset-bottom,0px)); padding: 0;
    font-size: 15px; font-weight: 700; line-height: 1.4; text-align: center;
    color: rgba(231,219,182,.66); text-shadow: 0 1px 2px rgba(0,0,0,.5);
  }

  /* purchase feel: bounce the coin counter on a successful buy.
     #shopCoins is .hudStarVal — centered by a base transform: translate(-50%,-50%). The pulse MUST keep
     that translate, else a bare scale() drops the number down/right for the animation then snaps it back
     (the "숫자가 아래로 이동했다 원상복구" bug — same class as heartSpendPulse / the 2026-06-23 wallet fix). */
  @keyframes shopBuyPulse { 0%,100% { transform: translate(-50%,-50%) scale(1); } 42% { transform: translate(-50%,-50%) scale(1.16); } }
  #shopCoins.buyPulse { animation: shopBuyPulse .32s ease; }

  /* badge-earned toast — premium pill: depth-kit card + the real painted badge medallion + a kicker */
  #achvToast { position: fixed; z-index: 90; top: calc(env(safe-area-inset-top,0px) + 14px); left: 50%; transform: translateX(-50%) translateY(-160%);
    display: flex; align-items: center; gap: 12px; max-width: 340px; padding: 10px 20px 10px 13px; border-radius: 16px;
    background: var(--grad-card); border: 1px solid var(--line);
    box-shadow: var(--lift-lg), var(--sheen); pointer-events: none; opacity: 0; }
  #achvToast.show { animation: toastDrop 2.8s ease both; }
  @keyframes toastDrop { 0% { transform: translateX(-50%) translateY(-160%); opacity: 0; } 10%,82% { transform: translateX(-50%) translateY(0); opacity: 1; } 100% { transform: translateX(-50%) translateY(-160%); opacity: 0; } }
  /* painted achievement medallion (assets/badge/icons/*) — same treatment as the gallery, no double frame */
  #achvToast .atIcon { flex: none; width: 48px; height: 48px;
    background-position: center; background-size: contain; background-repeat: no-repeat;
    filter: drop-shadow(0 2px 3px rgba(30,16,4,.45)); }
  #achvToast.show .atIcon { animation: atBadgePop .70s linear .12s both; }
  @keyframes atBadgePop {
    0%   { transform: scale(.3) rotate(-12deg); opacity: 0; animation-timing-function: cubic-bezier(.2,.72,.3,1); }
    52%  { transform: scaleX(1.04) scaleY(1.20) rotate(3deg); opacity: 1; animation-timing-function: ease-in-out; }
    76%  { transform: scaleX(1.08) scaleY(.95) rotate(1deg); animation-timing-function: ease-in-out; }
    91%  { transform: scaleX(.98) scaleY(1.02) rotate(0); animation-timing-function: ease-in-out; }
    100% { transform: scale(1) rotate(0); opacity: 1; }
  }
  #achvToast .atText { display: flex; flex-direction: column; gap: 1px; min-width: 0; text-align: left; }
  #achvToast .atKicker { font-size: 12px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase; color: #c8862f; text-shadow: 0 1px 0 rgba(255,246,205,.6); }
  #achvToast .atText b { font-size: 15px; font-weight: 950; color: #5e3410; line-height: 1.15; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  #achvToast #atDesc { font-size: 15px; color: var(--muted); }
  #badgeClaimToast {
    /* 2026-07-18 (owner): pushed down off the top edge everywhere this shared toast appears (game HUD,
       .mapBar-style headers on map/achievements/shop) — 18px sat inside those headers' own vertical
       space and read as overlapping clutter, not a toast. clamp clears #game #hud's tallest measured
       bottom edge (safe-area + ~6px + up to ~82px for .hudLeft's aspect-ratio box) with margin to spare. */
    position: fixed; z-index: 92; top: calc(env(safe-area-inset-top,0px) + clamp(88px, 11vh, 100px)); left: 50%;
    transform: translateX(-50%) translateY(-150%); opacity: 0; pointer-events: none;
    max-width: min(340px, calc(100vw - 36px)); padding: 10px 18px 11px; border-radius: 999px;
    border: 1.5px solid rgba(255,236,170,.9);
    background: linear-gradient(180deg, #4b2b14, #25160c);
    color: #fff1bd; font-size: 15px; font-weight: 950; line-height: 1; text-align: center; white-space: nowrap;
    text-shadow: 0 1px 2px rgba(20,8,2,.8);
    box-shadow: 0 5px 14px rgba(24,10,2,.45), 0 0 14px rgba(255,205,82,.42), inset 0 1px 0 rgba(255,246,205,.3);
    transition: opacity .25s ease, transform .25s ease;   /* only engages for .holdShown below — .show uses its own keyframe */
  }
  #badgeClaimToast.show { animation: claimToastDrop 1.8s ease both; }
  @keyframes claimToastDrop {
    0% { transform: translateX(-50%) translateY(-150%); opacity: 0; }
    12%,78% { transform: translateX(-50%) translateY(0); opacity: 1; }
    100% { transform: translateX(-50%) translateY(-150%); opacity: 0; }
  }
  /* tap-to-continue hold (2026-07-18, game/showWin.js showForceEliteBridge's second phase) — statically
     shown (no auto-exit keyframe) until the player taps, instead of guessing a "long enough" ms. Re-examined
     same day: matches this codebase's own "재도전 코치" pattern (game/stageEntrance.js showControlGuide's
     .ccRing) instead of a generic full-screen tap — a pulsing ring drawn around the exact tap target IS
     the "waiting for you, tap here" signal, same ccRingPulse keyframe/values as that reference. */
  #badgeClaimToast.holdShown { transform: translateX(-50%) translateY(0); opacity: 1; }
  #badgeClaimToast.tapToContinue { pointer-events: auto; cursor: pointer; }
  #badgeClaimToast.holdShown::before {
    content: ""; position: absolute; inset: -10px; border-radius: 999px; pointer-events: none;
    border: 3px solid rgba(255,224,122,.95);
    box-shadow: 0 0 0 4px rgba(255,224,122,.22), 0 0 16px rgba(255,224,122,.5);
    /* scale-only keyframe (styles/app/10-game-screen.css) — ccRingPulse bakes in translate(-50%,-50%)
       for .ccRing's point-based positioning, which misaligns an inset-based ring like this one. */
    animation: tapRingPulse 1.2s ease-in-out infinite;
  }
  html[data-motion="reduced"] #badgeClaimToast.holdShown::before { animation: none; }
  html[data-motion="reduced"] #badgeClaimToast.holdShown { animation: none; }
  html[data-motion="reduced"] { #achvToast.show, #badgeClaimToast.show { animation-timing-function: linear; } #achvToast.show .atIcon { animation: none; } }

  /* 데일리 주 CTA에 idle 넛지 — 각각 위상을 어긋나게 둔다(동시에 흔들리면 기계적으로 보인다). */
  #dailyCard #dailyStart { animation: ctaNudge 5.8s ease-in-out 2.9s infinite; }
  html[data-motion="reduced"] #dailyCard #dailyStart { animation: none; }
