/* app.css — 좌지우지 팔레트와 화면 스타일
   🟢 코인스쿱 www/css/app.css 를 복사해 이 저장소 소유로 (2026-10-22) — 시세·마켓·티커·거래소·디파이·밈·차트·네임드·뉴스 피드·
      PLUS·미니게임·프로모·웹 게이트·온보딩 구획은 걷었고, 맨 끝에 «맞대결» 구획을 새로 붙였다.
   🔴 이 파일은 **앱 전용**이다. `css/common.css` 는 «토큰 이름»을 쓰고 여기는 «그 이름에 넣을 값»을 둔다. */

/* ── 팔레트 ────────────────────────────────────────────────────────────────
   🔴 **다크가 기본 선언이다.** OS 의 라이트/다크 설정(`prefers-color-scheme`)은 보지 않는다 — 사용자가 더보기에서 테마를 고른다.
      그래서 `@media (prefers-color-scheme: dark)` 블록을 두지 않는다(두면 라이트를 고른 사람이 OS 다크일 때 두 규칙이 싸운다).
   🔴 기본값이 세 곳에 있고 **셋이 같아야 한다** — 여기 `:root` · 테마 목록의 기본값(JS 설정) · 첫 페인트 전에 저장값을 읽어
      `data-theme` 를 붙이는 부팅 스크립트(저장 키 `jw.theme`). 어긋나면 JS 가 뜨기 전에 반대 테마가 한 번 번쩍인다(FOUC).
   🔴 `common/theme.js` 는 `data-theme`(이름)·`data-tone`(바탕 밝기) 속성만 바꾼다. 색은 전부 여기 있다. */
:root {
  --bg: #0d1117;
  --surface: #161b22;
  --surface-2: #1c2129;
  --line: #21262d;
  --line-2: #30363d;
  --text: #e6edf3;
  --text-sub: #8b949e;
  --text-mut: #6e7681;
  /* 🔴 **성향 색 — 좌 = 파랑 · 우 = 빨강** (한국 정당색 관례 — 미국 관례로 바꾸지 않는다 · docs/revamp-concept.md §1-1).
     성향을 칠하는 색은 이 네 변수뿐이다(`-soft` 는 칩·칸의 옅은 바탕). 상승·하락(`--up`·`--hot`)이나 브랜드색과 섞어 쓰지 않는다 —
     테마 색(봄·여름 테마의 `--brand-500` 등)이 성향처럼 읽히지 않게, 성향 표시는 늘 이 변수 + 「좌파 성향」·「우파 성향」 글자로만 한다.
     밝은 바탕 테마는 아래 «밝은 바탕»(`data-tone`) 줄에서 한 단 진하게 되돌린다. */
  --l: #5b8ff0;
  --l-soft: rgba(91, 143, 240, .16);
  --r: #ef6a5f;
  --r-soft: rgba(239, 106, 95, .16);
  /* ── 🟢 **계열 색 — dataviz 기준 팔레트의 앞 칸** (오늘의 미션 화면 `.mv` 의 `--mv-*` 가 여기를 가리킨다).
     🔴 «이웃 쌍» 검증을 통과한 순서다 — 순서를 바꾸지 않는다. «모든 쌍»이 필요한 그림(도넛)은 앞 세 칸까지만 쓴다. */
  --sv-1: #3987e5;
  --sv-2: #d95926;
  --sv-3: #199e70;
  /* ── 🟢 **인용 카드의 축 색** — 카드의 **왼쪽 3px 선 + 라벨 글자** 둘에만 쓴다(`.b-quote` 의 `--qc`) — 바탕은 안 물들인다.
     🔴 축은 저장된 `k` 가 정한다(board/quote.js `quoteKindCls`). 밝은 바탕 테마는 아래 «밝은 바탕»(`data-tone`) 줄에서 한 단 어둡게 되돌린다. */
  --q-news: var(--brand-500);
  --q-post: #4c8ef7;
  --q-cmt: #2bb47e;
  /* 🗄 코인스쿱의 네임드(트레이더)·밈 인용 축 색 `--q-named`·`--q-meme` 은 그 인용과 함께 걷었다(2026-10-22) */

  --brand-500: #f0b90b;
  --brand-weak: rgba(240, 185, 11, .14);
  /* 브랜드색 바탕 위 글자색 — 금색 브랜드에는 검정(흰 글자가 안 읽힌다) · 브랜드색이 어두운 밝은 판 테마는 흰색(각 블록 · 2026-10-27 · 🟡 그 전에는 걷은 크림 `verbatim` 하나만 흰색이었다) */
  --on-brand: #0d1117;

  /* ── 🔴 **태그의 색은 테마를 안 따라간다** (2026-09-08 · 97차 · 사용자 요청) ─────────────────
     `#해시`(본문 `.b-at-tag` · 카드 `.b-hash`·`.kwchip`)는
     테마를 빨강·파랑으로 바꾸면 같이 물들어서, 「말을 가리키는 표식」이라는 뜻이 화면마다 달라졌다.
     🔴 그래서 브랜드색이 아니라 **금색 계열 고정**이다 — 해시태그처럼 이 표식의 색 자체가
        약속이라, 테마가 바뀌어도 같은 것을 가리킨다는 사실이 안 흔들려야 한다.
     🔴 `@닉`(사람)은 **그대로 브랜드색**이다 — 도착지가 다르므로(활동 화면) 색도 갈라 둔다. */
  --tagc: #f0b90b;

  --hot: #f85149;
  --hot-weak: rgba(248, 81, 73, .15);
  --up: #3fb950;
  --up-weak: rgba(63, 185, 80, .15);

  --sh-1: 0 1px 2px rgba(0, 0, 0, .3);
  --sh-2: 0 2px 8px rgba(0, 0, 0, .4);
  --sh-3: 0 8px 28px rgba(0, 0, 0, .55);

  --scrim: rgba(0, 0, 0, .6);
  --toast-bg: rgba(230, 237, 243, .95);
  --toast-fg: #0d1117;

  --bar-bg: rgba(13, 17, 23, .88);
  /* 🔴 유리 재질 — 하단 탭바 전용 (2026-08-14 · 요청). `--bar-bg` 보다 **훨씬 옅다**:
     앱바는 글자를 얹어 대비가 필요하지만 탭바는 아이콘 둘뿐이라 뒤가 비쳐도 읽힌다.
     그 "비침" 이 요청받은 느낌 그 자체다. 불투명하게 올리면 유리가 아니라 판이 된다. */
  --glass-bg: rgba(28, 33, 41, .58);
  --glass-line: rgba(255, 255, 255, .10);
  --glass-top: rgba(255, 255, 255, .07);
}

/* ── 🟢 **테마 열두 가지 — 기본 + 사계절 + 무드** (2026-10-27 · 코인스쿱 8b7dbe2 이식 · 사용자 «좌지우지 테마에도 동일하게») ─────────────────
   🗄 옛 테마(크림 `verbatim`·라이트·색 테마 아홉)는 걷었다. 옛 저장값을 어디로 옮기는지는 `config.js THEME_MIGRATE` 가 정본이다.
   🔴 이름이 곧 저장값·CSS 블록·문구 키다 — ①`config.js THEMES` ②여기 블록 ③`strings.js theme_*` ④아래 `.theme-opt[data-swatch]` 넷이 한 커밋(`scripts/check-themes.mjs` 대조).
   🔴 **밝은 바탕인지는 목록으로 적지 않는다** — `common/theme.js apply` 가 계산된 `--bg` 의 밝기로 `<html data-tone="light|dark">` 를 심는다.
      옛 판은 「밝은 테마」 목록을 선택자 넷(태그 금색·성향 색·순위 왕관·레벨 배지)에 손으로 적어, 테마 하나가 빠지면 조용히 어긋났다
      (실제로 레벨 배지의 밝은 판 보정은 `light`·`verbatim` 둘에만 걸려 레드·블루·그린·옐로우에서는 노랑이 묻혀 있었고, 태그 주황·보라 규칙
      `:root[data-theme]:not([data-theme="dark"])` 는 어두운 색 테마(오렌지·핑크·퍼플·민트)에까지 밝은 판 값을 입히고 있었다).
   🔴 밝은 판 블록은 표면·글자·그림자·유리 변수 세트 전체를 쓴다(빠지면 다크 값이 새어 흰 글씨에 흰 바탕) · 어두운 판은 `:root` 를 물려받고 표면·글자·브랜드만 바꾼다.
   🔴 밝은 판은 `--on-brand: #fff` 를 같이 적는다 — 브랜드색이 어두운 판이라 `:root` 의 검정 글자(금색 브랜드용)가 버튼에서 안 읽힌다(이 앱만의 변수 · 코인스쿱에는 없다).
   🔴 `--up`·`--hot`(의미색)은 밝은 판끼리 한 값이다 — 테마색을 입히면 봄 테마에서 의미색이 브랜드 분홍과 섞인다. 성향 색(`--l`·`--r`)은 아래 data-tone 줄이 정한다.
   🟢 장식은 둘이다: `--deco`(#app 바탕 위 머리 그림 · 맨 끝 「테마 장식」 절) · 흩날리는 입자(`js/themefx.js`). 🗄 날짜 줄 아이콘(`--date-ic`)은 2026-10-28 에 걷었다.
      그림은 전부 이 파일 안의 SVG·그라데이션이다 — 내려받는 것이 없다. */
:root {
  --ic-pastel: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Cpath%20fill='%23b79cf0'%20d='M10.0%201.0C10.7%206.0%2013.1%208.7%2019.0%2010.0C13.1%2011.3%2010.7%2014.0%2010.0%2019.0C9.3%2014.0%206.9%2011.3%201.0%2010.0C6.9%208.7%209.3%206.0%2010.0%201.0Z'/%3E%3C/svg%3E");
  --ic-spring: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Cg%20transform='translate(10%2010)%20scale(0.95)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3C/svg%3E");
  --ic-summer: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Ccircle%20cx='10'%20cy='10'%20r='4.2'%20fill='%23ffb627'/%3E%3Cg%20stroke='%23ffb627'%20stroke-width='1.6'%20stroke-linecap='round'%3E%3Cpath%20d='M10%201.5v2.2M10%2016.3v2.2M1.5%2010h2.2M16.3%2010h2.2M4%204l1.6%201.6M14.4%2014.4%2016%2016M4%2016l1.6-1.6M14.4%205.6%2016%204'/%3E%3C/g%3E%3C/svg%3E");
  --ic-autumn: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20%3E%3Cpath%20d='M12%2022v-8.5'%20stroke='%23b08a1e'%20stroke-width='1.2'/%3E%3Cpath%20d='M12%2013.5C6%2013.5%202.6%209.6%203.5%203.8%206.6%205.6%209.4%205%2012%202.6%2014.6%205%2017.4%205.6%2020.5%203.8%2021.4%209.6%2018%2013.5%2012%2013.5z'%20fill='%23e8b92f'/%3E%3Cpath%20d='M12%2013.5V5'%20stroke='%23cfa020'%20stroke-width='.7'/%3E%3C/svg%3E");
  --ic-winter: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Cg%20transform='translate(10%2010)'%3E%3Cg%20stroke='%238fd0ff'%20stroke-width='1.5'%20stroke-linecap='round'%20fill='none'%3E%3Cpath%20transform='rotate(0)'%20d='M0-8.5V8.5M-2.6-6.5%200-4%202.6-6.5M-2.6%206.5%200%204%202.6%206.5'/%3E%3Cpath%20transform='rotate(60)'%20d='M0-8.5V8.5M-2.6-6.5%200-4%202.6-6.5M-2.6%206.5%200%204%202.6%206.5'/%3E%3Cpath%20transform='rotate(120)'%20d='M0-8.5V8.5M-2.6-6.5%200-4%202.6-6.5M-2.6%206.5%200%204%202.6%206.5'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
  --ic-news: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Crect%20x='2.5'%20y='3.5'%20width='15'%20height='13'%20rx='1'%20fill='none'%20stroke='%231d1b17'%20stroke-width='1.5'/%3E%3Cpath%20d='M5%207h10M5%2010h4M5%2013h4M11%2010h4v3h-4z'%20stroke='%231d1b17'%20stroke-width='1.3'%20fill='none'/%3E%3C/svg%3E");
  --ic-neon: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Cpath%20d='M11%201%204%2011h5l-1%208%207-10h-5z'%20fill='%232ef2d0'/%3E%3C/svg%3E");
  --ic-space: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Cpath%20d='M10%201c3%202.5%204%206%204%209.5V14H6v-3.5C6%207%207%203.5%2010%201z'%20fill='%23c58bff'/%3E%3Ccircle%20cx='10'%20cy='8'%20r='1.8'%20fill='%23f3e9ff'/%3E%3Cpath%20d='M6%2011l-2.5%203.5H6zM14%2011l2.5%203.5H14z'%20fill='%238a5fd6'/%3E%3Cpath%20d='M8%2014.5h4L10%2019z'%20fill='%23ff9ec0'/%3E%3C/svg%3E");
  --ic-coffee: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Cellipse%20cx='10'%20cy='10'%20rx='5.5'%20ry='7.5'%20transform='rotate(30%2010%2010)'%20fill='%23c8956a'/%3E%3Cpath%20d='M7.6%204.4c3%202.6-.8%207.6%202.4%2011'%20stroke='%233a2a1f'%20stroke-width='1.3'%20fill='none'/%3E%3C/svg%3E");
  --ic-rain: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Cpath%20d='M2%2010a8%208%200%200%201%2016%200c-1.3-1-2.7-1-4%200-1.3-1-2.7-1-4%200-1.3-1-2.7-1-4%200-1.3-1-2.7-1-4%200z'%20fill='%233d6f9e'/%3E%3Cpath%20d='M10%2010v6a2%202%200%200%201-4%200'%20stroke='%233d6f9e'%20stroke-width='1.5'%20fill='none'%20stroke-linecap='round'/%3E%3C/svg%3E");
  --ic-ink: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2020%2020'%20%3E%3Crect%20x='3'%20y='3'%20width='14'%20height='14'%20rx='2'%20fill='%23b0302a'/%3E%3Cpath%20d='M7%207h6M10%207v7M7%2011h6'%20stroke='%23fbf8f1'%20stroke-width='1.4'/%3E%3C/svg%3E");
  --ic-snowcap: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20272%2012'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0%2012c4-8%2014-10%2026-8s20-4%2034-3%2018%206%2030%204%2022-5%2036-3%2016%206%2030%205%2022-4%2034-2%2014%205%2022%207%2022-6%2032-4%2018%203%2028%204z'%20fill='%23eef4ff'/%3E%3C/svg%3E");
  --ic-drops: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20120%2044'%20%3E%3Ccircle%20cx='14'%20cy='9'%20r='2.3'%20fill='rgba(255,255,255,.75)'%20stroke='rgba(61,111,158,.35)'%20stroke-width='.7'/%3E%3Ccircle%20cx='46'%20cy='33'%20r='1.6'%20fill='rgba(255,255,255,.75)'%20stroke='rgba(61,111,158,.35)'%20stroke-width='.7'/%3E%3Ccircle%20cx='78'%20cy='12'%20r='1.9'%20fill='rgba(255,255,255,.75)'%20stroke='rgba(61,111,158,.35)'%20stroke-width='.7'/%3E%3Ccircle%20cx='104'%20cy='36'%20r='2.2'%20fill='rgba(255,255,255,.75)'%20stroke='rgba(61,111,158,.35)'%20stroke-width='.7'/%3E%3Ccircle%20cx='62'%20cy='24'%20r='1.2'%20fill='rgba(255,255,255,.75)'%20stroke='rgba(61,111,158,.35)'%20stroke-width='.7'/%3E%3C/svg%3E");
}
:root[data-theme="pastel"] {
  --bg: #f7f2fc;
  --surface: #ffffff;
  --surface-2: #f2ecfa;
  --line: #ebe3f5;
  --line-2: #dccff0;
  --text: #3b3550;
  --text-sub: #6f6787;
  --text-mut: #9a92ad;
  --brand-500: #8a63d9;
  --brand-600: #6f4bbd;
  --brand-weak: rgba(138, 99, 217, 0.13);
  --on-brand: #fff;
  --hot: #b8412e;
  --hot-weak: rgba(184, 65, 46, .11);
  --up: #2f7d5c;
  --up-weak: rgba(47, 125, 92, .12);
  --sh-1: 0 1px 2px rgba(120, 90, 170, .06);
  --sh-2: 0 2px 8px rgba(120, 90, 170, .09);
  --sh-3: 0 8px 28px rgba(120, 90, 170, .18);
  --scrim: rgba(59, 53, 80, 0.42);
  --toast-bg: rgba(59, 53, 80, 0.94);
  --toast-fg: #ffffff;
  --bar-bg: rgba(255, 255, 255, 0.86);
  --glass-bg: rgba(255, 255, 255, 0.62);
  --glass-line: rgba(59, 53, 80, 0.1);
  --glass-top: rgba(255, 255, 255, .55);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20200'%20%3E%3Cpath%20fill='%23c5a8f5'%20d='M250.0%2052.0C250.5%2055.4%20252.0%2057.2%20256.0%2058.0C252.0%2058.8%20250.5%2060.6%20250.0%2064.0C249.5%2060.6%20248.0%2058.8%20244.0%2058.0C248.0%2057.2%20249.5%2055.4%20250.0%2052.0Z'/%3E%3Cpath%20fill='%23ff9cbf'%20d='M320.0%20105.5C320.4%20108.0%20321.5%20109.4%20324.5%20110.0C321.5%20110.6%20320.4%20112.0%20320.0%20114.5C319.6%20112.0%20318.5%20110.6%20315.5%20110.0C318.5%20109.4%20319.6%20108.0%20320.0%20105.5Z'/%3E%3Cpath%20fill='%238fc7ff'%20d='M196.0%2022.5C196.3%2024.5%20197.2%2025.5%20199.5%2026.0C197.2%2026.5%20196.3%2027.5%20196.0%2029.5C195.7%2027.5%20194.8%2026.5%20192.5%2026.0C194.8%2025.5%20195.7%2024.5%20196.0%2022.5Z'/%3E%3Cpath%20fill='%23ffcf6b'%20d='M342.0%2031.0C342.2%2032.7%20343.0%2033.6%20345.0%2034.0C343.0%2034.4%20342.2%2035.3%20342.0%2037.0C341.8%2035.3%20341.0%2034.4%20339.0%2034.0C341.0%2033.6%20341.8%2032.7%20342.0%2031.0Z'/%3E%3C/svg%3E") right top / auto 200px no-repeat, radial-gradient(ellipse 55% 95px at 8% 0, rgba(255, 196, 220, .75), transparent) top / 100% 210px no-repeat, radial-gradient(ellipse 50% 85px at 55% 0, rgba(207, 227, 255, .8), transparent) top / 100% 210px no-repeat, radial-gradient(ellipse 45% 95px at 100% 30px, rgba(198, 240, 223, .75), transparent) top / 100% 210px no-repeat, radial-gradient(ellipse 30% 60px at 30% 120px, rgba(255, 240, 184, .5), transparent) top / 100% 210px no-repeat, linear-gradient(#f1e6fd, rgba(247, 242, 252, 0)) top / 100% 210px no-repeat;
}
:root[data-theme="spring"] {
  --bg: #fdf4f3;
  --surface: #fffdfc;
  --surface-2: #faecec;
  --line: #f2dcdc;
  --line-2: #e8c7c9;
  --text: #3d2a2e;
  --text-sub: #7a5c62;
  --text-mut: #ab9296;
  --brand-500: #d6466c;
  --brand-600: #b8345a;
  --brand-weak: rgba(214, 70, 108, 0.13);
  --on-brand: #fff;
  --hot: #b8412e;
  --hot-weak: rgba(184, 65, 46, .11);
  --up: #2f7d5c;
  --up-weak: rgba(47, 125, 92, .12);
  --sh-1: 0 1px 2px rgba(160, 90, 110, .06);
  --sh-2: 0 2px 8px rgba(160, 90, 110, .09);
  --sh-3: 0 8px 28px rgba(160, 90, 110, .18);
  --scrim: rgba(61, 42, 46, 0.42);
  --toast-bg: rgba(61, 42, 46, 0.94);
  --toast-fg: #fffdfc;
  --bar-bg: rgba(255, 253, 252, 0.86);
  --glass-bg: rgba(255, 253, 252, 0.62);
  --glass-line: rgba(61, 42, 46, 0.1);
  --glass-top: rgba(255, 255, 255, .55);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20200'%20%3E%3Cpath%20d='M366%206C330%2012%20300%2026%20276%2046%20262%2058%20246%2070%20222%2084'%20stroke='%239b6a5a'%20stroke-width='3.2'%20fill='none'%20stroke-linecap='round'/%3E%3Cpath%20d='M304%2028c-2%2013%201%2024%209%2033M272%2050c7%204%2010%2011%2011%2020'%20stroke='%239b6a5a'%20stroke-width='1.9'%20fill='none'%20stroke-linecap='round'/%3E%3Cpath%20d='M240%2074c-4-6-11-8-17-7%203%206%209%208%2017%207zM318%2040c5-3%2011-3%2015%201-5%203-10%203-15-1z'%20fill='%238cc58a'/%3E%3Cg%20transform='translate(306%2022)%20scale(1.10)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3Cg%20transform='translate(286%2036)%20scale(0.90)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3Cg%20transform='translate(328%2046)%20scale(0.85)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3Cg%20transform='translate(262%2054)%20scale(0.80)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3Cg%20transform='translate(246%2070)%20scale(0.95)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3Cg%20transform='translate(296%2062)%20scale(0.75)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3Cg%20transform='translate(226%2082)%20scale(0.70)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3Cg%20transform='translate(344%2014)%20scale(0.65)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3Cg%20transform='translate(270%2030)%20scale(0.60)'%3E%3Cg%20fill='%23fbc3d0'%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(0)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(72)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(144)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(216)'/%3E%3Cpath%20d='M0%200C-3-3-3.4-7-1.3-9L0-7.8%201.3-9C3.4-7%203-3%200%200z'%20transform='rotate(288)'/%3E%3C/g%3E%3Ccircle%20r='1.8'%20fill='%23e8708f'/%3E%3C/g%3E%3C/svg%3E") right top / auto 200px no-repeat, linear-gradient(#ffe1e8, rgba(253, 244, 243, 0)) top / 100% 210px no-repeat;
}
:root[data-theme="summer"] {
  --bg: #edf8fb;
  --surface: #ffffff;
  --surface-2: #e4f2f7;
  --line: #d3eaf1;
  --line-2: #b9dbe6;
  --text: #102f3a;
  --text-sub: #47697a;
  --text-mut: #7f9ca8;
  --brand-500: #0a86a8;
  --brand-600: #076d89;
  --brand-weak: rgba(10, 134, 168, 0.13);
  --on-brand: #fff;
  --hot: #b8412e;
  --hot-weak: rgba(184, 65, 46, .11);
  --up: #2f7d5c;
  --up-weak: rgba(47, 125, 92, .12);
  --sh-1: 0 1px 2px rgba(20, 100, 140, .06);
  --sh-2: 0 2px 8px rgba(20, 100, 140, .09);
  --sh-3: 0 8px 28px rgba(20, 100, 140, .18);
  --scrim: rgba(16, 47, 58, 0.42);
  --toast-bg: rgba(16, 47, 58, 0.94);
  --toast-fg: #ffffff;
  --bar-bg: rgba(255, 255, 255, 0.86);
  --glass-bg: rgba(255, 255, 255, 0.62);
  --glass-line: rgba(16, 47, 58, 0.1);
  --glass-top: rgba(255, 255, 255, .55);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20200'%20%3E%3Cg%20stroke='%233d7f99'%20stroke-width='1.7'%20fill='none'%20stroke-linecap='round'%20opacity='.55'%3E%3Cpath%20d='M196%2070c4-6%208-6%209%200M206%2070c4-6%208-6%209%200'/%3E%3Cpath%20d='M228%2052c3-4%206-4%207%200M235%2052c3-4%206-4%207%200'/%3E%3C/g%3E%3C/svg%3E") right top / auto 200px no-repeat, url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20320%2056'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0%2022C40%208%2070%2034%20110%2022S180%208%20220%2022%20290%2034%20320%2022V56H0z'%20fill='%239ed9ec'%20opacity='.45'/%3E%3Cpath%20d='M0%2032C46%2020%2080%2044%20124%2032S196%2018%20240%2032%20300%2044%20320%2032V56H0z'%20fill='%236cc4e0'%20opacity='.35'/%3E%3C/svg%3E") left 0 top 150px / 320px 50px repeat-x, radial-gradient(circle at calc(100% - 22px) 4px, #fff6cf 0 34px, rgba(255, 214, 102, .5) 35px 50px, rgba(255, 214, 102, 0) 96px) top / 100% 210px no-repeat, linear-gradient(#b9e6fa, #dff3fb 60%, rgba(237, 248, 251, 0)) top / 100% 210px no-repeat;
}
:root[data-theme="autumn"] {
  --bg: #f4eadb;
  --surface: #fffaf1;
  --surface-2: #f4e7d2;
  --line: #e8d6ba;
  --line-2: #dbc39f;
  --text: #3a291b;
  --text-sub: #6f5843;
  --text-mut: #a08a72;
  --brand-500: #bd4f1c;
  --brand-600: #9c3f15;
  --brand-weak: rgba(189, 79, 28, 0.13);
  --on-brand: #fff;
  --hot: #b8412e;
  --hot-weak: rgba(184, 65, 46, .11);
  --up: #2f7d5c;
  --up-weak: rgba(47, 125, 92, .12);
  --sh-1: 0 1px 2px rgba(120, 80, 30, .06);
  --sh-2: 0 2px 8px rgba(120, 80, 30, .09);
  --sh-3: 0 8px 28px rgba(120, 80, 30, .18);
  --scrim: rgba(58, 41, 27, 0.42);
  --toast-bg: rgba(58, 41, 27, 0.94);
  --toast-fg: #fffaf1;
  --bar-bg: rgba(255, 250, 241, 0.86);
  --glass-bg: rgba(255, 250, 241, 0.62);
  --glass-line: rgba(58, 41, 27, 0.1);
  --glass-top: rgba(255, 255, 255, .55);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20200'%20%3E%3Cg%20transform='translate(250%2024)%20rotate(-20)%20scale(1.25)%20translate(-12%20-12)'%3E%3Cpath%20d='M12%201.5l1.7%204.4%203-1.7-.7%204.2%203.8-.8-2%203.4%203.4%201.6-4.4%201.1.9%202.6-3.8-1.3L13.2%2020h-.8v3h-.8v-3h-.8l-.6-5-3.8%201.3.9-2.6-4.4-1.1%203.4-1.6-2-3.4%203.8.8-.7-4.2%203%201.7z'%20fill='%23c8452a'/%3E%3C/g%3E%3Cg%20transform='translate(288%2046)%20rotate(25)%20scale(1.08)%20translate(-12%20-12)'%3E%3Cpath%20d='M12%201.5l1.7%204.4%203-1.7-.7%204.2%203.8-.8-2%203.4%203.4%201.6-4.4%201.1.9%202.6-3.8-1.3L13.2%2020h-.8v3h-.8v-3h-.8l-.6-5-3.8%201.3.9-2.6-4.4-1.1%203.4-1.6-2-3.4%203.8.8-.7-4.2%203%201.7z'%20fill='%23e07a2c'/%3E%3C/g%3E%3Cg%20transform='translate(326%2022)%20rotate(15)%20scale(1.08)%20translate(-12%20-12)'%3E%3Cpath%20d='M12%2022v-8.5'%20stroke='%23b08a1e'%20stroke-width='1.2'/%3E%3Cpath%20d='M12%2013.5C6%2013.5%202.6%209.6%203.5%203.8%206.6%205.6%209.4%205%2012%202.6%2014.6%205%2017.4%205.6%2020.5%203.8%2021.4%209.6%2018%2013.5%2012%2013.5z'%20fill='%23e8b92f'/%3E%3Cpath%20d='M12%2013.5V5'%20stroke='%23cfa020'%20stroke-width='.7'/%3E%3C/g%3E%3Cg%20transform='translate(310%2082)%20rotate(-40)%20scale(0.75)%20translate(-12%20-12)'%3E%3Cpath%20d='M12%201.5l1.7%204.4%203-1.7-.7%204.2%203.8-.8-2%203.4%203.4%201.6-4.4%201.1.9%202.6-3.8-1.3L13.2%2020h-.8v3h-.8v-3h-.8l-.6-5-3.8%201.3.9-2.6-4.4-1.1%203.4-1.6-2-3.4%203.8.8-.7-4.2%203%201.7z'%20fill='%23b8362a'/%3E%3C/g%3E%3Cg%20transform='translate(266%2064)%20rotate(-30)%20scale(0.83)%20translate(-12%20-12)'%3E%3Cpath%20d='M12%2022v-8.5'%20stroke='%23b08a1e'%20stroke-width='1.2'/%3E%3Cpath%20d='M12%2013.5C6%2013.5%202.6%209.6%203.5%203.8%206.6%205.6%209.4%205%2012%202.6%2014.6%205%2017.4%205.6%2020.5%203.8%2021.4%209.6%2018%2013.5%2012%2013.5z'%20fill='%23e8b92f'/%3E%3Cpath%20d='M12%2013.5V5'%20stroke='%23cfa020'%20stroke-width='.7'/%3E%3C/g%3E%3Cg%20transform='translate(342%2062)%20rotate(40)%20scale(0.83)%20translate(-12%20-12)'%3E%3Cpath%20d='M12%201.5l1.7%204.4%203-1.7-.7%204.2%203.8-.8-2%203.4%203.4%201.6-4.4%201.1.9%202.6-3.8-1.3L13.2%2020h-.8v3h-.8v-3h-.8l-.6-5-3.8%201.3.9-2.6-4.4-1.1%203.4-1.6-2-3.4%203.8.8-.7-4.2%203%201.7z'%20fill='%23e39b2b'/%3E%3C/g%3E%3C/svg%3E") right top / auto 200px no-repeat, linear-gradient(#f0cf9f, rgba(244, 234, 219, 0)) top / 100% 210px no-repeat, radial-gradient(rgba(120, 80, 30, .06) 1px, transparent 1.3px) 0 0 / 5px 5px;
}
:root[data-theme="winter"] {
  --bg: #0d1626;
  --surface: #162236;
  --surface-2: #1d2b42;
  --line: #25354e;
  --line-2: #33466a;
  --text: #e9f1fb;
  --text-sub: #93a7c2;
  --text-mut: #6a7f9b;
  --brand-500: #8fd0ff;
  --brand-600: #5fb8ef;
  --brand-weak: rgba(143, 208, 255, 0.15);
  --toast-bg: rgba(233, 241, 251, 0.95);
  --toast-fg: #0d1626;
  --bar-bg: rgba(13, 22, 38, 0.88);
  --glass-bg: rgba(22, 34, 54, 0.6);
  --glass-line: rgba(190, 220, 255, .12);
  --glass-top: rgba(255, 255, 255, .07);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20200'%20%3E%3Cpath%20d='M300%2030a17%2017%200%201%200%2017%2023%2014%2014%200%200%201-17-23z'%20fill='%23f2f6ff'%20opacity='.92'/%3E%3C/svg%3E") right top / auto 200px no-repeat, url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%2070'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0%2030C50%204%2096%208%20140%2026S230%204%20290%2014%20340%2024%20360%2030V70H0z'%20fill='%231a2944'%20opacity='.9'/%3E%3Cpath%20d='M0%2030C50%204%2096%208%20140%2026S230%204%20290%2014%20340%2024%20360%2030'%20stroke='%23dfeaff'%20stroke-width='2'%20fill='none'%20opacity='.5'/%3E%3Cpath%20d='M0%2050C70%2030%20130%2042%20200%2050S300%2038%20360%2050V70H0z'%20fill='%23132037'/%3E%3C/svg%3E") left 0 top 132px / 360px 70px repeat-x, url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20130'%20%3E%3Ccircle%20cx='117'%20cy='22'%20r='1.3'%20fill='%23dfeaff'%20opacity='0.49'/%3E%3Ccircle%20cx='193'%20cy='48'%20r='0.7'%20fill='%23dfeaff'%20opacity='0.70'/%3E%3Ccircle%20cx='13'%20cy='56'%20r='0.7'%20fill='%23dfeaff'%20opacity='0.50'/%3E%3Ccircle%20cx='153'%20cy='103'%20r='0.7'%20fill='%23dfeaff'%20opacity='0.56'/%3E%3Ccircle%20cx='226'%20cy='118'%20r='1.2'%20fill='%23dfeaff'%20opacity='0.65'/%3E%3Ccircle%20cx='351'%20cy='10'%20r='1.5'%20fill='%23dfeaff'%20opacity='0.59'/%3E%3Ccircle%20cx='52'%20cy='18'%20r='0.9'%20fill='%23dfeaff'%20opacity='0.86'/%3E%3Ccircle%20cx='65'%20cy='74'%20r='1.2'%20fill='%23dfeaff'%20opacity='0.64'/%3E%3Ccircle%20cx='197'%20cy='12'%20r='0.7'%20fill='%23dfeaff'%20opacity='0.55'/%3E%3Ccircle%20cx='245'%20cy='55'%20r='0.9'%20fill='%23dfeaff'%20opacity='0.74'/%3E%3Ccircle%20cx='163'%20cy='40'%20r='1.4'%20fill='%23dfeaff'%20opacity='0.80'/%3E%3Ccircle%20cx='88'%20cy='73'%20r='1.1'%20fill='%23dfeaff'%20opacity='0.89'/%3E%3Ccircle%20cx='263'%20cy='39'%20r='1.6'%20fill='%23dfeaff'%20opacity='0.51'/%3E%3Ccircle%20cx='151'%20cy='95'%20r='0.8'%20fill='%23dfeaff'%20opacity='0.69'/%3E%3Ccircle%20cx='14'%20cy='84'%20r='1.4'%20fill='%23dfeaff'%20opacity='0.74'/%3E%3Ccircle%20cx='315'%20cy='42'%20r='1.3'%20fill='%23dfeaff'%20opacity='0.75'/%3E%3C/svg%3E") left 0 top 0 / 360px 130px repeat-x, linear-gradient(#070d19, #0f1b30 70%, rgba(13, 22, 38, 0)) top / 100% 210px no-repeat;
}
:root[data-theme="news"] {
  --bg: #efebe2;
  --surface: #faf8f3;
  --surface-2: #e6e0d3;
  --line: #d4ccbb;
  --line-2: #bfb5a1;
  --text: #1d1b17;
  --text-sub: #57514a;
  --text-mut: #8a8378;
  --brand-500: #1d1b17;
  --brand-600: #000000;
  --brand-weak: rgba(29, 27, 23, 0.13);
  --on-brand: #fff;
  --hot: #b8412e;
  --hot-weak: rgba(184, 65, 46, .11);
  --up: #2f7d5c;
  --up-weak: rgba(47, 125, 92, .12);
  --sh-1: 0 1px 2px rgba(40, 36, 30, .06);
  --sh-2: 0 2px 8px rgba(40, 36, 30, .09);
  --sh-3: 0 8px 28px rgba(40, 36, 30, .18);
  --scrim: rgba(29, 27, 23, 0.42);
  --toast-bg: rgba(29, 27, 23, 0.94);
  --toast-fg: #faf8f3;
  --bar-bg: rgba(250, 248, 243, 0.86);
  --glass-bg: rgba(250, 248, 243, 0.62);
  --glass-line: rgba(29, 27, 23, 0.1);
  --glass-top: rgba(255, 255, 255, .55);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20200'%20%3E%3Cdefs%3E%3Cpattern%20id='d'%20width='5'%20height='5'%20patternUnits='userSpaceOnUse'%3E%3Ccircle%20cx='2.5'%20cy='2.5'%20r='1.15'%20fill='%231d1b17'/%3E%3C/pattern%3E%3CradialGradient%20id='g'%20cx='1'%20cy='0'%20r='1'%3E%3Cstop%20offset='0'%20stop-color='%23fff'/%3E%3Cstop%20offset='.8'%20stop-color='%23fff'%20stop-opacity='0'/%3E%3C/radialGradient%3E%3Cmask%20id='m'%3E%3Crect%20width='360'%20height='200'%20fill='url(%23g)'/%3E%3C/mask%3E%3C/defs%3E%3Crect%20width='360'%20height='200'%20fill='url(%23d)'%20mask='url(%23m)'%20opacity='.13'/%3E%3C/svg%3E") right top / auto 200px no-repeat;
}
:root[data-theme="neon"] {
  --bg: #0b0817;
  --surface: #140f26;
  --surface-2: #1d1636;
  --line: #2a2150;
  --line-2: #3a2e6b;
  --text: #efeaff;
  --text-sub: #9d93c4;
  --text-mut: #6f6696;
  --brand-500: #2ef2d0;
  --brand-600: #1fcfb1;
  --brand-weak: rgba(46, 242, 208, 0.15);
  --toast-bg: rgba(239, 234, 255, 0.95);
  --toast-fg: #0b0817;
  --bar-bg: rgba(11, 8, 23, 0.88);
  --glass-bg: rgba(20, 15, 38, 0.6);
  --glass-line: rgba(46, 242, 208, .3);
  --glass-top: rgba(255, 255, 255, .07);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20196'%20%3E%3Cg%20fill='%230f0a22'%3E%3Crect%20x='-4'%20y='71'%20width='19'%20height='79'/%3E%3Crect%20x='17'%20y='105'%20width='30'%20height='45'/%3E%3Crect%20x='49'%20y='73'%20width='34'%20height='77'/%3E%3Crect%20x='85'%20y='74'%20width='32'%20height='76'/%3E%3Crect%20x='119'%20y='77'%20width='41'%20height='73'/%3E%3Crect%20x='161'%20y='78'%20width='40'%20height='72'/%3E%3Crect%20x='203'%20y='78'%20width='30'%20height='72'/%3E%3Crect%20x='235'%20y='70'%20width='41'%20height='80'/%3E%3Crect%20x='278'%20y='94'%20width='38'%20height='56'/%3E%3Crect%20x='318'%20y='86'%20width='34'%20height='64'/%3E%3Crect%20x='354'%20y='100'%20width='30'%20height='50'/%3E%3C/g%3E%3Crect%20x='7'%20y='95'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='0'%20y='104'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='7'%20y='113'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='0'%20y='122'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='0'%20y='131'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='7'%20y='131'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='0'%20y='140'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='21'%20y='120'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='35'%20y='120'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='42'%20y='120'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='21'%20y='138'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='28'%20y='138'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='35'%20y='138'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='53'%20y='79'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='74'%20y='79'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='53'%20y='88'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='74'%20y='88'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='74'%20y='97'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='60'%20y='106'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='67'%20y='106'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='67'%20y='115'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='53'%20y='124'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='60'%20y='124'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='67'%20y='142'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='96'%20y='80'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='89'%20y='98'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='103'%20y='98'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='103'%20y='107'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='103'%20y='116'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='89'%20y='125'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='96'%20y='134'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='89'%20y='143'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='96'%20y='143'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='130'%20y='92'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='137'%20y='92'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='144'%20y='92'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='123'%20y='101'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='137'%20y='101'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='151'%20y='101'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='130'%20y='119'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='130'%20y='128'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='144'%20y='128'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='151'%20y='128'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='137'%20y='137'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='179'%20y='84'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='186'%20y='84'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='165'%20y='93'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='179'%20y='93'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='186'%20y='93'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='193'%20y='93'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='165'%20y='102'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='186'%20y='102'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='186'%20y='120'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='165'%20y='138'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='186'%20y='138'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='193'%20y='138'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='221'%20y='84'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='214'%20y='93'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='221'%20y='93'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='214'%20y='111'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='214'%20y='120'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='221'%20y='120'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='253'%20y='76'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='239'%20y='112'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='246'%20y='112'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='267'%20y='112'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='239'%20y='121'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='253'%20y='121'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='260'%20y='121'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='267'%20y='121'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='253'%20y='130'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='260'%20y='130'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='267'%20y='130'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='239'%20y='139'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='282'%20y='100'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='289'%20y='100'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='303'%20y='100'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='289'%20y='109'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='296'%20y='109'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='282'%20y='118'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='289'%20y='118'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='282'%20y='127'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='296'%20y='127'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='282'%20y='136'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='322'%20y='92'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='329'%20y='101'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='336'%20y='101'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='329'%20y='110'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='329'%20y='119'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='329'%20y='128'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='336'%20y='128'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='343'%20y='128'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='329'%20y='137'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='365'%20y='115'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='372'%20y='115'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='365'%20y='124'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Crect%20x='372'%20y='124'%20width='3'%20height='4'%20fill='%23ff4fd8'%20opacity='.75'/%3E%3Crect%20x='372'%20y='133'%20width='3'%20height='4'%20fill='%232ef2d0'%20opacity='.75'/%3E%3Cg%20stroke='%23ff4fd8'%20stroke-width='.8'%20opacity='.4'%3E%3Cpath%20d='M0%20160H360M0%20172H360M0%20190H360'/%3E%3C/g%3E%3Cpath%20d='M0%20150H360'%20stroke='%23ff4fd8'%20stroke-width='1.4'%20opacity='.9'/%3E%3C/svg%3E") left 0 top 0 / 360px 196px repeat-x, radial-gradient(ellipse 70% 60px at 50% 150px, rgba(255, 61, 139, .28), transparent) top / 100% 210px no-repeat, linear-gradient(#22104a, rgba(11, 8, 23, 0)) top / 100% 210px no-repeat;
}
:root[data-theme="space"] {
  --bg: #110b22;
  --surface: #1a1333;
  --surface-2: #241b44;
  --line: #2e2452;
  --line-2: #3d3170;
  --text: #eee8ff;
  --text-sub: #a59bc9;
  --text-mut: #766d9c;
  --brand-500: #c58bff;
  --brand-600: #a96cf0;
  --brand-weak: rgba(197, 139, 255, 0.15);
  --toast-bg: rgba(238, 232, 255, 0.95);
  --toast-fg: #110b22;
  --bar-bg: rgba(17, 11, 34, 0.88);
  --glass-bg: rgba(26, 19, 51, 0.6);
  --glass-line: rgba(197, 139, 255, .2);
  --glass-top: rgba(255, 255, 255, .07);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20200'%20%3E%3Cdefs%3E%3CradialGradient%20id='p'%20cx='.35'%20cy='.3'%3E%3Cstop%20offset='0'%20stop-color='%23ffd8ec'/%3E%3Cstop%20offset='.55'%20stop-color='%23c58bff'/%3E%3Cstop%20offset='1'%20stop-color='%234b2f96'/%3E%3C/radialGradient%3E%3C/defs%3E%3Cellipse%20cx='302'%20cy='66'%20rx='38'%20ry='9'%20fill='none'%20stroke='%23f3c9ff'%20stroke-width='2'%20opacity='.35'%20transform='rotate(-16%20302%2066)'/%3E%3Ccircle%20cx='302'%20cy='66'%20r='20'%20fill='url(%23p)'/%3E%3Cpath%20d='M266%2076a38%209-16%200%200%2073-21'%20stroke='%23f3c9ff'%20stroke-width='2.3'%20fill='none'%20opacity='.8'/%3E%3Ccircle%20cx='214'%20cy='40'%20r='5'%20fill='%23ffd28a'%20opacity='.85'/%3E%3C/svg%3E") right top / auto 200px no-repeat, url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20180'%20%3E%3Ccircle%20cx='209'%20cy='82'%20r='1.4'%20fill='%23f1e9ff'%20opacity='0.92'/%3E%3Ccircle%20cx='171'%20cy='117'%20r='0.6'%20fill='%23f1e9ff'%20opacity='0.80'/%3E%3Ccircle%20cx='233'%20cy='173'%20r='1.4'%20fill='%23f1e9ff'%20opacity='0.59'/%3E%3Ccircle%20cx='139'%20cy='118'%20r='0.5'%20fill='%23f1e9ff'%20opacity='0.68'/%3E%3Ccircle%20cx='60'%20cy='24'%20r='0.6'%20fill='%23f1e9ff'%20opacity='0.83'/%3E%3Ccircle%20cx='47'%20cy='46'%20r='0.9'%20fill='%23f1e9ff'%20opacity='0.89'/%3E%3Ccircle%20cx='29'%20cy='80'%20r='1.1'%20fill='%23f1e9ff'%20opacity='0.89'/%3E%3Ccircle%20cx='295'%20cy='151'%20r='0.8'%20fill='%23f1e9ff'%20opacity='0.66'/%3E%3Ccircle%20cx='129'%20cy='154'%20r='1.6'%20fill='%23f1e9ff'%20opacity='0.53'/%3E%3Ccircle%20cx='63'%20cy='43'%20r='0.8'%20fill='%23f1e9ff'%20opacity='0.69'/%3E%3Ccircle%20cx='212'%20cy='49'%20r='0.5'%20fill='%23f1e9ff'%20opacity='0.66'/%3E%3Ccircle%20cx='133'%20cy='100'%20r='1.5'%20fill='%23f1e9ff'%20opacity='0.80'/%3E%3Ccircle%20cx='186'%20cy='109'%20r='1.2'%20fill='%23f1e9ff'%20opacity='0.48'/%3E%3Ccircle%20cx='324'%20cy='137'%20r='1.5'%20fill='%23f1e9ff'%20opacity='0.85'/%3E%3Ccircle%20cx='141'%20cy='72'%20r='0.6'%20fill='%23f1e9ff'%20opacity='0.77'/%3E%3Ccircle%20cx='22'%20cy='15'%20r='0.7'%20fill='%23f1e9ff'%20opacity='0.53'/%3E%3Ccircle%20cx='122'%20cy='13'%20r='0.5'%20fill='%23f1e9ff'%20opacity='0.53'/%3E%3Ccircle%20cx='37'%20cy='66'%20r='0.5'%20fill='%23f1e9ff'%20opacity='0.89'/%3E%3Ccircle%20cx='221'%20cy='29'%20r='0.8'%20fill='%23f1e9ff'%20opacity='0.62'/%3E%3Ccircle%20cx='131'%20cy='25'%20r='1.4'%20fill='%23f1e9ff'%20opacity='0.95'/%3E%3Ccircle%20cx='168'%20cy='86'%20r='0.6'%20fill='%23f1e9ff'%20opacity='0.50'/%3E%3Ccircle%20cx='123'%20cy='49'%20r='1.4'%20fill='%23f1e9ff'%20opacity='0.53'/%3E%3C/svg%3E") left 0 top 0 / 360px 180px repeat-x, radial-gradient(ellipse 45% 70px at 10% 70px, rgba(107, 47, 179, .55), transparent) top / 100% 210px no-repeat, radial-gradient(ellipse 40% 60px at 50% 0, rgba(192, 64, 143, .4), transparent) top / 100% 210px no-repeat, radial-gradient(ellipse 40% 55px at 95% 120px, rgba(47, 79, 179, .45), transparent) top / 100% 210px no-repeat;
}
:root[data-theme="coffee"] {
  --bg: #241b14;
  --surface: #30251c;
  --surface-2: #3b2e23;
  --line: #46372a;
  --line-2: #5a4735;
  --text: #f0e6d9;
  --text-sub: #b9a690;
  --text-mut: #8a7863;
  --brand-500: #d9a26d;
  --brand-600: #c28a55;
  --brand-weak: rgba(217, 162, 109, 0.15);
  --toast-bg: rgba(240, 230, 217, 0.95);
  --toast-fg: #241b14;
  --bar-bg: rgba(36, 27, 20, 0.88);
  --glass-bg: rgba(48, 37, 28, 0.6);
  --glass-line: rgba(240, 230, 217, .12);
  --glass-top: rgba(255, 255, 255, .07);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20200'%20%3E%3Cg%20transform='translate(292%20112)'%3E%3Cellipse%20cx='26'%20cy='40'%20rx='24'%20ry='3.5'%20fill='%234a3829'/%3E%3Cpath%20d='M8%2012h36v10c0%2010-8%2017-18%2017S8%2032%208%2022z'%20fill='%23efe3d0'/%3E%3Cellipse%20cx='26'%20cy='12'%20rx='18'%20ry='4'%20fill='%236b4428'/%3E%3Cellipse%20cx='26'%20cy='12'%20rx='12'%20ry='2.3'%20fill='%23c8956a'/%3E%3Cpath%20d='M44%2016c7%200%207%2010%200%2010'%20stroke='%23efe3d0'%20stroke-width='3'%20fill='none'/%3E%3C/g%3E%3C/svg%3E") right top / auto 200px no-repeat, url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2070%2070'%20%3E%3Ccircle%20cx='35'%20cy='35'%20r='29'%20stroke='%23d9a26d'%20stroke-width='3'%20fill='none'%20opacity='.14'%20stroke-dasharray='120%2014%2036%208'/%3E%3C/svg%3E") left 14px top 40px / 76px 76px no-repeat, radial-gradient(circle at calc(100% - 40px) 130px, rgba(217, 162, 109, .2), transparent 120px) top / 100% 210px no-repeat, linear-gradient(#18110c, rgba(36, 27, 20, 0)) top / 100% 210px no-repeat;
}
:root[data-theme="rain"] {
  --bg: #e6ebef;
  --surface: #f7f9fa;
  --surface-2: #e7ecf0;
  --line: #d4dce3;
  --line-2: #bcc8d2;
  --text: #1e2a35;
  --text-sub: #52616e;
  --text-mut: #8695a2;
  --brand-500: #36658f;
  --brand-600: #28506f;
  --brand-weak: rgba(54, 101, 143, 0.13);
  --on-brand: #fff;
  --hot: #b8412e;
  --hot-weak: rgba(184, 65, 46, .11);
  --up: #2f7d5c;
  --up-weak: rgba(47, 125, 92, .12);
  --sh-1: 0 1px 2px rgba(40, 60, 80, .06);
  --sh-2: 0 2px 8px rgba(40, 60, 80, .09);
  --sh-3: 0 8px 28px rgba(40, 60, 80, .18);
  --scrim: rgba(30, 42, 53, 0.42);
  --toast-bg: rgba(30, 42, 53, 0.94);
  --toast-fg: #f7f9fa;
  --bar-bg: rgba(247, 249, 250, 0.86);
  --glass-bg: rgba(247, 249, 250, 0.62);
  --glass-line: rgba(30, 42, 53, 0.1);
  --glass-top: rgba(255, 255, 255, .55);
  --deco: radial-gradient(ellipse 50% 50px at 10% 0, rgba(244, 246, 248, .85), transparent) top / 100% 210px no-repeat, radial-gradient(ellipse 45% 45px at 60% 0, rgba(142, 157, 171, .55), transparent) top / 100% 210px no-repeat, radial-gradient(ellipse 40% 45px at 100% 30px, rgba(238, 241, 244, .75), transparent) top / 100% 210px no-repeat, linear-gradient(#aebbc7, #d3dbe2 60%, rgba(230, 235, 239, 0)) top / 100% 210px no-repeat;
}
:root[data-theme="ink"] {
  --bg: #f2ede2;
  --surface: #fbf8f1;
  --surface-2: #eee8da;
  --line: #e1d9c8;
  --line-2: #cfc5b0;
  --text: #1f1d1a;
  --text-sub: #5b554b;
  --text-mut: #8f887b;
  --brand-500: #a92d27;
  --brand-600: #87221d;
  --brand-weak: rgba(169, 45, 39, 0.13);
  --on-brand: #fff;
  --hot: #b8412e;
  --hot-weak: rgba(184, 65, 46, .11);
  --up: #2f7d5c;
  --up-weak: rgba(47, 125, 92, .12);
  --sh-1: 0 1px 2px rgba(60, 45, 35, .06);
  --sh-2: 0 2px 8px rgba(60, 45, 35, .09);
  --sh-3: 0 8px 28px rgba(60, 45, 35, .18);
  --scrim: rgba(31, 29, 26, 0.42);
  --toast-bg: rgba(31, 29, 26, 0.94);
  --toast-fg: #fbf8f1;
  --bar-bg: rgba(251, 248, 241, 0.86);
  --glass-bg: rgba(251, 248, 241, 0.62);
  --glass-line: rgba(31, 29, 26, 0.1);
  --glass-top: rgba(255, 255, 255, .55);
  --deco: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20200'%20%3E%3Cdefs%3E%3Cfilter%20id='b'%20x='-10%25'%20y='-10%25'%20width='120%25'%20height='120%25'%3E%3CfeGaussianBlur%20stdDeviation='2.4'/%3E%3C/filter%3E%3ClinearGradient%20id='g'%20x1='0'%20y1='0'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%232b2a28'%20stop-opacity='.6'/%3E%3Cstop%20offset='1'%20stop-color='%232b2a28'%20stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath%20filter='url(%23b)'%20d='M150%20160C180%20118%20205%2078%20236%2066c16-6%2024%2022%2042%2010%2020-14%2028-40%2050-30%2014%206%2024%2030%2040%2038V160z'%20fill='url(%23g)'%20opacity='.55'/%3E%3Cpath%20filter='url(%23b)'%20d='M190%20172c30-32%2052-58%2080-62%2020-3%2028%2022%2050%2010%2016-9%2028-14%2044-4V172z'%20fill='url(%23g)'%20opacity='.85'/%3E%3Cpath%20d='M98%2054q5-5%2010%200q5-5%2010%200M126%2040q4-4%208%200q4-4%208%200'%20stroke='%233a3833'%20stroke-width='1.3'%20fill='none'%20stroke-linecap='round'%20opacity='.7'/%3E%3Cg%20transform='translate(330%20140)%20rotate(-5)'%3E%3Crect%20x='-12'%20y='-12'%20width='24'%20height='24'%20rx='3'%20fill='%23b0302a'%20opacity='.92'/%3E%3Cpath%20d='M-6-6h12M0-6V7M-6%201h12M-6%207h12'%20stroke='%23fbf8f1'%20stroke-width='1.8'/%3E%3C/g%3E%3C/svg%3E") right top / auto 200px no-repeat, linear-gradient(#e7dfcf, rgba(242, 237, 226, 0)) top / 100% 210px no-repeat, radial-gradient(rgba(90, 70, 40, .05) 1px, transparent 1.6px) 0 0 / 7px 9px;
}

/* 🔴 밝은 바탕 테마 — 태그 금색·성향 색·계열 색·인용 축 색을 한 단 내린다 (`:root` 의 `--tagc`·`--l`·`--r`·`--sv-*`·`--q-*` 머리 문단).
   🟢 2026-10-27: 선택자가 테마 이름 목록이 아니라 `data-tone`(theme.js 가 `--bg` 밝기로 심는다)이다 — 새 밝은 테마가 저절로 탄다. */
:root[data-tone="light"] { --tagc: #a57609; }
:root[data-tone="light"] {
  /* 성향 색 — 밝은 바탕 단계 (`:root` 의 `--l`·`--r` 머리 문단 · 밝은 바탕 위 11px 굵은 글자가 읽히는 단) */
  --l: #1f5fd1;
  --l-soft: rgba(31, 95, 209, .11);
  --r: #c8352b;
  --r-soft: rgba(200, 53, 43, .11);
  /* 계열 색 다섯 — 밝은 바탕 단계 (`:root` 의 `--sv-*` 머리 문단) */
  --sv-1: #2a78d6;
  --sv-2: #eb6834;
  --sv-3: #1baf7a;
  /* 인용 카드의 축 색 — 밝은 바탕에서 11px 굵은 라벨이 읽히는 단까지 내린다 (`:root` 의 `--q-*` 머리 문단) */
  --q-post: #2a68cc;
  --q-cmt: #0f6e4a;
}

:root {
  /* 🔴 앱바 높이 — common.css 기본(54px)을 덮는다.
     이력: 54 → 64(제목+칩 두 줄) → 72(두 줄 실측 부족) → 62 (2026-08-14 · 요청
     "조금 줄여줘") → **48** (2026-09-10 · 112차 요청 10번 · 실기기 「상단칩 위로 여백이 많다」).
     🔴 하한은 뒤로가기 버튼의 터치 영역(`.appbar .back` 의 height · 그 규칙 주석)이다 — 칩 상자(`.chip` 의 padding + 글자 줄)와
        두 줄 제목(`.bar-ch`)은 그 안에 든다(기본 글꼴 배율에서). 🔴 안드로이드 글꼴 배율(웹뷰 textZoom · 180%·200%)에서는 그 둘이 이 값을 넘으므로 `.appbar` 는 이 값을 `min-height` 로 받아 바가 자란다 — `height` 로 되돌리면 두 줄 제목이 바 밖으로 삐져나오고 칩 상자 윗부분이 `.chips`(가로 스크롤 상자)에 잘린다.
     🔴 **칩 바·뒤로가기 바가 같은 값이다** — 전환 때 미끄러지는 것은 `#scroll` 하나이고(common.css `nav-fwd`) 앱바는 제자리에서
     🔴 이 값을 읽는 곳은 셋이다: `.appbar` 의 min-height · `#ptr` 의 top · `.cload` 의 min-height (전부 var — 숫자를 베껴 쓰지 말 것).
        🟢 `#ptr`·`.cload` 는 칩 바 화면에서 `--chip-gap`(아래) · `--scroll-gap` 도 같이 읽는다 (2026-09-14 · 125차 요청 9번). */
  --bar-h: 48px;
  /* 🟢 **칩 구분선 아래 여백** (2026-09-14 · 125차 요청 9번 · 사용자 「모든 공통 칩뷰 구분선 아래에 여백 · 콘텐츠가 구분선에 바로 닿지 않게」) —
     🔴 **바 높이(`--bar-h`)는 그대로**이고 여백은 칩 바 화면의 `#scroll` 이 «상자 밖» margin 으로 갖는다(`#appbar.has-chips ~ #scroll` 문단) —
        바를 키우면 뒤로가기 바와 갈려 전환 때 본문 시작점이 튄다(위 문단). 본문 안에 붙는 칩 줄(`.csecbar`)은 같은 값의 띠를 그린다(🟡 채널 구독 띠 `.chstrip` 은 139차에 걷었다).
     🔴 읽는 곳의 정본은 self-check 「칩 구분선 아래 여백」 — 숫자를 베껴 쓰지 말 것. */
  --chip-gap: 12px;
}

/* ── 기본 ─────────────────────────────────────────────────────────────────── */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
/* 🔴 글자 길게 눌러도 시스템 선택·복사 툴팁이 안 뜬다 (2026-08-13 밤 8차 · 요청 —
   체크비 styles.css:77 과 같은 두 줄). 웹뷰에서 텍스트가 선택되면 앱이 아니라
   웹페이지처럼 보인다. 입력칸만 예외 — 안 풀면 검색창에 커서를 못 놓는다.
   공유는 상세 앱바의 공유 버튼이 담당한다(본문 복사가 입구가 아니다). */
body { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
input, textarea, [contenteditable="true"] { -webkit-user-select: text; user-select: text; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  overflow: hidden;              /* 스크롤은 #scroll 이 담당 — 앱바·탭바 고정 */
  transition: background .2s, color .2s;

  /* 🔴 세이프에어리어를 body 에서 걷어낸다 (2026-08-10 · 실기기 사고).
        `common.css` 는 body 가 스크롤하는 앱(하루·Verbatim)을 전제로
          body { padding-top: calc(--safe-top + --ad-h); padding-bottom: --safe-bot }
        (.appbar 가 --safe-top, .tabbar 가 --safe-bot) 그 패딩이 살아 있으면
        상하 인셋이 **한 겹씩 더** 생긴다.

        증상: 노치 아이폰에서 하단 탭이 홈 인디케이터 위로 34px 떠오르고,
        탭 아이콘 아래로 68px(= 34 내부 패딩 + 34 body 패딩)의 빈칸이 남는다.
        🔴 .tabbar 만 고치면 안 된다 — 두 겹 중 어느 쪽을 지워도 나머지 한 겹은
           남으므로, "인셋의 주인은 바"라는 규칙 자체를 여기서 못 박는다.

     🟢 2026-10-01 부터 body 는 광고 배너만큼도 안 민다 — 배너는 바텀시트가 열린 동안만 어두워진 바깥 위에 뜬다
        (`common/ads.js` 머리 주석 · 사용자 결정). 옛 줄 `padding: var(--ad-h) 0 0` 을 되살리면 시트를 열 때마다 화면이 50px 내려앉는다. */
  padding: 0;
}
/* ── 🔴 **버튼의 바탕은 «투명»에서 시작한다** (2026-09-17 · 137차·H3 · 사용자 「유동성/시가총액 시트 버튼이 하얗게 보인다 · 자주 나온다 · 재발 안 되게」) ──
   옛 규칙은 글꼴·글자색만 물려받게 해서, 바탕을 따로 안 정한 버튼은 **웹뷰의 기본 버튼 모양**(밝은 회색·흰 바탕 · iOS 는 네이티브 모양)으로 그려졌다.
   다크 화면에서 흰 판 위에 밝은 글자가 얹혀 글자가 안 보였다 — 같은 사고가 세 번째다(38차 필터 초기화 · 47차 자산 상세 행동 줄 · 이번 유동성 시트).
   🔴 그래서 뿌리에서 막는다: 바탕 투명 · 기본 모양 끔. 클래스 규칙은 이 요소 규칙보다 언제나 이기므로 색을 정한 버튼은 그대로다.
   🔴 테두리는 여기서 안 건드린다 — 클래스가 `border-color` 만 주는 자리가 폭을 잃는다. 기본 모양을 끈 버튼의 기본 테두리는 `.btn` 과 각 클래스가 정한다.
   self-check 「버튼 기본 바탕」이 이 줄과 `.btn` 의 색을 대조한다. */
button { font: inherit; color: inherit; background-color: transparent; -webkit-appearance: none; appearance: none; }

/* 🔴 `position: relative` 는 당겨서 새로고침 표시등(#ptr)의 기준이다.
      빼면 #ptr 이 뷰포트 기준으로 떠서 태블릿(가운데 정렬 컬럼)에서 왼쪽으로 쏠린다. */
#app {
  position: relative;
  display: flex; flex-direction: column;
  height: 100%; max-width: var(--maxw); margin: 0 auto;
  background: var(--bg);
  overflow: hidden;              /* 당길 때 #scroll 이 밀려 내려가도 바깥으로 안 샌다 */
}

/* ── 🔴 **태블릿 — 가로를 가득 채운다** (2026-09-02 · 65차·A 요청 2번 · 사용자 결정) ──────
      통째로 빈 배경이 된다 — 사용자 판정으로 표 쪽을 골랐다.
   🔴 고치는 것은 **`--maxw` 하나뿐**이다 — `#app`·`#sheet`(common.css)·`#inapp` 이 전부
      이 토큰을 읽고, `.bfab` 은 그 값으로 자기 위치를 «계산»한다. 그래서 값이
      `100vw` 면 그 계산이 저절로 화면 끝으로 간다(`max(16px, calc(50vw - 50vw + 16px))`).
      🔴 셋 중 하나만 따로 적으면 시트만 폰 폭으로 남아 어긋난 채 뜬다.
   🔴 **경계선(그림자)도 같이 걷는다.** 그건 「가운데 컬럼과 좌우 여백을 가르는 선」이었고,
      여백이 없어지면 화면 가장자리에 붙은 정체 불명의 줄 하나로 남는다.
   🔴 이 변경의 **나머지 반**은 표가 남는 폭을 «쓰게» 하는 것이다(`.mk-name` 의 그 문단 ·
      같은 요청 3번) — 폭만 넓히면 표가 왼쪽에 몰려 오른쪽이 통째로 빈다. */
@media (min-width: 700px) {
  :root { --maxw: 100vw; }
}

/* ── 앱바 ─────────────────────────────────────────────────────────────────── */
/* 🔴 --safe-top 이 height 와 padding **두 곳 모두**에 들어간다 (체크비 .appbar 와 동일).
      하나만 넣으면 노치 밑에 제목이 깔리고, 반대로 하나만 빼면 바가 노치까지 안 올라와
      상단에 배경색만 남는다. */
/* 🔴 뿌리 화면(탭)의 앱바는 **한 줄이다** — [프로필][칩 …가로스크롤…][검색]. */
.appbar {
  position: relative; flex: 0 0 auto; display: flex; align-items: center; gap: 4px;
  min-height: calc(var(--bar-h) + var(--safe-top));
  padding: var(--safe-top) 6px 0 var(--pad);
  /* 🟢 `position: relative` 는 앱바 안에 절대 배치될 자식의 기준이다 — 🗄 코인스쿱의 종목 상세 가운데 제목(`.bar-sym`)은 걷었다(2026-10-22 · 지금은 그런 자식이 없다 ·
     새로 만들 때 이 줄을 같이 볼 것).
     🔴 그 선언을 **첫 줄 안**에 둔 이유: self-check 「인셋을 먹는 곳」이 `grep -A4` 로 `--safe-top` 을 찾으므로 줄이 밀리면 안 된다. */
  /* 🔴 `height` 가 아니라 `min-height` 다 (2026-09-10 · 112차 감사) — 안드로이드 웹뷰는 시스템 글꼴 크기대로 글자만 키운다
     (textZoom · px 여백은 그대로 · 앱이 끄지 않는다). 48px 에서는 180%·200% 배율에 두 줄 제목(`.bar-pv` = (17+11)px × line-height 1.2
     × 배율)이 바를 넘어 아래 본문 위로 삐져나오고, 칩 상자 윗부분이 `.chips` 에 잘린다 — 그때는 바가 자란다.
     기본 배율에서는 모든 자식이 44px 이하라 `height` 와 같다. 🔴 `.appbar.bare` 가 `min-height: 0` 으로 되돌린다(세트).
     🔴 이 두 줄(`min-height`·`padding`)을 규칙 머리에서 네 줄 안에 둔다 — self-check 「인셋을 먹는 곳」이 `grep -A4` 로 `--safe-top` 을 찾는다. */
  background: var(--bg); backdrop-filter: none;   /* 🟢 화면 바탕과 같은 색(위 겹친 앱바 규칙과 같은 뜻 — 넓은 폭·상세 화면의 옛 흐름도) */
}
.appbar.has-chips { box-shadow: inset 0 -1px 0 var(--line); }
/* 🔴 뿌리 화면의 바는 **한 줄**이다 — [프로필][칩 …가로스크롤…][검색] (2026-08-14 · 요청 4번).
   🔴 늘어나는 칸은 **칩 줄 하나뿐**이다(`.chips { flex: 1 }`). 아바타·검색은 고정폭이라
      남는 공간을 칩이 다 먹고, 그래서 검색 아이콘이 저절로 오른쪽 끝에 붙는다 —
      `margin-left: auto` 가 따로 필요 없다. */
.appbar.two { align-items: center; gap: 8px; }
/* 🔴 내용이 없는 바 — **노치 인셋만** 남기고 눌린다 (2026-08-17 · 요청 1번 · 커뮤니티 탭).
   요소를 없애지 않는 이유: 인셋(`--safe-top`)을 먹는 곳이 `.appbar`·`.tabbar` 둘뿐이라
   (self-check) 지우면 내용이 노치 밑에 깔린다. 높이만 0 으로 만든다. */
.appbar.bare { height: var(--safe-top); min-height: 0; padding: var(--safe-top) 0 0; }
/* 🔴 **`min-width: 0` 과 `overflow: hidden` 은 세트다** (2026-09-02 · 65차 요청 5번).
   쪽지 대화방 앱바가 버튼 셋([번역][차단][신고])이 되면서, 제목이 곧 «상대 닉네임»인 그
   화면에서 긴 이름이 줄을 넘길 수 있게 됐다. flex 자식의 `min-width` 기본값은 `auto` 라
   `flex: 1` 만으로는 내용 폭 아래로 안 줄어든다 — 앞엣것만 쓰면 글자가 버튼 위로 흘러
   겹치고(`.tl-row` 가 실기기에서 물린 그 사고), 뒤엣것만 쓰면 버튼이 밀린다. */
.appbar h1 {
  flex: 1; min-width: 0; margin: 0; font-size: 17px; letter-spacing: -.3px; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.appbar h1 span { color: var(--brand-500); }
/* 🟢 영상 화면 앱바 제목 — [채널명][성향 칩] 이어서 (2026-10-27 · 사용자 «성향칩은 앱바 타이틀 우측에 배치»). 채널명만 말줄임이고 칩은 줄지 않는다 */
.appbar h1.bar-v { display: flex; align-items: center; gap: 8px; }
.appbar h1.bar-v .bar-vn { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: inherit; }   /* 🔴 위 `.appbar h1 span` 의 금색을 되돌린다 */
/* ── 🔴 **채널 방의 앱바 제목 — [얼굴][이름 / 구독자 수]** (2026-09-07 · 93차·B 요청 3번 · board/channel-room.js `roomBarTitleHTML`) ──
   🔴 위 `.appbar h1 span { color: brand }` 를 **되돌려야 한다** — 그 규칙은 「앱 이름의 한 글자를 금색으로」의 것이고,
      여기는 span 이 구조(얼굴 옆 두 줄)라서 그대로 두면 이름·구독자 수가 통째로 금색이 된다.
   🔴 `h1` 은 한 줄 말줄임(`nowrap`)인데 이 제목은 flex 두 줄이다 — 그래서 여기서 셋을 되돌리고(display·white-space·
      line-height) 말줄임은 안쪽 이름 줄이 맡는다(`.barch-name`). */
.appbar h1.bar-ch {
  display: flex; align-items: center; gap: 8px; white-space: normal; line-height: 1.2; font-size: 15px;
}
.appbar h1.bar-ch span { color: inherit; }
/* 🔴 위 `span { color: inherit }`(특이도 0,2,2)가 태그의 글자색(`.b-lv.b-ai` 0,2,0)을 이겨 앱바에서만 AI 태그가 흰 글씨 + 옅은 보라 바탕(= 어두운 앱바 위에서 «까만 바탕»)으로 보였다
   (2026-10-18 · 사용자 「채널 앱바의 AI 태그가 까만 바탕 같다 · 보라색 통일」). 다른 자리와 같은 보라 글씨로 되돌린다 — 값은 `.b-lv.b-ai` 의 것과 같다(고치면 두 곳). */
.appbar h1.bar-ch .b-lv.b-ai { color: #7c4dff; }
/* 🔴 제목이 곧 문이다 (2026-09-10 · 110차 요청 4번 · app.js `_barHTML` chroom) — 버튼 기본 모양을 걷고 안쪽 배치는 h1 과 같게.
   `min-width: 0` 이 없으면 긴 채널 이름이 말줄임 대신 앱바를 민다(안쪽 `.barch-nm` 의 말줄임이 부모 폭을 알아야 한다). */
.appbar h1.bar-ch .bar-ch-btn {
  display: flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%;
  padding: 2px 6px 2px 0; margin: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left;
  border-radius: 8px; cursor: pointer;
}
.appbar h1.bar-ch .bar-ch-btn:active { background: var(--surface-2, rgba(255,255,255,.06)); }
.b-av.barch-av { width: 30px; height: 30px; flex: none; }
.barch-txt { display: flex; flex-direction: column; min-width: 0; }
/* 🟢 `--nm-gap` = 이 줄의 flex gap (2026-09-14 · 125차 요청 4번) — 배지·태그 margin 이 이 값을 빼서 «닉네임↔배지 1px · 태그 앞 5px» 를 만든다(`.b-plusbadge` 문단) */
.barch-name { --nm-gap: 4px; display: flex; align-items: center; gap: var(--nm-gap); min-width: 0; font-weight: 700; }
.barch-nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.appbar h1.bar-ch .barch-sub { font-size: 11px; font-weight: 500; color: var(--text-mut); letter-spacing: 0; }

/* 🟢 «누가 보고 있나» (2026-09-10 · 111차 · services/presence.js) — 숫자 하나 · 이름 없음 · 값은 그린 뒤 DOM 에 얹는다(app.js paintPresence).
   🔴 `.pv-on` 이 inline-flex 라 UA 의 `[hidden]` 을 이긴다 — 그래서 감춤 규칙을 여기 직접 둔다(안 두면 빈 자리가 점 하나로 보인다). */
.pv[hidden], .pv-on[hidden] { display: none; }
/* 🟢 실시간 표시 앞의 «움직이는 점» (2026-09-12 · 119차 요청 7번 · `paintPresence`) — 초록 점이 숨쉬듯 커졌다 작아진다.
   「움직임 줄이기」를 켠 기기는 멈춘 점이다(그 뜻은 색이 이미 말한다). 쪽지 「접속 중」의 점(`.pv-on i`)도 같은 숨을 쉰다. */
.pv { display: inline-flex; align-items: center; gap: 4px; }
.pv-dot, .pv-on i { width: 6px; height: 6px; border-radius: 50%; background: var(--up); flex: 0 0 auto; animation: pv-pulse 1.6s ease-in-out infinite; }
@keyframes pv-pulse { 0%, 100% { transform: scale(.8); opacity: .55; } 50% { transform: scale(1.15); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .pv-dot, .pv-on i { animation: none; opacity: 1; } }
/* 구분점은 «첫 자식이 아닌, 보이는» 조각 앞에만 — 채널 방은 구독자 수(`.barch-fc` · 🟢 채널명이 있으면 그 앞의 @닉네임 `.barch-nk` · 2026-10-15)가 첫 자식이라 그 뒤의 「N명 보는 중」·「접속 중」에 점이 붙고, */
.appbar h1.bar-ch .barch-sub > :not(:first-child):not([hidden])::before { content: '·'; margin: 0 4px; color: var(--text-mut); }
.appbar h1.bar-ch.bar-pv { font-size: 17px; }.pv-on { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; color: var(--up); letter-spacing: 0; }
.pv-on i { width: 6px; height: 6px; border-radius: 50%; background: var(--up); }
.appbar h1 > .pv-on { margin-left: 6px; vertical-align: middle; }
/* 🔴 `‹` 글자가 아니라 인라인 SVG 다 (2026-08-11). 글자로 그리면 폰트에 따라 굵기·
      세로 위치가 달라지고, 한중일 폰트가 잡히면 전각으로 렌더돼 버튼 안에서 밀린다.
      🔴 탭 영역은 44x44 를 지킨다 — 화면 맨 왼쪽 위 모서리라 이보다 작으면 잘 안 눌린다. */
.appbar .back {
  background: none; border: 0; padding: 0 8px 0 0; cursor: pointer; color: var(--text);
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 44px; flex: 0 0 auto;
}
.appbar .back svg { width: 22px; height: 22px; display: block; }
/* ── 앱바 필터 칩 ─────────────────────────────────────────────────────────
   🔴 가로 스크롤이다. 언어에 따라 낱말 길이가 크게 달라서(영어 `Regulation`)
      한 줄에 다 안 들어간다. 줄바꿈으로 두면 앱바 높이가 언어마다 달라진다.
   🔴 스크롤바를 숨기되 스크롤은 살린다. `overflow: hidden` 으로 막으면
      영어에서 뒤쪽 칩에 **영영 손이 닿지 않는다.**
   🔴 `overscroll-behavior-x: contain` 이 없으면 칩 줄 끝에서 더 밀었을 때 화면 전체가
      좌우로 흔들린다. */
.chips {
  flex: 1; min-width: 0; display: flex; gap: 6px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none;
  /* 🔴 **가로만** (2026-09-04 · 75차 실기기 — 「좌우 고정이 아니라 상하로도 스크롤된다」).
     이 줄에서 시작한 몸짓이 세로로 새면 그건 언제나 페이지 스크롤이지 이 줄의 일이 아니고,
     길게 눌러 끄는 동안에는 그 스크롤이 몸짓을 통째로 가져가 드래그를 끊는다(app.js
     `orderedChips` 머리 주석). 🔴 `none` 이 아니라 `pan-x` 인 이유: 평소의 줄 넘기기는
     그대로여야 한다. 🔴 그리고 이 값은 **손이 닿는 순간** 확정되므로 드래그가 시작된 뒤에
     JS 로 바꾸는 방법으로는 이 자리를 못 막는다 — CSS 에 상시로 적는 이유가 그것이다. */
  touch-action: pan-x;
  /* 🔴 밑줄 표시기(`.chips-ind`)의 좌표 기준 — `common/chips.js` 가 `offsetLeft` 로 잰다 (2026-09-10 · 108차 요청 4번) */
  position: relative;
  /* 🟢 바 높이만큼 늘리고 **글자를 바닥에 붙인다** (2026-09-10 · 111차 요청 5번 → 같은 날 사용자 첨부 「밑줄과 구분선이 붙어야 한다」):
     밑줄 표시기는 줄의 바닥(`bottom: 0`) = `.appbar.has-chips` 의 구분선 «위»에 앉고, 글자와 밑줄 사이는 칩의 padding-bottom 하나다
     (트레이딩뷰류 탭의 그 모양). 🟡 처음 판은 칩이 가운데(`center`)라 글자와 밑줄 사이가 십수 px 였고, 한 판은 밑줄을 글자 쪽으로
     올렸는데(`--b`) 그러면 밑줄과 구분선이 갈린다 — 글자를 내리는 것이 답이었다. 🔴 늘리기만 하면 칩이 바 높이로 늘어나므로
     `align-items` 가 세트다. */
  align-self: stretch; align-items: flex-end;
}
.chips::-webkit-scrollbar { display: none; }
/* ── 🟢 **밑줄 표시기 — 켜진 칩 아래로 미끄러진다** (2026-09-10 · 108차 요청 4번 · 사용자 첨부 «탭 밑줄» 그림) ─────
   자리(`--x`·`--w`)는 `common/chips.js` 가 켜진 칩을 재서 넣고, 움직임은 여기 전환이 만든다.
   🔴 `transform` + `width` 만 전환한다(left 는 레이아웃을 다시 돈다) · 첫 자리는 `.chips-ind-jump` 로 점프.
   🔴 옛 「글자 + 세로 칸막이」(2026-08-14 · 요청 7번)는 이것으로 «바뀌었다» — 켜진 칩은 글자색(밝음)과 밑줄 둘로 말하고
      칸막이는 걷었다(밑줄이 있는 줄에 칸막이까지 있으면 선이 둘이다). */
.chips-ind {
  /* 🔴 `bottom: 0` — 긴 구분선 «위»에 붙는다(위 `.chips` 문단 · 글자와의 간격은 `.chip` 의 padding-bottom 이 만든다) */
  position: absolute; left: 0; bottom: 0; height: 3px; width: var(--w, 0px);
  transform: translateX(var(--x, 0px)); border-radius: 2px; background: var(--brand-500);
  pointer-events: none; transition: transform .28s cubic-bezier(.2, .7, .2, 1), width .28s cubic-bezier(.2, .7, .2, 1);
}
.chips-ind.chips-ind-jump { transition: none; }
@media (prefers-reduced-motion: reduce) { .chips-ind { transition: none; } }
/* 🔴 알약(테두리+배경)이 아니라 **글자**다 (2026-08-14 · 요청 7번). 🟡 그때의 「+ 세로 구분선」은 2026-09-10(108차 요청 4번)에
      «밑줄 표시기»(위 `.chips-ind`)로 바뀌었다 — 선택 상태는 글자색(밝음)과 밑줄 둘로 말한다.
   🔴 gap 은 0 그대로 — 칩 사이는 `.chip` 의 좌우 padding 이 만든다(본문 칩 줄 `.df-chips` 도 같은 칸 · 2026-09-10 · 112차 요청 11번). */
/* 🔴 `gap: 0` 의 근거는 위 문단이다.
   🔴 `flex: 1` 은 위 규칙 그대로 살린다 (2026-08-14 · 앱바가 다시 한 줄이 됐다).
      칩 줄이 **유일하게 늘어나는 칸**이라, 이게 있어야 검색 아이콘이 오른쪽 끝에
      붙고 칩이 남는 폭을 다 쓴다. 한때 `flex: 0 0 auto` 로 덮었는데(두 줄 시절)
      지금 그러면 칩 줄이 안 늘어나 검색이 칩 바로 옆에 달라붙는다. */
.chips { gap: 0; }
/* 🔴 **칩을 키워도 앱바 높이(`--bar-h`)는 안 늘어난다** (2026-08-14 3차 · 요청 1번).
      `.appbar` 는 칩 상자가 그 안에 드는 동안 높이가 고정이고(`min-height` · `.appbar` 규칙 주석) 칩 줄은 그 바닥에 붙어 있어(`.chips` 의 flex-end · 111차) 칩이 커지면 «위로» 자란다.
      🔴 그러니 **폰트를 키울 때 `--bar-h` 를 따라 올리지 말 것** — 모든 바가 같이 커진다(`:root` 문단). 대신 칩 상자(`.chip` 의
      padding + 글자 줄)가 바 안에 드는지만 본다 — 넘으면 바가 자라서(`.appbar` 의 min-height) 칩 바와 뒤로가기 바의 높이가 갈린다(안드로이드 글꼴 배율 180%·200% 에서는 실제로 넘는다).
   🔴 늘어나는 건 높이가 아니라 **가로 폭**이다. 칩 줄은 가로 스크롤이라 넘쳐도 잘리진
      않지만, 한 화면에 보이는 개수가 줄면 뒤쪽 칩을 못 본 채로 쓰게 된다.
      (같은 이유로 2026-08-14 에 「국내」 칩을 뺐다 — 또 키우려면 개수를 먼저 본다.) */
.chip {
  flex: 0 0 auto; cursor: pointer;
  padding: 5px 12px; border: 0; background: none; border-radius: 0;
  font-size: 18px; font-weight: 700; color: var(--text-mut);
  white-space: nowrap; transition: color .15s, opacity .12s, transform .12s;
}
/* 🔴 밑줄(3px)이 이 안에 앉는다 — 글자 상자 바닥과 밑줄 윗선 사이가 «이 값 − 3px» (사용자 첨부의 간격 · 2026-09-10 · 111차) */
.chip { padding-bottom: 7px; }
/* 🔴 칩 줄이 바닥에 붙으면서 오른쪽 도구도 글자 줄에 맞춘다 — 가운데에 두면 글자만 내려앉아 어긋나 보인다. */
.appbar.has-chips > :not(.chips) { align-self: flex-end; margin-bottom: 1px; }
.chip.on { color: var(--text); font-weight: 800; }
.chip:active { color: var(--text-sub); }

/* ── 🔴 **상단 칩을 길게 눌러 끈다** (코인스쿱 2026-09-04 · 이 앱 2026-10-26 · `common/chipbar.js dragStart`) ────────
   하단 탭(`.tabbtn.tabdrag`)과 같은 말: 드는 칩은 **커지고 브랜드색**이고 나머지는 흐려진다 — 글자만 있는 줄이라 칩 하나만 커져서는 「들렸다」가 안 읽힌다.
   🔴 `touch-action` 은 여기서 안 끈다 — 평소엔 줄이 가로로 스크롤돼야 하고, 드래그가 시작된 뒤엔 JS 가 `preventDefault` 로 막는다. */
.chip.chipdrag {
  color: var(--brand-500); background: color-mix(in srgb, var(--brand-500) 22%, transparent);
  border-radius: var(--r-pill); transform: scale(1.08); position: relative; z-index: 2;
}
.chips.dragging .chip:not(.chipdrag) { opacity: .35; }
@media (prefers-reduced-motion: reduce) { .chip.chipdrag { transform: none; } }

.iconbtn {
  background: none; border: 0; padding: 8px; cursor: pointer;
  color: var(--text-sub); border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center;
}
.iconbtn:active { background: var(--surface-2); }

.iconbtn.on { color: var(--brand-500); }
.iconbtn svg { width: 21px; height: 21px; display: block; }
/* 채널 탭 앱바의 «내 채널» — 제목이 없는 바에 아이콘 하나뿐이라 조금 크게 (2026-09-07 · 88차 요청 3번) */
/* ── 🔴 **글자를 단 앱바 버튼** (코인스쿱 2026-08-25) ────────────
   이 앱바의 버튼은 전부 아이콘만이다 — 공유·편집처럼 **제자리에서 끝나는** 일이라
   그림만으로 뜻이 선다. 그런데 **탭을 바꾸는** 버튼은 다르다:
   어디로 가는지 모르고 누르면 화면이 통째로 바뀌어 길을 잃는다. 그래서 이것만 글자를 단다.
   🔴 아이콘을 없애고 글자만 두지 않는다 — 도착지(하단 커뮤니티 탭)와 **같은 그림**이어야
      「거기로 간다」가 눈에 이어진다. */
.iconbtn-t { gap: 5px; padding-left: 9px; padding-right: 9px; }
/* 🟢 글쓰기 앱바의 되돌리기·다시 실행 (2026-10-18) — 아이콘만이라 좌우를 좁혀 「초안·미리보기·등록」 폭을 덜 먹는다 · 할 수 없을 때는 흐리게 */
.iconbtn-ur { padding-left: 6px; padding-right: 6px; }
.iconbtn-ur:disabled { opacity: .3; pointer-events: none; }
/* 🟢 글쓰기 앱바의 간격만 줄였다 (2026-10-18 · 사용자 「앱바정리는 간격만 줄이는 걸로」) — 글자 버튼 좌우 9 → 5px, 버튼 사이 4 → 2px. 구성은 그대로 */
.iconbtn.cmp-b.iconbtn-t { padding-left: 5px; padding-right: 5px; }
.iconbtn.cmp-b + .iconbtn.cmp-b { margin-left: -2px; }
/* 🟢 글쓰기 앱바 — 되돌리기 그룹과 나머지 사이 구분선 · 오른쪽 끝 원형 보내기(`.dm-send` 를 앱바 크기로) (2026-10-18) */
.bar-sep { flex: none; width: 1px; height: 18px; margin: 0 2px; background: var(--line-2); }
.appbar .bar-send { width: 36px; height: 36px; align-self: center; margin: 0 2px 0 4px; }
.appbar .bar-send svg { width: 18px; height: 18px; }
.iconbtn-t span { font-size: 13px; font-weight: 700; white-space: nowrap; }
/* 🟢 **작성 화면 앱바의 「등록」 알약** (2026-09-15 · 사용자 요청 5번 · X 의 「게시하기」) — 🟡 2026-10-18 부터 글쓰기는 원형 아이콘(`.bar-send`)이고 이 알약은 댓글 쓰기만 쓴다. 키보드가 올라와도 보이는 자리다. */
.bar-pill {
  flex: none; margin: 0 4px 0 2px; padding: 7px 16px; border: 0; border-radius: 999px;
  background: var(--brand-500); color: var(--on-brand); font-size: 14px; font-weight: 800; white-space: nowrap; cursor: pointer;
}
.bar-pill:active { transform: scale(.96); }
.bar-pill:disabled { opacity: .55; }

/* ── 화면을 불러오는 동안 ────────────────────────────
   🔴 **화면 가운데**다. 글자만 왼쪽 위에 두면 "빈 화면 + 작은 글씨" 라 고장으로 읽힌다.
   🔴 높이를 `#scroll` 기준(`--tab-h`·`--safe-bot` 뺀 값)으로 잡는다 — `100%` 로 두면
      부모가 auto 라 내용 높이만큼만 차서 가운데가 화면 가운데가 아니다.
   🔴 스피너 조각(`.load-spin`·`.load-msg`)은 `common.css` 것을 그대로 쓴다.
      여기서는 **자리만** 잡는다 — 모양을 다시 만들면 덮는 로딩과 갈라진다. */
.cload {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; text-align: center;
  /* 🔴 `--scroll-gap` 은 칩 바 화면의 `#scroll` 이 여는 값이다(그 문단) — 안 빼면 안드로이드(`--safe-bot` 0)에서 로딩 상자가 8px 넘쳐 스크롤된다(125차 요청 9번 · 계산) */
  min-height: calc(100dvh - var(--bar-h) - var(--safe-top) - var(--tab-h) - var(--safe-bot) - 32px - var(--scroll-gap, 0px));
}
.cload .row { width: auto; padding: 0 18px; justify-content: center; }

/* ── 스크롤 영역 ─────────────────────────────────────────────────────────── */
/* 🔴 **아래 여백에 탭바 높이를 더한다** (2026-08-14 · 요청 — 뒤 화면이 탭 뒤로 지나간다).
      2026-08-10 의 반대 결정이다. 그때는 `.tabbar` 가 `#app`(flex column)의 **형제**라
      스크롤 영역을 안 덮었고, 그래서 여기 더하면 목록 끝에 112px 빈칸이 생겼다.
      지금은 `.tabbar` 가 `position: absolute` 로 목록 **위에** 뜨므로, 안 더하면
      마지막 카드가 버튼 뒤에 깔려 영영 안 읽힌다. 두 선언은 **세트**다 —
      `.tabbar` 를 다시 flex 형제로 되돌리면 여기도 같이 되돌린다(self-check 가 본다). */
/* 🔴 **위 패딩이 0 이다** (2026-08-12). 날짜 섹션(`.date-sect`)이 `position: sticky` 인데,
      sticky 요소는 **부모의 콘텐츠 상자 위로는 못 올라간다.** 여기 위 패딩이 8px 있으면
      고정된 머리와 앱바 사이에 8px 틈이 남고, 그 틈으로 **밑을 지나는 카드가 비친다.**
      대신 각 화면의 첫 요소가 자기 위 여백을 갖는다(`.date-sect:first-child` · `.searchbar` ·
      `.detail` · 더보기의 `.sect`). 🔴 여기 패딩을 되살리면 그 틈이 그대로 돌아온다.
   🟢 예외 하나 — 겹친 앱바 화면(좁은 폭의 뿌리 · 2026-10-08 · 「스크롤하면 숨는 바」 문단 ①)은 위 패딩이 **앱바 높이**다(`--bars-pad`).
      그 패딩은 바 «밑»에 있어서 틈이 아니다 — 머리가 패딩 끝(= 바 밑변)에 붙는다. 이 기본 규칙(0)은 그대로다(self-check).
   🟢 **칩 바 화면의 구분선 아래 여백은 padding 이 아니라 «상자 밖» margin 이다** (2026-09-14 · 125차 요청 9번 · 아래 `#appbar.has-chips ~ #scroll`) —
      padding 12px 로 넣어 보니 sticky 가 그 패딩을 존중해 `.date-sect` 가 y=60 에 붙고 그 위 틈으로 카드가 비쳤다(실측 · 바로 위 문단의 그 사고). */
/* 🔴 **키보드 높이를 아래 여백에 더한다** (2026-08-19 · 요청 6번). 키보드가 떠도
   **웹뷰가 안 줄어드는** 것이 전제다 — 앱바·탭바가 제자리에 있는 대신 키보드가 스크롤
   영역의 아래쪽을 덮으므로, 그 높이만큼 여백을 주면 입력칸이 가려지지 않는다.
   🔴 **그 전제를 만드는 것이 플랫폼마다 다르다** (2026-08-22 정정 — 옛 주석은
      `capacitor.config.json` 의 `resize: none` 하나면 되는 것처럼 적혀 있었고, 그 줄을
      믿은 탓에 「안드로이드에서 화면이 통째로 입력칸으로 바뀐다」의 원인을 한참 못 찾았다):
      · iOS      — `resize: none` + `setResizeMode`(플러그인이 실제로 처리한다)
      · 안드로이드 — `AndroidManifest.xml` 의 `windowSoftInputMode="adjustNothing"`.
                    🔴 그쪽 플러그인은 `resize` 키를 **읽지도 않는다**(`call.unimplemented`).
   🔴 그리고 창이 안 움직이면 OS 가 입력칸을 올려 주지 않으므로 `common/shell.js` 의
      `keepCaretVisible` 이 그 일을 한다 — 여백·매니페스트·그 함수가 **셋이 세트다.**
   🔴 `--kb` 를 안 더하면 댓글 입력칸이 키보드 뒤로 들어가 **자기가 친 글자가 안 보인다.** */
#scroll {
  flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch;
  /* 🔴 아래 여백은 탭 알약이 실제로 먹는 만큼이다 — `--tab-bot`(그쪽 주석) */
  padding: 0 12px calc(var(--tab-h) + var(--tab-bot) + 16px + var(--kb, 0px));
  /* 🔴 `contain` 이 없으면 목록 끝에서 더 당겼을 때 스크롤이 **부모(body)로 넘어가**
        WKWebView 가 화면 전체를 고무줄처럼 늘린다. 그러면 고정된 앱바·탭바가 같이 밀린다. */
  overscroll-behavior-y: contain;
  /* ── 🔴 **세로로만 구른다** (코인스쿱 2026-08-25) ────
   * 화면 하나가 실수로 폭을 넘기면 이 컨테이너가
   * 곧바로 **2차원 스크롤**이 된다. 그러면 세로로 훑는 손짓마다 목록이 좌우로 흔들리고,
   * 사용자에게는 「제스처가 이상하다」로 보인다 — 원인 화면을 짚기 매우 어려운 종류다.
   * 🔴 그래서 **가로 넘침은 여기서 통째로 막는다.** 원인은 원인대로 고치되, 다음 화면이 같은 실수를 해도 증상이 앱 전체로 번지지는 않게 한다.
   *    안쪽 상자에 `overflow-x: auto` 를 갖고 있다(`.chips`·`.hot-row`…).
   *    자기 스크롤러를 가진 자식은 부모의 `hidden` 에 안 걸린다. */
  overflow-x: hidden;
}
/* ── 🟢 **칩 구분선 아래 여백 — 칩 바 화면의 `#scroll` 은 `--chip-gap` 만큼 내려앉는다** (2026-09-14 · 125차 요청 9번 · `:root` 의 `--chip-gap` 문단) ──
   🔴 padding 이 아니라 margin 이다 — 여백이 스크롤 상자 «밖»이라 그 띠는 `#app` 의 바탕(`--bg`)이고, 스크롤하는 무엇도 그 위를 못 지나간다.
      sticky 머리(`.date-sect`·`#feed-new`)는 `#scroll` 기준 top 그대로라 규칙을 하나도 안 고친다 — 🔴 그 top 에 `--chip-gap` 을 더하면 여백을 두 번 센다.
   🔴 `:has()` 를 안 쓴다 — `#appbar`(index.html) → `#ptr` → `#scroll` 형제 순서라 `~` 로 되고, 칩 바인지는 app.js `renderAppbar` 의 `has-chips` 토글이 말한다.
   🔴 선택자를 `#scroll {` 로 시작하지 않는다 — self-check 「떠 있는 탭바」·check-native 가 그 머리로 `#scroll` 규칙 본문을 찾는다.
   🔴 `--scroll-gap` 을 같이 연다 — `.cload` 가 그 값을 빼야 로딩 상자가 안 넘친다(상속 · 뒤로가기 바 화면은 fallback 0).
   🟢 겹친 앱바 화면(좁은 폭의 뿌리 · 2026-10-08 · 「스크롤하면 숨는 바」 문단 ①)은 이 margin 을 0 으로 걷고 같은 값을 위 패딩(`--bars-pad`)에 넣는다 —
      여백 띠는 그 화면에서 앱바가 «달고» 다닌다(`#appbar.has-chips::after`). 이 규칙의 `--scroll-gap` 은 거기서도 그 계산의 재료다. */
#appbar.has-chips ~ #scroll { --scroll-gap: var(--chip-gap); margin-top: var(--scroll-gap); }

/* ── 당겨서 새로고침 ───────────────────────────────────────────────────────
   🔴 우측 상단 새로고침 버튼을 대체한 것이다 (2026-08-11). 버튼을 남겨 두지 않는다 —
      같은 일을 하는 입구가 둘이면 `refresh()` 가 겹쳐 돌아 read 가 두 번 나간다.

   🔴 표시등은 `#scroll` **안이 아니라 밖**이다. 안에 넣으면 목록과 함께 스크롤돼
      두 번째 화면부터 따라 내려가고, `syncCards` 의 `replaceChildren` 에 지워진다.

   🔴 `top` 이 앱바 높이다. `position: absolute` 의 기준은 `#app`(relative)이라
      body 의 --ad-h 는 이미 반영돼 있고 여기서 다시 더하면 배너 높이만큼 처진다. */
#ptr {
  position: absolute; z-index: 5;
  top: calc(var(--bar-h) + var(--safe-top)); left: 0; right: 0;
  display: flex; align-items: center; justify-content: center;
  height: 0; pointer-events: none;
}
/* 🟢 칩 바 화면은 `#scroll` 이 `--chip-gap` 만큼 내려앉으므로(`#appbar.has-chips ~ #scroll` 문단) 표시등도 같이 내린다 (2026-09-14 · 125차 요청 9번) —
   안 내리면 고리가 구분선 아래 여백 띠에서 돈다. 🔴 `#ptr` 은 `#scroll` 밖이라 margin 을 못 받는다 — top 을 여기서 따로 더한다. */
#appbar.has-chips ~ #ptr { top: calc(var(--bar-h) + var(--safe-top) + var(--chip-gap)); }
.ptr-ring {
  width: 26px; height: 26px; margin-top: 10px;
  border-radius: 50%;
  border: 2.5px solid var(--line-2);
  border-top-color: var(--brand-500);
  opacity: 0;
}
/* 당기는 동안은 손가락을 따라가고(회전은 JS 가 인라인으로 준다),
   놓아서 새로고침이 도는 동안만 CSS 애니메이션이 돈다. */
#ptr.busy .ptr-ring { opacity: 1; animation: spin .8s linear infinite; }
/* 손을 뗀 뒤 제자리로 돌아가는 구간 — JS 가 이 클래스를 붙였다 뗀다 */
#scroll.ptr-ease, #ptr.ptr-ease { transition: transform .26s cubic-bezier(.22, .8, .3, 1); }

.hot-head {
  display: flex; align-items: center; gap: 7px;
  padding: 14px 2px 9px; font-size: 13.5px; font-weight: 700; color: var(--text);
}
.hot-tag {
  padding: 2px 7px; border-radius: var(--r-xs);
  background: var(--hot); color: #fff; font-size: 10.5px; font-weight: 800; letter-spacing: .4px;
}
/* 🟢 날짜 섹션 머리(커뮤니티 목록 `board-ui.js` 와 피드 목록 `news.js withDayHeads` 의 «오늘»·«어제») (2026-10-23 · 사용자 「섹션 타이틀 '오늘' 이 큰 것 같다 · 코인스쿱 그대로」).
   🟡 정정: 코인스쿱 CSS 를 옮길 때 이 규칙이 통째로 빠져 있었다(주석 «위 패딩이 0 이다»·«날짜 머리 위 틈» 만 따라왔다) — 그래서 머리가 스타일 없는 `div`(본문 크기·검정)로 그려졌다.
   🔴 위 여백은 margin 이 아니라 padding 이다 — sticky 는 `top: 0` 일 때 마진 상자가 기준이라 마진이면 고정된 뒤에도 머리 위가 비고 그 틈으로 카드가 비친다(배경이 칠해지는 padding 으로).
   🔴 좌우 `-12px` 마진 + 같은 크기의 패딩 — `#scroll` 의 12px 패딩을 뚫고 가장자리까지 배경을 칠한다. */
.date-sect {
  position: sticky; top: 0; z-index: 2;
  margin: 0 -12px 8px; padding: 18px 12px 7px;
  background: var(--bg);
  font-size: 12.5px; font-weight: 700; letter-spacing: .2px;
  color: var(--text-mut);
}
/* 첫 섹션은 위 여백을 줄인다 — `#scroll` 의 위 패딩이 0 이라 여기가 화면 첫 칸이다 */
.date-sect:first-child { padding-top: 8px; }
/* 🟢 붙은 머리의 아래 가장자리를 부드럽게 (2026-10-28 · 사용자 «'오늘' 제목바가 위로 올라가면 테마컬러와 상반되는 느낌 · 개선 가능한지?» — 테마 바탕색 단색 띠가 장식 그림이 있는 앱바 밑에 칼로 자른 듯 붙고
   그 밑으로 카드 모서리가 바로 닿아 «딴 판»처럼 보였다). 바탕색(`--bg` = 테마색) 그대로 투명해지는 띠를 머리 «밑»에 단다 — 높이를 머리 아래 간격(`.feed > .date-sect` 의 margin-bottom 과 gap 이 만드는 값)과 같게 해 붙지 않은 평소에는
   `#app` 바탕과 같은 색이라 안 보이고(카드 위에도 안 얹힌다), 붙은 동안만 카드가 서서히 비친다. 🔴 머리 자체는 불투명 그대로다 — 글자 뒤로 카드가 비치면 안 읽힌다 · 위 여백 padding 사고(위 문단)도 그대로 막힌다. */
.date-sect::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 8px; pointer-events: none;
  background: var(--bg);
  -webkit-mask-image: linear-gradient(#000, transparent); mask-image: linear-gradient(#000, transparent);   /* 🔴 색이 아니라 «알파»만 줄인다 — `linear-gradient(var(--bg), transparent)` 는 옛 WebKit 이 투명 검정과 섞어 밝은 테마에 회색 띠가 졌다(`.tfx` 와 같은 방식) */
}
/* 🔴 좌우 `-12px` 마진 + 같은 크기의 패딩 — `#scroll` 의 12px 패딩을 뚫고 화면 끝까지
      흐르게 한다. 그래야 마지막 카드가 오른쪽 가장자리에서 잘리는 느낌이 안 난다. */
/* 🔴 **`scroll-padding-left` 가 패딩과 세트다** (2026-08-18 밤 · 요청 13번).
   증상: 첫 카드가 화면 왼쪽에 거의 붙어 보인다(목록 카드보다 12px 왼쪽).
   원인은 패딩이 아니라 **스냅**이었다. `scroll-snap-align: start` 의 기준선은
   스크롤포트의 시작이고 패딩을 안 본다 — 그래서 살짝만 스크롤해도 첫 카드가
   패딩을 **덮으며** 가장자리에 달라붙었다. 처음 그린 직후에만 12px 로 보이니
   "어떨 땐 맞고 어떨 땐 붙는" 것으로 관찰된다.
   🔴 `scroll-padding` 이 그 기준선을 안쪽으로 민다. **`padding` 값과 같이 바꾼다.** */
.hot-row {
  display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x mandatory;
  margin: 0 -12px; padding: 0 12px 2px;
  scroll-padding-left: 12px;
  scrollbar-width: none;
}
.hot-row::-webkit-scrollbar { display: none; }

/* 공감·비공감 — 카드와 **같은 컴포넌트**(`reactBarHTML`)다. 머리에서만 조금 작게.
   🔴 머리에서는 **조회용**이라 `<span class="… ro">` 다(app.js). 눌리지 않아야 하므로
      `pointer-events: none` — 이게 없으면 손가락이 버튼 위에서 시작할 때 카드 자체의
      가로 스크롤이 안 먹는다. `cursor` 도 기본으로 되돌린다. */
.hot-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; }

/* 🔴 조회용 표시 — **색도 테두리도 없다** (2026-08-14 · 요청). 누를 수 없는 것이
   눌리는 것처럼 보이면 안 되고, 무엇보다 HOT 카드에서 색이 있는 건 배지 하나여야
   시선이 제목으로 간다. 상세·목록의 누르는 버튼은 그대로 색을 쓴다.
   🔴 `.on`(내가 누른 것) 상태도 여기서는 **안 칠한다** — 아래 `.ro` 규칙이
      `.reactbtn.on` 보다 뒤에 와야 이긴다(가중치가 같다). 순서를 바꾸지 말 것. */
.reactbar.ro { pointer-events: none; }

.sk-line {
  height: 12px; border-radius: 6px; margin-bottom: 9px;
  background: var(--surface-2); animation: sk-pulse 1.2s ease-in-out infinite;
}
.sk-line.sk-sm { height: 10px; opacity: .7; }
.sk-thumb {
  flex: 0 0 auto; width: 72px; height: 72px; border-radius: var(--r-xs);
  background: var(--surface-2); animation: sk-pulse 1.2s ease-in-out infinite;
}
@keyframes sk-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* 🔴 움직임을 줄이도록 설정한 기기에서는 **둘 다 끈다.** 접근성 설정이기도 하고,
      멀미를 유발하는 목록은 그 자체로 나쁜 화면이다. */
@media (prefers-reduced-motion: reduce) {
  .sk-line, .sk-thumb { animation: none; }
}

.tag { font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: var(--r-xs); }

/* 🔴 썸네일은 **정사각 고정**이다. 매체마다 원본 비율이 제각각이라 높이를 원본에 맡기면
      목록이 들쭉날쭉해진다. `object-fit: cover` 가 가운데를 잘라 준다.
      🔴 `flex: 0 0 auto` — 이게 없으면 좁은 기기에서 썸네일이 먼저 찌그러진다. */
.thumb {
  flex: 0 0 auto; width: 72px; height: 72px; border-radius: var(--r-xs);
  object-fit: cover; background: var(--surface-2); display: block;
}

.tag { white-space: nowrap; }

.empty { text-align: center; color: var(--text-sub); padding: 72px 20px; font-size: 14px; line-height: 1.8; }

/* 「더 보기」 — 누를 때만 Firestore 읽기가 나간다 (feed.js loadOlder) */
.more {
  display: block; width: 100%; margin: 4px 0 8px; padding: 14px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
  color: var(--text-sub); font-size: 14px; font-weight: 600; cursor: pointer;
}
.more:active { background: var(--surface-2); }
.more:disabled { opacity: .5; }

/* `--fx-ld` = 다시 그려진 줄의 이어 틀기 */
@keyframes fx-land {
  0% { transform: none; animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  28% { transform: scale(1.14) rotate(-3deg) skewX(-6deg); text-shadow: 0 0 10px currentColor; animation-timing-function: ease-out; }
  55% { transform: scale(.97); }
  100% { transform: none; text-shadow: none; }
}

@keyframes fx-bar { from { width: 50%; } }

@media (prefers-reduced-motion: reduce) { .fx-line *,
.fx-bar > span { animation: none !important; } }

/* ── 🟢 **타격감 — 손맛과 크리티컬** (2026-10-07 · `hitfx.js` · 사용자 요청 「공감·리액션·레벨업·봇 잡기·별표에 크리티컬」) ──────────────
   조각(불꽃·고리·번쩍임·빛줄기·금화·떠오르는 이모지)은 `body` 직속 고정 위치 0×0 상자이고 그 안이 가운데 기준이다 — 누르기를 안 막는다.
   🟢 2026-10-08 «3D 게임 같은» 판 — 코인스쿱 서버 카드 효과와 같은 재료다(카메라 펀치 · 충격파 셋 · 가로 빛줄기 · 중력 불꽃 ·
      금화 분수 · 카드 빛 · 테두리 혜성 · 손실 글리치). 🔴 «3D»는 2D 변환(scale·rotate·skew)과 겹친 글자 그림자(돌출)로만 낸다 — preserve-3d 안 씀.
   🔴 움직이는 것은 `transform`·`opacity` 뿐이다(혜성만 선 오프셋) — `filter` 를 조각에 걸지 않는다(폰에서 수십 개가 한꺼번에 돈다).
   🔴 흔드는 것은 그 줄·카드 «안»뿐(`.hit-shake` · `lvup-slamland`) · 길이는 `hitfx.js` 의 타이머와 세트 · 움직임 줄이기에서는 없다. */
.hitfx-sparks, .hitfx-ring, .hitfx-flash, .hitfx-streak, .hitfx-coins, .hitfx-emo, .hitfx-impact, .hitfx-confetti, .hitfx-shard, .hitfx-beam, .hitfx-alert, .hitfx-stamp, .hitfx-banner { position: fixed; z-index: 1001; width: 0; height: 0; pointer-events: none; }
/* 불꽃 — 점은 바깥으로 튀었다가 중력을 받아 떨어진다(40% 에서 꺾인다) · `--c` 는 흰·그 색·금 중 하나 */
.hitfx-sparks i { position: absolute; left: -3px; top: -3px; width: 6px; height: 6px; border-radius: 50%;
  background: var(--c, var(--hc)); box-shadow: 0 0 6px var(--c, var(--hc));
  transform: scale(var(--w)); animation: hitfx-spark var(--ms) linear forwards; }
.hitfx-sparks i.st { left: -6px; top: -6px; width: 12px; height: 12px; border-radius: 0; background: var(--c); box-shadow: none;
  clip-path: polygon(50% 0, 61% 38%, 100% 50%, 61% 62%, 50% 100%, 39% 62%, 0 50%, 39% 38%); }
/* 손실 파편 — 빨간 삼각 조각이 돌며 떨어진다 */
.hitfx-sparks i.sh { left: -4px; top: -4px; width: 8px; height: 8px; border-radius: 0; box-shadow: none; background: var(--c);
  clip-path: polygon(0 0, 100% 30%, 35% 100%); }
@keyframes hitfx-spark {
  0% { opacity: 1; transform: translate(0, 0) rotate(0) scale(var(--w)); animation-timing-function: cubic-bezier(.15, .75, .35, 1); }
  40% { opacity: 1; transform: translate(calc(var(--dx) * .8), calc(var(--dy) * .8)) rotate(calc(var(--r) * .4)) scale(var(--w));
    animation-timing-function: cubic-bezier(.45, 0, .9, .55); }
  100% { opacity: 0; transform: translate(var(--dx), calc(var(--dy) + var(--g))) rotate(var(--r)) scale(calc(var(--w) * .35)); }
}
/* 빛살 — 바깥으로 곧게 긋고 짧아진다 */
.hitfx-sparks i.ln { left: 0; top: -1px; width: 16px; height: 2px; border-radius: 1px; background: linear-gradient(90deg, transparent, #fff);
  box-shadow: none; transform-origin: 0 50%; transform: rotate(var(--a)) translateX(6px) scaleX(1.3);
  animation: hitfx-line calc(var(--ms) * .6) cubic-bezier(.1, .8, .3, 1) forwards; }
@keyframes hitfx-line { 0% { opacity: 1; } 100% { opacity: 0; transform: rotate(var(--a)) translateX(var(--d)) scaleX(.2); } }
/* 고리 — 흰 가는 것(`thin`)이 먼저 · 옅고 큰 것(`late`)이 한 박자 늦게 */
.hitfx-ring::before { content: ''; position: absolute; left: calc(var(--sz) / -2); top: calc(var(--sz) / -2); width: var(--sz); height: var(--sz);
  border-radius: 50%; border: 2.5px solid var(--hc); box-shadow: 0 0 10px var(--hc); animation: hitfx-ring var(--ms) ease-out forwards; }
.hitfx-ring.soft::before { border-width: 1.5px; box-shadow: none; }
.hitfx-ring.thin::before { border-width: 1.5px; box-shadow: 0 0 6px #fff; animation-timing-function: cubic-bezier(.1, .8, .3, 1); }
.hitfx-ring.late::before { border-width: 1.5px; box-shadow: none; opacity: 0; animation-delay: 70ms; }
@keyframes hitfx-ring { 0% { transform: scale(.3); opacity: .95; } 100% { transform: scale(1.15); opacity: 0; } }
/* 번쩍임 — 흰 심 + 그 색의 번짐 */
.hitfx-flash::before { content: ''; position: absolute; left: calc(var(--fs) / -2); top: calc(var(--fs) / -2); width: var(--fs); height: var(--fs); border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, var(--hc) 24%, transparent 66%); animation: hitfx-flash var(--ms) ease-out forwards; }
@keyframes hitfx-flash { 0% { opacity: .95; transform: scale(.3); } 25% { opacity: .85; } 100% { opacity: 0; transform: scale(1.3); } }
/* 가로 빛줄기 — 가운데가 흰 가는 선 + 그 색의 옅은 띠 · 옆으로 펴졌다가 얇아지며 사라진다 */
.hitfx-streak::before, .hitfx-streak::after { content: ''; position: absolute; left: calc(var(--sw) / -2); width: var(--sw); border-radius: 50%;
  animation: hitfx-streak var(--ms) cubic-bezier(.1, .8, .3, 1) forwards; }
.hitfx-streak::before { top: -1.5px; height: 3px; background: linear-gradient(90deg, transparent, var(--hc) 25%, #fff 50%, var(--hc) 75%, transparent); }
.hitfx-streak::after { top: -9px; height: 18px; opacity: .35; background: linear-gradient(90deg, transparent, var(--hc) 40%, var(--hc) 60%, transparent); }
@keyframes hitfx-streak { 0% { transform: scaleX(.08); opacity: 0; } 18% { transform: scaleX(1); opacity: 1; } 100% { transform: scaleX(1.2) scaleY(.2); opacity: 0; } }
/* 🟢 타격 스파크 (2026-10-10 · `hitfx.js impact`) — 뾰족한 별(그 색) + 흰 심이 다 큰 채로 나타나 2~3프레임 뒤 사라진다(격투 게임의 히트 이펙트) */
.hitfx-impact::before, .hitfx-impact::after { content: ''; position: absolute; left: calc(var(--is) / -2); top: calc(var(--is) / -2); width: var(--is); height: var(--is);
  clip-path: polygon(50% 0%, 55% 32%, 72% 12%, 63% 37%, 93% 25%, 68% 45%, 90% 50%, 68% 55%, 92% 74%, 63% 63%, 73% 90%, 55% 68%, 50% 100%, 45% 68%, 29% 86%, 37% 63%, 9% 74%, 32% 55%, 0% 50%, 32% 45%, 13% 28%, 37% 37%, 25% 8%, 45% 32%);
  background: var(--hc); animation: hitfx-impact var(--ms) cubic-bezier(.2, .8, .4, 1) forwards; }
.hitfx-impact::after { background: #fff; transform: rotate(calc(var(--rot) + 15deg)) scale(.55); animation-name: hitfx-impact-core; }
@keyframes hitfx-impact { 0% { opacity: 1; transform: rotate(var(--rot)) scale(.7); } 45% { opacity: 1; transform: rotate(var(--rot)) scale(1.05); } 100% { opacity: 0; transform: rotate(var(--rot)) scale(1.18); } }
@keyframes hitfx-impact-core { 0% { opacity: 1; transform: rotate(calc(var(--rot) + 15deg)) scale(.5); } 40% { opacity: 1; } 100% { opacity: 0; transform: rotate(calc(var(--rot) + 15deg)) scale(.7); } }
/* 🟢 색종이 (`hitfx.js confetti`) — 바깥 상자는 솟았다 떨어지며 돌고 안의 종이는 세로 폭이 접혔다 펴지며 «팔랑»(유한 횟수) */
.hitfx-confetti i { position: absolute; left: -3px; top: -5px; width: 6px; height: 10px; opacity: 0; animation: hitfx-conf var(--ms) linear var(--dl) forwards; }
.hitfx-confetti b { display: block; width: 100%; height: 100%; border-radius: 1px; background: var(--c); animation: hitfx-flip var(--fl) linear 0s 7 alternate; }
@keyframes hitfx-conf {
  0% { opacity: 0; transform: translate(0, 0) rotate(0); animation-timing-function: cubic-bezier(.2, .7, .4, 1); }
  6% { opacity: 1; }
  30% { transform: translate(calc(var(--dx) * .6), var(--up)) rotate(calc(var(--r) * .3)); animation-timing-function: cubic-bezier(.4, 0, .8, .6); }
  80% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--fall)) rotate(var(--r)); }
}
@keyframes hitfx-flip { 0% { transform: scaleY(1); } 100% { transform: scaleY(.15); } }
/* 🟢 산산조각 (`hitfx.js shatter` · 봇 잡기) — 그 그림의 사본 넷을 감싼 상자의 `clip-path` 로 사분면만 보이게 하고 바깥으로 날린다(돌며 · 중력) */
.hitfx-shard i { position: absolute; left: calc(var(--w) / -2); top: calc(var(--h) / -2); width: var(--w); height: var(--h); clip-path: var(--cp);
  transform-origin: var(--o); animation: hitfx-shard var(--ms) linear forwards; }
.hitfx-shard img { display: block; width: 100%; height: 100%; }
.hitfx-shard.flip img { transform: scaleX(-1); }
@keyframes hitfx-shard {
  0% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(.15, .75, .35, 1); }
  35% { transform: translate(calc(var(--dx) * .75), calc(var(--dy) * .75)) rotate(calc(var(--r) * .4)); animation-timing-function: cubic-bezier(.45, 0, .9, .55); }
  75% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), calc(var(--dy) + var(--g))) rotate(var(--r)) scale(.7); }
}
/* 🟢 콤보 (`hitfx.js comboHit`) — 「×N COMBO」가 버튼 옆에 크게 떨어져 «꽝» 꽂히고 위로 빠진다 · 기울인 굵은 글자 + 돌출 그림자 · 단계마다 색과 크기가 오른다 */
/* 🔴 0×0 상자가 아니다 — 글자 폭이 있어야 왼쪽에 꽂을 때(`.l`)의 `translate(-100%)` 가 먹는다 */
.hitfx-combo { position: fixed; z-index: 1002; pointer-events: none; transform-origin: 0 50%; white-space: nowrap; line-height: 1; font-weight: 900; font-style: italic; color: var(--cc);
  text-shadow: 1px 1px 0 rgba(0, 0, 0, .4), 2px 2px 0 rgba(0, 0, 0, .32), 3px 3px 0 rgba(0, 0, 0, .22), 0 0 12px var(--cc);
  animation: hitfx-combo .95s linear forwards; }
.hitfx-combo.l { transform-origin: 100% 50%; --tx: -100%; }
.hitfx-combo b { font-size: calc(19px * var(--k)); letter-spacing: -.5px; }
.hitfx-combo small { font-size: calc(9.5px * var(--k)); letter-spacing: 1.2px; margin-left: 3px; color: #fff; }
@keyframes hitfx-combo {
  0% { opacity: 0; transform: translate(var(--tx, 0), -50%) scale(2.4) rotate(-10deg); animation-timing-function: cubic-bezier(.55, 0, .9, .4); }
  16% { opacity: 1; transform: translate(var(--tx, 0), -50%) scale(.86) rotate(2deg); animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  30% { transform: translate(var(--tx, 0), -50%) scale(1.1) rotate(-1deg); }
  42% { transform: translate(var(--tx, 0), -50%) scale(1); }
  72% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--tx, 0), -110%) scale(1); }
}
.hitfx-combo.mile { animation-duration: 1.15s; }
/* 🟢 봇 등장 (`hitfx.js spawnFx` · 2026-10-10 둘째 판) — 위에서 내리꽂히는 빛기둥(아래 끝이 봇의 발) · 바닥에 납작한 고리(`floor`) · 머리 위 「!」 */
.hitfx-beam::before { content: ''; position: absolute; left: calc(var(--bw) / -2); bottom: 0; width: var(--bw); height: 70vh; transform-origin: 50% 100%;
  background: linear-gradient(to top, rgba(255, 236, 160, .75), rgba(255, 210, 74, .35) 30%, transparent); border-radius: 50% 50% 0 0 / 12% 12% 0 0;
  animation: hitfx-beam .72s cubic-bezier(.2, .8, .4, 1) forwards; }
@keyframes hitfx-beam { 0% { transform: scaleX(.15); opacity: 0; } 18% { transform: scaleX(1); opacity: 1; } 55% { opacity: .85; } 100% { transform: scaleX(.05); opacity: 0; } }
.hitfx-ring.floor { transform: scaleY(.32); }
.hitfx-alert { width: auto; height: auto; transform: translate(-50%, -100%); font-size: 30px; font-weight: 900; color: #ffd24a; line-height: 1;
  text-shadow: 1px 1px 0 #c27c00, 2px 2px 0 #8a5500, 3px 3px 0 #523200, 0 0 12px rgba(255, 196, 0, .7); animation: hitfx-alert 1.25s linear forwards; }
@keyframes hitfx-alert {
  0%, 24% { opacity: 0; transform: translate(-50%, -100%) scale(.2); }
  32% { opacity: 1; transform: translate(-50%, -130%) scale(1.5, .8); animation-timing-function: ease-out; }
  40% { transform: translate(-50%, -112%) scale(.85, 1.25); }
  48% { transform: translate(-50%, -100%) scale(1.08, .94); }
  56% { transform: translate(-50%, -100%) scale(1); }
  80% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -150%) scale(1); }
}
/* 🟢 도장 (`hitfx.js stampHit` · 글·댓글 완료) — 두 겹 테두리 글자가 크게 떨어져 «쾅» · 기울어 찍히고 잠깐 머물다 빠진다 · 댓글은 작게(`sm`) */
.hitfx-stamp { width: auto; height: auto; }
.hitfx-stamp b { display: block; position: absolute; left: 0; top: 0; transform: translate(-50%, -50%) rotate(-8deg); white-space: nowrap;
  padding: 6px 16px; border: 3px solid var(--hc); border-radius: 10px; outline: 2px solid var(--hc); outline-offset: 3px;
  font-size: 30px; font-weight: 900; font-style: italic; letter-spacing: 1px; color: var(--hc); background: rgba(10, 12, 16, .72);
  text-shadow: 1px 1px 0 rgba(0, 0, 0, .5), 2px 2px 0 rgba(0, 0, 0, .35), 0 0 14px var(--hc);
  box-shadow: 0 0 22px color-mix(in srgb, var(--hc) 45%, transparent); animation: hitfx-stamp 1.2s linear forwards; }
.hitfx-stamp.sm b { font-size: 21px; padding: 4px 12px; border-width: 2px; outline-width: 1.5px; animation-duration: .95s; }
@keyframes hitfx-stamp {
  0% { opacity: 0; transform: translate(-50%, -50%) rotate(-22deg) scale(3); animation-timing-function: cubic-bezier(.55, 0, .9, .4); }
  13% { opacity: 1; transform: translate(-50%, -50%) rotate(-8deg) scale(.9); animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  22% { transform: translate(-50%, -50%) rotate(-9deg) scale(1.06); }
  30% { transform: translate(-50%, -50%) rotate(-8deg) scale(1); }
  72% { opacity: 1; transform: translate(-50%, -50%) rotate(-8deg) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -80%) rotate(-8deg) scale(1.04); }
}
/* 🟢 큰 배너 (`hitfx.js bannerSlam` · 「ALL CLEAR!」) — 머리글이 3배에서 «꽝» · 아랫줄은 한 박자 뒤 · 겉 상자는 끝에 빠진다 */
.hitfx-banner { width: auto; height: auto; }
.hitfx-banner b, .hitfx-banner small { position: absolute; left: 0; white-space: nowrap; transform: translate(-50%, -50%); text-align: center; }
.hitfx-banner b { top: 0; font-size: 44px; font-weight: 900; font-style: italic; letter-spacing: 1px; color: var(--hc);
  text-shadow: 1px 1px 0 #e0901a, 2px 2px 0 #c06e0c, 3px 3px 0 #934e06, 4px 4px 0 #623102, 5px 5px 0 #3a1c00, 5px 8px 12px rgba(0, 0, 0, .6), 0 0 22px rgba(255, 190, 40, .8);
  animation: hitfx-banner-in .6s linear both, hitfx-banner-out .5s ease-in 1.75s forwards; }
.hitfx-banner small { top: 38px; font-size: 15px; font-weight: 800; color: #fff; padding: 5px 14px; border-radius: 99px; background: rgba(10, 12, 16, .7);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--hc) 55%, transparent); animation: hitfx-banner-sub .5s ease-out .38s both, hitfx-banner-out .5s ease-in 1.75s forwards; }
@keyframes hitfx-banner-in {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(3.2) rotate(-10deg) skewX(-14deg); animation-timing-function: cubic-bezier(.55, 0, .9, .4); }
  36% { opacity: 1; transform: translate(-50%, -50%) scale(.86) rotate(2deg) skewX(4deg); animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  58% { transform: translate(-50%, -50%) scale(1.08) rotate(-1deg); }
  78% { transform: translate(-50%, -50%) scale(.98); }
  100% { opacity: 1; transform: translate(-50%, -50%); }
}
@keyframes hitfx-banner-sub { 0% { opacity: 0; transform: translate(-50%, -10%) scale(.8); } 100% { opacity: 1; transform: translate(-50%, -50%); } }
@keyframes hitfx-banner-out { to { opacity: 0; transform: translate(-50%, -90%); } }
/* 금화 — 바깥 상자는 솟았다 떨어지고(포물선 · 35% 꼭대기) 안의 동전은 가로 폭이 접혔다 펴지며 돈다(유한 횟수) */
.hitfx-coins i { position: absolute; left: -8px; top: -8px; width: 16px; height: 16px; opacity: 0;
  animation: hitfx-coin var(--ms) linear var(--dl) forwards; }
.hitfx-coins b { display: block; width: 100%; height: 100%; border-radius: 50%; box-sizing: border-box;
  background: radial-gradient(circle at 35% 30%, #fff8d6 0 14%, #ffd24a 38%, #e0a316 72%, #a86d00 100%);
  border: 1.5px solid #b07a00; box-shadow: inset 0 0 0 2px rgba(255, 240, 180, .45), 0 0 6px rgba(255, 210, 74, .6);
  animation: hitfx-coinspin var(--sp) linear 0s 8 alternate; }
@keyframes hitfx-coin {
  0% { opacity: 0; transform: translate(0, 0) scale(.4); animation-timing-function: cubic-bezier(.2, .7, .4, 1); }
  8% { opacity: 1; }
  35% { opacity: 1; transform: translate(calc(var(--dx) * .55), var(--up)) scale(var(--s)); animation-timing-function: cubic-bezier(.5, 0, .9, .6); }
  82% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--fall)) scale(calc(var(--s) * .9)); }
}
@keyframes hitfx-coinspin { 0% { transform: scaleX(1); } 100% { transform: scaleX(.12); } }
/* 카드를 가로지르는 빛 (`sweep`) · 테두리를 도는 혜성 (`comets`) — 카드 «안»에 잠깐 붙는다 */
.hitfx-sweep { position: absolute; inset: 0; border-radius: inherit; overflow: hidden; pointer-events: none; z-index: 3; }
.hitfx-sweep::before { content: ''; position: absolute; top: -30%; bottom: -30%; left: 0; width: 42%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .1) 30%, rgba(255, 255, 255, .32) 50%, rgba(255, 255, 255, .1) 70%, transparent);
  transform: translateX(-130%) skewX(-20deg); animation: hitfx-sweep var(--ms) cubic-bezier(.35, .1, .25, 1) forwards; }
@keyframes hitfx-sweep { to { transform: translateX(340%) skewX(-20deg); } }
.hitfx-comets { position: absolute; left: -1px; top: -1px; pointer-events: none; z-index: 3; animation: hitfx-cfade var(--ms) linear forwards; }
.hitfx-comets svg { display: block; overflow: visible; }
.hitfx-comets rect { fill: none; stroke-linecap: round; animation: hitfx-comet var(--ms) cubic-bezier(.4, .1, .3, 1) forwards; }
.hitfx-comets rect.g { stroke: var(--hc); stroke-width: 7; opacity: .3; }
.hitfx-comets rect.c { stroke: var(--hc); stroke-width: 3; }
.hitfx-comets rect.w { stroke: #fff; stroke-width: 1.2; }
@keyframes hitfx-comet { to { stroke-dashoffset: 0; } }
@keyframes hitfx-cfade { 0% { opacity: 0; } 10% { opacity: 1; } 75% { opacity: 1; } 100% { opacity: 0; } }
/* 이모지 (2026-10-10 «게임 느낌» 판 · `hitfx.js emoFloat`) — 큰 것 하나가 위로 튀어나와 «꽝» 눌렸다 펴지고 흔들린 뒤 떠오르며 사라진다 ·
   작은 사본 일곱이 분수처럼 솟았다 돌며 떨어진다 */
.hitfx-emo.main img { position: absolute; left: -12px; top: -12px; width: 24px; height: 24px; animation: hitfx-emo-main 1s linear both; }
@keyframes hitfx-emo-main {
  0% { opacity: 0; transform: translateY(0) scale(.4); animation-timing-function: cubic-bezier(.2, .9, .3, 1.3); }
  14% { opacity: 1; transform: translateY(-44px) scale(2.6) rotate(var(--rt)); animation-timing-function: ease-in; }
  22% { transform: translateY(-40px) scale(2.9, 1.9); animation-timing-function: ease-out; }
  30% { transform: translateY(-46px) scale(2, 2.6) rotate(calc(var(--rt) * -1)); }
  38% { transform: translateY(-44px) scale(2.3) rotate(calc(var(--rt) * .5)); }
  60% { opacity: 1; transform: translateY(-48px) scale(2.25) rotate(0); animation-timing-function: ease-in; }
  100% { opacity: 0; transform: translateY(-96px) scale(1.5); }
}
.hitfx-emo.mini i { position: absolute; left: -8px; top: -8px; width: 16px; height: 16px; opacity: 0; animation: hitfx-emo-mini .95s linear var(--dl) forwards; }
.hitfx-emo.mini img { display: block; width: 100%; height: 100%; }
@keyframes hitfx-emo-mini {
  0% { opacity: 0; transform: translate(0, 0) scale(.3); animation-timing-function: cubic-bezier(.2, .7, .4, 1); }
  8% { opacity: 1; }
  36% { transform: translate(calc(var(--dx) * .6), var(--up)) scale(1) rotate(calc(var(--r) * .4)); animation-timing-function: cubic-bezier(.45, 0, .9, .55); }
  80% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--fall)) scale(.8) rotate(var(--r)); }
}
/* 🟢 바로가기 «날아가 꽂히기» (`hitfx.js flyToDock` · 2026-10-08) — 사본은 탭바 칸과 같은 44px 유리 원이다(궤적·크기는 JS 의 `animate` 키프레임) ·
   받는 칸은 안의 그림이 Dock 처럼 «통» 튄다(기준점 아래 가운데 — 탭의 `tab-bounce` 와 같은 이유) · 날아오는 동안 새 칸은 비워 둔다(`dock-wait` · app.js `scBtn`) */
.hitfx-fly { position: fixed; z-index: 1002; width: 0; height: 0; pointer-events: none; will-change: transform, opacity; }
.hitfx-fly-in { position: absolute; left: -22px; top: -22px; width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center;
  color: var(--brand-500); background: var(--bar-bg); border: 1px solid var(--glass-line);
  box-shadow: var(--sh-3), 0 0 0 2px color-mix(in srgb, var(--brand-500) 45%, transparent), 0 0 18px color-mix(in srgb, var(--brand-500) 40%, transparent); }
.hitfx-fly-in svg { width: 22px; height: 22px; display: block; }

.tabbtn.dock-wait { opacity: 0; }
.hit-dock > :not(.hitfx-sweep) { transform-origin: 50% 100%; animation: hit-dock .74s cubic-bezier(.3, .7, .4, 1) 1; }
.hit-dock { color: var(--brand-500); }
@keyframes hit-dock {
  0% { transform: translateY(0) scale(1.18, .8); }
  26% { transform: translateY(-11px) scale(.9, 1.12); }
  46% { transform: translateY(0) scale(1.12, .88); }
  62% { transform: translateY(-4px) scale(.97, 1.04); }
  80% { transform: translateY(0) scale(1.03, .97); }
  100% { transform: none; }
}
/* 크리티컬 — 버튼이 «카메라 펀치»로 튄다(크게 · 비틀림 → 꽂힘 → 튕김) · 그 색으로 빛난다 · 줄이 흔들린다 */
.hit-crit { position: relative; z-index: 2; animation: hit-crit .7s linear 1; }
/* 🟢 히트스톱 (2026-10-10) — 정점(8%)에서 흰 테두리로 «멈췄다가»(8~19% ≈ 80ms · `hitfx.js HITSTOP_MS`) 꽂힌다 */
@keyframes hit-crit {
  0% { transform: none; animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  8% { transform: scale(1.45) rotate(-6deg) skewX(-8deg); box-shadow: 0 0 0 2px #fff, 0 0 26px var(--hc); animation-timing-function: steps(1); }
  19% { transform: scale(1.45) rotate(-6deg) skewX(-8deg); box-shadow: 0 0 0 2px var(--hc), 0 0 22px var(--hc); animation-timing-function: cubic-bezier(.5, 0, .8, .5); }
  30% { transform: scale(.86) rotate(2deg) skewX(3deg); animation-timing-function: ease-out; }
  48% { transform: scale(1.08) rotate(-1deg); box-shadow: 0 0 0 1px var(--hc), 0 0 10px var(--hc); }
  66% { transform: scale(.98); }
  100% { transform: none; box-shadow: none; }
}
/* 손실 쪽(비공감) — RGB 갈라짐이 몇 프레임 튄다 · 글자는 SVG 라 그림자 대신 테두리 그림자로 */
.hit-crit.hit-glitch { animation: hit-crit .7s linear 1, hit-glitch .4s steps(1) 1; }
@keyframes hit-glitch {
  0% { box-shadow: -3px 0 0 rgba(0, 229, 255, .6), 3px 0 0 rgba(255, 40, 80, .7); }
  20% { box-shadow: 4px 1px 0 rgba(0, 229, 255, .6), -4px -1px 0 rgba(255, 40, 80, .7); }
  40% { box-shadow: -2px -1px 0 rgba(0, 229, 255, .55), 2px 1px 0 rgba(255, 40, 80, .65); }
  60% { box-shadow: 3px 0 0 rgba(0, 229, 255, .45), -3px 0 0 rgba(255, 40, 80, .55); }
  80% { box-shadow: -1px 0 0 rgba(0, 229, 255, .35), 1px 0 0 rgba(255, 40, 80, .4); }
  100% { box-shadow: none; }
}
/* 🟢 한 단계 위 크리티컬(`critHit` 의 `big` · 공감 크리티컬 ×2 · 2026-10-08) — 더 크게 펀치하고 두 번 튕긴다 · 줄도 더 세게 */
.hit-crit-big { position: relative; z-index: 2; animation: hit-crit-big .9s linear 1; }
.hit-crit-big.hit-glitch { animation: hit-crit-big .9s linear 1, hit-glitch .4s steps(1) 1; }
/* 정점(7%)에서 «멈춤»이 7~19% ≈ 110ms(`hitfx.js HITSTOP_BIG_MS`) */
@keyframes hit-crit-big {
  0% { transform: none; animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  7% { transform: scale(1.85) rotate(-9deg) skewX(-10deg); box-shadow: 0 0 0 3px #fff, 0 0 40px var(--hc), 0 0 70px var(--hc); animation-timing-function: steps(1); }
  19% { transform: scale(1.85) rotate(-9deg) skewX(-10deg); box-shadow: 0 0 0 3px var(--hc), 0 0 34px var(--hc), 0 0 60px var(--hc); animation-timing-function: cubic-bezier(.5, 0, .8, .5); }
  28% { transform: scale(.8) rotate(3deg) skewX(4deg); animation-timing-function: ease-out; }
  44% { transform: scale(1.22) rotate(-2deg); box-shadow: 0 0 0 2px var(--hc), 0 0 20px var(--hc); }
  60% { transform: scale(.94); }
  78% { transform: scale(1.04); }
  100% { transform: none; box-shadow: none; }
}
.hit-shake-big { animation: hit-shake-big .66s cubic-bezier(.36, .07, .19, .97) 1; }
@keyframes hit-shake-big {
  0% { transform: none; }
  10% { transform: translate(-8px, 4px) rotate(-1deg); }
  22% { transform: translate(8px, -4px) rotate(1deg); }
  36% { transform: translate(-6px, 2px) rotate(-.5deg); }
  50% { transform: translate(5px, -2px); }
  66% { transform: translate(-3px, 1px); }
  82% { transform: translate(1px, 0); }
  100% { transform: none; }
}
.hit-shake { animation: hit-shake .5s cubic-bezier(.36, .07, .19, .97) 1; }
@keyframes hit-shake {
  0% { transform: none; }
  12% { transform: translate(-4px, 2px) rotate(-.5deg); }
  26% { transform: translate(4px, -2px) rotate(.5deg); }
  40% { transform: translate(-3px, 1px); }
  56% { transform: translate(2px, -1px); }
  72% { transform: translate(-1px, 0); }
  100% { transform: none; }
}
/* 채널 알림을 켤 때 — 종이 윗고리를 축으로 흔들린다 · 끌 때 — 꺼진 종이 움츠렸다 편다 (2026-10-12 · `hitfx.js bellFx` 의 720·520ms 와 세트) */
.hit-bell { animation: hit-bell .7s ease-out 1; transform-origin: 50% 12%; }
@keyframes hit-bell {
  0% { transform: none; }
  12% { transform: rotate(20deg) scale(1.18); }
  28% { transform: rotate(-16deg) scale(1.12); }
  44% { transform: rotate(11deg); }
  60% { transform: rotate(-7deg); }
  76% { transform: rotate(3deg); }
  100% { transform: none; }
}
.hit-mute { animation: hit-mute .5s cubic-bezier(.3, .7, .4, 1) 1; }
@keyframes hit-mute {
  0% { transform: none; }
  32% { transform: scale(.68) rotate(-10deg); opacity: .55; }
  64% { transform: scale(1.1) rotate(4deg); opacity: 1; }
  100% { transform: none; }
}
/* 별표를 켤 때 — 동전처럼 한 바퀴(가로 폭 1 → 0 → 1) 돌며 커졌다가 꽂힌다 · 40% 에서 앞면(`hitfx.js starPop` 의 200ms) */
.hit-star { animation: hit-star .6s linear 1; }
@keyframes hit-star {
  0% { transform: scale(.35) rotate(-30deg); animation-timing-function: ease-out; }
  20% { transform: scale(.12, 1.25) rotate(-8deg); animation-timing-function: ease-in; }
  40% { transform: scale(1.45) rotate(8deg) skewX(-6deg); animation-timing-function: cubic-bezier(.3, .6, .4, 1); }
  62% { transform: scale(.9) rotate(-2deg); }
  80% { transform: scale(1.06); }
  100% { transform: none; }
}
/* 레벨업·미션 완료 — 카드가 커진 채로 비틀려 떨어져 꽂히고(0.26초 · `hitfx.js T_SLAM`) 꽂힌 자리에서 눌렸다 튕기고 흔들리며 그 색으로 번쩍인다 */
.lvup.slam .lvup-card { position: relative; transition: none; opacity: 0; }
.lvup.slam.show .lvup-card { animation: lvup-slamland .86s linear forwards; }
@keyframes lvup-slamland {
  0% { transform: scale(2.1) rotate(-5deg) skewX(6deg); opacity: 0; animation-timing-function: cubic-bezier(.55, 0, .9, .45); }
  30% { transform: scale(.93, .9) rotate(0); opacity: 1; animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  36% { transform: translate(-8px, 5px) rotate(-1deg) scale(1.03); opacity: 1; animation-timing-function: linear; }
  44% { transform: translate(7px, -5px) rotate(.8deg); }
  53% { transform: translate(-5px, 3px) rotate(-.4deg); }
  63% { transform: translate(4px, -2px); }
  75% { transform: translate(-2px, 1px); }
  87% { transform: translate(1px, 0); }
  100% { transform: none; opacity: 1; }
}
.lvup.slam .lvup-card::after { content: ''; position: absolute; inset: -1px; border-radius: inherit; pointer-events: none; opacity: 0;
  box-shadow: 0 0 0 2px var(--hc, #ffc83d), 0 0 48px 6px var(--hc, #ffc83d), inset 0 0 30px var(--hc, #ffc83d); }
.lvup.slam.show .lvup-card::after { animation: lvup-glow .9s ease-out .24s 1 both; }
@keyframes lvup-glow { 0% { opacity: 0; } 8% { opacity: .95; } 100% { opacity: 0; } }
/* 배지·얼굴 — 꽂히는 순간 크게 튀며 비틀린다 · 배지 글자는 대각선으로 겹친 그림자로 «돌출»된 숫자다 */
.lvup.slam .lvup-badge { text-shadow: 1px 1px 0 rgba(0, 0, 0, .28), 2px 2px 0 rgba(0, 0, 0, .24), 3px 3px 0 rgba(0, 0, 0, .2), 0 0 12px currentColor; }
/* 🟢 2026-10-08 (사용자 요청 「미션 팝업의 봇 얼굴도 움직이게 · 레벨 배지도 동전처럼」) — 배지는 꽂히는 순간 튀며 동전처럼 두 번 접혔다 펴진다(한 바퀴) ·
   봇 얼굴은 «맞고»(눌림) → 튀어 오르며 늘어나고 → 공중에서 동전처럼 한 바퀴 → 내려앉아 눌리고 → 신나서 좌우로 흔들고 → 한 번 «두근». */
.lvup.slam.show .lvup-badge { animation: lvup-coin 1s linear .24s 1 both; }
@keyframes lvup-coin {
  0% { transform: none; animation-timing-function: cubic-bezier(.2, .9, .4, 1); }
  12% { transform: scale(1.32) rotate(-6deg) skewX(-5deg); animation-timing-function: ease-in; }
  24% { transform: scale(.06, 1.24); animation-timing-function: ease-out; }
  36% { transform: scale(1.18) rotate(3deg); animation-timing-function: ease-in; }
  48% { transform: scale(.06, 1.14); animation-timing-function: ease-out; }
  62% { transform: scale(1.08); }
  76% { transform: scale(.96); }
  88% { transform: scale(1.02); }
  100% { transform: none; }
}
.lvup.slam.show .mis-face { transform-origin: 50% 85%; animation: mis-face-hit 1.4s linear .24s 1 both; }
@keyframes mis-face-hit {
  0% { transform: none; animation-timing-function: ease-out; }
  8% { transform: translateY(8px) scale(1.3, .7); animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  18% { transform: translateY(-18px) scale(.82, 1.22); animation-timing-function: ease-out; }
  27% { transform: translateY(-26px) scale(1.04, .97) rotate(-6deg); animation-timing-function: ease-in; }
  33% { transform: translateY(-26px) scale(.06, 1.04); animation-timing-function: ease-out; }
  39% { transform: translateY(-24px) scale(1, 1) rotate(4deg); animation-timing-function: ease-in; }
  45% { transform: translateY(-20px) scale(.06, 1.02); animation-timing-function: ease-out; }
  51% { transform: translateY(-14px) scale(1); animation-timing-function: cubic-bezier(.5, 0, .9, .5); }
  58% { transform: translateY(0) scale(1.18, .84); animation-timing-function: ease-out; }
  65% { transform: scale(.95, 1.06) rotate(-8deg); }
  72% { transform: rotate(7deg); }
  79% { transform: rotate(-4deg); }
  85% { transform: rotate(2deg) scale(1.02); }
  92% { transform: scale(1.1); animation-timing-function: ease-in-out; }
  100% { transform: none; }
}
@keyframes lvup-pop {
  0% { transform: scale(1); animation-timing-function: cubic-bezier(.2, .9, .4, 1); }
  30% { transform: scale(1.34) rotate(-7deg) skewX(-6deg); animation-timing-function: cubic-bezier(.5, 0, .8, .5); }
  52% { transform: scale(.9) rotate(2deg); animation-timing-function: ease-out; }
  74% { transform: scale(1.05); }
  100% { transform: none; }
}
.lvup.slam .mis-line b { text-shadow: 1px 1px 0 rgba(0, 0, 0, .3), 2px 2px 0 rgba(0, 0, 0, .22); }
.lvup.slam.show .mis-line b { display: inline-block; animation: lvup-pop .55s linear .42s 1 both; }
/* 🟢 레벨업 팝업 — 「LEVEL UP!」 머리글 · 막대 넘침 · 「RANK UP!」 (2026-10-10 · `board/xp.js lvBarFill`·`hitfx.js lvOverflow`)
   머리글은 카드가 꽂히는 순간(0.2초) 3배에서 «꽝» · 배지의 동전 한 바퀴는 막대가 넘치는 때로 늦춘다(0.76초 + 24% = `LVUP_OVER_MS`) ·
   막대가 넘치면 흰 빛이 번쩍(`over`) · 랭크 업은 배지가 새 색으로 칠해지며 그 색의 고리가 퍼진다(`paint`) */
.lvup-head { font-size: 30px; font-weight: 900; font-style: italic; letter-spacing: 1px; line-height: 1; margin: -4px 0 14px; color: #ffd24a;
  text-shadow: 1px 1px 0 #e0901a, 2px 2px 0 #c06e0c, 3px 3px 0 #934e06, 4px 4px 0 #623102, 4px 7px 10px rgba(0, 0, 0, .5), 0 0 16px rgba(255, 190, 40, .6); }
.lvup-head.rank { color: #7cf2ff; text-shadow: 1px 1px 0 #2aa7c9, 2px 2px 0 #1d7e9b, 3px 3px 0 #135a70, 4px 4px 0 #0a3846, 4px 7px 10px rgba(0, 0, 0, .5), 0 0 18px rgba(80, 220, 255, .7); }
.lvup.slam.show .lvup-head { animation: xpfx-slam .55s linear .2s both; }
.lvup-bar { position: relative; height: 8px; margin: 2px 18px 14px; border-radius: 4px; background: var(--surface-2, rgba(255, 255, 255, .08)); overflow: hidden; }
.lvup-bar > i { display: block; height: 100%; width: 0; border-radius: inherit; background: linear-gradient(90deg, var(--brand-500), #ffd24a); }
.lvup-bar.over::after { content: ''; position: absolute; inset: 0; background: #fff; animation: lvup-barflash .45s ease-out forwards; }
@keyframes lvup-barflash { 0% { opacity: .95; } 100% { opacity: 0; } }
.lvup.lvbar.slam.show .lvup-badge { animation-delay: .76s; }
/* 🔴 동전(첫째)은 위 규칙과 같은 값이어야 «이어서» 돈다 — 값이 다르면 칠하는 순간 동전이 처음부터 다시 돈다 */
.lvup.lvbar.slam.show .lvup-badge.paint { animation: lvup-coin 1s linear .76s 1 both, lvup-paint .9s ease-out 0s 1; }
@keyframes lvup-paint { 0% { box-shadow: 0 0 0 0 currentColor, 0 0 0 0 #fff; } 30% { box-shadow: 0 0 0 10px transparent, 0 0 30px 8px currentColor; } 100% { box-shadow: 0 0 0 26px transparent, 0 0 0 0 transparent; } }
/* 🟢 업적 (`hitfx.js achieveHit` · 첫 글·첫 댓글) — 앱바 아래 가운데: 메달이 돌며 튀어나오고(0.28초에 앞면) 알약이 옆으로 펴진다 · 2.6초 뒤 접혀 올라간다 */
.hitfx-ach { position: fixed; z-index: 1004; left: 50%; top: calc(var(--safe-top) + var(--bar-h) + 10px); pointer-events: none; display: flex; align-items: center;
  transform: translateX(-50%); padding: 5px 16px 5px 5px; border-radius: 99px; background: linear-gradient(180deg, #2a2312, #17130a); border: 1.5px solid #c98a0b;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .5), 0 0 18px rgba(255, 196, 0, .25); animation: ach-box 3.3s linear forwards; }
.hitfx-ach .ach-medal { width: 40px; height: 40px; flex: none; display: block; animation: ach-medal .7s linear both; }
.hitfx-ach .ach-medal svg { width: 100%; height: 100%; display: block; }
.hitfx-ach .ach-txt { display: flex; flex-direction: column; margin-left: 10px; overflow: hidden; white-space: nowrap; max-width: 0; animation: ach-open .45s cubic-bezier(.2, .8, .3, 1) .45s forwards; }
.hitfx-ach small { font-size: 9.5px; font-weight: 800; letter-spacing: 1.6px; color: #e0a316; }
.hitfx-ach b { font-size: 16px; font-weight: 900; font-style: italic; color: #ffd24a; line-height: 1.15; text-shadow: 1px 1px 0 #7a4a00; }
.hitfx-ach i { font-style: normal; font-size: 11.5px; color: #f3e6c4; }
@keyframes ach-box { 0% { opacity: 0; transform: translate(-50%, -30px); } 8% { opacity: 1; transform: translate(-50%, 0); } 84% { opacity: 1; transform: translate(-50%, 0); } 100% { opacity: 0; transform: translate(-50%, -40px); } }
@keyframes ach-medal {
  0% { transform: scale(.2) rotate(-30deg); animation-timing-function: ease-out; }
  20% { transform: scale(.08, 1.3); animation-timing-function: ease-in; }
  40% { transform: scale(1.4) rotate(8deg); animation-timing-function: cubic-bezier(.3, .6, .4, 1); }
  60% { transform: scale(.9); } 80% { transform: scale(1.06); } 100% { transform: none; }
}
@keyframes ach-open { to { max-width: 240px; } }

/* 🟢 업적 · 대표 배지 (2026-10-11 · `board/xp.js achTagHTML`·`achShelfHTML`·`achPickSheet` · 그림 `services/achievements.js achIconSVG`)
   단계 여섯: 동·은·금·플래티넘·다이아·레전드(무지개) — 색은 메달 그림이 들고, 여기서는 테두리·글자색·빛만.
   이름 줄: 레벨 태그 오른쪽에 **메달 아이콘만**(사용자 결정 「공간이 부족하다」 · 칭호 글자는 `full` — 활동 화면 머리) · 플래티넘 위는 빛이 지난다 */
.b-ach { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; vertical-align: middle; position: relative; line-height: 1; }
.b-ach svg { width: 15px; height: 15px; flex: none; display: block; }
.b-ach:not(.full) { width: 15px; height: 15px; border-radius: 50%; overflow: hidden; }
.b-ach.full { height: 22px; padding: 0 10px 0 3px; border-radius: 99px; font-size: 12px; font-weight: 800; overflow: hidden; }
.b-ach.full svg { width: 17px; height: 17px; }
.b-ach.full i { font-style: normal; white-space: nowrap; }
.b-ach.full.t1 { color: #e7a56f; background: rgba(208, 138, 78, .14); box-shadow: inset 0 0 0 1px rgba(208, 138, 78, .4); }
.b-ach.full.t2 { color: #d6e0ea; background: rgba(196, 208, 220, .13); box-shadow: inset 0 0 0 1px rgba(196, 208, 220, .4); }
.b-ach.full.t3 { color: #ffd24a; background: rgba(255, 210, 74, .14); box-shadow: inset 0 0 0 1px rgba(255, 210, 74, .5); }
.b-ach.full.t4 { color: #9eefff; background: rgba(58, 166, 196, .16); box-shadow: inset 0 0 0 1px rgba(120, 220, 245, .5); }
.b-ach.full.t5 { color: #cdb8ff; background: rgba(108, 76, 255, .18); box-shadow: inset 0 0 0 1px rgba(170, 140, 255, .55); }
.b-ach.full.t6 { color: #fff; background: linear-gradient(90deg, rgba(255, 122, 217, .3), rgba(255, 210, 74, .3), rgba(79, 209, 255, .3), rgba(155, 107, 255, .3));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .45); text-shadow: 0 0 6px rgba(255, 255, 255, .5); }
.b-ach.t4::after, .b-ach.t5::after, .b-ach.t6::after { content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 45%; pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent); transform: translateX(-140%) skewX(-20deg); animation: ach-shine 4.2s ease-in-out infinite; }
.b-ach.t6::after { animation-duration: 2.8s; }
@keyframes ach-shine { 0%, 66% { transform: translateX(-140%) skewX(-20deg); } 100% { transform: translateX(330%) skewX(-20deg); } }
.buser-ach { margin-top: 8px; }
/* 진열장 — 셈마다 한 줄 «사다리»: 머리(셈 이름 · 지금 가장 높은 칭호) · 메달 줄(동 → 레전드) · 다음 목표 진행 막대 · 마지막에 숨은 업적 줄 */
.ach-shelf { margin-top: 14px; }
.ach-h { display: flex; align-items: baseline; gap: 6px; margin: 0 0 8px; font-size: 14px; font-weight: 800; }
.ach-h small { font-size: 12px; color: var(--text-mut); font-weight: 700; }
.ach-row { padding: 10px 12px; border-radius: 12px; background: var(--surface-2, rgba(255, 255, 255, .04)); border: 1px solid var(--line); margin-bottom: 8px; }
.ach-row-h { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.ach-row-h b { font-size: 13px; font-weight: 800; }
.ach-top { font-size: 11.5px; font-weight: 800; font-style: italic; }
.ach-top.t1 { color: #e7a56f; } .ach-top.t2 { color: #d6e0ea; } .ach-top.t3 { color: #ffd24a; }
.ach-top.t4 { color: #9eefff; } .ach-top.t5 { color: #cdb8ff; }
.ach-top.t6 { background: linear-gradient(90deg, #ff7ad9, #ffd24a, #4fd1ff, #9b6bff); -webkit-background-clip: text; background-clip: text; color: transparent; }
.ach-medals { display: flex; gap: 8px; flex-wrap: wrap; }
.ach-m { position: relative; width: 38px; height: 38px; padding: 0; border: 0; border-radius: 50%; background: none; flex: none; }
.ach-m svg { width: 100%; height: 100%; display: block; }
.ach-m:active { transform: scale(.92); }
.ach-m.locked { opacity: .5; }
.ach-m.t5:not(.locked) { box-shadow: 0 0 10px rgba(140, 110, 255, .45); }
.ach-m.t6:not(.locked) { box-shadow: 0 0 12px rgba(255, 200, 120, .55); animation: ach-legend 3s linear infinite; }
@keyframes ach-legend { 0%, 100% { box-shadow: 0 0 12px rgba(255, 122, 217, .55); } 33% { box-shadow: 0 0 12px rgba(255, 210, 74, .6); } 66% { box-shadow: 0 0 12px rgba(79, 209, 255, .6); } }
.ach-m.rep::after { content: '★'; position: absolute; right: -3px; bottom: -3px; width: 15px; height: 15px; border-radius: 50%; display: grid; place-items: center;
  font-size: 9px; color: #3a2600; background: #ffd24a; box-shadow: 0 0 0 2px var(--surface); }
.ach-next { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; font-size: 11.5px; color: var(--text-sub); }
.ach-next.done { color: #ffd24a; font-weight: 800; }
.ach-prog { display: block; height: 5px; border-radius: 3px; background: rgba(255, 255, 255, .08); overflow: hidden; }
.ach-prog > i { display: block; height: 100%; background: linear-gradient(90deg, var(--brand-500), #ffd24a); border-radius: inherit; }
.ach-row.hid .ach-m.locked { opacity: .7; }
.ach-note { margin-top: 4px; }
/* ── 🟢 **업적 화면** (2026-10-11 · board/xp.js `renderAch` · 프로필 레벨 카드의 「업적」 줄이 연다) — 머리 카드(트로피 · 개수 · 전체 막대 · 이름 옆 배지 · 안내 한 줄) + 진열장 줄.
   열 때(`uifx.js SEC_UNITS.ach`) 줄이 들어서며 메달이 차례로 «톡» · 다음 목표 막대가 찬다 · 아래 줄은 스크롤로 보일 때 */
.prof-lv-ach .prof-lvi svg { display: block; }
.ach-scr > .ach-shelf { margin-top: 0; }
.ach-scr .ach-row:last-child { margin-bottom: 0; }
.ach-hero { display: flex; flex-direction: column; gap: 10px;
  border-color: color-mix(in srgb, var(--brand-500) 35%, var(--line));
  background: linear-gradient(135deg, color-mix(in srgb, var(--brand-500) 14%, var(--surface)), var(--surface) 62%); }
.ach-hero-top { display: flex; align-items: center; gap: 8px; }
.ach-hero-top > b { font-size: 15px; font-weight: 800; }
.ach-hero-ic { flex: none; width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; color: #3a2600; background: var(--brand-500); }
.ach-hero-ic svg { width: 18px; height: 18px; }
.ach-hero-n { margin-left: auto; font-size: 13px; font-weight: 700; color: var(--text-mut); font-variant-numeric: tabular-nums; }
.ach-hero-n > b { display: inline-block; font-size: 22px; font-weight: 800; color: var(--text); letter-spacing: -.02em; }
.ach-hero-rep { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12.5px; font-weight: 700; color: var(--text-sub); }
.ach-hero-rep > em { font-style: normal; color: var(--text-mut); font-weight: 600; }
.ach-hero .ach-note { margin: 0; padding: 0; font-size: 12px; line-height: 1.5; }
.fx-enter .ach-m, .ach-row.fx-enter .ach-m { animation: ach-pop .42s cubic-bezier(.3, 1.5, .5, 1) calc(var(--fx-t, 0ms) + 160ms + var(--mi, 0) * 45ms) 1 backwards; }
@keyframes ach-pop { 0% { transform: scale(.3); opacity: 0; } 60% { transform: scale(1.15); opacity: 1; } 100% { transform: none; } }
.fx-enter .ach-prog > i, .ach-row.fx-enter .ach-prog > i { transform-origin: 0 50%; animation: ufx-fill .7s cubic-bezier(.2, .8, .3, 1) calc(var(--fx-t, 0ms) + 260ms) 1 backwards; }
@media (prefers-reduced-motion: reduce) { .fx-enter .ach-m, .ach-row.fx-enter .ach-m, .fx-enter .ach-prog > i, .ach-row.fx-enter .ach-prog > i { animation: none !important; } }
/* 메달 시트 — 큰 메달 · 단계 이름 · 칭호 · 조건 · 점수/진행 · 걸기/내리기 */
.ach-sheet { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px; padding: 6px 4px 4px; }
.ach-sheet .ach-ic { display: block; }
.ach-sheet .ach-ic svg { width: 100%; height: 100%; display: block; }
.ach-sheet .ach-ic.big { width: 84px; height: 84px; }
.ach-sheet .ach-ic.big.locked { opacity: .55; }
.ach-tier { font-size: 11px; font-weight: 900; letter-spacing: 1.5px; text-transform: uppercase; }
.ach-tier.t1 { color: #e7a56f; } .ach-tier.t2 { color: #d6e0ea; } .ach-tier.t3 { color: #ffd24a; } .ach-tier.t4 { color: #9eefff; } .ach-tier.t5 { color: #cdb8ff; }
.ach-tier.t6 { background: linear-gradient(90deg, #ff7ad9, #ffd24a, #4fd1ff, #9b6bff); -webkit-background-clip: text; background-clip: text; color: transparent; }
.ach-sheet h3 { margin: 0; font-size: 21px; font-weight: 900; font-style: italic; }
.ach-sheet .ach-next { width: 100%; align-items: center; }
.ach-sheet .ach-prog { width: 100%; }
.ach-xp { color: #ffd24a; font-weight: 900; font-size: 14px; margin: 0; }
.ach-sheet .btn { width: 100%; margin-top: 6px; }
/* 업적 알림의 테두리 — 단계 색 · 레전드는 무지개 */
.hitfx-ach.t1 { border-color: #b06a34; } .hitfx-ach.t2 { border-color: #9aaabc; } .hitfx-ach.t4 { border-color: #5ac2df; } .hitfx-ach.t5 { border-color: #8a6cff; }
.hitfx-ach.t6 { border-color: transparent; background: linear-gradient(180deg, #2a2312, #17130a) padding-box, linear-gradient(90deg, #ff7ad9, #ffd24a, #4fd1ff, #9b6bff) border-box; }

/* 🟢 출석 띠 (`board/xp.js attendFx` · 2026-10-11) — 미션 알약과 같은 자리 · 주기 칸 일곱 · 오늘 칸에 도장이 «쾅»(0.62초에 꽂힌다 — `pingAt` 시각) · 마지막 칸은 보너스 별 */
.att-strip { position: fixed; z-index: 1003; left: 50%; top: calc(var(--safe-top) + var(--bar-h) + 10px); pointer-events: none;
  display: flex; align-items: center; gap: 12px; padding: 10px 18px; border-radius: 99px;
  /* 🟢 더 크게 · 더 눈에 띄게 (2026-10-13 · 사용자 「미션 진행 바 · 출석 진행 바 · 조금 더 크게 아니면 눈에 좀 더 띄게」) — 미션 알약과 같은 «빛 테두리»(`.quest` 그 문단) */
  background: linear-gradient(180deg, color-mix(in srgb, var(--surface) 90%, var(--brand-500)), var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--brand-500) 60%, transparent);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .45), 0 0 0 4px color-mix(in srgb, var(--brand-500) 12%, transparent), 0 0 22px color-mix(in srgb, var(--brand-500) 32%, transparent);
  transform: translate(-50%, -140%); opacity: 0; transition: transform .34s cubic-bezier(.2, 1.3, .4, 1), opacity .2s ease; }
.att-strip.show { transform: translate(-50%, 0); opacity: 1; }
.att-t { font-size: 15px; font-weight: 900; white-space: nowrap; }
.att-cells { display: flex; gap: 5px; }
.att-c { position: relative; width: 22px; height: 22px; border-radius: 6px; background: var(--surface-2, rgba(255, 255, 255, .08)); border: 1px solid var(--line); }
.att-c.on { background: var(--brand-500); border-color: transparent; }
.att-c.gift::after { content: '★'; position: absolute; inset: 0; display: grid; place-items: center; font-size: 13px; font-style: normal; color: #ffd24a; }
.att-c.gift.on::after, .att-c.gift.today::after { color: #3a2600; }
.att-c.today { background: var(--brand-500); border-color: transparent; animation: att-stamp .9s linear .3s both; }
.att-c.today.gift { background: #ffd24a; }
@keyframes att-stamp {
  0% { opacity: 0; transform: scale(2.8) rotate(-25deg); animation-timing-function: cubic-bezier(.55, 0, .9, .4); }
  36% { opacity: 1; transform: scale(.82) rotate(4deg); animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  52% { transform: scale(1.18) rotate(-2deg); }
  70% { transform: scale(.96); }
  100% { opacity: 1; transform: none; }
}
.att-xp { font-size: 16px; font-weight: 900; color: #ffd24a; text-shadow: 1px 1px 0 #7a4a00; white-space: nowrap; }
/* 미션 화면의 출석 카드 — 같은 칸을 크게 · 오늘 칸은 «톡» 한 번 */
.mv-att-h { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.mv-att-h b { font-size: 14px; font-weight: 900; color: var(--brand-500); }
.mv-att-cells { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; margin: 4px 0 8px; }
.mv-att-c { aspect-ratio: 1; border-radius: 10px; display: grid; place-items: center; background: var(--surface-2, rgba(255, 255, 255, .06)); border: 1px solid var(--line); }
.mv-att-c i { font-style: normal; font-size: 13px; font-weight: 800; color: var(--text-mut); }
.mv-att-c.on { background: var(--brand-500); border-color: transparent; }
.mv-att-c.on i { color: #111; }
.mv-att-c.gift i { color: #ffd24a; font-size: 16px; }
.mv-att-c.gift.on { background: #ffd24a; }
.mv-att-c.gift.on i { color: #3a2600; }
.mv-att-c.today { animation: att-stamp .7s linear .15s both; }

/* 🟢 미션 진행 알약 (`board/xp.js questTick` · 2026-10-10) — 게임의 «퀘스트 추적»처럼 앱바 아래 가운데에 하나만 · 이어 오르면 그 자리에서 숫자가 «톡»
   막대가 차고 · 마지막 하나 남으면 금색 · 1.8초 조용하면 올라가 사라진다 · 누르기를 안 막는다 */
.quest { position: fixed; z-index: 1003; left: 50%; top: calc(var(--safe-top) + var(--bar-h) + 10px); pointer-events: none;
  display: flex; align-items: center; gap: 10px; width: min(380px, calc(100vw - 24px)); padding: 8px 16px 8px 8px; border-radius: 99px;
  /* 🟢 더 크게 · 더 눈에 띄게 (2026-10-13 · 사용자 「조금 더 크게 아니면 눈에 좀 더 띄게」) — 높이 ≈38 → 50px · 글씨 12.5 → 14.5 · 막대 54×6 → 폭 늘림×10 ·
     테두리를 회색 선 → 브랜드색 빛(바깥 고리 + 번짐)으로 · 막대에 빛이 지나간다(`q-shine`). 🟡 옛 판은 `max-width` 340 이라 짧은 미션은 알약도 작았다 — 이제 폭이 고정이라 막대가 늘 길다 */
  background: linear-gradient(180deg, color-mix(in srgb, var(--surface) 90%, var(--brand-500)), var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--brand-500) 60%, transparent);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .45), 0 0 0 4px color-mix(in srgb, var(--brand-500) 12%, transparent), 0 0 22px color-mix(in srgb, var(--brand-500) 32%, transparent);
  transform: translate(-50%, -140%); opacity: 0; transition: transform .32s cubic-bezier(.2, 1.3, .4, 1), opacity .2s ease; }
.quest.show { transform: translate(-50%, 0); opacity: 1; }
.quest img { width: 34px; height: 34px; flex: none; }
.quest .q-t { flex: 0 1 auto; min-width: 0; font-size: 14.5px; font-weight: 800; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.quest .q-bar { position: relative; flex: 1 1 auto; min-width: 64px; height: 10px;   /* 남는 폭을 막대가 먹는다 — 짧은 미션 이름 뒤에 빈칸이 안 생긴다 */
   border-radius: 5px; background: var(--surface-2, rgba(255, 255, 255, .08)); overflow: hidden; box-shadow: inset 0 1px 2px rgba(0, 0, 0, .35); }
.quest .q-bar::after { content: ''; position: absolute; inset: 0; background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, .45) 50%, transparent 70%); transform: translateX(-100%); animation: q-shine 1.6s ease-in-out .3s infinite; }
@keyframes q-shine { 0% { transform: translateX(-100%); } 60%, 100% { transform: translateX(100%); } }
.quest .q-bar > i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, color-mix(in srgb, var(--brand-500) 70%, #fff), var(--brand-500)); transition: width .4s cubic-bezier(.2, .8, .3, 1); }
.quest .q-n { flex: none; font-size: 16px; font-weight: 900; font-variant-numeric: tabular-nums; color: var(--brand-500); display: inline-block; }
.quest.last .q-bar > i { background: #ffc83d; box-shadow: 0 0 10px #ffc83d; }
.quest.last { border-color: #ffc83d; box-shadow: 0 10px 28px rgba(0, 0, 0, .45), 0 0 0 4px rgba(255, 200, 61, .16), 0 0 26px rgba(255, 200, 61, .45); }
.quest.last .q-n { color: #ffc83d; }
.quest .q-n.bump { animation: quest-bump .42s cubic-bezier(.3, .7, .4, 1); }
@keyframes quest-bump { 0% { transform: scale(1); } 30% { transform: scale(1.6) rotate(-6deg); } 60% { transform: scale(.9); } 100% { transform: none; } }
.lvup.gift .lvup-card { position: relative; overflow: visible; }
.chest-stage { position: relative; height: 128px; display: flex; align-items: flex-end; justify-content: center; margin: -6px 0 10px; }
.chest-rays { position: absolute; left: 50%; top: 50%; width: 230px; height: 230px; margin: -125px 0 0 -115px; border-radius: 50%; opacity: 0; pointer-events: none;
  background: repeating-conic-gradient(rgba(255, 214, 90, .38) 0 10deg, transparent 10deg 24deg);
  -webkit-mask: radial-gradient(circle, #000 18%, transparent 68%); mask: radial-gradient(circle, #000 18%, transparent 68%); }
.chest { position: relative; width: 104px; height: 88px; transform-origin: 50% 100%; }
.chest svg { position: absolute; left: 0; bottom: 0; width: 104px; height: 88px; overflow: visible; }
.chest .ch-lid { transform-box: fill-box; transform-origin: 85% 100%; }
.chest-plus { position: absolute; left: 50%; bottom: 46px; width: 64px; height: 64px; margin-left: -32px; opacity: 0; pointer-events: none; }
.chest-plus .b-plusbadge { width: 64px; height: 64px; }
.chest-plus svg { width: 100%; height: 100%; display: block; }
.lvup.gift .gift-txt { opacity: 0; }
.lvup.gift.show .chest { animation: chest-drop .5s cubic-bezier(.5, 0, .9, .5) both, chest-shake .9s linear .55s 1; }
.lvup.gift.open .chest .ch-lid { animation: chest-lid .6s cubic-bezier(.2, .8, .3, 1) forwards; }
.lvup.gift.open .chest-rays { animation: chest-rays-in .5s ease-out forwards, chest-spin 9s linear infinite; }
.lvup.gift.open .chest-plus { animation: chest-plus 1s linear .05s forwards; }
.lvup.gift.open .gift-txt { animation: lvup-pop .5s ease-out .5s both; opacity: 1; }
@keyframes chest-drop { 0% { transform: translateY(-120px) scale(.8, 1.2); opacity: 0; } 70% { transform: translateY(0) scale(1.18, .8); opacity: 1; } 85% { transform: scale(.95, 1.06); } 100% { transform: none; opacity: 1; } }
@keyframes chest-shake {
  0%, 30%, 62%, 100% { transform: none; }
  8% { transform: rotate(-7deg) translateY(-3px); } 16% { transform: rotate(6deg); } 24% { transform: rotate(-3deg); }
  40% { transform: rotate(-10deg) translateY(-5px) scale(1.04); } 48% { transform: rotate(9deg) scale(1.04); } 56% { transform: rotate(-4deg); }
  72% { transform: rotate(-13deg) translateY(-8px) scale(1.08); } 80% { transform: rotate(12deg) scale(1.08); } 90% { transform: scale(1.12, .9); }
}
@keyframes chest-lid { 0% { transform: none; } 40% { transform: translate(14px, -46px) rotate(38deg); } 100% { transform: translate(30px, -26px) rotate(62deg); } }
@keyframes chest-rays-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes chest-spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
@keyframes chest-plus {
  0% { opacity: 0; transform: translateY(30px) scale(.2); animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  30% { opacity: 1; transform: translateY(-34px) scale(1.35) rotate(-8deg); }
  44% { transform: translateY(-30px) scale(.06, 1.2); }
  58% { transform: translateY(-30px) scale(1.15) rotate(4deg); }
  72% { transform: translateY(-28px) scale(.95); }
  100% { opacity: 1; transform: translateY(-28px) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .hit-crit, .hit-crit.hit-glitch, .hit-crit-big, .hit-crit-big.hit-glitch, .hit-shake, .hit-shake-big, .hit-star, .hit-bell, .hit-mute, .hit-dock > :not(.hitfx-sweep), .lvup.slam.show .lvup-card, .lvup.slam.show .lvup-card::after,
  .lvup.slam.show .lvup-badge, .lvup.slam.show .mis-face, .lvup.slam.show .mis-line b { animation: none !important; }
  .lvup.slam .lvup-card { opacity: 1; }
  .hitfx-sweep, .hitfx-comets { display: none; }
  .quest { transition: opacity .2s ease; }
  .quest .q-bar::after { animation: none; display: none; }
  .lvup.gift.show .chest, .lvup.gift.open .chest .ch-lid, .lvup.gift.open .chest-rays, .lvup.gift.open .chest-plus, .lvup.gift.open .gift-txt { animation: none !important; }
  .lvup.gift.open .chest-plus, .lvup.gift.open .chest-rays, .lvup.gift .gift-txt { opacity: 1; }
  .mascot.in.drop { animation: none; transform: none; opacity: 1; }
  .att-c.today, .mv-att-c.today { animation: none; }
  .b-ach::after, .ach-m.t6 { animation: none !important; }
  .att-strip { transition: opacity .2s ease; }
  .lvup-badge.paint, .hitfx-ach, .hitfx-ach .ach-medal { animation: none !important; }
  .hitfx-ach .ach-txt { animation: none; max-width: 240px; }
}

/* ── 🟢 **보통 화면의 손맛** (2026-10-08 · `uifx.js` · 사용자 요청 「더보기·프로필·미션·알림 설정·일반 버튼에도 어울리는 곳에」) ──────────────
   🔴 자주 쓰는 화면이라 작고 빠르다 — 흔들림 없음 · 조각 없음 · 0.3~0.9초. 크리티컬·보상의 큰 장면은 위 「타격감」의 몫이다.
   · 열 때 한 번(`.fx-enter` — 화면 뿌리에 `WIN` 동안만 · `--fx-t` 는 창 안에서 다시 그려졌을 때의 음수 지연): 진행 막대가 0 에서 차오르고
     빛 한 줄기 · 미션 단계 칸이 차례로 · 끝낸 미션의 체크가 «도장» · 레벨 배지에 반짝 · 켜진 스위치·고른 칸이 제자리로 들어선다.
   · 누를 때(`.ufx-sw`·`.ufx-seg`): 스위치 점이 늘어났다 붙고 둘레에 옅은 고리 · 고른 칸이 «톡».
   🔴 가장 늦게 끝나는 것(빛 · 도장)이 `uifx.js WIN`(1.5초) 안이어야 한다 — 창이 닫히며 표식이 떨어지면 끝 장면으로 선다(= 원래 모양). */
.fx-enter .prof-lv-bar > i, .fx-enter .mv-bar > i { position: relative; overflow: hidden; transform-origin: 0 50%;
  animation: ufx-fill .85s cubic-bezier(.2, .8, .3, 1) calc(var(--fx-t, 0ms) + 120ms) 1 both; }
.fx-enter .prof-lv-bar > i::after, .fx-enter .mv-bar > i::after { content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 40%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent);
  transform: translateX(-110%); animation: ufx-shine .55s ease-out calc(var(--fx-t, 0ms) + 820ms) 1 both; }
@keyframes ufx-fill { 0% { transform: scaleX(0); } 100% { transform: none; } }
@keyframes ufx-shine { to { transform: translateX(260%); } }
/* 단계 칸 — 켜진 칸이 왼쪽부터 하나씩 «톡» (--si 는 칸 순번 · 칸은 12 개까지라 순번도 12 까지) */
.fx-enter .mv-steps > i.on { animation: ufx-step .34s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-t, 0ms) + 140ms + var(--si, 0) * 45ms) 1 both; }
.mv-steps > i:nth-child(2) { --si: 1; } .mv-steps > i:nth-child(3) { --si: 2; } .mv-steps > i:nth-child(4) { --si: 3; }
.mv-steps > i:nth-child(5) { --si: 4; } .mv-steps > i:nth-child(6) { --si: 5; } .mv-steps > i:nth-child(7) { --si: 6; }
.mv-steps > i:nth-child(8) { --si: 7; } .mv-steps > i:nth-child(9) { --si: 8; } .mv-steps > i:nth-child(10) { --si: 9; }
.mv-steps > i:nth-child(11) { --si: 10; } .mv-steps > i:nth-child(12) { --si: 11; }
@keyframes ufx-step { 0% { transform: scale(.5, .3); opacity: .2; } 100% { transform: none; opacity: 1; } }
/* 끝낸 미션 — 단계 칸이 거의 찬 뒤 체크가 위에서 «도장»으로 찍히고 보상 알약이 한 번 튄다 (--mi 는 줄 순번 · 여섯 줄째도 `WIN` 안에 끝난다) */
.fx-enter .mv-mis.is-done .mv-mis-ck, .mv-mis.fx-enter.is-done .mv-mis-ck { animation: ufx-stamp .46s linear calc(var(--fx-t, 0ms) + 620ms + var(--mi, 0) * 70ms) 1 both; }
.fx-enter .mv-mis.is-done .mv-mis-p, .mv-mis.fx-enter.is-done .mv-mis-p { animation: ufx-pill .4s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-t, 0ms) + 740ms + var(--mi, 0) * 70ms) 1 both; }
.mv-mis:nth-of-type(2) { --mi: 1; } .mv-mis:nth-of-type(3) { --mi: 2; } .mv-mis:nth-of-type(4) { --mi: 3; } .mv-mis:nth-of-type(5) { --mi: 4; } .mv-mis:nth-of-type(6) { --mi: 5; }
@keyframes ufx-stamp {
  0% { transform: scale(2.4) rotate(-25deg); opacity: 0; animation-timing-function: cubic-bezier(.55, 0, .9, .45); }
  45% { transform: scale(.84) rotate(4deg); opacity: 1; animation-timing-function: ease-out; }
  70% { transform: scale(1.08); }
  100% { transform: none; opacity: 1; }
}
@keyframes ufx-pill { 0% { transform: none; } 45% { transform: scale(1.14); } 100% { transform: none; } }
/* 레벨 배지 — 반짝이는 빛 한 줄기 + 살짝 톡 (프로필 카드 · 미션 머리 카드의 배지만 · 글 목록의 배지는 안 건드린다) */
.fx-enter .prof-lv-top .b-lv, .fx-enter .mv2-lvrow .b-lv { position: relative; overflow: hidden;
  animation: ufx-pill .42s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-t, 0ms) + 200ms) 1 both; }
.fx-enter .prof-lv-top .b-lv::after, .fx-enter .mv2-lvrow .b-lv::after { content: ''; position: absolute; top: -40%; bottom: -40%; left: 0; width: 45%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .7), transparent);
  transform: translateX(-120%) skewX(-20deg); animation: ufx-glint .6s ease-out calc(var(--fx-t, 0ms) + 420ms) 1 both; }
@keyframes ufx-glint { to { transform: translateX(260%) skewX(-20deg); } }
/* 알림 설정 — 켜진 스위치의 점이 왼쪽에서 미끄러져 들어서고 · 고른 칸이 제자리로 */
.fx-enter .sw.on .sw-dot { animation: ufx-swin .32s cubic-bezier(.3, 1.4, .5, 1) calc(var(--fx-t, 0ms) + 80ms) 1 both; }
@keyframes ufx-swin { 0% { transform: none; } 100% { transform: translateX(18px); } }
.fx-enter .seg-btn.on { animation: ufx-segin .3s cubic-bezier(.3, 1.4, .5, 1) calc(var(--fx-t, 0ms) + 60ms) 1 both; }
@keyframes ufx-segin { 0% { transform: scale(.86); opacity: .4; } 100% { transform: none; opacity: 1; } }
/* 누를 때 — 스위치 점이 늘어났다 붙고(켜짐·꺼짐 방향이 다르다) 둘레에 옅은 고리 · 고른 칸이 «톡» */
.sw.ufx-sw.on .sw-dot { animation: ufx-swon .34s cubic-bezier(.3, 1.2, .5, 1) 1 both; }
.sw.ufx-sw:not(.on) .sw-dot { animation: ufx-swoff .34s cubic-bezier(.3, 1.2, .5, 1) 1 both; }
@keyframes ufx-swon { 0% { transform: none; } 45% { transform: translateX(9px) scale(1.3, .82); } 100% { transform: translateX(18px); } }
@keyframes ufx-swoff { 0% { transform: translateX(18px); } 45% { transform: translateX(9px) scale(1.3, .82); } 100% { transform: none; } }
.sw.ufx-sw::after { content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: 0 0 0 2px var(--brand-500); opacity: 0; animation: ufx-swring .42s ease-out 1; }
@keyframes ufx-swring { 0% { opacity: .55; transform: none; } 100% { opacity: 0; transform: scale(1.22, 1.4); } }
.seg-btn.ufx-seg { animation: ufx-seg .34s cubic-bezier(.3, 1.6, .5, 1) 1; }
@keyframes ufx-seg { 0% { transform: scale(.9); } 55% { transform: scale(1.06); } 100% { transform: none; } }
/* 누른 순간(상태가 바뀌기 «전») — 점이 살짝 눌린다 · 토픽 구독을 기다리는 스위치(기본 알림·봇 알림·마케팅)의 «들었다»는 대답 (2026-10-08) */
.sw.ufx-swp .sw-dot { animation: ufx-swp .26s ease-out 1; }
.sw.on.ufx-swp .sw-dot { animation-name: ufx-swpon; }
@keyframes ufx-swp { 0%, 100% { transform: none; } 40% { transform: scale(.8, .9); } }
@keyframes ufx-swpon { 0%, 100% { transform: translateX(18px); } 40% { transform: translateX(18px) scale(.8, .9); } }
.ufx-chip { animation: ufx-chip .38s cubic-bezier(.3, 1.6, .5, 1) 1; }
@keyframes ufx-chip { 0% { transform: scale(.88); box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand-500) 55%, transparent); }
  55% { transform: scale(1.07); } 100% { transform: none; box-shadow: 0 0 0 7px transparent; } }
/* 더보기의 줄 — 누르는 동안 머리 그림이 살짝 움츠러들고 꺾쇠가 앞으로 (뒤 배경색 바뀜은 `.row:active` 그대로) */
.row .sect-ic, .row .chev { transition: transform .14s ease-out; }
.row:active .sect-ic { transform: scale(.84); }
.row:active .chev { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .fx-enter .prof-lv-bar > i, .fx-enter .mv-bar > i, .fx-enter .mv-steps > i.on, .fx-enter .mv-mis.is-done .mv-mis-ck, .fx-enter .mv-mis.is-done .mv-mis-p,
  .mv-mis.fx-enter.is-done .mv-mis-ck, .mv-mis.fx-enter.is-done .mv-mis-p,
  .fx-enter .prof-lv-top .b-lv, .fx-enter .mv2-lvrow .b-lv, .fx-enter .sw.on .sw-dot, .fx-enter .seg-btn.on,
  .sw.ufx-sw .sw-dot, .seg-btn.ufx-seg, .sw.ufx-swp .sw-dot, .ufx-chip { animation: none !important; }
  .fx-enter .prof-lv-bar > i::after, .fx-enter .mv-bar > i::after, .fx-enter .prof-lv-top .b-lv::after, .fx-enter .mv2-lvrow .b-lv::after, .sw.ufx-sw::after { content: none; }
  .row:active .sect-ic, .row:active .chev { transform: none; }
}

/* ── 🟢 **나타날 때 한 번 — 오늘의 미션 · 경험치 획득 내역의 아래 구획** (2026-10-08 · `uifx.js secFx` · 사용자 요청 「아래로 스크롤 내리면 아래 화면들
   나타날때 효과 발동」 · 「경험치획득내역 화면에도 효과」) ──────────────
   · `.fx-hold` — 열 때 화면 밖이던 구획: 그 구획에 `.fx-enter` 가 달린 채 «멈춰» 있다 → 모든 효과가 시작 장면(빈 막대 · 투명한 카드)에 선다.
   · `.fx-sec.fx-enter` — 나타난 구획(카드 · 카드 안의 줄·묶음): 들어서고(`ufx-card`) 안의 효과(위 문단의 막대·단계 칸·도장·스위치 + 아래의 도넛·선·시간표)가
     그때 처음부터 · 🔴 구획 «자신»이 `.fx-enter` 라 `.fx-enter .구획.상태` 꼴 규칙은 `.구획.fx-enter.상태` 짝이 같이 있어야 한다(위 미션 도장).
   🔴 열 때 보이던 구획은 이 표식이 없다 — 뿌리의 `.fx-enter` 로 옛 그대로(카드 등장 없음).
   🔴 아래 새 효과도 뿌리의 창(`WIN` 1.5초) 안에 끝난다 — 큰 화면에서는 열 때 보이는 자리라 뿌리의 표식으로 돈다. */
.fx-hold, .fx-hold *, .fx-hold::after, .fx-hold *::after { animation-play-state: paused !important; }
.fx-sec.fx-enter { transform-origin: 50% 0; animation: ufx-card .55s cubic-bezier(.2, 1.3, .4, 1) var(--fx-t, 0ms) 1 backwards; }
/* 오늘 획득 구성 — 도넛이 감기며 들어서고 · 가운데 합계와 계열 합계가 0 에서 세어 오르다(`secFx` 의 `count`) «착» */
.fx-enter .mv-donut { animation: ufx-donut .8s cubic-bezier(.2, .8, .3, 1) calc(var(--fx-t, 0ms) + 120ms) 1 both; }
@keyframes ufx-donut { 0% { transform: rotate(-140deg) scale(.55); opacity: 0; } 55% { opacity: 1; } 100% { transform: none; opacity: 1; } }
.fx-enter .mv-ringtxt b, .fx-enter .mv-lg-h b { display: inline-block; animation: ufx-land .42s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-t, 0ms) + 830ms) 1 both; }
.fx-enter .mv-line { animation: ufx-spark .9s cubic-bezier(.3, .7, .3, 1) calc(var(--fx-t, 0ms) + 150ms) 1 both; }
/* 봇 시간표 — 칸이 왼쪽부터 하나씩 «톡» (--si 는 칸 순번 · 위 단계 칸과 같은 무늬) */
.fx-enter .mv-slot { animation: ufx-slot .4s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-t, 0ms) + 160ms + var(--si, 0) * 40ms) 1 both; }
.mv-slots > .mv-slot:nth-child(2) { --si: 1; } .mv-slots > .mv-slot:nth-child(3) { --si: 2; } .mv-slots > .mv-slot:nth-child(4) { --si: 3; }
.mv-slots > .mv-slot:nth-child(5) { --si: 4; } .mv-slots > .mv-slot:nth-child(6) { --si: 5; } .mv-slots > .mv-slot:nth-child(7) { --si: 6; }
.mv-slots > .mv-slot:nth-child(8) { --si: 7; } .mv-slots > .mv-slot:nth-child(9) { --si: 8; } .mv-slots > .mv-slot:nth-child(10) { --si: 9; }
.mv-slots > .mv-slot:nth-child(n+11) { --si: 10; }
@keyframes ufx-slot { 0% { transform: scale(.6); opacity: 0; } 60% { transform: scale(1.08); opacity: 1; } 100% { transform: none; opacity: 1; } }
/* 경험치 획득 내역(🟢 2026-10-12 부터 «오늘의 획득 경험치» 맨 아래 카드) — 내역 줄이 차례로 «착» 내려앉고(--ri — 열 때 보이던 줄의 순번 · 나타난 줄은 0 과 `--fx-t`) ·
   +XP 가 0 에서 세어 오르다 «착» · ×2 배지가 «톡» 하고 빛 한 줄기 (🗄 옛 머리 «하루 상한» 줄의 등장 `.xp-cap-row` 는 그 표와 함께 걷었다) */
.fx-enter .xp-row, .xp-row.fx-enter { transform-origin: 50% 0;
  animation: ufx-row .5s cubic-bezier(.2, 1.3, .4, 1) calc(var(--fx-t, 0ms) + 80ms + var(--ri, 0) * 32ms) 1 backwards; }
.fx-enter .xp-row .xp-pt, .xp-row.fx-enter .xp-pt { display: inline-block;
  animation: ufx-land .42s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-t, 0ms) + 830ms + var(--ri, 0) * 32ms) 1 backwards; }
.fx-enter .xp-row .xp-x2, .xp-row.fx-enter .xp-x2 { position: relative; overflow: hidden;
  animation: ufx-pill .4s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-t, 0ms) + 360ms + var(--ri, 0) * 32ms) 1 backwards; }
.fx-enter .xp-row .xp-x2::after, .xp-row.fx-enter .xp-x2::after { content: ''; position: absolute; top: -40%; bottom: -40%; left: 0; width: 60%; pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .85), transparent);
  transform: translateX(-140%) skewX(-18deg); animation: ufx-sweep .6s ease-out calc(var(--fx-t, 0ms) + 620ms + var(--ri, 0) * 32ms) 1 both; }
@media (prefers-reduced-motion: reduce) {
  .fx-sec.fx-enter, .fx-enter .mv-donut, .fx-enter .mv-ringtxt b, .fx-enter .mv-lg-h b, .fx-enter .mv-line, .fx-enter .mv-slot,
  .fx-enter .xp-row, .xp-row.fx-enter, .fx-enter .xp-row .xp-pt, .xp-row.fx-enter .xp-pt, .fx-enter .xp-row .xp-x2, .xp-row.fx-enter .xp-x2 { animation: none !important; }
  .fx-enter .xp-row .xp-x2::after, .xp-row.fx-enter .xp-x2::after { content: none; }
}

/* ── 🟢 **칩 목록 효과** (코인스쿱 2026-10-08 · `uifx.js listFx`) ──────────────
   칩·하위 칩·정렬을 바꿀 때 한 번(`.fx-list` — 몸통에 `LIST_WIN` 동안만 · `--fx-lt` 는 창 안에서 다시 그려졌을 때의 음수 지연 ·
   `.fx-ri`/`--ri` 는 맨 위 `LIST_N` 줄과 그 순번): 🗄 줄이 젖혀진 채 내려앉던 것은 2026-10-16 에 걷었다동으로 튀고 ·
   🔴 `backwards` 만 쓴다 — 끝나면 줄의 원래 transform(누를 때 `:active`)을 돌려준다. 🔴 젖힘에 `scale(.94)` 가 세트다 — 원근으로 가까워진 줄 끝이 폭을 넘어 잠깐 가로 막대가 생기지 않게. */
/* 🔴 **줄 «자체»는 움직이지 않는다** (2026-10-16 · 사용자 「펼침 효과만 빼는 거였다 · 숫자 올라가는 것·아이콘 회전·헤더 카드 반짝임은 살린다」) — 옛 판은 줄이
   `ufx-row`(투명 + 젖힘 → 내려앉기)로 들어서서, 칩을 넘기는 동안 미끄러질 줄이 화면에 거의 없었다(넘김이 안 보였다).
   그리고 칩 전환의 끊긴 프레임 대부분이 그 줄 애니메이션이었다(헤드리스 CPU ×4 실측 · `docs/where-to-fix.md` 「보통 화면의 손맛」 2026-10-16 줄). */
@keyframes ufx-row {
  0% { opacity: 0; transform: perspective(700px) rotateX(-42deg) translateY(-10px) scale(.94); }
  45% { opacity: 1; }
  100% { opacity: 1; transform: none; }
}

@keyframes ufx-spark { 0% { clip-path: inset(0 100% 0 0); } 100% { clip-path: inset(0 0 0 0); } }

/* 🔴 글자 빛(`text-shadow`)은 뺐다 (2026-10-16 · 사용자 「빛만 빼봐」) — 그림자 애니메이션은 프레임마다 다시 칠해서(transform 처럼 합성만으로 안 끝난다)
   머리 카드의 «착»(`fx-land`)·테두리 빛·빛 한 줄기는 이 규칙이 아니라 그대로 빛난다. */
@keyframes ufx-land { 0% { transform: none; } 40% { transform: scale(1.13); } 100% { transform: none; } }
.fx-list .fx-ri .chl-n {
  animation: ufx-pill .4s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-lt, 0ms) + 360ms + var(--ri, 0) * 32ms) 1 backwards; }
@keyframes ufx-card {
  0% { opacity: 0; transform: perspective(900px) rotateX(-16deg) translateY(-12px) scale(.95); }
  45% { opacity: 1; }
  100% { opacity: 1; transform: none; }
}
@keyframes ufx-bub {
  0% { opacity: 0; transform: perspective(900px) rotateX(16deg) translateY(12px) scale(.95); }
  45% { opacity: 1; }
  100% { opacity: 1; transform: none; }
}

/* ── 🟢 **빛 지나가기 — HOT 태그 · 레벨 배지 · PLUS·공식 배지** (코인스쿱 2026-10-08)
   · 레벨 배지 — 목록이 들어설 때 한 번 · 🟢 상위 세 단계(`lvt7~9` 주황·금)는 몇 초마다 — 오래 활동한 사람의 표식이 «살아 있다».
   · HOT 태그 — 몇 초마다 한 줄기 · PLUS·공식 배지(`.pb-shine`·`.pb-spark`) — 빛 두 줄기가 늘 지나가고 작은 별이 반짝(아래 문단).
   🔴 빛(`::after` · 폭 60% · 위아래 −40%)은 **배지 안에서만** 돈다 — 아래 `position: relative; overflow: hidden` 한 줄이 그 상자다.
      🟡 2026-10-22 이식 때 코인스쿱의 코인 아이콘 줄을 걷으며 이 주석의 닫는 표시까지 같이 지워, 그 한 줄이 **주석 안에 들어가 무시됐다**(2026-10-26 · 사용자
      실기기 「앱 진입 후 커뮤니티 탭을 누르면 배지가 반짝이면서 카드에 빛이 난다」). 빛의 기준 상자가 배지 대신 가장 가까운 `relative` 조상인 **카드**(`.bcard`)가 되어,
      카드 폭 60% · 카드 높이 1.8배의 흰 띠가 카드와 카드 사이 틈까지 훑고 지나갔다. */
.fx-list .fx-ri .b-lv { position: relative; overflow: hidden; }

/* 🗄 목록 아이콘의 «한 바퀴»(코인스쿱 2D `scaleX` 판)는 코인 아이콘과 함께 걷었다 — 남은 회전은 캡처 카드의 3D 하나(`.capfx-l .cfx-i` · 하나라 비용이 없다). */
@keyframes ufx-icspin3d { 0% { transform: perspective(160px) rotateY(0deg); } 100% { transform: perspective(160px) rotateY(360deg); } }

.fx-list .fx-ri .b-lv::after {
  content: ''; position: absolute; top: -40%; bottom: -40%; left: 0; width: 60%; pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .9), transparent);
  transform: translateX(-140%) skewX(-18deg); animation: ufx-sweep .6s ease-out calc(var(--fx-lt, 0ms) + 880ms + var(--ri, 0) * 32ms) 1 both; }
@keyframes ufx-sweep { to { transform: translateX(240%) skewX(-18deg); } }
.fx-list .fx-ri .b-lv { animation: ufx-land .42s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-lt, 0ms) + 860ms + var(--ri, 0) * 32ms) 1 backwards; }
/* 몇 초마다 — 앞 2할에 한 줄기가 지나가고 나머지는 쉰다 */
.hot-tag,
.b-lv.lvt7,
.b-lv.lvt8,
.b-lv.lvt9 { position: relative; overflow: hidden; }
.hot-tag { display: inline-block; }
.hot-tag::after,
.b-lv.lvt7::after,
.b-lv.lvt8::after,
.b-lv.lvt9::after {
  content: ''; position: absolute; top: -30%; bottom: -30%; left: 0; width: 45%; pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .7), transparent);
  transform: translateX(-130%) skewX(-18deg); animation: ufx-sweep-loop 3.6s ease-in-out infinite; }
.b-lv.lvt7::after, .b-lv.lvt8::after, .b-lv.lvt9::after { animation-duration: 5.4s; animation-delay: .8s; }

@keyframes ufx-sweep-loop { 0% { transform: translateX(-130%) skewX(-18deg); } 22%, 100% { transform: translateX(320%) skewX(-18deg); } }
.b-plusbadge { animation: pb-flip 6.4s ease-in-out infinite; }
@keyframes pb-flip { 0%, 84% { transform: none; } 100% { transform: perspective(60px) rotateY(360deg); } }
.pb-shine { fill: rgba(255, 255, 255, .9); transform: skewX(-20deg); }
.pb-shine2 { fill: rgba(255, 255, 255, .75); }
.b-plusbadge .pb-shine { animation: pb-shine 3.2s cubic-bezier(.5, 0, .3, 1) infinite; }
@keyframes pb-shine { 0% { transform: translateX(0) skewX(-20deg); } 26%, 100% { transform: translateX(46px) skewX(-20deg); } }
.pb-spark { fill: #fff; opacity: 0; transform-box: fill-box; transform-origin: center; }
.b-plusbadge .pb-spark { animation: pb-spark 3.2s ease-out infinite; }
@keyframes pb-spark { 0%, 18% { opacity: 0; transform: scale(.2) rotate(0deg); } 26% { opacity: 1; transform: scale(1) rotate(45deg); } 40%, 100% { opacity: 0; transform: scale(.3) rotate(90deg); } }
.b-plusbadge svg { overflow: visible; }
/* 🟢 화면 밖에서는 멈춘다 (2026-10-08 · 사용자 요청 「화면에 보이는 배지만 돌게」 · `uifx.js bindViewFx` 가 `.fx-off` 를 단다) — 멈춘 자리에서 다시 보이면 잇는다.
   🔴 반복 규칙보다 뒤 · 특이도가 같거나 높다(재생 상태만 덮는다 — `animation` 한꺼번에 쓰기가 재생 상태를 «running» 으로 되돌리므로) */
.b-plusbadge.fx-off,
.b-plusbadge.fx-off .pb-shine,
.b-plusbadge.fx-off .pb-spark,
.hot-tag.fx-off::after,
.b-lv.lvt7.fx-off::after,
.b-lv.lvt8.fx-off::after,
.b-lv.lvt9.fx-off::after { animation-play-state: paused; }
.b-chip.fx-seen { animation: ufx-chippop .4s cubic-bezier(.3, 1.6, .5, 1) 1; }
@keyframes ufx-chippop { 0% { transform: scale(.86); } 55% { transform: scale(1.07); } 100% { transform: none; } }

.fx-sheet .fx-sr { transform-origin: 50% 0; animation: ufx-card .5s cubic-bezier(.2, 1.3, .4, 1) calc(60ms + var(--ri, 0) * 45ms) 1 backwards; }

/* ── 🟢 **캡처 첨부의 움직임** (2026-10-08 · 사용자 「캡처 인용을 효과 있는 그림으로 · 첨부인용 모든 화면에」 · `services/capfx.js`) ──
   아래에서 그어지며 빛나고 · 칸·줄이 차례로 들어서고 · 알약이 크게 꽂혀 튕기며 빛 한 줄기 · 큰 숫자가 «착» · 등락 알약이 톡 · 아이콘이 한 바퀴.
   끝 장면 = 사진(조각은 그 사진을 잘라 쓴 사본이다). 🔴 `.play` 가 붙어 있는 동안만 돈다 — 보일 때마다 엔진이 뗐다 단다(본문) · 확대는 열 때 한 번.
   🔴 층은 그림의 «형제»다(`.capfx-o` — 그림 상자 · 넘치는 몫은 자른다) · 안의 `.capfx-l` 이 «사진이 실제로 그려진 자리»(object-fit 을 따른다). */
.capfx-o { position: absolute; pointer-events: none; overflow: hidden; z-index: 1; }
.capfx-l { position: absolute; pointer-events: none; overflow: hidden; }
.capfx-z { position: absolute; pointer-events: none; transform-origin: center center; will-change: transform; }
.capfx-z.snap { transition: transform .18s ease-out; }
.capfx-l > i { position: absolute; display: block; font-style: normal; opacity: 0; }
.capfx-l.play > i { opacity: 1; }
.capfx-l.play .cfx-c { background: var(--cfx-b); animation: cfx-wipe .95s cubic-bezier(.45, .05, .3, 1) .25s 1 both; }
.capfx-l .cfx-cs { overflow: visible; }
.capfx-l.play .cfx-cs::before { content: ''; position: absolute; top: 0; bottom: 0; width: 3px; margin-left: -3px;
  background: linear-gradient(180deg, transparent, #fff 20%, #fff 80%, transparent); box-shadow: 0 0 10px rgba(255, 255, 255, .8);
  animation: cfx-scan .95s cubic-bezier(.45, .05, .3, 1) .25s 1 both; }
@keyframes cfx-wipe { 0% { clip-path: inset(0 0 0 0); } 100% { clip-path: inset(0 0 0 100%); } }
@keyframes cfx-scan { 0% { left: 0; opacity: 1; } 92% { opacity: 1; } 100% { left: 100%; opacity: 0; } }
.capfx-l.play .cfx-h, .capfx-l.play .cfx-v { background: var(--cfx-c); box-shadow: 0 0 4px var(--cfx-c), 0 0 10px var(--cfx-c); }
.capfx-l .cfx-h { height: 2px; margin-top: -1px; transform-origin: 100% 50%; }
.capfx-l .cfx-v { width: 2px; margin-left: -1px; transform-origin: 50% 100%; }
.capfx-l.play .cfx-h { animation: cfx-traceh .55s cubic-bezier(.3, .7, .3, 1) 1.15s 1 backwards, cfx-glowout .9s ease-out 1.7s 1 forwards; }
.capfx-l.play .cfx-v { animation: cfx-tracev .55s cubic-bezier(.3, .7, .3, 1) 1.3s 1 backwards, cfx-glowout .9s ease-out 1.85s 1 forwards; }
@keyframes cfx-traceh { 0% { transform: scaleX(0); } 100% { transform: none; } }
@keyframes cfx-tracev { 0% { transform: scaleY(0); } 100% { transform: none; } }
@keyframes cfx-glowout { 0% { opacity: 1; } 100% { opacity: 0; } }
.capfx-l .cfx-p, .capfx-l .cfx-n, .capfx-l .cfx-g, .capfx-l .cfx-i, .capfx-l .cfx-r { background-repeat: no-repeat; overflow: hidden; }
/* 알약 — 막(그 자리의 바탕색)이 사진의 알약을 가리고 있다가 사본이 꽂히는 순간 걷힌다 */
.capfx-l.play .cfx-pm { background: var(--cfx-b); animation: cfx-unmask .2s linear calc(1.77s + var(--cfx-d, 0ms)) 1 both; }
@keyframes cfx-unmask { 0% { opacity: 1; } 100% { opacity: 0; } }
.capfx-l.play .cfx-p { animation: cfx-stamp .62s cubic-bezier(.3, 1.5, .5, 1) calc(1.55s + var(--cfx-d, 0ms)) 1 backwards; }
@keyframes cfx-stamp {
  0% { opacity: 0; transform: scale(2.2); }
  40% { opacity: 1; transform: scale(.9); box-shadow: 0 0 0 2px var(--cfx-c), 0 0 16px var(--cfx-c); }
  70% { transform: scale(1.06); }
  100% { transform: none; box-shadow: none; }
}
/* 칸·줄 — 막이 사진의 칸을 가리고 · 사본이 아래에서 올라서며 내려앉는다(목록 줄 `ufx-card` 의 결) */
.capfx-l.play .cfx-rm { background: var(--cfx-b); animation: cfx-unmask .18s linear calc(var(--cfx-d, 0ms) + .12s) 1 both; }
.capfx-l.play .cfx-r { animation: cfx-rise .5s cubic-bezier(.2, 1.3, .4, 1) var(--cfx-d, 0ms) 1 backwards; }
@keyframes cfx-rise { 0% { opacity: 0; transform: translateY(40%) scale(.92); } 100% { opacity: 1; transform: none; } }
.capfx-l.play .cfx-n { animation: cfx-land .55s cubic-bezier(.3, 1.6, .5, 1) calc(.1s + var(--cfx-d, 0ms)) 1 backwards; }
@keyframes cfx-land { 0% { opacity: 0; transform: scale(1.45); filter: brightness(1.8); } 55% { opacity: 1; transform: scale(.95); } 100% { transform: none; filter: none; } }
.capfx-l.play .cfx-g { animation: cfx-pop .42s cubic-bezier(.3, 1.6, .5, 1) calc(.4s + var(--cfx-d, 0ms)) 1 backwards; }
@keyframes cfx-pop { 0% { opacity: 0; transform: scale(.4); } 60% { opacity: 1; transform: scale(1.12); } 100% { transform: none; } }
.capfx-l.play .cfx-i { animation: ufx-icspin3d .7s cubic-bezier(.45, .05, .35, 1) calc(.25s + var(--cfx-d, 0ms)) 1 backwards; }
.capfx-l.play .cfx-p::after, .capfx-l.play .cfx-n::after, .capfx-l.play .cfx-i::after {
  content: ''; position: absolute; top: -40%; bottom: -40%; left: 0; width: 45%; pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .85), transparent);
  transform: translateX(-140%) skewX(-18deg); animation: ufx-sweep .6s ease-out calc(2.1s + var(--cfx-d, 0ms)) 1 both; }
.capfx-l.play .cfx-n::after { animation-delay: calc(.65s + var(--cfx-d, 0ms)); }
.capfx-l.play .cfx-i::after { animation-delay: calc(.9s + var(--cfx-d, 0ms)); }
@media (prefers-reduced-motion: reduce) { .capfx-l > i { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .fx-list .fx-ri,
.fx-list .fx-ri * { animation: none !important; }

  .fx-list .fx-ri .b-lv::after,
.hot-tag::after,
.b-lv.lvt7::after,
.b-lv.lvt8::after,
.b-lv.lvt9::after { content: none; }
  .b-plusbadge,
.pb-shine,
.pb-spark { animation: none !important; }

  .b-chip.fx-seen { animation: none !important; }

  .fx-sheet .fx-sr, .fx-sheet .fx-sr * { animation: none !important; }

}

/* 🔴 화면에는 안 보이고 스크린리더에만 읽히는 글자 — 뼈대에서 「불러오는 중」을
   그림(스피너)으로만 말하면 소리로 듣는 사람에게는 아무 말도 안 한 것이 된다. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── 자동완성 후보 패널 ─────────────────────────────────────────────────────
   🔴 **떠 있는 목록이다.** 예전엔 화면 흐름에 그대로 끼워 넣어서 글자를 칠 때마다
      아래 관심목록이 통째로 밀려 내려갔다 — 목록을 보며 고르는 화면인데 그 목록이
      계속 움직였다(요청 6번). 이제 목록은 제자리에 있고 후보가 그 위를 덮는다.
   🔴 `top: 100%` + `left/right: 0` — 입력칸 **바로 아래**, 같은 폭.
      🔴 기준은 `.watch-field` 다. 그 상자에서 `position: relative` 를 빼면 패널이
         화면 왼쪽 위로 날아간다(가장 가까운 위치 지정 조상까지 올라간다).
   🔴 `max-height` + `overflow-y` 세트다. 후보가 12개까지 오는데(searchSymbols)
      상한이 없으면 작은 기기에서 패널이 화면 밖으로 나가 아래쪽을 못 고른다.
   🔴 `z-index` 는 하단 탭(6)보다 낮게 둔다 — 탭을 덮으면 빠져나갈 길이 가려진다. */
.sugg {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 5;
  margin-top: 6px; padding: 4px;
  max-height: min(56vh, 380px); overflow-y: auto; overscroll-behavior: contain;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--sh-3);
}
/* 패널 안에서는 섹션 머리를 얇게 — 바깥 목록의 `.sect` 여백을 그대로 쓰면 첫 후보가 밀린다 */
.sugg .sect { margin: 2px 6px 4px; }

/* 회원 탈퇴 — 최하단 · 작게 · 위험색. 눈에 띄는 행이 아니라 찾아오는 버튼이다 */
/* 🔴 **밑줄을 긋는다** (2026-08-19 · 요청 5번). 색만으로는 "누를 수 있는 것" 이 안
   전해진다 — 위의 로그아웃(`.linkbtn`)이 이미 밑줄인데 여기만 없어서, 같은 성격의
   두 글자 링크가 서로 다른 것처럼 보였다. 색각 이상에서는 빨강마저 안 읽히므로
   밑줄이 유일한 단서가 된다. */
.danger-link {
  display: block; margin: 26px auto 8px; padding: 6px 12px;
  font-size: 12px; color: var(--hot); opacity: .75; background: none; border: none;
  text-decoration: underline; text-underline-offset: 3px;
}
.danger-link:active { opacity: 1; }
/* 시트의 위험 동작 버튼 — 삭제류만. 일반 opt 와 색으로 갈라야 오터치가 준다 */
.opt.danger span { color: var(--hot); font-weight: 700; }

/* 시트 안 이메일 입력 (로그인 · 2026-08-13). .searchbar input 과 같은 톤 */
.input {
  width: 100%; box-sizing: border-box; margin: 6px 0 8px;
  padding: 12px 14px; border-radius: var(--r-sm); font-size: 15px;
  background: var(--surface-2); color: var(--text); border: 1px solid var(--line-2);
}
.input:focus { border-color: var(--brand-500); outline: none; }

/* ── 🔴 **초안 목록 시트** (2026-08-31 · 57차 요청 4번 · board/drafts.js `openDraftSheet`) ────────
   🔴 한 줄에 **문이 둘**이다(집기 · 버리기). 그래서 ✕ 를 집기 버튼 «안»에 넣지 않는다 —
      버튼 안의 버튼은 안드로이드 웹뷰에서 안쪽이 먼저 먹고 바깥이 안 눌리는 자리를
      만든다(컨트랙트 줄의 복사 아이콘이 같은 이유로 «그림»으로 남아 있다).
   🔴 ✕ 는 44px 과녁을 지킨다 — 되돌릴 수 없는 버튼이 작으면 오탭이 곧 손실이다. */
.dr-list { display: flex; flex-direction: column; margin-top: 4px; }
.dr-row { display: flex; align-items: center; gap: 4px; }
.dr-row + .dr-row { border-top: 1px solid var(--line-2); }
.dr-pick {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px;
  background: none; border: 0; padding: 11px 2px; text-align: left; color: var(--text);
}
.dr-pick:active { background: var(--surface-2); border-radius: 10px; }
.dr-t { font-size: 14px; font-weight: 700; }
/* 🔴 제목·요약 둘 다 **한 줄**에서 끊는다 — 초안은 길어서 여기 담긴 것이므로, 안 끊으면
   목록이 그 글의 사본이 된다(고르는 자리이지 읽는 자리가 아니다). */
.dr-t, .dr-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dr-sub { font-size: 12px; color: var(--text-sub); }
.dr-at { font-size: 11px; color: var(--text-mut); }
.dr-x {
  flex: none; width: 44px; height: 44px; background: none; border: 0;
  color: var(--text-mut); font-size: 15px; border-radius: var(--r-sm);
}
.dr-x:active { background: var(--surface-2); }

/* ── 공감 바 — 불리시·베어리시 (2026-08-13 밤 4차 · 6차 개편) ────────────────
   🔴 색은 .chg 와 같은 팔레트 축이다: 불리시 = --up · 베어리시 = --hot. 직접 색 금지
   🔴 **카드와 상세가 같은 모양이다** (6차 · 요청 3번). `.mini` 하나만 쓴다 — 상세용
      큰 버튼(height 44 · flex:1 · 라벨)은 지웠다. 되살리기 전에 app.js reactBarHTML
      위 주석부터 읽을 것.
   🔴 자리는 **칩 줄 아래 독립된 줄**이다 (요청 2번). 칩 줄(.card-foot) 안의 오른쪽
      붙박이로 되돌리지 말 것 — 칩이 두 줄로 접히면 버튼의 세로 위치가 카드마다 달라진다.
   🟢 목록 카드에서는 이 줄의 오른쪽 끝에 「관련 보도 N」이 선다(2026-09-14 · 128차 요청 11번 · `.reactbar .relmark`).
   🔴 `justify-content: flex-start` 를 지운다고 가운데로 오지 않는다. 폭을 안 늘리는
      건 `.reactbtn` 의 `flex: 0 0 auto` 다 — 지우면 버튼 둘이 화면 폭을 반씩 먹는다. */
.reactbar { display: flex; gap: 6px; margin: 8px 0 0; }
/* 🔴 중요도 숫자 — **디버그 빌드에만 그려진다**(mediareact.js `impDebugHTML`). CSS 는 모양만 맡는다. 🔴 여기에 `display:none` 류의 숨김을 넣어 릴리즈를 막으려 하지 말 것 —
   릴리즈에서는 노드 자체가 안 만들어지는 게 방어선이다. 🟡 눈에 덜 띄게 흐린 색이다(내가 보려고 두는 숫자지 사용자 정보가 아니다 · 코인스쿱 `.impdbg` 와 같다). */
.impdbg {
  align-self: center; margin-left: 2px; padding: 1px 5px;
  font-size: 11px; font-weight: 700; font-style: normal;
  font-variant-numeric: tabular-nums;
  color: var(--text-mut); background: var(--surface-2);
  border-radius: var(--r-xs);
}

.reactbtn {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  height: 28px; padding: 0 9px; border-radius: var(--r-xs); font-size: 12px; font-weight: 600;
  background: var(--surface); border: 1px solid var(--line); color: var(--text-sub);
  font-family: inherit; transition: transform .1s, border-color .15s, color .15s;
}
.reactbtn:active { transform: scale(.94); }
/* 🔴 `b` 안은 이제 글자가 아니라 인라인 SVG 다 (app.js ICON.bull/bear).
      `display: block` 이 없으면 SVG 밑에 baseline 여백이 생겨 버튼이 세로로 안 맞는다. */
.reactbtn b { display: inline-flex; }
.reactbtn b svg { display: block; width: 15px; height: 15px; }
.reactbtn .rcount { font-style: normal; font-size: 11.5px; color: var(--text-mut); }
.reactbtn.bull.on { border-color: var(--up); color: var(--up); background: color-mix(in srgb, var(--up) 10%, transparent); }
.reactbtn.bear.on { border-color: var(--hot); color: var(--hot); background: color-mix(in srgb, var(--hot) 10%, transparent); }
/* 🟢 **북마크** (2026-10-12 · board/reactions.js `bmBtnHTML`) — 공감 둘 바로 오른쪽 · 켜지면 브랜드 색으로 «채운» 책갈피(그림의 `fill` 만 바뀐다) */
.reactbtn.bmbtn.on { border-color: var(--brand-500); color: var(--brand-500); background: color-mix(in srgb, var(--brand-500) 10%, transparent); }
.bmbtn.on svg path, .bmrow-x svg path { fill: currentColor; }
/* ── 🔴 **고르면 아이콘이 «그 방향으로» 움직인다** (2026-08-24 · 요청 1번) ────────────────
   불리시는 위로, 베어리시는 아래로 한 번 튄다 — 이 버튼이 말하는 것이 곧 방향이라
   움직임도 같은 방향이어야 한다(반대로 두면 애니메이션이 뜻을 거스른다).
   🔴 `b`(아이콘 칸)만 움직인다 — 버튼째 움직이면 옆 숫자와 테두리까지 흔들려서
      가로로 붙어 있는 두 버튼이 서로 밀린 것처럼 보인다.
   🔴 `prefers-reduced-motion` 에서는 안 돈다(탭 아이콘과 같은 규칙).

   ── 🔴 **`.on` 이 아니라 «방금 눌렀다»에 건다** (2026-08-25 · 요청 18번) ──────────────
   옛 판은 `.reactbtn.bull.on b` 였다. 그런데 `.on` 은 **상태**이지 사건이 아니다 —
   화면에 이미 켜져 있던 버튼들도 그 규칙에 해당하므로, 목록이 다시 그려지거나
   한꺼번에 튀었다.** 사용자가 본 그대로다.
   🔴 그래서 애니메이션의 방아쇠를 **`.pop` 한 클래스**로 옮긴다. 그 클래스는 클릭
      핸들러가 «누른 버튼 하나»에만 붙이고 `animationend` 에서 스스로 뗀다
      (app.js `data-react` 분기). 상태(`.on`)는 색과 테두리만 맡는다. */
@keyframes react-up {
  0% { transform: translateY(0) scale(1); }
  40% { transform: translateY(-3px) scale(1.18); }
  100% { transform: translateY(0) scale(1); }
}
@keyframes react-down {
  0% { transform: translateY(0) scale(1); }
  40% { transform: translateY(3px) scale(1.18); }
  100% { transform: translateY(0) scale(1); }
}
/* 🔴 클래스가 `rpop` 인 이유 — `pop` 은 **가운데 팝업 오버레이의 전역 클래스**라
   (아래 `.pop` 규칙 · `services/popup.js`) 버튼에 붙는 순간 그것이 이겨 버튼이
   `position: fixed; inset: 0` 짜리 띠가 된다. 2026-08-29(54차)에 실기기에서
   「공감을 누르면 카드 상단에 색 테두리가 깜빡인다」로 관측된 그 사고다.
   🔴 이름을 되돌리지 말 것 — 증상이 0.36초라 눈으로는 못 잡는다. */
.reactbtn.bull.rpop b { animation: react-up .36s cubic-bezier(.34, 1.56, .64, 1); }
.reactbtn.bear.rpop b { animation: react-down .36s cubic-bezier(.34, 1.56, .64, 1); }
@media (prefers-reduced-motion: reduce) {
  .reactbtn.bull.rpop b, .reactbtn.bear.rpop b { animation: none; }
}
/* 처리 중 — 누른 버튼은 스피너, 바 전체는 잠금 (2026-08-13 밤 9차 · 연타 중첩 방지).
   🔴 `pointer-events: none` 이 바 **전체**에 걸린다. 누른 버튼만 잠그면 반대 버튼을
      연타해서 요청이 겹친다 — 그게 숫자가 부풀던 경로다(app.js data-react 분기).
   🔴 스피너는 아이콘 자리를 **대체**한다(b 를 숨기고 ::before 를 띄운다). 옆에 붙이면
      버튼 폭이 늘어나 카드 아랫줄이 눌리는 순간 흔들린다. */
.reactbar.busy { pointer-events: none; }
.reactbar.busy .reactbtn:not(.load) { opacity: .5; }
.reactbtn.load b { display: none; }
.reactbtn.load::before {
  content: ""; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor; animation: spin .7s linear infinite;
}

/* ── 🟢 **X 식 액션 줄** (2026-10-24 · 사용자 「비공감 없애고 X처럼 댓글·재게시·하트·조회수·북마크·공유」 · board/reactions.js `bReactHTML`·`cmtHTML`) ──────────────
   테두리·배경 없는 아이콘 + 숫자. 글 상세는 폭을 고르게 나누고(space-between) 댓글은 왼쪽부터 간격(조회수가 0 이면 숨겨져 자리가 비므로 고르게 나누지 않는다).
   🔴 `.reactbar .quotebtn { margin-left: auto }`(아래 `.reactbar .quotebtn`)는 «인용 단추를 오른쪽 끝으로 미는» 옛 규칙이라 이 줄에서는 푼다.
   🟢 하트는 테두리 → 눌리면 채움(`path` 의 `fill`)·분홍. 목록 카드의 보기 전용 하트(`.ro`)는 옛 알약 모양 그대로다. */
/* 🟢 줄 «위»의 선은 없다 (2026-10-25 · 사용자 「액션줄 위에 선 있는 거 지워」 — 코인스쿱 main 의 같은 규칙에는 아직 border-top 이 남아 있다) · 아래 선만 남는다 */
.reactbar.bx { align-items: center; justify-content: space-between; gap: 2px; margin: 10px 0 2px; padding: 2px 0; border-bottom: 1px solid var(--line); }
.reactbar.bx.bx-c { justify-content: space-between; gap: 2px; margin: 6px 0 0; padding: 0; border: 0; }   /* 🟢 글 액션 줄처럼 폭을 고르게 나눈다(옛 flex-start 는 왼쪽으로 쏠렸다 · 코인스쿱 148f965) */
.reactbar.bx .reactbtn { height: 32px; min-width: 34px; padding: 0 6px; background: none; border: 0; color: var(--text-mut); }
.reactbar.bx .quotebtn, .reactbar.bx .quotebtn + .quotebtn { margin-left: 0; padding: 0 6px; }
.reactbar.bx .reactbtn b svg { width: 18px; height: 18px; }
.reactbar.bx .reactbtn .rcount { font-size: 12px; }
.reactbar.bx .b-views { padding: 0 6px; }
.reactbar.bx .reactbtn.bx-heart.on { color: #f91880; background: none; border: 0; }
.reactbar.bx .reactbtn.bmbtn.on { color: var(--brand-500); background: none; border: 0; }
.bx-heart.on svg path { fill: currentColor; }
.reactbar.bx .reactbtn.bx-rt.on { color: #00ba7c; background: none; border: 0; }   /* 이미 재게시 — X 의 초록 */

/* ── 로그인 카드 + 동의 게이트 (2026-08-13 밤 4차 · 체크비 .btn 계열 이식) ────
   🔴 구글=흰 바탕 테두리(4색 로고) · 애플=검정 · 이메일=브랜드색. 브랜드가 금색이라
      체크비(흰 글자)와 달리 **어두운 글자**다 — 금색 위 흰 글자는 대비가 안 나온다. */
.logincard {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 16px 14px; margin-bottom: 8px;
}
.login-cta { margin: 0 0 12px; font-size: 14.5px; font-weight: 600; text-align: center; }
.btn {
  width: 100%; height: 50px; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 15px; font-weight: 700; font-family: inherit;
  /* 🔴 **기본 색이 있다** (137차·H3 · 위 `button` 문단) — 옛 판은 「색은 보조 클래스가 정한다」는 골격이라 보조 클래스 없이 쓴 자리
     (`btn lq-all`·`btn lq-close`·`btn` 로그아웃·`btn b-sweep`)가 흰 판이었다. 이제 보조 클래스가 없으면 «두 번째 버튼» 모양이다. */
  background: var(--surface-2); color: var(--text); border: 1px solid var(--line);
  transition: transform .1s;
}
/* 🟢 정의가 없던 두 보조 클래스 (137차·H3) — 옛 주석이 「`.ghost` 는 죽은 클래스」라 적어 둔 그것 · `primary` 는 `btn-primary` 와 같은 뜻으로 쓰였다 */
.btn.ghost { background: transparent; border-color: var(--line-2, var(--line)); }
.btn.primary { background: var(--brand-500); color: var(--on-brand); border-color: transparent; }
.btn:active { transform: scale(.975); }
.btn + .btn { margin-top: 8px; }
.btn svg { width: 20px; height: 20px; }
/* 🔴 구글 버튼은 **테마와 무관하게 밝은 배경 + 어두운 글자**다 (2026-08-13 밤 9차 · 요청).
   예전엔 `var(--surface-2)`(다크에서 짙은 회색)라 4색 구글 로고가 어두운 판에 얹혀
   흐릿했고, 무엇보다 구글 브랜드 가이드가 로고를 어두운 배경에 쓸 때 **흰 로고**를
   요구한다 — 우리 로고는 4색 고정(ICON.google)이라 그 조건을 못 맞춘다. 밝은 판이 정답.
   🔴 `var(--text)` 로 되돌리지 말 것 — 다크에서 흰 글자가 흰 배경에 얹혀 사라진다. */
.btn-social { background: #fff; border: 1.5px solid #dadce0; color: #1f1f1f; }
.btn-apple { background: #111; color: #fff; border-color: transparent; }
.btn-primary { background: var(--brand-500); color: var(--on-brand); border-color: transparent; }

.opt.primary { background: var(--brand-500); color: var(--on-brand); font-weight: 700; border-color: var(--brand-500); }
/* 🔴 **보조 줄** (2026-08-26 · 오보 신고 메일 · app.js `newsReportSheet`) — 위의 선택지들과
   «축이 다른» 항목이다. 같은 무게로 나란히 두면 목록의 마지막 항목처럼 읽혀서, 고르는
   사람이 다섯 중 하나로 센다. 테두리를 지우고 한 단 낮춰 「그 밖의 길」로 읽히게 한다. */
.opt.sub {
  border-color: transparent; background: none;
  font-size: 13px; color: var(--text-sub); margin-top: 2px;
}
.opt.primary:disabled { opacity: .5; }

/* ── 🔴 **링크·컨트랙트 줄 앞의 표시** (2026-08-25 · 요청 22·23번 · app.js `clink`) ────────
   🔴 아이콘이 라벨과 **한 덩어리**여야 한다 — `.row` 는 [라벨][값] 두 끝을 미는 flex 라,
      아이콘을 형제로 두면 라벨과 사이가 벌어져 어느 줄에 속한 그림인지 흐려진다.
   🔴 크기는 글자를 따라간다(16px) — 배지처럼 키우면 목록의 무게가 그림 쪽으로 넘어간다. */
.link-lab { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.link-ic { display: inline-grid; place-items: center; flex: none; opacity: .9; }
.link-ic svg { display: block; width: 16px; height: 16px; }

/* ── 목록 행 (설정·알림) ─────────────────────────────────────────────────── */
.hint { font-size: 13px; color: var(--text-sub); padding: 4px 4px 14px; line-height: 1.6; }
.hint.foot { padding-top: 16px; font-size: 12px; opacity: .85; }
.sect {
  font-size: 12px; font-weight: 700; color: var(--text-mut);
  padding: 18px 4px 8px; letter-spacing: .3px;
  display: flex; align-items: center; gap: 6px;
}

/* 🔴 섹션 머리 아이콘은 **글자 크기를 따라간다**(14px). 배지(`.rowic`)를 여기 쓰면
      머리줄이 행보다 무거워져서 목록의 위계가 뒤집힌다 — 머리는 라벨이지 항목이 아니다. */
.sect-ic { display: inline-grid; place-items: center; opacity: .85; }
.sect-ic svg { width: 14px; height: 14px; display: block; }
.row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 14px; margin-bottom: 8px;
  width: 100%; text-align: left; cursor: pointer;
}
.row:active { background: var(--surface-2); }
.row-title { font-size: 14.5px; font-weight: 500; }

.row-desc { font-size: 12px; color: var(--text-sub); margin-top: 3px; line-height: 1.5; }
.row-val { font-size: 13px; color: var(--text-mut); flex: 0 0 auto; }

/* ── 앱 정보 화면 (체크비 Information 과 같은 구성) ─────────────────────────
   🔴 `.grow` 가 없으면 제목이 아이콘과 화살표 사이에서 안 늘어나, 화살표가 글자에
      바짝 붙는다. `min-width: 0` 은 긴 제목이 화살표를 밀어내지 않게 하는 짝이다. */
.grow { flex: 1 1 auto; min-width: 0; }
.rowic {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 36px; height: 36px; border-radius: var(--r-xs, 8px);
  background: var(--brand-weak); color: var(--brand-600, var(--brand-500));
}
.rowic svg { width: 19px; height: 19px; }
/* 🔴 `.rowic`(더보기·앱 정보 행의 아이콘 배지)는 코인스쿱 app.css 에서 옮길 때 빠져 있었다 — 마크업(`navRow`)은 아이콘을 그렸지만 크기·바탕 규칙이 없어 SVG 가 보이지 않았다(2026-10-23 사용자 확인).
   🟡 `--brand-600` 은 이 앱 테마에 없을 수 있어 `--brand-500` 으로 받는다. */

/* 화살표는 **장식**이다. 눈에 띄면 누를 것이 둘로 보인다 — 행 전체가 버튼이다 */
.chev { flex: 0 0 auto; display: grid; place-items: center; color: var(--text-mut); }
.chev svg { width: 18px; height: 18px; }

.info-head { text-align: center; padding: 24px 12px 4px; }
/* 🔴 실제 스토어 아이콘 파일이다(앱 정보 화면이 그 경로를 준다). SVG 글리프로 대체하지 말 것 —
      사용자가 홈 화면에서 보는 그림과 같아야 "이 앱 맞다" 가 한눈에 읽힌다. */
.info-icon { width: 72px; height: 72px; border-radius: 20px; box-shadow: var(--sh-2, 0 6px 16px rgba(0, 0, 0, .35)); }
/* 🟢 **앱 정보 — 들어올 때 한 번** (2026-10-08 · 사용자 「앱 정보만 효과 넣어줘」 · app.js `PAGE_LIST_FX.about`) — 아이콘이 작게 떨어져 «톡» 튀며 살짝 돌고 빛이 한 번 번지고 ·
   이름이 «착» · 버전 알약이 톡 · 설명이 스며들고 · 아래 줄(문의·약관·정책)이 차례로 내려앉는다. */
@keyframes ufx-fade { 0% { opacity: 0; } 100% { opacity: 1; } }
.fx-list .fx-ri.info-head .info-icon { animation: ufx-appic .8s cubic-bezier(.3, 1.45, .5, 1) calc(var(--fx-lt, 0ms) + 60ms) 1 backwards, ufx-appglow 1.1s ease-out calc(var(--fx-lt, 0ms) + 600ms) 1; }
@keyframes ufx-appic {
  0% { opacity: 0; transform: translateY(-26px) scale(.4) rotate(-24deg); }
  55% { opacity: 1; transform: translateY(3px) scale(1.14) rotate(6deg); }
  78% { transform: translateY(-1px) scale(.96) rotate(-2deg); }
  100% { transform: none; }
}
@keyframes ufx-appglow { 0% { box-shadow: 0 0 0 0 rgba(240, 185, 11, .75); } 100% { box-shadow: 0 0 0 18px rgba(240, 185, 11, 0), var(--sh-2, 0 6px 16px rgba(0, 0, 0, .35)); } }
.fx-list .fx-ri.info-head .info-name { animation: ufx-land .42s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-lt, 0ms) + 520ms) 1 backwards; }
.fx-list .fx-ri.info-head .info-ver { animation: ufx-pill .4s cubic-bezier(.3, 1.6, .5, 1) calc(var(--fx-lt, 0ms) + 700ms) 1 backwards; }
.fx-list .fx-ri.info-head .info-lead { animation: ufx-fade .5s ease-out calc(var(--fx-lt, 0ms) + 820ms) 1 backwards; }
@media (prefers-reduced-motion: reduce) { .fx-list .fx-ri.info-head .info-icon, .fx-list .fx-ri.info-head .info-name, .fx-list .fx-ri.info-head .info-ver, .fx-list .fx-ri.info-head .info-lead { animation: none; } }
.info-name { font-size: 19px; font-weight: 800; margin-top: 10px; letter-spacing: -.3px; }
.info-ver {
  display: inline-block; margin-top: 8px; padding: 4px 10px;
  border-radius: var(--r-pill); background: var(--surface-2); color: var(--text-sub);
  font-size: 12px; font-variant-numeric: tabular-nums;
}
/* 🟢 앱 정보 머리의 문단 셋 (2026-10-02 · 사용자 요청 「정리」): 소개 한 줄 → 사업자 정보(항목마다 한 줄) → 고지.
   셋 사이는 간격과 가는 선으로 갈라 한 덩어리로 뭉쳐 보이지 않게 한다. 한글은 낱말 중간에서 안 끊는다(`keep-all` — 「아닙/니다」 방지) */
.info-lead { font-size: 14px; color: var(--text); line-height: 1.5; margin: 14px 6px 0; word-break: keep-all; }
.info-co { margin: 18px 12px 0; padding-top: 16px; border-top: 1px solid var(--line, var(--surface-2)); word-break: keep-all; }
.info-co-name { font-size: 13px; font-weight: 700; color: var(--text); }
.info-co-row { font-size: 12px; color: var(--text-sub); line-height: 1.5; margin-top: 5px; }
.info-note { font-size: 11.5px; color: var(--text-mut); line-height: 1.55; margin: 16px 18px 0; word-break: keep-all; text-wrap: balance; }

/* 토글 스위치 — 체크비 `.sw` 를 그대로 옮겼다 (색만 이 앱 팔레트).
   🔴 네이티브 `<input type="checkbox">` 로 되돌리지 말 것. 실기기에서 안 눌렸다
      (2026-08-10 · app.js toggleRow 주석). 상태는 부모 버튼의 aria-pressed 가 원본이고
      이 클래스는 그걸 눈에 보이게 한 것뿐이다 — 둘은 setTopic() 에서 함께 바뀐다. */
.sw {
  flex: 0 0 auto; position: relative; width: 46px; height: 28px;
  border-radius: var(--r-pill); background: var(--line-2); transition: background .18s;
}
.sw.on { background: var(--brand-500); }
.sw-dot {
  position: absolute; top: 3px; left: 3px; width: 22px; height: 22px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
  transition: transform .18s;
}
.sw.on .sw-dot { transform: translateX(18px); }

.seg {
  /* 🔴 4칸 고정 그리드였다 (2026-08-22 심야2 · 요청 3번) — 멘션 범위(버튼 3개)가
     4칸에 얹혀 마지막 칸이 비고 전체가 왼쪽으로 쏠렸다. flex 균등분배면 빈도(4)· */
  display: flex; gap: 4px;
  margin-top: 10px; padding: 3px;
  background: var(--line-2); border-radius: var(--r-sm);
}
.seg-btn {
  flex: 1 1 0; min-width: 0;
  padding: 8px 0; border: 0; border-radius: calc(var(--r-sm) - 3px);
  background: transparent; color: var(--text-mut);
  font-size: 13px; font-weight: 500; font-family: inherit;
}
.seg-btn.on { background: var(--surface); color: var(--brand-500); font-weight: 700; }

/* ── 🟢 **알림 설정 화면** (2026-10-26 · 코인스쿱 app.css «알림 설정 두 화면»·«카테고리 빈도 세그먼트»를 옮겼다 · app.js `BasicNotifScreen`·`mentionScopeRow`·`infoFreqRow`) ──
   🟡 정정: 10-22 이식 때 마크업(`.ntf`·`.ntf-card`·`.nsx-h`·`.nsx-ic`·`.freqrow`)만 오고 이 규칙들이 빠져 있었다 — 카드 머리 아이콘의 svg 가 크기 없이 화면 폭만큼 커졌다(2026-10-26 브라우저 확인).
   섹션마다 카드 하나(머리 `.ntf-h` + 줄은 카드 안에서 구분선) · 단계 줄은 `.freqrow`(머리 + `.seg` + 부연 `.freqdesc`) · 색은 브랜드 한 색. */
.freqrow { display: block; }
.freqhead { display: flex; align-items: center; justify-content: space-between; }
/* 부연 한 줄 — 내용이 바뀌어도 높이가 안 튀게 min-height 를 잡는다 */
.freqdesc { min-height: 16px; margin-top: 7px; font-size: 12px; color: var(--text-mut); }
.nsx-h { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.nsx-h b { font-size: 14px; font-weight: 800; color: var(--text); }
.nsx-ic { flex: none; width: 28px; height: 28px; border-radius: 9px; display: grid; place-items: center; color: var(--brand-500); background: var(--brand-weak); }
.nsx-ic svg { width: 16px; height: 16px; }
.ntf { display: grid; gap: 12px; padding: 4px 0 8px; }
.ntf > .row, .ntf-card { margin: 0; border-radius: var(--r-md, 14px); }
.ntf > .row, .ntf > .ntf-card { animation: ufx-card .5s cubic-bezier(.2, 1.3, .4, 1) 1 backwards; }
.ntf .freqhead { justify-content: flex-start; gap: 10px; }
.ntf .freqhead .grow { min-width: 0; }
.ntf .freqrow .row-title { font-weight: 700; }
.ntf-card { background: var(--surface); border: 1px solid var(--line); padding: 14px 14px 4px; }
.ntf-h { margin-bottom: 4px; }
.ntf-card > .row { background: none; border: 0; border-radius: 0; margin: 0; padding: 13px 0; border-top: 1px solid var(--line); }
.ntf-card > .ntf-h + .row { border-top: 0; }
.ntf-card > .row:first-child { border-top: 0; padding-top: 4px; }
.ntf-card > .row:active { background: none; opacity: .7; }
.ntf-ic { flex: none; display: inline-flex; width: 20px; height: 20px; color: var(--brand-500); align-self: flex-start; margin-top: 1px; }
.ntf-ic svg { width: 100%; height: 100%; }
.ntf-card > .row.freqrow .freqhead { align-items: flex-start; }
@media (prefers-reduced-motion: reduce) { .ntf > .row, .ntf > .ntf-card { animation: none !important; } }

/* ── 검색 ─────────────────────────────────────────────────────────────────── */
/* 🟢 검색칸은 **앱바 안**이다 (2026-09-26 · 사용자 요청 · app.js `searchBar` — [뒤로][검색칸][검색] · 제목 「통합 검색」은 걷었다).
   `.searchbar` 의 세로 패딩 12px 은 옛 자리(본문 첫 칸)의 것이라 앱바 안(`.bar-search`)에서는 0 이고, 칸·버튼 높이는 바(`--bar-h` 44px)에 맞춘 36px 이다. */
.searchbar { display: flex; gap: 8px; padding: 12px 0; }
.appbar .bar-search { flex: 1; min-width: 0; padding: 0; margin-right: 4px; }
.appbar .bar-search input { padding: 0 12px; height: 36px; font-size: 15px; }
.appbar .bar-search > button { height: 36px; padding: 0 14px; }   /* 🟢 직계만 — `.in-xwrap` 안의 ✕(`.in-x`)에 36px·좌우 14px 이 먹어 알약으로 늘어났다(2026-10-05 실기기) */
html.cv-sheet:not(.wide) #app {
  --safe-top: 0px;
  height: calc(100% - var(--cv-gap)); margin-top: var(--cv-gap);
}
html.cv-sheet.cv-look:not(.wide), html.cv-sheet.cv-look:not(.wide) body { background: color-mix(in srgb, #000 45%, var(--bg)); }
html.cv-sheet.cv-look:not(.wide) #app { border-radius: var(--r-lg) var(--r-lg) 0 0; box-shadow: 0 -6px 24px rgba(0, 0, 0, .28); }
.searchbar input {
  flex: 1; min-width: 0; padding: 12px 14px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  font-size: 15px; outline: none;
}
.searchbar input:focus { border-color: var(--brand-500); }
.in-xwrap { position: relative; flex: 1; min-width: 0; display: flex; }
.in-xwrap > input { flex: 1; min-width: 0; width: 100%; padding-right: 36px; }
.in-xwrap > .in-x {
  position: absolute; right: 7px; top: 50%; transform: translateY(-50%); width: 22px; height: 22px; padding: 0; z-index: 2; cursor: pointer;
  border: 0; border-radius: 50%; background: color-mix(in srgb, var(--text-mut) 22%, transparent); color: var(--text-sub);
  font-size: 10px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; -webkit-tap-highlight-color: transparent;
}
.in-xwrap > input:placeholder-shown + .in-x { display: none; }
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; display: none; }
.searchbar button {
  padding: 0 18px; border: 0; border-radius: var(--r-sm);
  background: var(--brand-500); color: #fff; font-weight: 700; font-size: 14px; cursor: pointer;
}
/* ── 🔴 **검색 머리 — 세그먼트가 앱바에 붙어 있다** (2026-09-11 · 113차 후속 4 · 사용자 요청 「헤더는 앱바와 고정 · 본문부터 스크롤」) ──
   `#scroll` 이 유일한 세로 스크롤 상자이고 `top: 0` 이 곧 앱바 바로 아래다(앱바가 겹친 화면도 — `.date-sect` 머리 주석).
   🟢 2026-09-26 부터 검색칸은 앱바 안이라(위 `.bar-search`) 이 상자에는 세그먼트 하나다 — 옛 「둘을 한 상자로 묶는다(따로 sticky 면 둘째가 첫째 밑으로
      파고든다)」는 그 시절의 이유이고, 상자 자체는 그대로 둔다(세그먼트의 sticky·불투명 배경·좌우 틈 막기가 여기 산다). 위 패딩 8px 이 화면 첫 칸의 숨이다.
   🔴 좌우 음수 마진 + 같은 양의 패딩 · 불투명 배경 — 안 그러면 `#scroll` 의 좌우 12px 틈과 머리 뒤로 결과 카드가 비친다(`.chroom-sticky` 와 같다).
   🔴 세그먼트 아래 간격은 상자 «안»의 패딩이다 — 자식의 아래 마진은 부모 밖으로 새어(마진 겹침) 붙은 뒤 그 틈으로 카드가 보인다.
      상자 아래 마진 4px + 세그먼트 패딩 8px = 옛 `.srch-seg` 의 아래 마진 12px 그대로다. */
.srch-head {
  position: sticky; top: 0; z-index: 5;
  margin: 0 -12px 4px; padding: 0 12px;
  background: var(--bg);
}
.srch-head .srch-seg { margin: 0; }
/* 🟢 통합 검색의 「사람」 줄 (2026-10-16 · board/search.js `peopleStripHTML`) — 지금 이름이 걸린 사람을 결과 맨 위 가로 한 줄로 · 넘치면 옆으로 민다 */
.srch-people { display: flex; gap: 8px; overflow-x: auto; padding: 2px 0 10px; scrollbar-width: none; }
.srch-people::-webkit-scrollbar { display: none; }
.srch-person {
  flex: none; display: inline-flex; align-items: center; gap: 6px; max-width: 70%;
  padding: 6px 12px 6px 6px; border-radius: 999px; background: var(--surface); border: 1px solid var(--line);
  color: var(--text); font-size: 13.5px; font-weight: 600;
}
.srch-person .srch-person-av { width: 26px; height: 26px; flex: none; }
.srch-person-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.srch-head #search-seg:not(:empty) { padding: 8px 0; }

/* ── 하단 탭바 — 떠 있는 알약 + 가운데 FAB (2026-08-14 · 요청 4번) ────────────
   예전에는 화면 폭을 꽉 채운 막대에 [아이콘 + 글자] 2칸이었다. 지금은 인스타그램식
   알약이다: 모서리에서 떨어진 둥근 상자 안에 탭 아이콘들(🟡 옛 줄 「아이콘 4개, 가운데 하나는 채워진 FAB」은 낡았다 — FAB 은
   2026-08-26 에 떠 있는 `.bfab` 으로 나갔고 탭 수의 정본은 app.js `TAB_DEFS` 다 · 2026-09-14 부터는 바로가기가 최대 셋 더 선다).

   🔴 **`.tabbar` 는 여전히 `--safe-bot` 을 먹는 유일한 하단 요소다.** 알약이 떠 보이는
      것은 `.tabbar` 의 좌우 패딩과 `.tabpill` 의 모서리일 뿐, 구조는 그대로 `#app`
      (flex column) 의 **형제**다 — `position: fixed` 로 띄우지 말 것. 띄우는 순간
      `#scroll` 이 탭바 밑으로 흘러서 마지막 카드가 알약에 가리고, 그걸 메우려고
      `#scroll` 에 아래 패딩을 더하면 self-check 가 잡는 그 중복이 된다.
   🔴 `.tabbar` 배경은 **투명**이다. 알약이 배경을 갖는다. 여기에 `--bar-bg` 를
      되돌리면 알약이 같은 색 판 위에 떠서 테두리만 보이는 이상한 모양이 된다.
   🔴 총 높이는 `--tab-h + --safe-bot` 그대로다 — `common.css` 의 토스트 위치가 그 값을 쓴다. */
/* 🔴 **`#app`(flex column) 의 형제가 아니라 그 위에 뜬다** (2026-08-14 · 요청 —
      "뒤쪽 화면을 탭 뒤로 지나가게"). 그래서 두 가지가 예전과 반대로 바뀌었다:
        ① `.tabbar` 가 `position: absolute` 다 → 스크롤 영역의 높이를 안 깎는다
        ② 그 대신 **`#scroll` 이 아래 여백을 직접 갖는다** — 안 그러면 마지막 카드가
           버튼 뒤에 영원히 깔려서 읽을 수가 없다. 이 둘은 **세트**이고 self-check 가
           둘 다 있는지 본다. 하나만 되돌리지 말 것.
   🔴 `pointer-events: none` — 바 자체는 **투명한 판**이라 뒤 목록을 눌러야 한다.
      버튼이 있는 `.tabpill` 만 `auto` 로 되돌린다. 이 두 줄이 없으면 탭바가 차지한
      가로 전체 폭에서 스크롤·탭이 죽는다(버튼은 가운데 둘뿐인데도).
   🔴 `.tabbar` 가 `--safe-bot` 을 먹는 유일한 하단 요소라는 규칙은 그대로다. */
/* ── 🔴 **하단 여백은 기기와 무관하게 같아야 한다** (2026-08-20 · 안드로이드 실기기) ──────
   `--safe-bot`(= `env(safe-area-inset-bottom)`)은 아이폰의 홈 인디케이터에서만 값이
   나온다. 안드로이드 3버튼 내비게이션 기기(S9+ 등)에서는 **0** 이라, 떠 있어야 할
   알약이 화면 바닥에 딱 붙었다 — iOS 에서는 34px 위에 떠 있는 그 알약이다.
   🔴 그래서 **바닥 여백에 최소값을 준다**: 인셋이 있으면 그것을, 없으면 `--tab-min-bot`.
      「인셋이 있고 없고에 따라 여백이 달라지지 않는다」가 이 변수의 전부다.
   🔴 `--safe-bot` 자체는 안 건드린다 — 그건 **기기가 알려 준 사실**이고, 여기서 덮으면
      노치 계산이 필요한 다른 자리(오버레이·시트)까지 같이 틀어진다. */
:root {
  --tab-min-bot: 12px;
  --tab-bot: max(var(--safe-bot), var(--tab-min-bot));
}
.tabbar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 6;
  display: flex; align-items: center; justify-content: center;
  height: calc(var(--tab-h) + var(--tab-bot));
  /* 🟢 좌우 14 → 8px (2026-09-14 · 125차 요청 11번) — 바로가기 셋이 더 서는 알약이 320px 에서도 한 줄이어야 한다(`.tabpill` 식) */
  padding: 0 8px var(--tab-bot);
  background: transparent; pointer-events: none;
  /* 🔴 `visibility 0s` 가 목록에 있어야 한다 — 아래 `.no-tabs` 쪽 주석 참조 */
  transition: transform .22s cubic-bezier(.2, .7, .3, 1), opacity .18s, visibility 0s;
}
/* 🔴 서브 화면에서는 **내려가서 사라진다** (2026-08-14 · 요청 1번 · app.js render 주석).
      `display: none` 이 아니라 transform 이라 들어갈 때·나올 때 미끄러진다.
   🔴 `visibility` 까지 꺼야 사라진 뒤에 손가락이 안 걸린다 — `.tabpill` 이
      `pointer-events: auto` 라, 화면 밖으로 밀려나도 그 자리는 여전히 반응한다.
   🔴 그런데 `visibility` 는 **애니메이션이 끝난 뒤에** 꺼야 한다 (2026-08-14 리뷰 지적).
      전환 목록에 안 넣으면 즉시 `hidden` 이 되어, 나가는 애니메이션이 **이미 안 보이는
      요소 위에서** 도는다 — 들어올 때만 미끄러지고 나갈 때는 툭 사라진다.
      그래서 여기서는 `.22s`(transform 과 같은 시간) 만큼 **지연**시키고,
      돌아올 때는 위 규칙의 `0s` 가 즉시 켠다. 두 줄이 세트다. */
html.no-tabs .tabbar {
  transform: translateY(calc(100% + 8px)); opacity: 0; visibility: hidden;
  transition: transform .22s cubic-bezier(.2, .7, .3, 1), opacity .18s, visibility 0s .22s;
}
/* 탭바가 없으면 목록 아래 여백도 그만큼 줄인다 (안 줄이면 끝에 100px 빈칸) */
html.no-tabs #scroll { padding-bottom: calc(var(--tab-bot) + 16px); }
/* 🔴 토스트도 같이 내려온다. 위치는 `common.css` 가 탭바 높이를 더해 잡는데, 탭바가 없는 화면에서는 그만큼 붕 떠 보인다.
   (🟡 옛 괄호 「5개 저장소 동일 사본이라 거기선 못 고친다」는 2026-08-20 단절 뒤로 거짓이다 — 기본값은 탭바가 있는 화면의 것이라 거기 두었다.) */
html.no-tabs #toast { bottom: calc(var(--tab-bot) + 18px); }
   /* 안내 줄이 두 줄(안내 + 버튼)이 됐다 (2026-09-15) */
/* ══════════════════════════════════════════════════════════════════════════
   🟢 **스크롤하면 숨는 바 — 손가락을 따라 움직이다, 놓으면 절반 기준으로 붙는다** (2026-10-08 · 사용자 요청 「X처럼」 →
   ══════════════════════════════════════════════════════════════════════════
 * 움직이는 값은 app.js 한 자리가 쓴다(`barsSet` — 앱바에 `--bars-bar-y` · 탭바에 `--bars-tab-y` · `<html>` 의 `bars-moved`·`bars-hidden`).
 * ① **겹친 앱바** — 좁은 폭의 뿌리 화면(`html:not(.wide):not(.no-tabs)`)에서만. 앱바는 `#app` 위에 `absolute` 로 뜨고 `#scroll` 은 화면 맨 위부터
 *    시작하되 위 패딩(`--bars-pad` = 앱바 높이 + 칩 바의 구분선 아래 여백)만큼 내용이 내려앉는다 — 가만히 있을 때의 모습은 옛 흐름과 같다.
 *    움직임은 `transform` 하나라 합성기만 일한다(레이아웃 0 · 본문은 손가락과 정확히 같이 간다).
 *    🔴 앱바 바탕은 **불투명**이다(`--bar-bg` 를 `--bg` 위에 깐 것) — 옛 흐름에서는 앱바 밑에 늘 `#app` 바탕뿐이라 보이는 색이 정확히 이것이었고,
 *       겹친 뒤로는 그 밑을 카드가 지나간다. 블러를 걷은 것은 그래서다(비칠 것이 없다 · 합성 레이어는 `will-change` 로 따로 세운다).
 *    🔴 칩 바의 구분선 아래 여백 띠(`--chip-gap`)는 바가 «달고» 다닌다(`::after`) — 옛 판은 `#scroll` 의 margin 이라 그 띠로 아무것도 안 지나갔는데,
 *       겹친 뒤로는 띠가 바와 같이 움직이지 않으면 날짜 머리 위 틈으로 카드가 비친다(`.date-sect` 문단의 그 사고).
 *    🔴 sticky 머리(`.date-sect`·`#feed-new`…)는 규칙을 안 고친다 — sticky 는 스크롤 상자의 **위 패딩을 존중**해 `top: 0` 이 곧 패딩 끝(= 바 바로 아래)이다
 *       (125차 실측의 그 성질 · 아래 ③ 이 그 위에서 움직인다). 노치 띠(`--safe-top`)는 `#app::before` 가 늘 덮는다 — 바가 올라가도 상태 표시줄 글자가
 *       본문 위에 얹히지 않는다(바와 같은 색이라 가만히 있을 때는 안 보인다).
 * ② **움직이는 동안**(`html.bars-moved` — 바가 조금이라도 비켜 있을 때 · 다 숨은 채 멈춘 때 포함) 앱바·탭바는 JS 가 실은 거리만큼 `translateY`.
 *    🔴 탭바의 전환(`html.no-tabs` 용 0.22초)은 끈다 — 켜 두면 매 프레임의 값이 0.22초씩 늦게 따라온다. 붙는 움직임(놓은 뒤 0 또는 끝까지)은 JS 가 그린다.
 * ③ **sticky 머리** — 움직이는 동안만 `top` 을 «노치 띠 끝»으로 낮춘다(아래 목록 · 바뀌는 순간 한 번의 레이아웃). 그 위에서 JS 가 보이는 바 바로 아래로
 *    `translateY` 로 밀어 내린다(`barsPaintSticky` — 매 프레임 쓰는 것은 transform 뿐이다). 🔴 「바 높이에 붙여 두고 위로 당기기」가 아니라
 *    「낮게 붙여 두고 아래로 밀기」인 이유: 그래야 붙은 머리든 막 붙으려는 머리든 «지금 자리»만 보고 밀 거리가 정해진다(그 함수 머리 문단).
 *    🔴 이 목록은 app.js `BARS_STICKY_SEL` 과 **세트**다(self-check) — 뿌리 화면에 올 수 있는 위쪽 sticky 를 새로 만들면 둘 다에 넣는다.
 *    안 넣으면 그 머리는 바가 비키는 동안 바 밑에 붙은 채로 남는다(틈은 안 생긴다 — 바가 덮을 뿐).
 * 🔴 **조건을 선택자에도 건다**(`:not(.wide)` · `:not(.no-tabs)` · `:not(.tab-editing)`) — JS 가 어느 길에서 값을 못 걷어도 넓은 폭의
 *    사이드바·상세 화면의 「뒤로」·탭 편집이 숨은 채로 갇히지 않는다. JS 쪽 같은 목록은 `barsMayHide`.
 * 🔴 **`#scroll` 의 아래 여백은 안 바꾼다** — 탭바가 숨은 동안 목록 끝에는 그 높이만큼 빈 바닥이 남는다. 바꾸면 움직일 때마다 목록 끝이 튄다.
 * 🟢 떠 있는 버튼(`.bfab` · 🗄 상세의 인용 FAB `.qfab` 은 2026-10-19 에 걷었다)도 탭바가 내려간 만큼 따라 내려간다 (2026-10-08 · 사용자 요청 — 옛 판은 «제자리»였다: 같이 내리면 누르려던 버튼이 움직인다는 판단) ·
 *    🔴 탭 높이(`--tab-h`)까지만 — 홈 인디케이터 띠(`--tab-bot`)는 안 덮는다(그 자리가 상세 화면의 FAB 자리 `html.no-tabs .bfab` 와 같다) ·
 *    값은 app.js `barsPaintFabs` 하나가 버튼마다 싣는다(새로 그려진 버튼까지 — 그 함수 머리 문단). */
@property --bars-bar-y { syntax: '<length>'; inherits: false; initial-value: 0px; }   /* 🔴 상속 안 함 — 매 프레임 쓰는 값이 바 안의 칩까지 스타일을 다시 계산시키지 않게 */
@property --bars-tab-y { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --bars-fab-y { syntax: '<length>'; inherits: false; initial-value: 0px; }
html.bars-moved:not(.wide):not(.no-tabs):not(.tab-editing) .bfab { translate: 0 min(var(--bars-fab-y), var(--tab-h)); }
html:not(.wide):not(.no-tabs) #appbar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 7;   /* 🔴 sticky 머리(z 2~5)·당김 고리(#ptr z5)보다 위 · 노치 덮개(z8)보다 아래 */
  background: var(--bg); backdrop-filter: none;   /* 🟢 앱바 바탕 = 화면 바탕(2026-10-23 · 사용자 «앱바도 배경색과 동일하게» · 코인스쿱 다크 테마가 그렇다) — 불투명 그대로(밑으로 카드가 지나간다) */
  will-change: transform;
}
html:not(.wide):not(.no-tabs) #appbar.has-chips::after {
  content: ''; position: absolute; left: 0; right: 0; top: 100%; height: var(--chip-gap);
  background: var(--bg); pointer-events: none;
}
/* `--bars-bar-h` 는 app.js 가 앱바 높이를 재서 `#app` 에 싣는다(`#scroll`·`#ptr` 이 같이 읽는다)(ResizeObserver — 빈 바 · 글꼴 배율로 자란 바) · 그 전의 첫 그림은 기본 높이로 */
html:not(.wide):not(.no-tabs) #scroll {
  --bars-pad: calc(var(--bars-bar-h, calc(var(--bar-h) + var(--safe-top))) + var(--scroll-gap, 0px));
  padding-top: var(--bars-pad);
}
html:not(.wide):not(.no-tabs) #appbar.has-chips ~ #scroll { margin-top: 0; }
/* 🟢 브라우저가 스스로 하는 스크롤(`scrollIntoView` 의 start · 포커스 · 키보드 탐색)은 padding 이 아니라 scroll-padding 을 본다 — 겹친 앱바 밑으로 안 가게(2026-10-08 최종 점검) */
html:not(.wide):not(.no-tabs) #scroll { scroll-padding-top: var(--bars-pad); }
/* 🟢 당겨서 새로고침 고리도 잰 높이를 읽는다(`#app` 의 `--bars-bar-h` — 글꼴 배율로 자란 바 · 최종 점검) */
html:not(.wide):not(.no-tabs) #ptr { top: var(--bars-bar-h, calc(var(--bar-h) + var(--safe-top))); }
html:not(.wide):not(.no-tabs) #appbar.has-chips ~ #ptr { top: calc(var(--bars-bar-h, calc(var(--bar-h) + var(--safe-top))) + var(--chip-gap)); }   /* 여백은 위 `--bars-pad` 에 들었다 — 두 번 세지 않는다 */
html:not(.wide):not(.no-tabs) #app::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: var(--safe-top); z-index: 8;
  background: var(--bg); pointer-events: none;   /* 노치 띠도 앱바와 같은 색 */
}
html.bars-moved:not(.wide):not(.no-tabs):not(.tab-editing) #appbar { transform: translateY(calc(-1 * var(--bars-bar-y))); }
html.bars-moved:not(.wide):not(.no-tabs):not(.tab-editing) .tabbar { transform: translateY(var(--bars-tab-y)); transition: none; }
html.bars-moved:not(.wide):not(.no-tabs):not(.tab-editing) .date-sect,
html.bars-moved:not(.wide):not(.no-tabs):not(.tab-editing) .chroom-sticky,
html.bars-moved:not(.wide):not(.no-tabs):not(.tab-editing) .srch-head { top: calc(var(--safe-top) - var(--bars-pad)); }
/* 탭바가 다 숨어 멈춘 동안 토스트는 탭바 없는 화면의 자리로 (위 `html.no-tabs #toast` 와 같은 값) */
html.bars-hidden:not(.wide):not(.no-tabs):not(.tab-editing) #toast { bottom: calc(var(--tab-bot) + 18px); }
/* 🔴 **떠 있는 버튼도 같이 내려온다** (2026-08-19 · 요청 16번 — 게시글 상세의 댓글 FAB).
   `.bfab` 의 `bottom` 은 탭바 높이(`--tab-h`)를 더해 잡는데, 상세는 `no-tabs` 라
   그 자리가 비어 있다 — 안 내리면 버튼이 화면 중간쯤에 붕 떠 있다. */
html.no-tabs .bfab { bottom: calc(var(--safe-bot, 0px) + 26px); }
/* ══════════════════════════════════════════════════════════════════════════
   🔴 **화면 전환이 도는 동안 FAB 을 감춘다** (2026-08-30 · 56차 요청 7번 · 실기기)
   ══════════════════════════════════════════════════════════════════════════
 * 증상: 게시글 상세에서 뒤로 나오면 글쓰기 FAB 이 **화면 오른쪽 «위»에 잠깐 뜬 뒤**
 * 제자리로 내려왔다.
 * 🔴 원인은 CSS 의 고전적인 규칙 하나다: **`transform` 이 걸린 요소는 `position: fixed`
 *    자손의 «기준 상자»가 된다.** `.bfab` 은 목록·상세와 함께 `#scroll` 안에 그려지는데,
 *    화면 전환 애니메이션(`nav-fwd`·`nav-back` · common.css)이 그 `#scroll` 에
 *    `translateX` 를 건다. 그 0.24초 동안 FAB 의 기준이 «뷰포트»에서 «스크롤 내용»으로
 *    바뀌므로, `bottom` 이 가리키는 자리가 통째로 달라진다 — 그게 「위에 떴다 내려온다」다.
 * 🔴 값을 조절해서는 못 고친다(기준 상자 자체가 바뀐 것이라 `bottom` 이 무엇이든 틀린다).
 * 🟢 그래서 **그동안 안 그린다.** 전환은 «내용»이 움직이는 연출이고 FAB 은 내용이 아니라
 *    화면에 얹힌 것이라, 같이 미끄러질 이유가 애초에 없다.
 * 🔴 `visibility` 다(`display` 가 아니다) — `display` 를 만지면 레이아웃이 다시 계산되고,
 *    그 리플로우가 전환 애니메이션의 첫 프레임을 끊는다. */
#scroll.nav-fwd .bfab,
#scroll.nav-back .bfab { visibility: hidden; }
/* 버튼 둘을 담는 상자 — **폭을 안 채운다.** 내용만큼만 넓고 가운데에 뜬다. */
/* 🔴 배경은 **반투명 유리**다 (2026-08-14 · 요청 2번 — iOS 글래스 느낌).
      `--bar-bg`(앱바용 · 불투명에 가까운 .88)를 쓰면 뒤가 안 비친다. 여기만 더 옅은
      값을 따로 둔다 — 앱바는 글자를 얹는 자리라 대비가 필요하지만, 탭바는 아이콘
      둘뿐이라 옅어도 읽힌다.
   🔴 `backdrop-filter` 는 **saturate 를 같이** 건다. blur 만 걸면 뒤에 깔린 색이
      회색으로 죽어서 "유리" 가 아니라 "안개" 가 된다 — iOS 재질이 채도를 올리는 이유다.
   🔴 안쪽 흰 테두리 한 줄(`inset` 그림자)이 유리의 두께를 만든다. 이게 없으면
      그냥 반투명 판이다. */
.tabpill {
  pointer-events: auto;   /* 🔴 `.tabbar` 의 none 을 여기서만 되돌린다 (self-check 가 본다) */
  display: flex; align-items: center; gap: 3px;
  padding: 4px; border-radius: var(--r-pill);
  max-width: 100%;
  /* ── 🟢 **버튼 폭은 «선 버튼 수»로 화면을 나눈다** (2026-09-14 · 125차 요청 11번 · 사용자 「기본 사이즈를 약간 줄여 바로가기 최대 3개」) ──
   * `--tab-n` 은 app.js `renderTabbar` 가 알약에 싣는 버튼 수(편집 중 ＋ 포함)다. 식 = (화면 폭 − 바 좌우 8·2 − 알약 안쪽 4·2 − 테두리 1·2
   * − 간격 3 × (n − 1)) ÷ n, 상한 44px. 그래서 기본 다섯이면 어느 폭이든 44px 이고, 다섯 + 셋이면 375px 에서 41px · 320px 에서 34px 다.
   * 🔴 식의 상수(8·4·1·3)는 이 규칙·`.tabbar` 의 padding 과 세트다 — 하나만 바꾸면 끝 칸이 알약 밖으로 밀린다. */
  --tab-btn-w: min(44px, calc((100vw - 26px - (var(--tab-n, 5) - 1) * 3px) / var(--tab-n, 5)));
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-3), inset 0 1px 0 var(--glass-top);
  /* 🔴 이 요소에는 `backdrop-filter` 가 걸려 있어 **transform 이 바뀌면 블러 영역이 다시 래스터화된다** — 스크롤에 «연속으로» 묶인
        transform 을 여기 다시 걸지 말 것(2026-08-14 의 삐걱거림 · 옛 `--tab-k` 판의 교훈 — 크기가 바뀌면 블러 판을 새 크기로 다시 굽는다).
      🟢 숨는 바(2026-10-08 · 「스크롤하면 숨는 바」 문단)는 바깥 `.tabbar` 를 손가락 따라 «옮기기만» 한다 — 크기가 그대로인 이동이라 이미 합성된
      블러 판의 자리만 바뀐다(뒤의 목록이 굴러갈 때 매 프레임 다시 거르는 일은 탭바가 서 있어도 원래 한다). 🔴 그래서 여기 scale 을 걸지 않는 규칙은 그대로다. */
}
/* 🔴 레이어 승격을 상시로 예고하지 않는다 — `will-change` 를 걸어 두면 블러 레이어가 계속 메모리에 떠 있어 발열로 돌아온다. */
.tabpill { will-change: auto; }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tabpill { background: var(--bar-bg); }
}
/* 🔴 **1:1 정사각**이다 (요청). `flex: 1 1 0` 으로 되돌리면 다시 가로를 채운다.
   🟢 **48 → 44px** (2026-09-14 · 125차 요청 11번 · 사용자 「기본 사이즈를 약간 줄여 바로가기 최대 3개」) — 옛 줄 「48 은 44 를 넘긴 값 ·
      더 줄이지 말 것」을 사용자가 뒤집었다. 대신 두 겹을 같이 옮겼다: ①폭은 선 버튼 수로 나눠 44 이하(`.tabpill` 의 `--tab-btn-w` ·
      다섯 + 셋이면 정사각이 아니라 조금 좁은 알약이 된다) ②옛 스크롤 축소의 하한을 0.76 → 0.8 로 올렸다(🗄 그 축소는 2026-10-08 에 걷었다).
   🔴 모서리는 **알약(`--r-pill`)** 이다 (2026-08-14 · 요청 1번). 16px 각진 사각이었는데,
      바깥 상자가 완전한 알약이라 안쪽만 각지면 둘이 따로 놀았다. 정사각 + 알약 =
      원이고, 바깥 알약과 동심이다. */
.tabbtn {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  width: var(--tab-btn-w, 44px); height: 44px; padding: 0; cursor: pointer;
  background: none; border: 0; color: var(--text-mut);
  border-radius: var(--r-pill); transition: background .16s, color .16s, transform .1s;
}
.tabbtn svg { width: 22px; height: 22px; display: block; flex: 0 0 auto; }
.tabbtn:active { transform: scale(.92); }
/* 선택된 탭 — **둥근 사각 판**이 아이콘 뒤에 깔린다.
   🔴 색만으로 말하지 않는다. 다크에서 브랜드 노랑과 회색은 밝기가 비슷해서, 판이
      없으면 어느 탭에 있는지가 흘긋 봐서는 안 읽힌다(`aria-current` 는 그 짝이다).
   🔴 판 색은 `--surface-2` 가 **아니다** (2026-08-14 리뷰 지적). 알약 바탕(`--bar-bg`)
      위에 올리면 다크에서 대비가 1.25:1 이라 사실상 안 보였고, 그러면 판을 둔 이유가
      통째로 사라진다. 브랜드색을 옅게 섞어 두 테마 모두에서 판이 먼저 읽히게 한다. */
.tabbtn.active { color: var(--brand-500); background: color-mix(in srgb, var(--brand-500) 18%, transparent); }

/* ══════════════════════════════════════════════════════════════════════════
   🔴 **탭을 누르면 아이콘이 «통통» 튄다** (2026-08-24 · 요청 1번 → 2026-09-30 · 사용자 요청 2번 「가볍게 통통 튀는 느낌으로」)
   ══════════════════════════════════════════════════════════════════════════
 * 한 번 움츠렸다(찌그러짐) → 위로 튀어 올랐다 → 내려앉으며 한 번 더 작게 튄다(`tab-bounce`).
 * 🔴 **JS 가 붙는 곳은 «이미 켜진 탭을 또 누를 때» 하나뿐이다**(app.js `rebounceTab`). 새로 켜지는 탭은
 *    `.active` 가 새로 붙는 순간 CSS 애니메이션이 저절로 처음부터 돈다(`renderTabbar`·`patchTree` 가 클래스만 옮긴다).
 * 🔴 **svg 하나만 움직인다.** SVG «자식»을 `transform` 으로 움직이려면 `transform-box: fill-box` 가 필요한데 구형 웹뷰에서
 *    조용히 무시돼 아이콘이 엉뚱한 자리로 날아간 채 멈춘다 — svg 루트는 평범한 CSS 상자라 그 함정이 없다.
 * 🔴 기준점은 **아래 가운데**(`50% 100%`)다 — 바닥에 닿아 찌그러지는 모양이 «통통»의 전부다(가운데 기준이면 그냥 커졌다 작아진다).
 * 🔴 튀는 높이는 7px 이하다 — 알약(44px) 안에서 아이콘(22px)이 판 밖으로 안 나간다.
 * 🔴 `prefers-reduced-motion` 에서는 **안 돈다**(이 파일의 다른 애니메이션과 같은 규칙). */
@keyframes tab-bounce {
  0% { transform: translateY(0) scale(1, 1); }
  18% { transform: translateY(0) scale(1.12, .86); }
  42% { transform: translateY(-7px) scale(.94, 1.08); }
  62% { transform: translateY(0) scale(1.07, .93); }
  78% { transform: translateY(-2.5px) scale(.98, 1.02); }
  100% { transform: translateY(0) scale(1, 1); }
}
.tabbtn.active svg { transform-origin: 50% 100%; animation: tab-bounce .58s cubic-bezier(.3, .7, .4, 1); }
@media (prefers-reduced-motion: reduce) {
  .tabbtn.active svg { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   🔴 **탭 편집 — 알약 위에서 그대로** (2026-08-25 · 요청 12번 · app.js `renderTabbar`)
   ══════════════════════════════════════════════════════════════════════════
 * 🔴 편집 중에는 `.tabbar` 가 **세로 두 칸**이 된다(안내 줄 + 알약). 그래서 높이를
 *    `auto` 로 풀고 아래에 붙인다 — 높이를 고정한 채로 두면 안내 줄이 알약을 위로
 *    밀어내 손가락이 닿는 자리가 통째로 어긋난다.
 * 🔴 편집 중에는 **탭바가 숨지 않는다**(`html.tab-editing` — 아래 「스크롤하면 숨는 바」 선택자 · app.js `barsMayHide`). 끄는 도중 목록이
 *    굴러 알약이 내려가면 붙잡은 탭·✕ 배지(`.tabsc-del`)를 놓친다. 🗄 옛 줄 「편집 중에는 축소(`--tab-k`)를 끈다」는 그 축소와 함께 2026-10-08 에 걷었다.
 * 🔴 알약에 `touch-action: none` 을 **편집 중에만** 건다. 상시로 걸면 알약 위에서
 *    시작한 평범한 세로 스크롤이 죽는다(알약은 목록 위에 떠 있다). */
.tabbar.editing { height: auto; align-items: stretch; flex-direction: column; gap: 8px; }
/* 🟢 편집 안내 줄의 폭은 «알약 왼쪽 끝 ~ 더보기(바로가기 묶음 버튼 앞)»다 (2026-09-15 · 사용자 후속 요청 2번 — 탭바 폭 전부였던 줄이
   바로가기 세로 줄 자리까지 덮었다) — 좌우 여백은 app.js `placeEditBar` 가 재서 인라인으로 준다(넓은 폭은 말풍선이라 안 잰다). */
/* 🟢 탭 편집 중에는 알약 위 안내 줄(`.tabedit-bar` · 두 줄: 안내 + 버튼 둘)을 넘겨 토스트를 띄운다 (app.js `setTabEdit`) */
html.tab-editing #toast { bottom: calc(var(--tab-h) + var(--tab-bot) + 112px); }
.tabbar.editing .tabpill { align-self: center; touch-action: none; }
.tabedit-bar {
  pointer-events: auto;   /* 🔴 `.tabbar` 의 none 을 되돌린다 (`.tabpill` 과 같은 이유) */
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; align-self: stretch;
  max-width: 100%; padding: 10px; border-radius: var(--r-md);
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-3), inset 0 1px 0 var(--glass-top);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tabedit-bar { background: var(--bar-bg); }
}
/* 🔴 안내는 두 줄까지만 — 언어마다 길이가 제각각이고 세 줄이 되면 안내가 알약보다 커진다 */
.tabedit-tip {
  min-width: 0; flex: 1 1 100%; padding: 0 2px; font-size: 11.5px; line-height: 1.35; color: var(--text-sub);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.tabedit-btn {
  flex: 1 1 0; height: 38px; padding: 0 12px; border-radius: var(--r-pill); cursor: pointer;
  font-size: 12.5px; font-weight: 700; font-family: inherit;
  color: var(--text-sub); background: var(--surface-2); border: 1px solid var(--line);
}
/* 🔴 `.btn` 을 안 쓴다 — 그 클래스는 색이 없어서 밝은 바탕에 밝은 글자가 된다 */
.tabedit-btn.ok { color: #0d1117; background: var(--brand-500); border-color: transparent; }
.tabedit-btn:active { transform: scale(.96); }
/* 편집 중인 탭 한 칸 — 끌기의 자리 판정이 `[data-tabslot]` 하나로 잰다 */
.tabslot { position: relative; flex: 0 0 auto; display: block; }
/* 끌고 있는 칸 — 살짝 들어 올린다(그림자 대신 색과 크기로 말한다 · 알약이 좁다) */
.tabbtn.tabdrag {
  color: var(--brand-500); background: color-mix(in srgb, var(--brand-500) 22%, transparent);
  transform: scale(1.12);
}
@media (prefers-reduced-motion: reduce) {
  .tabbtn.tabdrag { transform: none; }
  .tabedit-btn:active { transform: none; }
}
/* 사이드바(넓은 화면) — 안내 줄은 사이드바 «옆 말풍선»으로 띄우고 알약은 세로 그대로 둔다 (코인스쿱 `html.side .tabedit-bar`) */
html.side .tabbar.editing { height: auto; flex-direction: column; gap: 0; }
html.side .tabbar.editing .tabpill { align-self: stretch; transform: none; }
html.side .tabedit-bar {
  position: absolute; z-index: 1;
  left: calc(100% + 12px); top: calc(var(--safe-top) + 10px);
  width: max-content; max-width: min(360px, calc(100vw - var(--side-w) - 28px));
  padding: 8px 8px 8px 14px; border-radius: var(--r-md);
  background: var(--bar-bg);
}
html.side .tabedit-bar::before {
  content: ''; position: absolute; left: -6px; top: 17px; width: 10px; height: 10px;
  background: inherit; border-left: 1px solid var(--glass-line); border-bottom: 1px solid var(--glass-line);
  transform: rotate(45deg);
}
html.side .tabedit-tip { -webkit-line-clamp: 3; }
html.side .tabslot { display: block; width: 100%; }

/* ── 🔴 **안 본 것이 있다는 빨간 점** (2026-08-20 · 요청) ─────────────────────────────
      여러 사람이 나눠 읽는 문서 하나라(read 1) 사람마다 그 경계를 서버가 모른다.
      점 하나는 「새 것이 있다」만 말하므로 그 경계 없이도 정직하다.
   🔴 `.tabbtn` 이 아니라 **`::after`** 다 — 아이콘은 `renderTabbar` 가 통째로 갈아 끼우는
      SVG 문자열이라, 안에 요소를 넣으면 다시 그릴 때마다 사라진다.
   🔴 색은 `--hot` 이다 — 이 팔레트에 따로 「빨강」이 없고(2705줄 주석), 새로 만들면
      라이트 테마에서 대비를 다시 맞춰야 한다.
   🔴 테두리로 아이콘에서 **떼어 놓는다.** 알약은 유리(`--glass-bg` · 뒤가 비친다)라
      바탕색을 그대로 쓸 수가 없어서, 획과 점 사이를 비우는 쪽으로 한다. */
.tabbtn { position: relative; }

/* 🟢 줄이 열려 있는 동안 오른쪽 아래 FAB 들은 잠깐 숨는다 (2026-10-11 · 사용자 요청 · app.js `setScOpen` 의 `sc-open`) — 줄과 겹쳐 아래 칸을 가렸다.
   🔴 `display` 가 아니라 투명 + 누름 끔이다 — 상자 자리·`bars-moved` 의 `translate` 를 그대로 두어 닫으면 제자리로 돌아온다 */
html.sc-open .bfab { opacity: 0; pointer-events: none; transition: opacity .12s ease; }

/* 탭 아이콘의 «새 것» 표시 — 자리만 여기 · 모양은 파일 끝 「N 배지」 구획(132차·B 후속 2번) */
.tabbtn.tabdot::after { position: absolute; top: 3px; right: 2px; pointer-events: none; }

/* ── 포그라운드 알림 배너 ─────────────────────────────────────────────────
   🔴 `position: fixed` 라 기준이 뷰포트다 — 상단 인셋과 광고 높이를 **여기서 직접** 더한다
      (`--ad-h` 는 시트가 열려 배너가 떠 있는 동안만 0 이 아니다 — 그동안 온 알림이 배너 밑에 깔리지 않는다). 앱바 위에 떠야 하고
      (z-index 930), 시트(940)·로딩(950)보다는 아래여야 한다. */
#inapp {
  position: fixed; z-index: 930;
  left: 12px; right: 12px; top: calc(var(--safe-top) + var(--ad-h) + 8px);
  max-width: calc(var(--maxw) - 24px); margin: 0 auto;
  display: flex; align-items: flex-start; gap: 4px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--sh-3);
  opacity: 0; transform: translateY(-14px); transition: opacity .22s, transform .22s;
}
#inapp.show { opacity: 1; transform: translateY(0); }
.ia-open {
  flex: 1; min-width: 0; text-align: left; cursor: pointer;
  background: none; border: 0; padding: 12px 4px 12px 14px;
}
.ia-title {
  font-size: 14.5px; font-weight: 700; line-height: 1.4; letter-spacing: -.3px;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.ia-body {
  margin-top: 3px; font-size: 12.5px; line-height: 1.5; color: var(--text-sub);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
/* 🔴 `✕` 글자가 아니라 인라인 SVG 다 (2026-08-11 · `.appbar .back` 과 같은 이유) */
.ia-x {
  flex: 0 0 auto; background: none; border: 0; cursor: pointer;
  padding: 12px; color: var(--text-mut);
  display: inline-flex; align-items: center; justify-content: center;
}
.ia-x svg { width: 16px; height: 16px; display: block; }

/* ── 시트 안 내용 ─────────────────────────────────────────────────────────── */
.sheet-title { font-size: 16px; font-weight: 700; margin: 0 0 4px; }
/* ── 🔴 시트 머리줄 — [제목] …… [저장] (2026-08-19 · 요청 13번 · 체크비 방식) ──────
   저장을 시트 **아래**에 두면 내용이 길어질 때(아바타 줄 + 입력칸 + 안내) 화면 밖으로
   밀리고, 키보드가 뜨면 확실히 가려진다. 머리줄은 시트 맨 위라 언제나 보인다.
   🔴 `.btn` 을 안 쓴다 — 그 클래스는 `width: 100%` 라 flex 줄에서 통째로 자란다
      (`.b-iconbtn` 주석의 그 함정). 크기·색을 여기서 직접 준다. */
.sheet-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.sheet-head .sheet-title { flex: 1; min-width: 0; margin: 0; }
.sheet-save {
  flex: none; padding: 8px 16px; border: 0; border-radius: 10px; cursor: pointer;
  background: var(--brand-500); color: var(--on-brand); font-size: 14px; font-weight: 700;
}
.sheet-save:active { transform: scale(.96); }
.sheet-sub { font-size: 13px; color: var(--text-sub); margin: 0 0 14px; line-height: 1.6; }
.opt {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; padding: 14px; margin-bottom: 8px; cursor: pointer;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--r-sm);
  font-size: 14.5px;
}
.opt[aria-selected="true"] { border-color: var(--brand-500); color: var(--brand-500); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ── 커뮤니티 게시판 (2026-08-14 · board-ui.js) ─────────────────────────────
   🔴 색·간격은 전부 토큰(--surface·--line·--brand-500)으로 — 라이트 테마가 공짜로 따라온다.
   🔴 클릭 대상은 data-b* 속성이다 (board-ui.js 위임 · app.js data-act 와 불간섭). */
/* 🔴 **좌우 여백은 0 이다** (2026-08-17 · 요청 4번). `#scroll` 이 이미 12px 을 주므로
   🔴 위 여백도 0 — `#scroll` 의 위 패딩이 0 이라(그쪽 주석) 첫 요소가 자기 여백을 갖는다.
      게시판의 첫 요소는 실시간 랭킹(`.btrend`)이고 거기 `margin-top` 이 있다. */
.board { padding: 0 0 calc(var(--tab-h, 56px) + 24px); }
.board > .btrend:first-child { margin-top: 12px; }

/* 🔴 **답답해 보이지 않게 키웠다** (2026-08-17 · 요청 3번): 패딩 12/14 → 15/16,
   모서리 12 → 14, 줄간격도 함께. 카드가 화면 폭을 거의 다 쓰므로(.board 여백 0)
   안쪽 여백이 좁으면 글자가 테두리에 붙어 빽빽해 보인다. */
/* 🔴 게시글 카드도 같은 이유로 화면 밖은 안 그린다 (위 `.card` 주석) */
.bcard {
  display: block; width: 100%; text-align: left; background: var(--surface);
  border: 1px solid var(--line); border-radius: 14px; padding: 15px 16px;
  margin-bottom: 10px; color: var(--text);
  content-visibility: auto;
  contain-intrinsic-size: auto 132px;
}
.bcard:active { background: var(--surface-2); }
html.plat-ios .bcard { content-visibility: visible; contain-intrinsic-size: auto; }
/* ── 🔴 **읽지 않음 표시** (2026-08-28 · 47차 요청 4번 · board-ui 「읽지 않음 표시」 구획) ──
   전부 기기 안에서 판정한다(서버 비용 0). 점은 셋 다 «같은 색·같은 크기»여야 한다 —
   뜻이 조금씩 다르지만(새 글 / 새 댓글 / 그 게시판에 새 글) 사용자에게는 하나의 신호다.
   🔴 카드 점은 `position: absolute` 라 `.bcard` 가 기준 상자여야 한다. 그 한 줄이
      빠지면 점이 **화면 왼쪽 위 구석**으로 날아간다(조용한 종류의 어긋남이다).
   🔴 `content-visibility: auto` 와 함께 쓴다 — 그 속성은 화면 밖 카드의 «렌더»만 건너뛰고
      레이아웃 상자는 남기므로 절대 배치가 깨지지 않는다.
   🟢 모양(빨간 원 안의 N · 크기)은 파일 끝 「N 배지」 구획이 정한다(132차·B 후속 2번) — 여기는 자리만 본다. */
.bcard { position: relative; }
.bcard-dot {
  position: absolute; top: 9px; right: 10px;
  width: 7px; height: 7px; border-radius: 50%; background: var(--hot);
}
/* 🔴 **댓글 아이콘의 점** — 읽은 글에 댓글이 붙었다.
   🔴 기준 상자는 **말풍선 아이콘**(`.b-cmti`)이다 (2026-08-29 · 54차 요청 1번 —
      board/list.js `cmtCountHTML` 주석). 옛 판은 `.b-cmtn`(아이콘+숫자) 전체에 걸어서 점이
      **숫자 오른쪽 위**에 떴다: ①자릿수가 바뀌면 점이 가로로 옮겨 다녀 목록을 훑는 눈에
      흔들림으로 읽혔고 ②「새 댓글」이라는 뜻을 지고 있는 그림에서 멀어 무엇이 새것인지가
      한 눈에 안 맺혔다. 아이콘에 붙이면 숫자가 몇 자리든 점 자리가 안 움직인다.
   🔴 아이콘 상자 **바깥으로 반 칸** 나간다 — 안쪽에 두면 13px 짜리 말풍선 획 위에 겹쳐
      그려져 둘 다 못 읽는다. 그 대신 부모(`.b-stats`·`.bhot-n`)에 `overflow: hidden` 이
      생기면 반달로 잘린다(칩의 점이 같은 규칙에 물린 적이 있다 · `.bseg-dot` 주석). */
.b-cmti { display: inline-flex; align-items: center; position: relative; }
.b-cmtn-new .b-cmti::after {
  content: ''; position: absolute; top: -2px; right: -3px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--hot);
}
/* 🔴 **칩의 점** — 칩 오른쪽 위 «안쪽»이다. `.bseg-btn`·`.bseg-pick`·`.bpick-chip` 에 기준 상자를 준다.
   🔴 모서리 **바깥으로 내밀지 않는다**: 이 줄의 부모(`.bseg-scroll`)가 `overflow-x: auto`
      인데, 한 축이 `visible` 이 아니면 다른 축도 `visible` 이 될 수 없다(CSS 규격) —
      즉 위로 2px 만 나가도 그만큼 «잘린 반달»이 된다. 같은 규칙에 이 파일이 한 번 물린 */
/* 🔴 **고정 셋(전체·자유·질문)도 기준 상자가 있어야 한다** (2026-08-29 · 54차 요청 7번) —
   없으면 그 점이 칩 줄이 아니라 화면 좌상단으로 날아간다(`.bcard` 의 같은 경고). */
.bseg-pick, .bseg-btn { position: relative; }
.bseg-dot {
  position: absolute; top: 3px; right: 4px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--hot);
}

.bcard.bdim { color: var(--text-mut); font-size: 13px; }
/* 🟢 `--nm-gap` 은 이 줄의 gap 이다 (2026-09-14 · 125차 요청 4번) — 아바타↔이름 · 태그↔시각은 그대로 이 값이고, 이름↔배지(1px)·배지↔태그(5px)만
   `.b-plusbadge` 문단의 margin 이 이 값을 빼서 좁힌다. 🔴 gap 을 숫자로 되돌리면 그 margin 이 fallback 0 을 읽어 배지가 한 칸씩 벌어진다. */
.b-meta { --nm-gap: 6px; display: flex; align-items: center; gap: var(--nm-gap); font-size: 12px; color: var(--text-sub); }
.b-meta .b-name { font-weight: 600; color: var(--text); }
/* ── 프로필 썸네일 (2026-08-17 · 요청 8번) ──────────────────────────────────
   🔴 **닉네임 글자 크기에 맞춘 아주 작은 원**이다. 키우면 목록이 프로필 사진 나열처럼
      보이고, 글 제목보다 얼굴이 먼저 읽힌다 — 여기는 글을 고르는 자리다.
   🔴 `flex: none` 이 없으면 이름이 길 때 원이 타원으로 눌린다. */
.b-av {
  flex: none; width: 18px; height: 18px; border-radius: 50%;
  background: var(--surface-2) center/cover no-repeat;
}
.bhot-who { --nm-gap: 5px; display: inline-flex; align-items: center; gap: var(--nm-gap); min-width: 0; }   /* `--nm-gap` — `.b-meta` 문단 (125차 요청 4번) */
.bhot-who .b-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 상세·프로필의 큰 자리 (글 상세 머리는 목록보다 한 치수 크다) */
.bpost .b-av { width: 22px; height: 22px; }
/* 🔴 상세의 첫 줄은 **앱바에서 10px 띄운다** (2026-08-18 밤 · 요청 11번). 목록 카드는
   자기 테두리와 패딩이 있어 저절로 떨어지지만, 상세는 카드가 없어 이름 줄이 곧바로
   앱바에 붙는다 — 두 요소가 한 덩어리로 읽혀 어디까지가 앱바인지 흐려졌다. */
.bpost > .b-meta { margin-top: 10px; }

/* ── 카드 본문 + 대표 썸네일 (2026-08-18 · 요청 8번 — 오른쪽에서 **왼쪽**으로) ────
   🔴 썸네일이 **왼쪽 고정폭**이고 글 묶음이 남는 폭을 먹는다. 마크업 순서로만 정한다
      (`order` 를 쓰지 않는다 — 스크린리더는 마크업 순서를 읽으므로 시각과 어긋난다).
   🔴 `align-items: flex-start` 를 지킨다. 늘리면(stretch) 제목이 한 줄인 카드에서
      그림이 세로로 늘어나 비율이 깨진다.
   🔴 `min-width: 0` — flex 자식은 기본이 min-content 라 이게 없으면 긴 제목이 카드를 넓힌다.
   🔴 그림이 **없으면 자리 자체를 안 만든다**(board/list.js `cardHTML`) — 빈 네모를 남기면
      그림 없는 글의 제목이 그만큼 밀려 목록이 들쭉날쭉해진다. */
.bcard-in { display: flex; gap: 10px; align-items: flex-start; }
.bcard-txt { flex: 1; min-width: 0; }
/* 🔴 머리줄이 `.bcard-in` **밖**으로 나오면서 둘 사이 여백이 필요해졌다 (2026-08-31 ·
   57차 요청 5번 · board/list.js `cardHTML` 의 그 문단). 안에 있을 때는 `.bcard-txt` 안의
   블록 흐름이 알아서 띄웠다. 🟡 값은 제목 위 여백과 같은 눈금(6px)이다. */
.bcard > .b-meta { margin-bottom: 6px; }
.bcard-th {
  flex: none; width: 56px; height: 56px; border-radius: 8px; object-fit: cover;
  background: var(--surface-2); border: 1px solid var(--line);
  /* 🔴 **오른쪽 끝** (2026-09-02 · 68차 요청 8번) — DOM 은 제목 앞에 두고 `order` 로만 옮긴다(board/list.js `cardThumbHTML`) */
  order: 2;
}
/* 유튜브 썸네일 (68차 요청 7번) — 사진과 같은 칸, 재생 표식 한 겹 */
.bcard-thw { position: relative; display: block; overflow: hidden; }
.bcard-thw img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bcard-thw i {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 20px; height: 20px; border-radius: 50%; background: rgba(0, 0, 0, .55); color: #fff;
  font-style: normal; display: grid; place-items: center;
}
.bcard-thw i svg { width: 11px; height: 11px; }   /* 재생 SVG(board/core.js `PLAY_IC`) — 🟡 옛 판은 글자 «▶»(9px · 왼쪽 2px 밀어 가운데 맞춤 · iOS 는 컬러 이모지로 그렸다) */
/* 🔴 **첨부가 여럿일 때의 「+N」** (2026-09-08 · 101차 요청 · board/list.js `cardThumbHTML`) — 말풍선의 `.chbub-more` 와
   같은 뜻·같은 자리(오른쪽 아래)지만 **칸이 56px 이라 값이 다르다**: 그쪽 값(11.5px · padding 2/8)을 그대로 쓰면
   배지가 썸네일 폭의 절반을 먹는다. 🔴 `i` 가 아니라 `span` 인 것은 세트다 — 위 `.bcard-thw i` 가 유튜브의
   ▶ 를 «가운데 원»으로 그리므로, 같은 태그를 쓰면 개수가 재생 표식 자리에 동그랗게 앉는다. */
.bcard-more {
  position: absolute; right: 2px; bottom: 2px; padding: 0 4px; border-radius: 999px;
  background: rgba(0, 0, 0, .62); color: #fff; font-size: 9.5px; font-weight: 700; line-height: 14px;
}
.b-meta .b-actions { margin-left: auto; display: flex; gap: 10px; }
.b-mini { background: none; border: 0; color: var(--text-mut); font-size: 12px; padding: 2px 4px; }
/* 관리자 판정 점 세 개 (2026-08-18 밤 · 요청 5번) — 🔴 **관리자에게만 보인다**(마크업에서
   아예 안 만든다). 옆 글자 버튼보다 크게: 기호 하나는 글자보다 과녁이 좁다. */
/* 🔴 **관리자 판정 시트의 구분선** (2026-08-22 · 요청) — 위 넷은 해제로 되돌아오고
   아래 하나는 안 돌아온다. 목록에서 **위아래가 다른 종류**임을 말하는 것이 경고
   문장보다 먼저 읽히므로, 문구가 아니라 선이 그 일을 한다. */
.mod-sep {
  margin: 14px 0 10px; padding-top: 12px;
  border-top: 1px solid var(--line-2); font-size: 12.5px;
}
.b-mod { font-size: 17px; line-height: 1; padding: 0 6px; letter-spacing: 1px; }
.b-hid {
  display: inline-block; font-size: 11px; color: var(--hot);
  background: var(--hot-weak); border-radius: 6px; padding: 2px 6px; margin: 4px 0;
}
/* 🔴 **가림 배지의 «자기 줄»** (2026-08-25 · 요청 13번 · board/list.js `cardHTML`).
   옛 판은 이름·시각·조회수가 있는 머리줄 안이라, 배지가 끼면 이름이 밀려 잘렸다.
   줄을 따로 주면 배지가 온전히 보이고 머리줄도 안 흔들린다. 🟡 위 여백은 `.b-hid` 의
   `margin` 이 이미 든다 — 여기서 또 주면 두 값이 겹쳐 카드가 들쭉날쭉해진다. */
.b-hidrow { display: block; }
.b-title { font-size: 15.5px; font-weight: 700; margin: 7px 0 3px; line-height: 1.5; }
.bpost .b-title { font-size: 17px; margin: 8px 0 4px; }
/* 🔴 **제목 아래 자리** — 선은 2026-08-21 에 **뺐다**(요청). 넣어 보니 제목과 인용을
   가르기보다 카드의 윗변처럼 읽혀서, 인용이 없는 글에서는 줄이 하나 뜬 것처럼 보였다.
   🔴 **여백은 그대로 둔다** — 선이 없어도 그만큼 떨어져 있어야 제목이 본문에 붙지 않는다.
      그래서 요소를 지우지 않고 높이만 남겼다(지우면 여백이 두 값으로 흩어진다). */
.bpost .b-titlerule { height: 0; margin: 8px 0 10px; border: 0; }
/* ── 🔴 **링크 미리보기 배너** (2026-08-31 · 57차 요청 · board/quote.js `linkCardHTML`) ─────────────
 *    이건 **바깥으로 나가는 문**이라, 그림이 있고 도메인을 밝힌다. */
.b-lp {
  display: flex; align-items: stretch; gap: 0; width: 100%; text-align: left;
  margin: 10px 0 2px; padding: 0; overflow: hidden; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 12px;
  color: var(--text); font: inherit;
}
.b-lp:active { background: var(--surface); }
.b-lp-im { flex: none; width: 84px; align-self: stretch; object-fit: cover; background: var(--surface); }
.b-lp-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; padding: 10px 12px; }
.b-lp-t {
  font-size: 13.5px; font-weight: 700; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.b-lp-d {
  font-size: 12px; color: var(--text-sub); line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.b-lp-s { font-size: 11px; color: var(--text-mut); }
/* 🔴 목록용 한 줄 — 인용 카드의 목록판(`.b-quote-slim`)과 **나란히 설 수 있어야** 한다
   (한 글이 인용과 링크를 둘 다 가리키면 두 줄이 된다). */
.b-lp-slim {
  display: flex; align-items: center; gap: 5px; margin-top: 2px;
  font-size: 12px; color: var(--text-mut); min-width: 0;
  background: none; border: 0; border-radius: 0; padding: 0;
}
.b-lp-slim img { flex: none; width: 16px; height: 16px; border-radius: 4px; object-fit: cover; }
.b-lp-slim .b-lp-t {
  font-size: 12px; font-weight: 600; color: var(--text-sub);
  display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.b-lp-slim .b-lp-s { flex: none; }
.b-foot { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
/* 채널(활동 화면) 글 줄의 아랫줄 — 받아 둔 목록 행이 있을 때만 (board-ui `rowFoot` · 85차·B 후속) */
.buser-foot { margin-top: 6px; }
/* ── 🟢 **채널 탭 구독 칩 = 텔레그램의 대화방 목록** (2026-09-17 · 139차 요청 5번 · board/channel-tab.js `subListHTML`·`subRowHTML`) ──────────────
   한 줄 = [얼굴 52px] [이름+배지 …… 종 꺼짐 · 시각] / [제목·발췌 두 줄 …… 작은 그림 · 안 읽은 수]. 줄 사이는 가는 선(얼굴 오른쪽부터 —
   텔레그램의 들여 쓴 구분선). 카드가 아니라 목록이다(채널 목록은 사용자가 준 텔레그램 캡처의 모양 · 🟢 139차·D 부터 쪽지 칩의 대화 목록도 이 줄이다 — 아래 구획).
   🔴 안 읽은 수의 원은 브랜드색 · 알림을 끈 채널은 회색(`.mute` — 텔레그램의 «조용한 채널»).
   🔴 넓은 폭은 `#scroll` 의 읽기 폭 상한(`html.wide` 문단)을 그대로 탄다 — `#ch-list` 가 그 직계 자식이다. */
/* 🟢 최신 칩 목록 맨 위(위쪽 끝까지 올렸을 때)의 첫 머리는 제 위 여백을 걷는다 — 옛 판은 `.chfeed` 6px + 첫 날짜 알약 10px(또는 「이전 글」 10px)이
   `#scroll` 의 여백과 겹쳐 구분선에서 28px 떨어졌다(실측 · 125차 요청 9번). 말풍선 사이 간격은 그대로다. */
#appbar.has-chips ~ #scroll #ch-body > .chfeed { padding-top: 0; }
#appbar.has-chips ~ #scroll #ch-body > .chfeed > .chbub-day:first-child,
#appbar.has-chips ~ #scroll #ch-body > .chroom-older:first-child { margin-top: 0; }
.chlist { display: flex; flex-direction: column; margin: 0 -12px; }
/* 🟢 구독·쪽지 목록의 캐러셀 (2026-10-23 · board/channel-tab.js `withListPromo`) — 목록은 좌우 끝까지 붙지만(-12px) 캐러셀은 다른 화면과 같은 안쪽 여백 */
.chlist > .promo-slot { margin: 8px 12px; }

.chl-row {
  display: flex; align-items: center; gap: 12px; width: 100%; min-width: 0; margin: 0; padding: 8px 12px;
  border: 0; background: none; color: var(--text); font: inherit; text-align: left; cursor: pointer; position: relative;
}
.chl-row:active { background: var(--surface-2); }
.chl-row + .chl-row::before { content: ''; position: absolute; top: 0; left: 76px; right: 0; border-top: 1px solid var(--line); }
.b-av.chl-av { flex: none; width: 52px; height: 52px; }
.chl-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.chl-top, .chl-bot { display: flex; align-items: center; gap: 6px; min-width: 0; }
.chl-bot { align-items: flex-start; }
/* 🟢 `--nm-gap` = 이 줄의 flex gap (2026-10-18 · 사용자 「채널 쪽 닉네임+배지+레벨 간격을 커뮤니티와 같게」) — 옛 판은 gap 을 숫자 4px 로 둬서
   배지 margin 이 fallback 0 을 읽어 닉↔배지 5px · 배지↔태그 9px 로 벌어졌다. 이제 커뮤니티와 같은 «닉↔배지 1px · 태그 앞 5px»(`.b-plusbadge` 문단) */
.chl-name { --nm-gap: 4px; flex: 0 1 auto; min-width: 0; display: flex; align-items: center; gap: var(--nm-gap); font-size: 15px; font-weight: 700; }
.chl-nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chl-name > :not(.chl-nm) { flex: none; }
/* 🟢 구독 목록의 내 방 표식 (2026-10-13 · `subRowHTML` 의 `own`) — 배지보다 한 단 약한 테두리 알약 */
/* 🟢 최신 칩 맨 위의 «최근 글만» 한 줄 (2026-10-13 · board/channel-tab.js `CH_FEED_MAX`) */
.chfeed-cap { margin: 10px 16px 4px; text-align: center; font-size: 12px; color: var(--text-mut); }
.chl-own { padding: 0 6px; border: 1px solid var(--line); border-radius: 999px; font-size: 10.5px; font-weight: 600; line-height: 16px; color: var(--text-mut); }
.chl-mute { flex: none; display: inline-flex; color: var(--text-mut); }
.chl-mute svg { width: 13px; height: 13px; }
.chl-time { margin-left: auto; flex: none; padding-left: 6px; font-size: 12px; color: var(--text-mut); font-variant-numeric: tabular-nums; }
.chl-last {
  flex: 1; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  font-size: 13.5px; line-height: 1.4; color: var(--text-mut); white-space: normal; word-break: break-word;
}
.chl-tt { color: var(--text-sub); font-weight: 600; }
.chl-none { font-style: italic; }
.chl-th { flex: none; width: 36px; height: 36px; border-radius: 6px; object-fit: cover; background: var(--surface-2); }
/* 안 읽은 수 — 시각 아래 오른쪽 끝(텔레그램) · 한 자리면 원, 두 자리부터 알약 */
.chl-n {
  flex: none; align-self: center; box-sizing: border-box; min-width: 22px; height: 22px; padding: 0 7px; border-radius: 11px;
  background: var(--brand-500); color: var(--on-brand); font: 700 12px/22px system-ui, -apple-system, "Segoe UI", sans-serif; font-style: normal;
  text-align: center; font-variant-numeric: tabular-nums;
}
.chl-n.mute { background: var(--text-mut); color: var(--bg); }
/* 🟢 @ — 그 채널 글에서 내가 불렸다(멘션·답글) · 안 읽은 수 왼쪽의 같은 크기 원 (2026-10-01 · 텔레그램의 그 표시 · board/channel-tab.js `subRowHTML`) */
.chl-at {
  flex: none; align-self: center; box-sizing: border-box; width: 22px; height: 22px; border-radius: 11px;
  background: var(--brand-500); color: var(--on-brand); font: 700 13px/22px system-ui, -apple-system, "Segoe UI", sans-serif; font-style: normal; text-align: center;
}
/* ── 🟢 **쪽지 칩 = 쪽지 대화 목록** (2026-09-17 · 139차·D · board/channel-tab.js `dmListHTML`·`dmChatRowHTML`) — 줄은 위 `.chl-*` 그대로다 ──────────
   안 읽은 대화는 미리보기 글자를 한 단 진하게(옛 쪽지함 `.dmrow.unread` 의 그 규칙). */
.chl-row.unread .chl-last { color: var(--text-sub); }
/* 앱바 쪽지 칩의 안 읽은 수 — 글자 뒤 한 칸 · 🔴 자리는 늘 있고(0 이면 `hidden`) 글자만 바뀐다(app.js `dmChipBadgeHTML` · 칩 최적화의 `norm`) */
.chip .chip-n {
  display: inline-block; box-sizing: border-box; min-width: 17px; height: 17px; margin-left: 3px; padding: 0 5px; border-radius: 9px;
  vertical-align: 3px; background: var(--hot); color: #fff; font: 800 10.5px/17.6px system-ui, -apple-system, "Segoe UI", sans-serif;
  font-style: normal; text-align: center; font-variant-numeric: tabular-nums;
}
.chip .chip-n[hidden] { display: none; }
/* ── 🔴 **채널 방** (2026-09-07 · 91차 요청 8번 · board/channel-room.js `renderChannelRoom` · 텔레그램의 채널 대화방) ──────────────────────
   붙박이 머리(`.chroom-sticky` · sticky · 얼굴·이름·구독자 수 → 활동 화면 · 그 아래 고정 글 한 줄) · 말풍선은 아래 `.chbub` 그대로(전부 왼쪽 —
   채널 글은 주인 한 사람의 것) · 바닥은 «입력줄 자리»(`.chbar` · `.dmbar` 와 같은 sticky · `html.dm-room #scroll` 의 패딩 0 을 같이 쓴다). */
.chroom-scr { display: flex; flex-direction: column; min-height: 100%; }
.chroom-sticky { position: sticky; top: 0; z-index: 4; margin: 0 -12px; padding: 0 12px; background: var(--bg); border-bottom: 1px solid var(--line); }
/* 🔴 `border-top` 이 없다 — 머리가 앱바로 간 뒤로 이 줄이 붙박이의 **첫 요소**라, 위 테두리를 두면 앱바 바로 밑에 선이 둘이 된다 */
.chroom-pinbar {
  display: flex; align-items: center; gap: 8px; width: 100%; min-width: 0; padding: 8px 0; cursor: pointer;
  background: none; border: 0; color: inherit; font: inherit; text-align: left;
}
.chroom-pintag { flex: none; font-size: 11px; font-weight: 700; color: var(--accent); }
.chroom-pint { flex: 1; min-width: 0; font-size: 12.5px; color: var(--text-sub); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 🔴 고정 여럿 (2026-09-07 · 94차 요청 10번 · board/channel-room.js `pinBarHTML`·`goPinned`) — `1/N` 표식 · 누른 뒤 «그 전 고정»으로 넘어갈 때 글자가
   공지 배너처럼 아래에서 미끄러져 올라온다(`.roll` · 클래스는 배너를 갈아 끼울 때마다 새로 붙어 애니메이션이 매번 돈다). */
.chroom-pinbar.roll .chroom-pint { animation: chpin-roll .32s cubic-bezier(.22, .8, .3, 1); }
@keyframes chpin-roll { from { transform: translateY(14px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
/* 배너에서 온 말풍선 — 댓글 찾기의 그 링(`hl-ring`) 하나를 같이 쓴다 */
.chbub.hl { animation: hl-ring 1.6s ease-out; }
/* 🟢 링이 꼬리도 두른다 (2026-10-01 · 사용자 실기기 「노란 테두리가 꼬리 뒤로 지나간다」) — 꼬리 두 겹은 말풍선의 그림자(링) 위에 그려져 링을 끊는다.
   밝히는 동안 뒤 겹(테두리 색)을 링 색으로 바꾸고 조금 키워 꼬리의 윤곽이 링과 이어지게 한다. */
.chbub.hl:not(.chbub-dim)::before { transform-origin: 100% 55%; animation: hl-tail 1.6s ease-out; }
@keyframes hl-tail { 0%, 40% { background: var(--brand-500); transform: scale(1.32); } 100% { background: var(--line); transform: none; } }
/* ── 🟢 **안 읽은 줄 · 맨 아래로 원** (2026-09-17 · 139차 요청 5번 · board/channel-room.js `renderChannelRoom`·`roomWatchNow`) ──────────────────
   줄: 첫 안 읽은 말풍선(과 그 날짜 알약) 위의 가로 띠 — 방문 동안 그 자리에 머문다(텔레그램의 「Unread Messages」).
   원: 끝이 아니면 오른쪽 아래에 뜨는 40px 원(아래 꺾쇠) + 그 위 안 읽은 수 알약. 🔴 자리는 방의 바닥 모양이 가른다 —
   내 방은 글 입력줄(`.chroom-compose` · 2026-10-01 — 옛 글쓰기 FAB 자리) 위 · 남의 방은 바닥 줄(`.chbar` 의 알약 버튼) 위 — 둘이 같은 높이라 값이 하나다(🟢 2026-10-01 부터
   구독 중에도 그 줄이 선다 — 「알림 끄기 / 알림 켜기」 · 옛 빈 자리 `.nobar` 는 걷었다). 방은 언제나 `no-tabs` 라 탭바 높이를 안 더한다(두 열에서도 오른쪽 상자 — 원은 뷰포트 오른쪽 끝). */
.chroom-unread { display: flex; justify-content: center; margin: 6px -12px 8px; padding: 4px 0; background: var(--surface-2); }
.chroom-unread span { font-size: 12px; font-weight: 600; color: var(--text-sub); }
.chroom-down {
  position: fixed; right: 16px; z-index: 39; bottom: calc(var(--safe-bot, 0px) + 16px + 62px);
  width: 40px; height: 40px; padding: 0; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; border: 1px solid var(--line); background: var(--surface); color: var(--text-sub); box-shadow: var(--sh-3); cursor: pointer;
}
.chroom-down svg { width: 22px; height: 22px; }
.chroom-down:active { transform: scale(.94); }
.chroom-down-n {
  position: absolute; top: -10px; left: 50%; transform: translateX(-50%); box-sizing: border-box; min-width: 20px; height: 20px; padding: 0 6px;
  border-radius: 10px; background: var(--brand-500); color: var(--on-brand); box-shadow: 0 0 0 2px var(--bg);
  font: 700 11px/20px system-ui, -apple-system, "Segoe UI", sans-serif; font-style: normal; text-align: center; white-space: nowrap;
}
@media (min-width: 700px) {
  .chroom-down { right: max(16px, calc(50vw - var(--maxw) / 2 + 16px)); }
}
#scroll.nav-fwd .chroom-down,
#scroll.nav-back .chroom-down { visibility: hidden; }
/* ── 채널 탭 소식·구독 칩의 말풍선 «꼬리» (2026-09-07 · 94차 요청 3번 · board/channel-room.js `chBubbleHTML` 의 `from`) ──
   말풍선 아래 왼쪽, 꼬리(왼쪽 아래 모서리 4px)가 가리키는 자리에 얼굴·닉네임 한 줄 — 누르면 그 채널의 방. 말풍선(`article`)과
   형제라 위임이 안쪽 버튼을 먼저 잡는다(누르는 곳이 곧 도착지). */
/* 🔴 2026-09-07 · 94차·B 후속 5 (사용자 지정): 꼬리는 **왼쪽 세로 한 덩이**(얼굴 40px · 그 아래 닉네임 · 왼쪽 정렬)이고 말풍선은 그
   오른쪽에 **바닥을 맞춰**(`align-items: flex-end`) 선다 — 꼬리(왼쪽 아래 모서리 4px)가 얼굴을 가리킨다. 첫 판(말풍선 «아래» 한 줄)은 걷었다. */
/* 🔴 94차·B 후속 6: 말풍선 바닥 = **얼굴 바닥**(닉네임 제외). 닉네임은 얼굴 아래로 «매달린다»(절대 위치 · 폭 제한 없이 말풍선 밑의 빈 폭을
   쓴다 — 56px 상자에 갇혀 말줄임되던 둘째 판의 그 문제). 그 높이만큼 `.chbub-wrap` 의 아래 여백이 한 줄 더 넓다. */
/* ── 🟡 **말풍선이 적으면 캐러셀을 «바닥»에 붙인다** (2026-09-08 · 98차 요청 3번 · 사용자 「하단에 여백이 굉장히 많이 남는다」) ──
   목록이 화면을 못 채우는 채널(글 두세 편)에서 마지막 슬롯 아래에 빈 화면이 그대로 남았다. 상자를 화면 높이까지 늘리고
   슬롯이 없어져서 이제 **말풍선 묶음 전체**를 바닥으로 민다(`.chfeed` 의 `justify-content` — 짧은 대화처럼 빈 자리가 위에 남는다).
   🔴 목록이 화면을 넘치면 남는 자리가 0 이라 아무것도 안 바뀐다(`#ch-list` 는 `min-height` 라 넘치는 만큼 자란다).
   🔴 `#ch-list` 는 게시판 목록과 **같은 `.board`** 라 이 규칙들은 `#ch-list` 로 좁혀 건다 — 게시판 탭의 레이아웃을 안 건드린다.
   🔴 **그리고 `.board` 의 아래 패딩을 걷는다** (2026-09-09 · 104차 요청 1번 · 실기기 「채널 탭 하단에 여백이 여전히 많다」):
      `.board` 는 게시판 목록용으로 탭바 높이(`--tab-h` + 24px)를 «한 번 더» 비우는데 `#scroll` 이 이미 같은 높이를 비워 둔다
      (탭바 + `--tab-bot` + 틈). 게시판 탭에서는 긴 목록의 «끝»이라 눈에 안 띄고 글쓰기 FAB 의 자리이기도 하지만, 채널 탭은 */
#scroll > #ch-list { display: flex; flex-direction: column; min-height: 100%; padding-bottom: 0; }
#scroll > #ch-list > #ch-body { display: flex; flex-direction: column; flex: 1; }
.chfeed { display: flex; flex-direction: column; flex: 1; justify-content: flex-end; padding: 6px 0 0; }
/* 🟢 추천 칩 (2026-10-18 · board/channel-tab.js `recoFeedHTML`) — 순위 목록이라 위에서부터 쌓는다(최신 칩의 바닥 붙이기를 안 탄다) ·
   왜 떴는지 한 줄은 말풍선 머리 위, 얼굴 칸(40 + 틈) 오른쪽에 맞춘다 */
.chfeed-reco { justify-content: flex-start; }
.chreco-why { margin: 0 0 4px 50px; font-size: 11.5px; font-weight: 600; color: var(--accent); }
.chbub-wrap { display: flex; align-items: flex-end; gap: 6px; margin: 0 0 30px; }
.chfeed > .chbub-wrap:last-child { margin-bottom: 24px; }
html.ch-tab #scroll { padding-bottom: calc(var(--tab-h) + var(--tab-bot) + var(--kb, 0px)); }
.chbub-wrap .chbub { margin: 0; flex: 1 1 auto; min-width: 0; max-width: calc(100% - 46px); }
/* 🔴 폭은 **얼굴 그대로**다 (2026-09-07 · 96차 · 사용자 「말풍선을 썸네일 쪽으로 더 가까이」) — 56px 이던 것은 닉네임이
   이 상자 «안»에 있던 둘째 판의 잔재이고, 닉네임이 절대 위치로 매달린 뒤로는 16px 이 그냥 빈자리로 남아 말풍선을 밀고
   있었다. 얼굴과 말풍선 사이는 이제 `gap` 하나뿐이다(`.chbub-wrap .chbub` 의 `max-width` 와 세트 — 40 + gap). */
.chbub-from {
  position: relative; flex: none; width: 40px; height: 40px; display: block; margin: 0; padding: 0;
  border: 0; background: none; color: var(--text-sub); font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; text-align: left;
}
.chbub-from:active { opacity: .7; }
.b-av.chbub-av { display: block; width: 40px; height: 40px; }
/* 🔴 **배지(AI·공식)는 이름 «옆»이다** (2026-09-11 · 113차·B 요청 4번 · board/channel-room.js `chBubbleHTML`) — 옛 판은 AI 배지를 이름 아래 줄
   (`top: 60px`)에 따로 매달았다. 이제 이름 칸이 한 줄짜리 플렉스이고 말줄임은 이름(`.chbub-fnm`)만 한다 — 배지가 먼저 잘리면
   「봇이다」라는 사실이 사라진다. 높이가 한 줄로 줄어 `.chbub-wrap` 의 아래 여백 안에 든다.
   🟢 배지·태그의 «가로» 간격은 `--nm-gap` 규칙이 정한다 (2026-09-14 · 125차 요청 4번 · `.b-plusbadge` 문단) — 그래서 아래 자식 규칙은
      위아래 margin 만 0 으로 둔다. 🟡 옛 `margin: 0` 은 특이도(0,2,0)로 배지·태그의 margin-left 를 전부 이겨 이 자리만 조용히 gap 4px 두 칸이었다. */
.chbub-fname {
  --nm-gap: 4px;
  position: absolute; left: 0; top: 44px; display: flex; align-items: center; gap: var(--nm-gap); max-width: 220px; line-height: 1.2;
  white-space: nowrap;
}
.chbub-fnm { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.chbub-fname > :not(.chbub-fnm) { flex: none; margin-top: 0; margin-bottom: 0; }
.chroom-msgs { display: flex; flex-direction: column; flex: 1; padding: 10px 0 8px; min-height: 120px; }
.chroom-older { align-self: center; margin: 0 0 10px; }
#ch-body > .chroom-older { display: block; margin: 10px auto 0; }   /* 소식·구독 칩의 맨 위 「이전 글」 (후속 9) */
/* 🟢 바닥 버튼도 떠 있는 유리 알약이다 (2026-10-01 · 사용자 「알림 켜기·끄기 버튼도 알약 스타일로」 · 입력줄 `.inpill` 과 같은 재질) — 띠는 투명하고
   버튼만 눌린다(옆의 빈 자리로는 뒤의 말풍선이 눌린다). */
.chbar {
  position: sticky; bottom: 0; z-index: 5; display: flex; margin: 4px -12px 0;
  padding: 6px 10px calc(8px + var(--safe-bot, 0px)); pointer-events: none;
}
.chbar > * { pointer-events: auto; }
.chbar-btn {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 48px; padding: 0 16px;
  border-radius: 25px; border: 1px solid var(--glass-line); background: var(--glass-bg); color: var(--text); font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
  -webkit-backdrop-filter: blur(18px) saturate(180%); backdrop-filter: blur(18px) saturate(180%);
  box-shadow: var(--sh-3), inset 0 1px 0 var(--glass-top);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .chbar-btn { background: var(--bar-bg); } }
.chbar-btn.primary { -webkit-backdrop-filter: none; backdrop-filter: none; }
.chbar-btn svg { width: 18px; height: 18px; }
.chbar-btn.primary { background: var(--brand-500); border-color: transparent; color: var(--on-brand); }
.chbar-btn:active { opacity: .8; }
.chbar-note { flex: 1; text-align: center; font-size: 13px; color: var(--text-mut); padding: 10px 0; }
.chbub-empty { padding: 28px 12px; text-align: center; color: var(--text-mut); font-size: 13.5px; line-height: 1.6; }
.chbub-day { display: flex; justify-content: center; margin: 10px 0 8px; }
.chbub-day span { font-size: 11.5px; color: var(--text-mut); background: var(--surface-2); border-radius: 999px; padding: 3px 10px; }
.chbub {
  position: relative; max-width: 92%; margin: 0 0 8px; padding: 10px 12px; color: var(--text);
  background: var(--surface); border: 1px solid var(--line); border-radius: 16px 16px 16px 4px; cursor: pointer;   /* 왼쪽 아래만 각진 «상대의 말풍선» 무늬 — 뾰족한 꼬리는 걷었다(94차·B 후속 8 · 위 문단) */
}
.chbub:active { background: var(--surface-2); }
/* 흐림(30~59)으로 접힌 말풍선 — 게시판 카드(`.bcard.bdim`)와 같은 결(2026-09-08 · 99차·C · board/channel-room.js `chBubbleHTML`).
   말풍선 모양은 그대로 두고 글자만 옅게: 접힌 것도 «그 사람이 한 말»의 자리라 상자가 갈리면 안 읽힌다. */
.chbub.chbub-dim { display: block; width: 100%; text-align: left; color: var(--text-mut); font-size: 13px; cursor: pointer; }
/* 안 읽음 점 — 🔴 옛 `.chbub.new::after` 는 꼬리 삼각형(`::after`)에 자리를 내주고 요소(`.chbub-new` · board-ui)가 됐다 (94차·B 후속 6).
   꼬리는 후속 8 에 걷었지만 요소로 남긴다 — 글자 없는 표식은 가상 요소보다 요소가 위임·검사에 잡히기 쉽다. */
/* 🔴 `z-index` 는 부각판과 세트다 (2026-09-08 · 101차) — 점이 마크업에서 «먼저» 나오므로 뒤따르는 인용 카드가 커지면
   그 위에 얹혀 점을 덮는다. 지금까지 안 보인 이유는 카드가 한 줄이라 겹침이 3px 이었을 뿐이다. */
.chbub-new { position: absolute; z-index: 1; top: 8px; right: 8px; width: 7px; height: 7px; border-radius: 50%; background: var(--hot); }
/* ── 🔴 **말풍선 안의 그림 격자** (2026-09-07 · 93차·B 요청 2번 · board/channel-room.js `chBubMediaHTML`) ──────────────────────
   타일이 하나면 통으로, 둘이면 반반이다 — 개수는 클래스(`n1`·`n2`)가 말하고 자리는 여기가 정한다. */
.chbub-media { display: grid; gap: 4px; grid-template-columns: 1fr; margin: 8px 0 0; }
.chbub-media.n2 { grid-template-columns: 1fr 1fr; }
.chbub-tile { position: relative; display: block; overflow: hidden; border-radius: 10px; background: var(--surface-2); }
.chbub-media.n1 .chbub-tile img { height: 230px; }
.chbub-media.n2 .chbub-tile img { height: 140px; }
/* 🔴 **인용 히어로가 «그림을 이고» 서면 첨부를 줄인다** (2026-09-08 · 101차) — 둘 다 크면 말풍선 하나가 400~500px 이 되어
   채널 탭의 꼬리(얼굴·닉네임)가 화면 밖으로 밀리고(`.chbub-wrap` 은 바닥 정렬이다) 「훑는 자리」라는 이 화면의 전제가 깨진다.
   🔴 판정을 JS 로 안 넘긴다 — 인용 카드가 격자 «바로 앞»이라 인접 형제로 그대로 읽힌다(마크업 순서가 곧 규칙이다). */
.b-quote-hero.has-im + .chbub-media.n1 .chbub-tile img { height: 120px; }
.b-quote-hero.has-im + .chbub-media.n2 .chbub-tile img { height: 88px; }
.chbub-tile img { display: block; width: 100%; object-fit: cover; }
/* ── 🟢 **사진 «앨범»** (2026-09-30 · board/channel-room.js `albumHTML` · 사용자 요청 「말풍선에 가득 채워서 · 너무 길면 가운데 맞춤 · 여러 장이면 격자」) ──────────
   자리(한 장의 상자 비율 · 두 장의 폭 나눔 · 세 장의 갈래)는 JS 가 그림의 비율로 정해 인라인으로 싣고, 여기는 뼈대만 든다.
   🔴 그림은 칸에 «절대 위치»로 깐다 — 칸의 높이는 `aspect-ratio` 가 정하는데, 그 안의 `height: 100%` 를 옛 웹뷰(iOS 15)가 못 읽는 일이 있다.
   🔴 위 옛 격자(`.chbub-media`)는 비율을 모르는 옛 글의 것이다 — 목록에서 높이가 흔들리면 안 돼서 고정 칸 그대로 둔다. */
/* 🔴 높이 상한(520px)은 넓은 화면(폴드·태블릿·웹)의 것이다 — 폭이 560px 인 말풍선에서 세로 사진 한 장이 700px 을 먹는다. 폰에서는 안 닿는다.
   상한에 닿으면 상자가 그림의 비율과 달라지므로 세로·정사각 한 장은 처음부터 가운데 맞춤이다(board/channel-room.js `ALB_FIT_BELOW`). */
.alb { display: flex; gap: 2px; overflow: hidden; margin: 8px 0 0; border-radius: 12px; background: var(--surface-2); max-height: 520px; }
.alb.n1 .alb-t { max-height: 520px; }
.alb-t { position: relative; display: block; overflow: hidden; min-width: 0; background: var(--surface-2); }
.alb-t img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }
.alb-t img[data-bimgview] { cursor: pointer; }
.alb-t.dead img { visibility: hidden; }
/* 칸의 모서리는 각지다 — 둥근 것은 앨범의 바깥 모서리뿐이다(목록의 칸이 같이 다는 옛 클래스 `.chbub-tile` 의 10px 를 덮는다) */
.alb .alb-t { border-radius: 0; }
.alb.n1 { display: block; }
/* 한 장 · 너무 길거나 납작한 그림 — 전부 보이게 가운데에 맞추고, 남는 자리는 같은 그림을 흐려 깐다.
   🟢 흐린 바탕은 같은 그림의 «두 번째 img»(`.alb-bg`)다 (2026-10-01 · 감사 — 옛 판은 도착한 뒤 칸의 인라인 `--im` 이었는데 다시 그리기가 칸의
   스타일을 틀의 값으로 되돌려 흐림이 회색 띠가 됐다 · board/channel-room.js `albumHTML`). 앞 그림보다 먼저 놓여 뒤에 깔린다. */
.alb.n1 .alb-t.fit img { object-fit: contain; }
.alb.n1 .alb-t.fit img.alb-bg {
  inset: -14% auto auto -14%; width: 128%; height: 128%; object-fit: cover;
  filter: blur(18px) brightness(.72); pointer-events: none;
}
/* 한 장 · 비율을 모르는 옛 글(스레드에서만) — 제 높이로 그리되 화면을 넘기지 않는다 */
.alb.n1 .alb-t.auto img { position: static; height: auto; max-height: min(125vw, 480px); object-fit: contain; }
.alb.n3.l { display: grid; grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr; }
.alb.n3.l .alb-t:first-child { grid-row: 1 / 3; }
.alb.n3.t, .alb.n4 { display: grid; grid-template-columns: 1fr 1fr; }
.alb.n3.t .alb-t:first-child { grid-column: 1 / 3; }
/* 🔴 높이는 칸이 아니라 «상자»가 정한다 — 상자에 높이 상한이 걸릴 때 줄이 같이 줄어야 아랫줄이 안 잘린다(n3.t 의 상자 비율·줄 비는 JS 가 인라인으로) */
.alb.n4 { aspect-ratio: 1.3; grid-template-rows: 1fr 1fr; }
.alb.n3 .alb-t, .alb.n4 .alb-t { min-height: 0; }
/* 말풍선 «가득» — 옆은 말풍선의 안쪽 여백(12px)만큼 밖으로, 첫 줄이면(`alb-top`) 위쪽 모서리까지. 카드 안(`alb-in` · 재게시 원글)은 안 넘긴다 */
.chbub > .alb { margin: 8px -12px 0; border-radius: 0; }
.chbub > .alb.alb-top { margin-top: -10px; border-radius: 15px 15px 0 0; }
.chbub > .alb + .chbub-t, .chbub > .alb + .chbub-x { margin-top: 8px; }
/* 인용 히어로가 그림을 이고 서면 앨범을 낮춘다 (위 `.b-quote-hero.has-im + .chbub-media` 와 같은 이유 — 말풍선 하나가 화면을 다 먹는다) */
.b-quote-hero.has-im + .alb.n1 .alb-t, .b-quote-hero.has-im + .alb:not(.n1) { max-height: 170px; }
/* 스레드의 글 말풍선 — 같은 조각 · 옆으로 가득(그 말풍선의 안쪽 여백 12px) */
.chth-post .alb { margin: 8px -12px 10px; border-radius: 0; }
/* 카드 안의 앨범(재게시 원글 카드) — 밖으로 안 넘긴다 · 🔴 위 두 규칙(말풍선·스레드의 «가득»)보다 뒤에 둔다(무게가 같아 순서가 이긴다) */
.alb.alb-in, .chbub .alb.alb-in, .chth-post .alb.alb-in { margin: 0 0 8px; border-radius: 10px; }
/* 「+N」 — 첨부가 여러 장일 때 «몇 장인지»만 말한다(옛 글의 행은 2·3번째 주소를 만들 재료가 없다 · `chBubMediaHTML` 머리 주석 · 앨범은 토큰 없는 장만 센다) */
.chbub-more {
  position: absolute; right: 6px; bottom: 6px; padding: 2px 8px; border-radius: 999px;
  background: rgba(0, 0, 0, .62); color: #fff; font-size: 11.5px; font-style: normal; font-weight: 700;
}
.chbub-play {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: #fff; font-style: normal;
}
.chbub-play svg, .vs-th .play svg { width: 30px; height: 30px; filter: drop-shadow(0 1px 4px rgba(0, 0, 0, .55)); }   /* 재생 SVG(board/core.js `PLAY_IC`) */
/* 🔴 제목 두 줄 · 본문 다섯 줄 말줄임 (2026-09-07 · 96차 · 사용자 지정) — 말풍선은 «훑는» 자리라 한 글이 화면을 통째로
   먹으면 대화가 안 보인다. 🔴 본문 줄 수는 서버 `community/core.js CH_EXCERPT_MAX` 와 **세트**다(그 값이 다섯 줄어치 글자
   수다) — 한쪽만 늘리면 늘린 만큼이 안 보이거나 자리가 안 찬다. */
.chbub-t {
  margin: 0; font-size: 15px; font-weight: 600; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* 🔴 인용 카드는 제목 «위»(말풍선 첫 줄)이고 링크 배너는 그림 아래다 — 조각은 목록 카드와 같은 것을 쓰고 자리만 다르다
   (board/channel-room.js `chBubbleHTML` 머리 주석). 여기서는 여백만 정한다. */
.chbub > .b-quote { margin: 0 0 6px; }
/* 🟢 재게시의 «전달» 몸통(`board/repost.js repostFwdHTML`) — 글상세·스레드에서 원글을 글자 그대로 · 눌리면 원글 상세 */
.b-rt-fwd { cursor: pointer; }
.b-rt-fwd > .chbub-t + .chbub-x { margin-top: 4px; }
.b-rt-fwd > .alb + .chbub-t, .b-rt-fwd > .alb + .chbub-x { margin-top: 8px; }
.b-rt-fwd > .b-lp { margin: 8px 0 0; }
.chbub > .b-lp { margin: 8px 0 0; }
/* 🟢 말풍선의 글자는 스레드의 본문과 같은 크기다 (2026-10-01 · 사용자 「글자가 작고 말줄임된다」 · 값은 `.bpost .b-body`). 줄 수로 자르지 않는다 —
   서버가 자른 발췌(`CH_EXCERPT_MAX`)까지 전부 그린다. 전문은 스레드의 몫이다(방에서 본문을 따로 묻는 판은 read 가 말풍선 수만큼이라 같은 날 걷었다). */
.chbub-x { margin: 4px 0 0; font-size: 15px; line-height: 1.75; color: var(--text); white-space: pre-wrap; overflow-wrap: anywhere; }
/* 🔴 **아랫줄은 넘치면 «접힌다»** (2026-09-10 · 112차 요청 8번) — 조회·댓글·공감·[고정]·시각이 한 줄에 안 드는 폭이 있다(포르투갈어
   「… visualizações」 · 폭 360px 안팎). 줄바꿈이 없으면 말풍선 밖으로 넘쳐 `#scroll` 의 가로 자르기에 잘린다. 한 줄에 들어가는 폭에서는
   아무것도 안 바뀐다. 🟡 그날의 직접 원인이던 «오늘이 아니면 날짜»(board-ui `stampOfAt`)는 2026-09-11 · 113차 요청 7·8번에 걷었다 — 시각이
   다시 시:분이라 넘치는 폭은 좁아졌지만 다시 재지 않았고, 한 줄에 드는 폭에서는 이 규칙이 아무 일도 안 하므로 남긴다. */
.chbub-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 6px; font-size: 12px; color: var(--text-sub); }
.chbub-foot:empty { display: none; }
.chbub-time { margin-left: auto; flex: none; font-size: 11.5px; color: var(--text-mut); }
/* 🟢 오른쪽 한 덩이 = 댓글 · 조회수 · 시각 (2026-09-17 · 139차·B 요청 3번 · board/channel-room.js `chBubbleHTML` — 옛 판(136차 요청 7번)은 조회수만
   시각 옆이고 댓글 수는 아랫줄 맨 왼쪽이었다). 덩이(`.chbub-cnt`)가 오른쪽으로 밀고 안의 칸은 제 여백을 버린다(`.b-cmtn`·`.chbub-time` 의
   `margin-left:auto` 는 다른 화면의 규칙이다). [고정]이 있으면 그 버튼이 이미 밀었으므로 덩이는 붙는다. 비면 자리를 안 먹는다. */
.chbub-cnt { margin-left: auto; flex: none; display: inline-flex; align-items: center; gap: 8px; }
.chbub-cnt:empty { display: none; }
.chbub-cnt > * { margin-left: 0; flex: none; }
/* 🟢 공유 버튼 — 말풍선 오른쪽 아래 끝 (139차·B 요청 2번 · `chPostSheet`) · [고정]과 같은 크기·같은 옅은 색 */
.chbub-share { flex: none; width: 26px; height: 22px; padding: 0; border: 0; background: none; color: var(--text-mut); cursor: pointer; }
.chbub-share svg { width: 15px; height: 15px; display: block; margin: 0 auto; }
.chbub-cnt:empty + .chbub-share { margin-left: auto; }
/* 🟢 북마크 — 공유 바로 왼쪽 · 공유와 같은 크기·색 · 켜지면 브랜드 색으로 채운 책갈피 (2026-10-12 · board/reactions.js `chBmBtnHTML`) */
.chbub-bm { flex: none; width: 26px; height: 22px; padding: 0; border: 0; background: none; color: var(--text-mut); cursor: pointer; }
.chbub-bm svg { width: 15px; height: 15px; display: block; margin: 0 auto; }
.chbub-bm.on { color: var(--brand-500); }
.chbub-bm.on svg path { fill: currentColor; }
.chbub-cnt:empty + .chbub-bm { margin-left: auto; }
/* ── 🟢 **말풍선 바닥의 「댓글 N개」 줄** (2026-09-30 · 사용자 요청 「텔레그램처럼」 · board/channel-room.js `chBubCmtHTML`) ─────────────────────
   말풍선 «안»의 맨 아래 한 줄 — 위에 가는 선 · [겹친 얼굴들 또는 말풍선 그림] [댓글 N개(강조색)] [빨간 숫자] [›].
   🔴 선은 말풍선의 좌우 패딩(12px)을 뚫고 끝까지 간다(음수 margin) — 텔레그램의 그 줄처럼 «말풍선의 바닥 칸»으로 읽혀야 한다.
   🔴 줄 자체는 문이 아니다(말풍선이 문이다 · `data-bfind`) — 누르는 느낌만 말풍선의 `:active` 가 준다. */
.chbub-cmt {
  display: flex; align-items: center; gap: 8px; margin: 8px -12px -10px; padding: 9px 12px;
  border-top: 1px solid var(--line); font-size: 13.5px; font-weight: 700; color: var(--accent);
}
.chbub-cmt.none { font-weight: 600; color: var(--text-sub); }
.chbub-cfs { display: inline-flex; flex: none; }
.chbub-cfs .chbub-cf { width: 22px; height: 22px; margin: 0; border: 1.5px solid var(--surface); border-radius: 50%; }
.chbub-cfs .chbub-cf + .chbub-cf { margin-left: -7px; }
.chbub-ci { display: inline-flex; flex: none; }
.chbub-ci svg { width: 16px; height: 16px; }
.chbub-cl { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chbub-cn {
  flex: none; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; background: var(--hot); color: #fff;
  font-size: 11px; font-weight: 800; font-style: normal; line-height: 18px; text-align: center;
}
.chbub-cgo { margin-left: auto; flex: none; display: inline-flex; opacity: .8; }
.chbub-cgo svg { width: 15px; height: 15px; }
/* 🟢 재게시 머리줄 (139차 요청 4번 · board/repost.js `repostLineHTML`) — 말풍선·목록 카드·글 상세의 맨 위 한 줄 · 작고 옅게 · 누르면 원글 */
.b-rt { display: flex; align-items: center; gap: 5px; margin: 0 0 6px; font-size: 12px; font-weight: 600; color: var(--text-mut); min-width: 0; }
.b-rt svg { width: 13px; height: 13px; flex: none; color: var(--brand-500); }
.b-rt-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.b-rt[role="link"] { cursor: pointer; }
.bcard > .b-rt { margin: 2px 0 6px; }
/* 🟢 재게시의 원글 카드 — 참조 우선 (139차·E 요청 4번 · board/repost.js `repostOrigCardHTML`) · 원글을 알면 그림·제목·발췌·링크 배너가 인용 상자 «안»에 선다.
   그림 격자는 말풍선의 것(`chBubMediaHTML`) 그대로이고 상자 맨 위라 위 여백을 뺀다 · 발췌는 부각판에서 세 줄까지 · 볼 수 없는 원글은 한 줄(`.b-quote-del`) */
.b-rt-orig > .chbub-media { margin: 0 0 8px; }
.b-rt-orig > .b-lp-slim { margin: 6px 0 0; }
.b-rt-x { display: block; margin-top: 3px; color: var(--text-sub); font-size: 12.5px; line-height: 1.45; overflow-wrap: anywhere; }
.b-quote-hero .b-rt-x { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.b-rt-orig.b-quote-gone .b-quote-del { color: var(--text-mut); }
/* 행동 시트 줄 끝의 그림 (`chPostSheet`) */
.row-ic svg { width: 18px; height: 18px; display: block; color: var(--text-sub); }
.chbub .reactbtn.ro, .chbub .reactbtn.ro.on { cursor: default; background: none; border-color: transparent; color: var(--text-mut); height: auto; padding: 0 2px; }
.chbub .reactbar.ro { margin: 0; gap: 5px; }
.chbub .reactbtn.ro b svg { width: 13px; height: 13px; }
.chbub .reactbtn.ro .rcount { font-size: 11.5px; }
/* ── 🟢 **채널 글의 이모지 반응** (2026-09-17 · 132차 요청 7번 · board/reactions.js `emoRowHTML`) ────────────────────────────
 * 알약 = [이모지 수] · 내가 켠 것은 브랜드 테두리·옅은 바탕(라이트·다크 둘 다 토큰에서) · 빈 줄은 자리를 안 먹는다.
 * 팝오버는 몸통 위 fixed 한 줄(텔레그램) · 길게 누르는 말풍선은 글자 고르기·길게 누르기 메뉴를 끈다(`[data-emo]`). */
.emorow { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-top: 8px; }
.emorow:empty { display: none; }
.chbub-foot .emorow, .reactbar .emorow { margin-top: 0; }
.emorow.ro { pointer-events: none; gap: 4px; margin-top: 0; }
.emopill {
  display: inline-flex; align-items: center; gap: 4px; height: 26px; padding: 0 9px 0 7px;
  border-radius: 13px; border: 1px solid var(--line); background: var(--surface-2); color: var(--text-sub);
  font: inherit; font-size: 12.5px; font-variant-numeric: tabular-nums; cursor: pointer;
}
.emopill .emo-e { display: inline-flex; font-weight: normal; line-height: 1; }
/* 🟢 반응 그림은 앱 전용 SVG (2026-09-17 · 133차 요청 2번 · `www/img/reactions` · board/reactions.js `emoImgHTML`) — 크기는 자리마다 여기서 */
.emo-i { display: block; width: 16px; height: 16px; flex: none; -webkit-user-drag: none; pointer-events: none; }
/* 켜는 순간 통통 (144차 · board/reactions.js `doEmo` 의 `.emo-pop`) — 옛 탭 아이콘(`tab-pop` · 2026-09-30 에 `tab-bounce` 로 바뀌었다)과 같은 튕김 곡선 */
.emo-pop .emo-i { animation: emo-boing .44s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes emo-boing { 0% { transform: scale(1); } 30% { transform: scale(1.38) translateY(-2px); } 55% { transform: scale(.88); } 75% { transform: scale(1.08); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .emo-pop .emo-i { animation: none; } }
.emopill i { font-style: normal; }
.emopill.on { border-color: var(--brand-500); color: var(--brand-500); background: color-mix(in srgb, var(--brand-500) 14%, transparent); }
.emorow.ro .emopill { height: 20px; padding: 0 6px 0 4px; font-size: 11.5px; border-color: transparent; }
.emorow.ro .emopill .emo-i { width: 13px; height: 13px; }
.emoadd {
  display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 26px; padding: 0;
  border-radius: 13px; border: 1px solid var(--line); background: none; color: var(--text-sub); cursor: pointer;
}
.emoadd svg { width: 16px; height: 16px; }
.chbub-foot .emoadd { width: 26px; height: 22px; border-color: transparent; }
.chbub[data-emo] { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
/* 🔴 허용 목록이 최대 열둘이라 한 줄(36px × 12)은 폰 폭을 넘는다 — 여섯 칸에서 접는다
   🟢 격자다 (137차 요청 2번) — 칸 수(`--emo-cols`)는 board/reactions.js `openEmoPop` 이 «줄마다 개수가 고르게» 정한다(`EMO_POP_COLS` 6 과 세트) */
.emopop {
  position: fixed; z-index: 960; display: grid; grid-template-columns: repeat(var(--emo-cols, 6), 38px); justify-content: center; gap: 2px; padding: 5px 6px;
  max-width: calc(100vw - 16px);
  border-radius: 22px; background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 6px 24px rgba(0, 0, 0, .28); animation: emo-pop-in .14s ease-out;
}
/* 🟢 길게 누른 말풍선의 한 덩이 — 위는 반응 줄 · 아래는 메뉴(답글·수정·삭제) (2026-10-01 · board/reactions.js `openEmoPop`·`bubMenuHTML`) */
.emowrap {
  position: fixed; z-index: 960; display: flex; flex-direction: column; align-items: center; gap: 6px;
  max-width: calc(100vw - 16px); animation: emo-pop-in .14s ease-out;
}
.emowrap .emopop { position: static; animation: none; }
/* 🟢 칸 사이·줄 사이의 선은 «틈»이다(바탕이 선 색 · `gap: 1px`) — 좁은 폰·긴 말(스페인어·포르투갈어)에서 줄이 넘치면 넘친 칸이 다음 줄을 채우고
   그 줄 위에도 선이 선다. 옛 판은 왼쪽 선(`+ .emomenu-b`)이라 넘친 칸의 머리에 선이 매달리고 줄 사이에는 선이 없었다(2026-10-01 · 감사). */
.emomenu {
  display: inline-flex; flex-wrap: wrap; justify-content: center; gap: 1px; max-width: 100%; overflow: hidden; border-radius: 14px; background: var(--line); border: 1px solid var(--line);
  box-shadow: 0 6px 24px rgba(0, 0, 0, .28);
}
.emomenu-b {
  flex: 1 1 auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 40px; padding: 0 13px; border: 0; background: var(--surface);
  color: var(--text); font: inherit; font-size: 13.5px; font-weight: 600; white-space: nowrap; cursor: pointer;
}
.emomenu-b svg { width: 16px; height: 16px; flex: none; }
.emomenu-b.danger { color: var(--hot); }
/* 🟢 길게 누름 메뉴의 «아이콘 + 작은 글자» 칸 (2026-10-24 · 사용자 「C」 · board/reactions.js `bubMenuHTML` 의 `.emomenu.ic`) — 쪽지 메뉴(`openDmPop`)는 옛 가로 글자 칸 그대로다 */
.emomenu.ic .emomenu-b { flex-direction: column; gap: 3px; min-width: 56px; min-height: 54px; padding: 6px 4px; font-size: 10.5px; font-weight: 500; }
.emomenu.ic .emomenu-b svg { width: 20px; height: 20px; }
.emomenu.ic .emomenu-b span { max-width: 64px; overflow: hidden; text-overflow: ellipsis; }
.emomenu-b:active { background: var(--surface-2); }
@media (hover: hover) { .emomenu-b:hover { background: var(--surface-2); } }
@media (prefers-reduced-motion: reduce) { .emowrap { animation: none; } }
.emopop-b {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0; border: 0; border-radius: 19px; background: none; cursor: pointer;
}
.emopop-b .emo-i { width: 26px; height: 26px; }
/* 🟢 **채널 반응 고르기 격자의 바탕** (2026-10-27 · 사용자 실기기 «스티커에 숫자가 왜 적혀 있지» — 🟡 정정: 번호는 «고른 순서» 표시가 맞는데 그 스타일이 이 저장소에 없었다.
   코인스쿱의 `.exgrid`·`.exgrid-cell`·`.exgrid-n`(거래소 편집 시트와 같이 쓰던 규칙)이 이식 때 빠져, 번호가 그림 위에 «줄»로 얹혀 칸 높이가 들쭉날쭉했다 · 쓰는 곳은 `board/profile.js paintEmoSet` 하나).
   🔴 번호는 `position: absolute` 라 칸의 높이를 안 바꾼다 — 켜고 끌 때 격자가 들썩이면 방금 누른 칸이 손가락 밑에서 움직인다. */
.exgrid { display: grid; gap: 8px; margin-bottom: 8px; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); }
.exgrid-cell {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 12px 6px; cursor: pointer; min-width: 0;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--r-sm);
}
.exgrid-cell.on { border-color: var(--brand-500); }
.exgrid-n {
  position: absolute; top: 4px; right: 4px;
  min-width: 18px; height: 18px; padding: 0 4px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-500); color: var(--bg); font-size: 11px; font-weight: 700;
}
/* 🟢 그림이 백스물둘이라 구획으로 나눴다 (2026-09-23) — 🔴 스크롤은 바깥 상자 하나다(`.emoset-scroll`): 구획마다 격자가
   제 스크롤을 가지면(`.exgrid` 의 `max-height`) 상자 안에 상자가 생겨 손가락이 어디를 미는지 모른다 · 아래 「기본으로」가 늘 보이게 높이를 묶는다 */
.emoset-scroll { max-height: 52vh; overflow-y: auto; overscroll-behavior: contain; margin-bottom: 8px; }
.emoset-h { margin: 12px 2px 6px; font-size: 12.5px; font-weight: 700; color: var(--text-sub); }
.emoset-h:first-child { margin-top: 2px; }
.emogrid { grid-template-columns: repeat(auto-fill, minmax(46px, 1fr)); gap: 6px; max-height: none; overflow: visible; }
.emogrid-cell { padding: 8px 4px; }
.emogrid-cell .emo-i { width: 28px; height: 28px; }
.sheet-n { flex: none; font-size: 13px; color: var(--text-sub); font-variant-numeric: tabular-nums; }
.row-val.emo-prev { display: inline-flex; gap: 3px; }
.row-val.emo-prev .emo-i { width: 18px; height: 18px; }
.emopop-b.on { background: color-mix(in srgb, var(--brand-500) 20%, transparent); }
@keyframes emo-pop-in { from { opacity: 0; transform: translateY(4px) scale(.96); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .emopop { animation: none; } }
@media (hover: hover) { .emopill:hover, .emoadd:hover, .emopop-b:hover { background: var(--surface-2); } .emopill.on:hover { background: color-mix(in srgb, var(--brand-500) 20%, transparent); } }
.b-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.bpost .b-foot-detail { margin-bottom: 8px; }
/* 🔴 **캐러셀 아래 구분선을 되살렸다** (2026-08-19 · 요청 9번). 8/18 밤에 뺐던 것인데
   (그때는 `.b-cmthead` 의 border-top 이었고, 선이 광고를 감싸는 상자의 **아랫변**처럼
   읽혀 광고가 글의 일부로 보였다), 선 없이 여백만 두니 이번엔 반대로 광고와 댓글이
   한 덩어리로 흘러 어디서 글이 끝나는지가 사라졌다.
      **아랫변**이다. 선이 광고 블록을 닫는 것으로 읽히므로 옛 오해가 안 생긴다.
   🔴 `:not(:empty)` 가 핵심이다 — 광고가 없는 기기(구독·무광고)에서 슬롯은 빈 div 로
      남는데, 조건 없이 걸면 아무것도 없는 자리에 선 하나만 떠 있게 된다. */
.bpost #b-promo:not(:empty) {
  margin-top: 8px; padding-bottom: 14px; border-bottom: 1px solid var(--line);
}
.b-chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 700; color: var(--brand-500);
  background: var(--brand-weak); border: 0; border-radius: 6px; padding: 3px 7px 3px 4px;
}

/* 댓글 수 — 🔴 이모지가 아니라 SVG (요청 5번 · board/list.js `CMT_ICON`) */
.b-cmtn, .bhot-cmt {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 12px; color: var(--text-sub);
}
.b-cmtn { margin-left: auto; }
.b-cmtn svg, .bhot-cmt svg { width: 13px; height: 13px; }
.bhot-n { display: inline-flex; align-items: center; gap: 6px; }
.bhot-n i { font-style: normal; }
.bskel {
  height: 84px; border-radius: 12px; background: var(--surface);
  border: 1px solid var(--line); margin-bottom: 10px; opacity: .55;
}

/* 글 상세 */
.bpost .b-body { font-size: 15px; line-height: 1.75; margin: 10px 0 14px; white-space: pre-wrap; }
/* 🔴 번역이 아직 없는 조각 (2026-08-26 · 요청 9번) — 「지워짐」과 다르게 보여야 한다 */
.b-xlate { color: var(--text-mut); font-style: italic; font-size: 14px; }
/* ── 🟢 **「{언어}에서 번역됨」** (2026-09-16 · 사용자 요청 7번 · board-ui `xlateFromHTML`) ────────────────────────
   본문 «맨 위 왼쪽»의 잔글씨 한 줄이다(글 상세 · 댓글). 🔴 색은 문장이 아니라 **언어 이름**에만 — 답글 대상 줄
   (`.x-to b`)이 이름만 브랜드색으로 칠하는 그 규칙과 같은 말이고, 사용자가 가리킨 화면도 그 모양이다.
   🔴 `--brand-500` 이다(팔레트의 노랑) — 새 색을 만들면 라이트 테마 대비를 다시 맞춰야 한다. */
.b-xfrom { margin: 0 0 4px; font-size: 12px; color: var(--text-mut); }
.b-xfrom b { color: var(--brand-500); font-weight: 700; }
/* ── 🟢 **구분점 + 「원문 보기」** (2026-09-16 · 사용자 요청 2번 · `toggleOriginal`) ──────────────────────────
   🔴 잔글씨 줄 «안»의 버튼이라 크기·색이 그 줄을 안 넘는다 — 눌리는 것임을 말하는 것은 밑줄 하나다
      (알약·테두리를 주면 본문보다 먼저 읽힌다).
   🔴 손가락 자리는 글자보다 넓다(위아래 padding) — 12px 글자를 정확히 찍게 만들지 않는다. */
.b-xdot { padding: 0 4px; opacity: .55; }
.b-xorig {
  background: none; border: 0; padding: 2px 0; margin: 0;
  font: inherit; color: var(--text-sub); text-decoration: underline; cursor: pointer;
}
.b-xorig.load { opacity: .5; pointer-events: none; }
.b-cmthead {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 13px; font-weight: 700; color: var(--text-sub);
  padding-top: 4px; margin-top: 18px;
}
/* ── 🔴 **댓글 머리의 오른쪽 도구 — [정렬][찾기]** (2026-09-02 · 65차·A 요청 4번) ────────────
 * 🔴 둘을 **한 상자로 묶는다**: `.b-cmthead` 는 `space-between` 이라 자식이 셋이 되면
 *    정렬 버튼이 줄 가운데로 떠 버린다(개수 라벨과 돋보기 사이에 낀다). 묶으면
 *    「왼쪽=이 목록이 무엇인가 · 오른쪽=이 목록을 어떻게 볼까」로 읽힌다.
 * 🔴 정렬은 **글자 + 아래 화살표**다: 지금 무엇으로 정렬돼 있는지가 화면에 적혀 있어야
 *    한다(아이콘만 두면 눌러 본 사람만 아는 문이 된다 — 앱바 「초안」과 같은 판단). */
.b-cmtsect { justify-content: space-between; }
.b-cmttools { flex: none; display: flex; align-items: center; gap: 2px; }
.b-cmtsort {
  flex: none; display: inline-flex; align-items: center; gap: 2px;
  background: none; border: 0; padding: 4px 2px;
  color: var(--text-mut); font-size: 12px; font-weight: 700; letter-spacing: 0;
  white-space: nowrap; cursor: pointer;
}
.b-cmtsort i { display: inline-grid; place-items: center; }
.b-cmtsort svg { width: 13px; height: 13px; display: block; }
/* 지금 고른 정렬 — 시트의 그 줄에 ✓ 와 함께 테두리로도 표시한다(✓ 하나는 눈에 잘 안 걸린다) */
.row.on { border-color: var(--brand-500); }
.b-cmt { padding: 10px 2px; border-bottom: 1px solid var(--line); font-size: 14px; }
.b-cmt p { margin: 4px 0 0; line-height: 1.6; white-space: pre-wrap; }
/* ── 🟢 **댓글은 하나의 게시물이다** (2026-10-03 · board/detail.js `cmtHTML`·`cmtFocusHTML`·`renderBoardCmt`) ─────────────────────────
   본문을 누르면 그 댓글의 화면(`bcmt`)이 열린다 — 누르는 문이라는 표시는 손가락 모양 하나다(밑줄·색은 안 바꾼다: 본문은 글이다).
   `.b-reto` 는 본문 위 「{n} 님에게 댓글」 한 줄(답한 상대가 원 댓글이 아닐 때) · `.b-focus` 는 댓글 화면 가운데 카드(글자가 글 본문 크기) ·
   `.xchain` 은 그 위의 부모 사슬(댓글 작성 화면의 원글 줄 `.xrow` 와 같은 조각 · 여기서는 누르는 문이라 `pointer-events` 를 되살린다). */
.b-cmt p[data-bcmt-open] { cursor: pointer; }
.b-reto { margin: 2px 0 0; color: var(--text-mut); font-size: 12.5px; }
.b-reto b { color: var(--brand-500); font-weight: 600; }
.b-cmt.b-focus { border-bottom: 1px solid var(--line-2); padding-bottom: 12px; font-size: 15px; }
.b-cmt.b-focus > p { font-size: 16.5px; line-height: 1.55; margin-top: 8px; }
.b-cmt.b-focus .b-meta { font-size: 14px; }
.xchain { padding: 10px 2px 0; }
.xchain .xrow-chain { pointer-events: auto; cursor: pointer; padding-bottom: 6px; }
.xchain .xrow-chain .x-body { font-size: 14px; color: var(--text-sub); }
.xchain .x-body-3 { -webkit-line-clamp: 3; }
.xchain .xrow-post .x-body b { color: var(--text); }
.bcmtv .b-cmtreplies .b-cmt:last-child { border-bottom: 0; }

/* ── 🟢 **«남은» 글자 수는 입력상자 «안» 오른쪽 아래 구석** (2026-10-04 · 사용자 결정 「100 하나만 적고 줄어드는 방식으로 구석에」 · board/compose.js `paintCount` · 10-03 의
   「n/N」 알약은 전송 버튼 옆 어중간한 자리에 섰다) ── 넷이 같은 자리다 — 글쓰기 본문(`.b-inwrap`) · 댓글 작성(`.b-inwrap`) · 스레드 입력줄(`.dm-inwrap`) · 채널 글
   입력줄(`.dm-inwrap`). 상자가 `position: relative` 라 절대 좌표로 붙이고, 바탕 없이 작게(구석의 숫자 하나) · 넘치면(`.over`) 빨간 음수가 되고 칸 테두리도 같은 색이다. */
.b-inwrap > .b-count, .dm-inwrap > .b-count {
  position: absolute; right: 8px; bottom: 5px; margin: 0; z-index: 2; pointer-events: none;  font-size: 10px; line-height: 1; padding: 0; opacity: .75;
}
.dm-inwrap > .b-count { right: 9px; bottom: 3px; font-size: 9.5px; }

/* ── 🟢 **입력칸의 ✕ (비우기)** (2026-10-04 · 사용자 요청 「입력창들에 클리어 버튼」 · board/toolbar.js `inClearHTML`) ── textarea 바로 뒤의 형제라 `:placeholder-shown`
   (빈 칸)·`:disabled` 가 숨긴다 — JS 없이 값이 바뀌어도(초안 되꽂기·보낸 뒤 비우기) 따라온다. 자리는 오른쪽 «위»(첫 줄 가운데 · 알약 40px 기준) · 숫자는 오른쪽 아래. */
.b-inwrap > .in-x, .dm-inwrap > .in-x {
  position: absolute; right: 6px; top: 8px; width: 24px; height: 24px; padding: 0; z-index: 3; cursor: pointer;
  border: 0; border-radius: 50%; background: color-mix(in srgb, var(--text-mut) 22%, transparent); color: var(--text-sub);
  font-size: 11px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; -webkit-tap-highlight-color: transparent;
}
.b-inwrap > .in-x { right: 8px; top: 8px; }
.b-inwrap > .in-x:active, .dm-inwrap > .in-x:active { background: color-mix(in srgb, var(--text-mut) 40%, transparent); }
.b-inwrap > textarea:placeholder-shown + .in-x, .dm-inwrap > textarea:placeholder-shown + .in-x,
.b-inwrap > textarea:disabled + .in-x, .dm-inwrap > textarea:disabled + .in-x { display: none; }
.b-count.over, .b-count .b-count-over { color: var(--hot); font-weight: 800; }
.b-bodyin.over, .dm-in.over { border-color: var(--hot); }
/* X 모양 작성 화면의 테두리 없는 칸 — 글자 수가 마지막 줄을 덮지 않게 아래 여백 (옛 `8px 0 12px`) */
.bform.xform .b-inwrap .b-bodyin, .bform .xrow-me .b-inwrap .b-bodyin { padding-bottom: 26px; }
.b-cmt.bdim { display: block; width: 100%; text-align: left; background: none; border: 0; color: var(--text-mut); font-size: 13px; }
/* ── 🔴 댓글 작성은 **화면**이다 (2026-08-19 · 요청 16번) ──────────────────────
   옛 붙박이 입력줄(`.b-cmtrow` · sticky + `--kb` + 4줄 자동 성장 + 정사각 보내기 버튼)은
   통째로 지웠다. 좁은 한 줄에 입력·답글 대상·멘션 후보·글자 수·보내기가 다 얹혀 있었고,
   그 자리를 만들려고 키보드 높이까지 계산해야 했다.
   지금은 상세 오른쪽 아래 **FAB**(`.bfab-cmt`)이 `bcmtwrite` 화면을 연다 —
   목록의 글쓰기 FAB 와 같은 자리·같은 몸짓이다. 🔴 되살리지 말 것: 입구가 둘이면
   두 곳이 같은 상태(`replyTo`)를 만져 한쪽이 다른 쪽을 덮는다.

   ── 댓글 작성 화면 ──────────────────────────────────────────────────────────
   🔴 입력칸 높이를 **JS 가 아니라 CSS 가** 정한다. 화면이 통째로 그 칸이므로 늘릴
      이유가 없다 — 처음부터 넓게 열어 두는 쪽이 300자를 쓰는 사람에게 맞다.
   🔴 선택자가 **세 겹**인 이유: `.bform .b-bodyin { min-height: 190px }` 가 이 파일
      **뒤쪽**에 있어서, 두 겹(0,2,0)으로는 동률에 순서로 진다 — 그러면 이 줄이 조용히
      무시되고 칸이 190px 로 남는다(요청 16번의 요점이 통째로 안 먹는 상태). */
/* 🔴 **28vh 다** (2026-08-19 3차 · 요청 6번 — 40vh 에서 30% 줄였다). 40vh 는 키보드가
   올라온 상태에서 입력칸 아래쪽이 키보드 뒤로 들어가, 자기가 친 글자를 보려면 스크롤을
   해야 했다. 300자는 28vh 로도 다 보인다.
   🔴 이 값과 `#scroll` 의 `--kb` 여백은 **세트다** — 칸만 줄이고 여백을 안 주면 여전히
      덮이고, 여백만 주고 칸이 크면 화면이 불필요하게 길어진다. */
.bcmtform .b-bodyin.b-cmtin-full { min-height: 28vh; }
/* 🔴 **상단 여백** (2026-08-19 · 요청 6번). `#scroll` 의 위 패딩은 0 이고(sticky 헤더가
   그 위로 못 올라가서 · 그쪽 주석), `.board` 도 0 이라 이 화면의 첫 요소는 앱바에 붙어
   있었다. 그래서 `.board > .btrend:first-child` 와 **같은 방식**으로 여기만 준다 —
   `#scroll` 의 패딩을 되살리면 목록 화면에 그 틈이 그대로 돌아온다. */
.bcmtform > *:first-child { margin-top: 10px; }
/* ── 🟢 **X 모양 작성 화면** (2026-09-15 · 사용자 요청 4·5번 · board/comments.js `cmtParentHTML`·`myFaceHTML`) ─────────────────────
   왼쪽 열(`.xgut`)은 얼굴 40px, 오른쪽 열(`.xcol`)이 내용이다. 원글 줄(`.xrow-par`)의 세로 줄이 내 얼굴까지 이어진다 —
   원글과 내 칸 사이에 틈을 두지 않는 것이 그 선의 조건이다(`.x-line` 이 열 끝까지 자란다).
   🔴 입력칸 테두리를 걷는 선택자는 네 겹이다 — `.bform .b-inwrap .b-bodyin`(세 겹)이 뒤쪽에서 padding 을 덮는다. */
.xrow { display: flex; gap: 10px; }
.xgut { flex: none; width: 40px; display: flex; flex-direction: column; align-items: center; }
.xcol { flex: 1; min-width: 0; }
.b-av.x-av { width: 40px; height: 40px; }
.x-av-src { display: grid; place-items: center; color: var(--text-sub); font-size: 16px; font-weight: 800; border: 1px solid var(--line-2); }
/* 🟢 매체 아이콘이 있으면 이니셜 위를 덮는다 (133차·B 요청 3번) — 이니셜과 그림이 한 칸에 겹친다(그림이 죽으면 이니셜이 드러난다) */
.x-av-src.has-ic { color: transparent; overflow: hidden; background: #fff; }
.x-av-src > i, .x-av-src > img { grid-area: 1 / 1; }
.x-av-src > i { font-style: normal; }
.x-av-src > img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.x-line { flex: 1; width: 2px; min-height: 14px; margin: 4px 0 -2px; border-radius: 1px; background: var(--line-2); }
.xrow-par { pointer-events: none; }
.x-head { display: flex; align-items: baseline; gap: 5px; min-width: 0; font-size: 15px; line-height: 1.3; }
.x-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-weight: 800; }
.x-time { flex: none; color: var(--text-mut); font-size: 13.5px; }
.x-bodyrow { display: flex; align-items: flex-start; gap: 10px; margin-top: 3px; }
.x-body {
  flex: 1; min-width: 0; color: var(--text); font-size: 15.5px; line-height: 1.5; white-space: pre-wrap;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 6; overflow: hidden;
}
.x-body b { font-weight: 700; }
.x-thumb { flex: none; width: 92px; height: 92px; border-radius: 14px; object-fit: cover; background: var(--surface-2); }
/* 🟢 입력칸과의 틈을 좁혔다 (2026-09-17 · 131차 요청 7번 — 옛 아래 14px) */
.x-to { margin: 12px 0 4px; color: var(--text-mut); font-size: 14px; }
.x-to b { color: var(--brand-500); font-weight: 600; }
.xform .xrow-me .xgut { padding-top: 2px; }
.bform.xform .b-inwrap .b-bodyin, .bform .xrow-me .b-inwrap .b-bodyin {
  border: 0; border-radius: 0; background: transparent; padding: 8px 0 12px; font-size: 17px; line-height: 1.5;
}
/* 🟢 **초점 테두리를 안 그린다** (2026-09-15 · 사용자 요청 1번) — 전역 `:focus-visible` 노란 테두리(파일 끝 「마우스·키보드 동선」)는
   텍스트 칸에서는 손가락으로 눌러도 켜진다. 테두리 없는 작성 칸에서는 그 선이 곧 «칸의 모양»이 되어 거슬렸다. 캐럿이 초점을 말한다. */
.bform .b-titlein:focus-visible, .bform .b-bodyin:focus-visible { outline: none; }
/* ── 🟢 키보드 위 도구줄 (2026-09-15 · board/toolbar.js `KB_ACC_FIELDS`) — 키보드 윗변에 붙는다. 높이 46px 는 `KB_ACC_H` 와 세트다. */
.kb-acc {
  position: fixed; left: 0; right: 0; bottom: var(--kb, 0px); z-index: 930; height: 46px; box-sizing: border-box;
  display: flex; align-items: center; gap: 2px; padding: 0 10px;
  background: var(--bar-bg); backdrop-filter: blur(12px); border-top: 1px solid var(--line);
  /* 🟢 버튼이 폭을 넘으면 가로로 민다 (2026-10-17 · [링크][인용문] · board/toolbar.js `kbAccEl` 의 `keepFocusTaps(el, true)` 와 세트) */
  overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: none;
}
.kb-acc::-webkit-scrollbar { display: none; }
.kb-acc button {
  flex: 0 0 auto; position: relative;
  /* 🟢 누르는 영역 44px (2026-10-18 · 공통 마감 — 옛 38px) · 줄 높이 46px 안에 들어간다 */
  width: 44px; height: 44px; padding: 0; border: 0; border-radius: 50%; background: none;
  color: var(--text-sub); display: grid; place-items: center; cursor: pointer; -webkit-tap-highlight-color: transparent;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;   /* 길게 누르면 순서 바꾸기 — 웹뷰의 선택·복사 메뉴가 안 뜨게 */
}
.kb-acc button:active { background: var(--surface-2); }
.kb-acc svg { width: 23px; height: 23px; display: block; }
/* 🟢 묶음 버튼 [Aa][사진] — 누르면 위에 팝업이 선다는 표시로 오른쪽 아래에 작은 ▲ (2026-10-18 · board/toolbar.js `toggleAccPop`) */
.kb-acc button.grp::after {
  content: ''; position: absolute; right: 8px; bottom: 9px; width: 0; height: 0;
  border-left: 3px solid transparent; border-right: 3px solid transparent; border-bottom: 4px solid currentColor; opacity: .55;
}
.kb-acc button.grp.open { background: var(--surface-2); }
.kba-aa { font-size: 17px; font-weight: 800; letter-spacing: -.5px; line-height: 1; }
/* 길게 눌러 순서를 바꾸는 동안 — 잡은 버튼은 들리고 나머지는 흐려진다 */
.kb-acc.dragging button:not(.kbadrag) { opacity: .5; }
.kb-acc button.kbadrag { background: var(--surface-2); transform: scale(1.14); box-shadow: 0 4px 14px rgba(0, 0, 0, .35); z-index: 1; }
/* 줄이 폭을 넘을 때만 가장자리가 옅어져 「더 있다」를 알린다 (board/toolbar.js `syncAccFade`) */
.kb-acc.fade-r { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent); }
.kb-acc.fade-l { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 22px), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 22px), transparent); }
.kb-acc.fade-l.fade-r { -webkit-mask-image: linear-gradient(to right, transparent, #000 22px, #000 calc(100% - 22px), transparent); mask-image: linear-gradient(to right, transparent, #000 22px, #000 calc(100% - 22px), transparent); }
/* 🟢 묶음 팝업 — 아이콘 + 글자 (시트 스크림 940 아래 · 도구줄 930 위) */
.kb-pop {
  position: fixed; z-index: 935; display: flex; flex-direction: column; padding: 6px; min-width: 150px; max-width: calc(100vw - 16px); box-sizing: border-box;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: 14px; box-shadow: 0 8px 24px rgba(0, 0, 0, .38);
}
.kb-pop[hidden] { display: none; }
.kb-pop button {
  display: flex; align-items: center; gap: 12px; height: 44px; padding: 0 16px 0 10px; border: 0; border-radius: 10px; background: none;
  color: var(--text); font-size: 15px; white-space: nowrap; text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.kb-pop button:active { background: var(--surface-2); }
.kb-pop svg { width: 22px; height: 22px; flex: none; display: block; color: var(--text-sub); }
html.kb-acc-on .board.bform { padding-bottom: calc(var(--tab-h, 56px) + 24px + 46px); }
.bform .xrow-me .b-titlein {
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent;
  padding-left: 0; font-size: 18px; font-weight: 700;
}
.bform .xrow-me .b-titlerow { flex-wrap: wrap; align-items: center; }
.bform .xrow-me .b-titlerow .b-titlein { flex: 1 1 150px; min-width: 0; }
/* ── 글자 수 (2026-08-18 밤 · 요청 9번 → 🟢 2026-10-04 «남은 수» 하나 — board/compose.js `paintCount`) ────────────── */
.b-count {
  text-align: right; font-size: 11.5px; color: var(--text-mut);
  font-variant-numeric: tabular-nums; margin-top: 4px;
}
.b-count:empty { display: none; }

.stk { pointer-events: none; user-select: none; -webkit-user-select: none; }
.stk.stk-a { pointer-events: auto; cursor: pointer; -webkit-touch-callout: none; }
.stk-s { display: inline-block; width: 28px; height: 28px; vertical-align: -9px; margin: 0 1px; }
.stk-m { display: block; width: 120px; height: 120px; margin: 4px 0; }
.dmb .stk-m { width: 96px; height: 96px; }
.b-inwrap { position: relative; margin-bottom: 10px; }
/* 🔴 세 겹이다 — 뒤쪽의 `.bform .b-bodyin`(padding·margin·resize)을 이겨야 한다(`.bcmtform` 의 그 함정과 같다) */
.bform .b-inwrap .b-bodyin { margin-bottom: 0; padding-right: 44px; padding-bottom: 36px; resize: none; display: block; }
/* 쪽지 입력줄 — 상자가 `flex: 1` 을 갖고 textarea 는 그 안에서 100% (`.dm-in` 의 `min-width: 0` 규칙은 그대로) */
.dm-inwrap { position: relative; flex: 1; min-width: 0; display: flex; }
.dm-inwrap .dm-in { padding-right: 40px; }
/* ── 시트 — 머리줄 오른쪽 끝 [소형|중형] (69차·B 요청 3번) · 본문은 세트별 구획 (요청 2번) ── */
.stk-head { margin-bottom: 2px; }
.stk-seg { flex: none; display: inline-flex; border: 1px solid var(--line-2); border-radius: 999px; padding: 2px; background: var(--surface); }
.stk-segbtn {
  border: 0; background: none; color: var(--text-mut); font-size: 12px; font-weight: 700;
  padding: 4px 10px; border-radius: 999px; cursor: pointer;
}
.stk-segbtn.on { background: var(--brand-500); color: var(--on-brand); }
/* 최근 사용 — 세그 왼쪽 끝의 그림 버튼 (2026-09-03 · 73차 요청 7번 · board/toolbar.js `STK_RECENT_ICON`) */
.stk-segbtn.stk-segico { display: inline-flex; align-items: center; padding: 3px 8px; }
.stk-segbtn.stk-segico svg { width: 15px; height: 15px; }
.stk-scroll { max-height: 52vh; overflow-y: auto; padding-bottom: 8px; }
/* 🔴 세트 머리는 **스크롤 안에 붙는다** (2026-09-03 · 70차 요청 — 팩마다 세트가 둘에서 여섯이 되면서
   한 팩이 72장이다). 안 붙이면 훑는 도중에 「지금 어느 세트인가」가 화면에서 사라지고, 그러면 세트로
   나눈 이유가 없어진다. 🔴 배경은 시트와 같은 `--surface` 여야 한다(`#sheet` 의 그 값 · 투명하면 밑에서
   올라오는 그림이 글자를 뚫고 지나간다). */
.stk-sect {
  position: sticky; top: 0; z-index: 1; background: var(--surface);
  margin: 10px 0 6px; padding: 4px 0 3px; font-size: 12px; font-weight: 700; color: var(--text-mut);
}
.stk-grid { display: grid; gap: 8px; }
.stk-grid-s { grid-template-columns: repeat(6, 1fr); }
.stk-grid-m { grid-template-columns: repeat(3, 1fr); }
.stk-cell {
  padding: 4px; border: 1px solid transparent; border-radius: 12px; background: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.stk-cell img { width: 100%; height: auto; display: block; }
.stk-cell:active { background: var(--surface-2); border-color: var(--line-2); }
/* 🟢 시트가 안 닫히므로(132차·B 요청 3번 · board/toolbar.js `pickStickerInSheet`) «들어갔다»를 칸이 말한다 */
.stk-cell.stk-picked { animation: stk-picked .42s ease-out; }
@keyframes stk-picked {
  0% { transform: scale(.86); background: color-mix(in srgb, var(--brand-500) 22%, transparent); }
  100% { transform: scale(1); background: transparent; }
}
@media (prefers-reduced-motion: reduce) { .stk-cell.stk-picked { animation: none; } }
/* 상한 가까이 — 여기서부터는 "남았다" 가 아니라 "곧 막힌다" 다 */
.b-count.near { color: var(--hot); font-weight: 700; }

/* ── 🔴 **글쓰기 미리보기** (2026-08-20 · 요청 6번 · board/compose.js `toggleBodyPreview`) ──────
   🔴 버튼은 **오른쪽 끝, 글자만**이다. 등록(`.b-submit`)과 나란한 알약으로 만들면 둘 중
      무엇이 「끝내는 버튼」인지가 흐려진다 — 여기는 보조 동작이라 무게가 달라야 한다.
   🔴 미리보기 상자는 **입력칸이 있던 자리와 최소 높이가 같다**(`--prev-min`). 짧은 글에서
      상자가 확 줄면 아래 버튼들이 위로 튀어 올라, 누르려던 자리가 바뀐다. */
#appbar [data-bprev] span, #appbar2 [data-bprev] span { color: var(--brand-500); }
.b-preview {
  --prev-min: 120px;
  min-height: var(--prev-min); margin-top: 6px; padding: 12px;
  border: 1px dashed var(--line); border-radius: var(--r-md);
  background: var(--surface);
}
/* 🔴 미리보기 안은 상세와 **같은 클래스**를 쓴다 — 여기서 따로 꾸미면 그만큼 거짓말이 된다.
   제목만 상세(`.bpost .b-title`)의 크기를 빌려 온다(그 규칙은 `.bpost` 안에서만 산다). */
.b-preview .b-title { font-size: 17px; margin: 8px 0 4px; }
.b-preview .empty { padding: 24px 0; }

.kwchip, .btag {
  padding: 2px 0; margin-right: 10px; border: 0; background: none; cursor: pointer;
  color: var(--tagc); font-size: 13px; font-weight: 700;   /* 금색 고정 — `:root` 의 `--tagc` 머리 문단 */
}
.kwchip:active, .btag:active { opacity: .6; }

/* 댓글 검색 결과 카드 (2026-08-18 밤 · 요청 1번 · board/search.js `commentResultCardHTML`)
   🔴 글 카드와 **구별돼야 한다** — 제목이 없고 본문 한 줄뿐이라, 같은 모양이면
      "제목이 빠진 글" 로 읽힌다. 왼쪽에 인용 선을 둬 "누가 한 말" 로 보이게 한다. */
/* ── 🔴 가운데 팝업 (2026-08-18 밤 · 요청 6·7번 · app.js `centerPopup`) ──────────
   시트와 **역할이 다르다**: 시트는 고르기, 팝업은 **답 받기**다. 되돌릴 수 없는 동작
   (탈퇴·로그아웃)과 증빙이 남는 동작(마케팅 동의) 앞에서 화면 한가운데를 막는다. */
/* 🔴 **시트 위에 뜬 팝업은 자기 스크림을 안 깐다** (2026-08-21 · services/popup.js 주석).
   시트 스크림(45%)과 겹쳐 70% 넘게 어두워지면 아래 시트가 닫힌 것처럼 보인다 —
   실기기에서 「시트가 닫히면서 팝업이 떴다」로 관찰된 것이 이것이다. */
.pop.pop-over { background: rgba(0, 0, 0, .12); }
.pop {
  position: fixed; inset: 0; z-index: 980;
  display: grid; place-items: center; padding: 24px;
  /* 🔴 **팝업도 키보드를 피한다** (2026-08-25 · 요청 2번 — 「다른 모든 시트도 점검」).
     입력칸이 있는 팝업이 실제로 있다(이메일 로그인 · `services/popup.js` 의 `o.input`).
     시트(`#sheet-scrim`)와 **같은 처방**을 쓴다 — 두 겹의 오버레이가 키보드를 다르게
     다루면 같은 앱에서 두 규칙이 생긴다. */
  padding-bottom: calc(24px + var(--kb, 0px));
  background: rgba(0, 0, 0, .5); opacity: 0; transition: opacity .18s;
  /* 🔴 **카드가 화면보다 크면 스크롤한다** (2026-09-01 · 차단 팝업 실측).
     본문이 긴 팝업이 실제로 생겼다 — 차단 고지(가리기의 범위 + 운영팀에 무엇이 가나)가
     선택 셋과 같이 서면 **320×568**(iOS 15 최소 기기)에서 영어·일본어가 여유 8px 이다.
     🔴 그리고 `place-items: center` 만 있으면 넘칠 때 **위쪽이 못 닿는 곳으로 간다** —
        스크롤 가능 영역에 끝 방향만 들어가는 것이 CSS 규칙이라, `overflow` 만 켜도
        잘린 위쪽은 영영 안 보인다. 카드의 `margin: auto` 가 그 짝이다(아래 주석). */
  overflow-y: auto;
}
.pop.show { opacity: 1; }
.pop-card {
  /* 🔴 **`margin: auto` 가 `place-items: center` 의 «안전한» 짝이다** (2026-09-01).
     auto 마진은 정렬 속성보다 우선하고, 컨테이너의 시작 변 밖으로는 절대 밀지 않는다 —
     들어갈 때는 지금과 똑같이 가운데에 서고, 넘칠 때는 위가 잡혀 스크롤로 닿는다.
     🔴 `safe center` 로 안 쓴다 — iOS 15 를 지원하는데 그 키워드는 그 시절 사파리에서
        믿을 수 없다(`IPHONEOS_DEPLOYMENT_TARGET = 15.0`). */
  margin: auto;
  width: 100%; max-width: 320px; padding: 22px 20px 16px;
  background: var(--surface); border-radius: 18px; box-shadow: var(--sh-2);
  transform: scale(.94); transition: transform .18s;
}
.pop.show .pop-card { transform: scale(1); }
.pop-card h3 { margin: 0 0 8px; font-size: 17px; font-weight: 800; letter-spacing: -.3px; }
/* 🔴 `pre-line` — 본문의 줄바꿈(`\n`)을 줄로 그린다 (2026-09-06 · 85차·G 의 홍보 안내가 처음 썼다 — 그 팝업은 2026-09-30 에 걷었고 규칙은 남긴다:
   줄바꿈이 든 본문은 줄로 서고, 한 문단짜리는 그대로다). */
.pop-sub { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--text-sub); white-space: pre-line; }
/* 🟢 글자 링크 팝업의 주소 (2026-10-17 · board/body.js `openTextLink`) — 다 보인다 · 긴 주소는 아무 데서나 꺾는다 · 너무 길면 상자 안에서 내린다 */
.pop-url { margin: 4px 0 0; padding: 10px 12px; border-radius: 10px; background: var(--surface-2); color: var(--text); font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; word-break: break-all; max-height: 30vh; overflow-y: auto; user-select: text; -webkit-user-select: text; }
.pop-in { width: 100%; margin-top: 14px; }
.pop-btns { display: flex; gap: 8px; margin-top: 18px; }
/* ── 🔴 **「다시 묻지 않기」 줄** (2026-08-25 · 요청 1번 · services/popup.js) ────────────
   🔴 버튼 줄 «위»다 — 아래에 두면 확인을 누르러 가는 손가락 경로에 걸려 오탭이 난다.
   🔴 네이티브 체크박스가 아니라 버튼이다(그쪽 주석) — 네모는 CSS 로 그린다. */
.pop-check {
  display: flex; align-items: center; gap: 8px; width: 100%;
  margin: 14px 0 0; padding: 6px 2px; border: 0; background: none; cursor: pointer;
  font-size: 12.5px; font-family: inherit; color: var(--text-sub); text-align: left;
}
.pop-checkbox {
  flex: none; width: 17px; height: 17px; border-radius: 5px;
  border: 1.5px solid var(--line-2); background: var(--surface-2);
  display: inline-flex; align-items: center; justify-content: center;
}
.pop-check[aria-pressed="true"] { color: var(--text); }
.pop-check[aria-pressed="true"] .pop-checkbox { background: var(--brand-500); border-color: var(--brand-500); }
.pop-check[aria-pressed="true"] .pop-checkbox::after {
  content: ''; width: 9px; height: 5px; margin-top: -2px;
  border-left: 2px solid #0d1117; border-bottom: 2px solid #0d1117; transform: rotate(-45deg);
}
/* 🔴 `.btn` 을 안 쓴다 — 그 클래스는 `width: 100%` 라 flex 줄에서 한쪽이 통째로 자란다
   (댓글 보내기·닉네임 저장에서 같은 사고가 두 번 났다). 여기서 크기·색을 직접 준다. */
.pop-btn {
  flex: 1; height: 44px; border: 0; border-radius: 12px;
  background: var(--surface-2); color: var(--text); font-size: 14.5px; font-weight: 700;
  font-family: inherit; cursor: pointer;
}
.pop-btn.primary { background: var(--brand-500); color: var(--on-brand); }
/* 🔴 **선택지 팝업의 버튼은 「고르는 것」이지 「확인」이 아니다** (2026-08-20 · 요청 3번).
   사진 출처·팔로우 모드처럼 세로로 서는 버튼(`.pop-photo`)은 전부 primary 로 그리면
   노란 덩어리 넷이 되어 무엇이 기본인지가 사라진다. 바탕은 중립으로 두고 **글자만**
   브랜드 색으로 — 고를 수 있는 것들이라는 사실은 색 하나로 충분하다. */
.pop-photo .pop-btn { background: var(--surface-2); color: var(--brand-500); }
.pop-photo .pop-btn.on { background: var(--brand-weak); box-shadow: inset 0 0 0 1.5px var(--brand-500); }
.pop-photo .pop-btn.danger { background: var(--surface-2); color: var(--hot); }
.pop-photo .pop-btn .pop-ic { color: var(--brand-500); }
.pop-btn.danger { background: var(--hot); color: #fff; }
.pop-btn:active { transform: scale(.975); }

/* ── 🔴 「내 댓글」에서 들어왔을 때의 표시 (2026-08-20 · 요청 5·6번) ────────────────
   🔴 스크롤만 하면 어느 줄이 내 것인지 여전히 못 찾는다 — 잠깐 테두리를 준다.
      2초 뒤 클래스가 걷힌다(board/comments.js `scrollToFocusCmt`).
   🔴 원글 제목(`.b-onpost`)은 **인용 무늬**다: 왼쪽 선 + 흐린 글자. 제목처럼 굵게 쓰면
      내가 쓴 댓글보다 그쪽이 먼저 읽힌다 — 그 줄은 맥락이지 내용이 아니다. */
.b-cmt.hl { animation: hl-ring 1.6s ease-out; border-radius: 8px; }
@keyframes hl-ring { 0%, 40% { box-shadow: 0 0 0 2px var(--brand-500); } 100% { box-shadow: none; } }

.b-cmt.b-op { border-bottom: 0; }
/* 🟢 **뉴스·발언 상세의 댓글 스레드** (2026-10-25 · 코인스쿱 뉴스 상세 이식 · board/news-comments.js `newsThreadHTML`) — 댓글 줄은 게시글 상세의 `.b-cmt` 그대로,
   여기는 상세 본문과 떨어져 보이게 하는 여백과 «본문을 누르면 그 글·댓글로 간다»는 신호뿐이다. 아래 여백은 댓글 쓰기 FAB 이 마지막 줄을 덮지 않게. */
.news-thread { margin-top: 18px; padding-top: 10px; border-top: 1px solid var(--line); padding-bottom: 84px; }
.news-thread .sect { padding-top: 4px; }
.news-thread .empty.small { padding: 10px 4px 4px; }
/* 🟢 포인트 상세의 댓글 = 영상 방(`duel.js renderPoint` · 2026-10-28) — 상세 본문(`.pt-art`)과 떨어져 보이게 하는 여백뿐, 댓글 줄은 맞대결·영상 화면의 `.cms` 그대로 */
.pt-cm { margin-top: 6px; padding-top: 10px; border-top: 1px solid var(--line); }
.pt-cm .sect { padding: 4px 0 8px; }
#news-thread #b-cmts .b-cmt > p { cursor: pointer; -webkit-tap-highlight-color: transparent; }
#news-thread #b-cmts .b-cmt > p:active { opacity: .6; }

.b-onpost {
  margin: 2px 0 6px; padding-left: 8px; border-left: 2px solid var(--line-2);
  font-size: 12px; color: var(--text-mut);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── 🔴 **글이 든 자리는 낱말을 안 쪼갠다** (2026-08-20 · 요청 3번) ────────────────────
   토스트에서 시작된 규칙이지만(common.css `#toast`) 같은 일이 팝업·시트·안내문에서도
   난다 — 「주세요」가 「주세」+「요」로 갈리는 그 모양이다.
   🔴 그래서 **글이 흐르는 자리 전부**에 한 번에 건다. 화면마다 찾아다니지 않는다:
      새 팝업·새 시트를 만들어도 이 규칙을 저절로 받는다.
   🔴 `overflow-wrap: anywhere` 를 같이 준다 — 낱말을 안 쪼개는 규칙만 두면 아주 긴 */
.pop-card h3,
.pop-sub,
.pop-btn,
.sheet-title,
.sheet-list li,
.hint,
.opt,
.empty {
  word-break: keep-all; overflow-wrap: anywhere; text-wrap: pretty;
}

/* ── 🔴 사진 출처 팝업 (2026-08-20 · 요청 7번 · board/media.js `askPhotoSource`) ──────────
   버튼 셋이 **세로로** 선다. 가로로 두면 아이콘+글자가 44px 안에서 줄바꿈된다.
   🔴 타이틀이 없으므로 카드 위쪽 여백을 줄인다 — 제목 자리를 비워 두면 팝업이 떠 보인다.
   🔴 두 갈래(카메라·앨범)는 **같은 색**이고 취소만 빨강이다: 색이 「기본 선택」을
      만들면 안 되고, 되돌리는 길만 색으로 구별한다(요청). */
.pop-card.pop-photo { padding: 14px; display: flex; flex-direction: column; gap: 8px; }
.pop-photo .pop-btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 50px; flex: none; width: 100%;
}
.pop-ic { display: inline-flex; }
.pop-ic svg { width: 19px; height: 19px; }

/* ── 이메일 발송 안내 (2026-08-18 밤 · 요청 8번 · 체크비 이식) ────────────────
   🔴 **붉은 글씨**가 핵심이다. 매직링크는 실제로 스팸함에 자주 가고, 이 한 줄이
      "메일이 안 왔다" 문의의 대부분을 막는다 — 다른 안내와 같은 회색이면 그냥 지나친다. */
.em-ic {
  width: 66px; height: 66px; margin: 4px auto 12px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--brand-weak); color: var(--brand-500);
}
.em-ic svg { width: 32px; height: 32px; }
.em-center { text-align: center; }
.em-spam {
  margin: 12px 0 16px; padding: 10px 12px; border-radius: 10px;
  background: color-mix(in srgb, var(--hot) 14%, transparent);
  color: var(--hot); font-size: 12.5px; font-weight: 700; line-height: 1.5;
}

.bcmtcard { border-left: 3px solid var(--line-2); }
.bcmtcard .b-ex { margin-top: 4px; font-size: 14px; color: var(--text); }
/* ── 🟢 **댓글 검색 결과의 «어느 글인가» 줄** (2026-09-22 · 사용자 요청 3번 · board/search.js `commentResultCardHTML`) ──
 * 🔴 **댓글보다 작고 흐리다** — 이 줄은 주인공이 아니라 «자리 표시»다. 제목이 댓글만큼 진하면 결과 목록이
 *    글 목록처럼 읽혀서, 정작 찾은 댓글이 부제처럼 보인다(고치려던 문제와 정확히 반대가 된다).
 * 🔴 한 줄로 자른다 — 여러 줄이 되면 카드 높이가 제각각이 되어 «훑기»가 다시 어려워진다. */
.bcmt-src {
  display: flex; align-items: center; gap: 4px; margin-bottom: 6px;
  font-size: 11.5px; color: var(--text-mut); min-width: 0;
}
.bcmt-src-ic { flex: none; opacity: .7; }
.bcmt-src-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.empty.small { padding: 28px 12px; font-size: 13px; }

/* 글쓰기 */
.bform .b-titlein, .bform .b-bodyin {
  width: 100%; background: var(--surface); border: 1px solid var(--line-2); border-radius: 10px;
  padding: 11px 12px; color: var(--text); font-size: 15px; margin-bottom: 10px;
}
/* 🔴 글쓰기 화면의 **첫 요소는 앱바에서 10px 띄운다** (2026-08-20 · 요청 4번).
   제목칸이 툴바에 붙어 보였다. 🔴 `.b-titlein` 이 아니라 **첫 자식**에 준다 — 인용해서
   쓰면 첫 요소가 인용 카드라, 제목칸만 고치면 그 경우에 여백이 다시 사라진다. */
.bform > *:first-child { margin-top: 10px; }
.bform .b-bodyin { min-height: 190px; line-height: 1.65; resize: vertical; }
.bform ::placeholder { color: var(--text-mut); }
.b-kwrow { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
.b-kwin {
  background: var(--surface); border: 1px dashed var(--line-2); border-radius: 8px;
  padding: 6px 9px; color: var(--text); font-size: 13px; width: 130px;
}
.b-submit, .b-login {
  width: 100%; background: var(--brand-500); color: var(--on-brand); border: 0; border-radius: 12px;
  padding: 13px; font-size: 15px; font-weight: 700;
}
.b-submit:active { transform: scale(.985); }

/* 인용 카드 (작성 화면·글 상세) */
.b-quote {
  position: relative; display: block; background: var(--surface-2); border: 1px solid var(--line);
  border-left: 3px solid var(--qc); border-radius: 10px; padding: 9px 30px 9px 12px;
  margin-bottom: 10px; font-size: 13px;
  /* 🟢 **축 색은 카드 «자신»의 변수 하나다** (2026-09-18 · 145차 요청 6번 · `:root` 의 `--q-*` 머리 문단) —
     선과 라벨이 같은 값을 보므로 갈릴 수가 없고, 축을 더할 때 늘어나는 규칙이 한 줄이다. */
  --qc: var(--q-news);
}
.b-q-post { --qc: var(--q-post); }
.b-q-cmt { --qc: var(--q-cmt); }
.b-quote-tag { display: block; font-size: 11px; color: var(--qc); font-weight: 700; }
.b-quote-title { display: block; margin-top: 2px; color: var(--text); line-height: 1.4; }
.b-quote-src { display: block; margin-top: 2px; font-size: 11px; color: var(--text-mut); }
/* 🔴 인용 원본이 지워졌을 때 (2026-08-19 · 요청 5번 · board/quote.js `quoteGone`).
   🔴 제목을 **지우지 않는다** — 인용은 "그때 이 글이 있었다" 는 기록이고, 지우면 남의
      글에서 맥락이 사라진다. 대신 흐리게 하고 지금 상태를 옆에 적는다. */
.b-quote-gone .b-quote-title { opacity: .55; text-decoration: line-through; }
.b-quote-del { display: block; margin-top: 2px; font-size: 11px; color: var(--hot); }
.b-quote-x {
  position: absolute; top: 6px; right: 6px; background: none; border: 0;
  color: var(--text-mut); font-size: 14px; padding: 4px;
}
/* ── 🔴 **인용이 여럿일 때 — 가로 페이징** (2026-09-01 · 58차 요청 1번 · `quotesHTML`) ─────────
 *    (`.b-imgpager`)이 이미 쓰는 그 규약이다: `overflow-x: auto` + `scroll-snap-type: x mandatory`.
 * 🔴 카드 폭이 100% 가 아니라 **88%** 다 — 다음 장의 끝이 살짝 보여야 「옆에 더 있다」가
 *    화면에 나타난다(그게 없으면 가로 스크롤을 아무도 안 한다 · 첨부 사진에서 배운 것).
 * 🔴 카드 자신의 `margin-bottom` 을 여기서 **끈다** — 줄이 이미 아래 여백을 갖는다
 *    (안 끄면 페이저 안에서 카드마다 여백이 붙어 줄 높이가 들쭉날쭉해진다). */
.b-qpager {
  display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory;
  margin-bottom: 10px; padding-bottom: 2px;
  -webkit-overflow-scrolling: touch;
}
.b-qpager > .b-quote {
  flex: 0 0 88%; scroll-snap-align: start; margin-bottom: 0; min-width: 0;
}
.b-qpager > .b-quote .b-quote-title {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.b-qpager::-webkit-scrollbar { height: 4px; }
.b-qpager::-webkit-scrollbar-thumb { background: var(--line); border-radius: 2px; }

/* ── 🔴 **목록판** (2026-09-01 · 58차 추가 요청 3번 · board/quote.js `quoteCardHTML(…, slim)`) ──────
 * 상세와 **같은 조각**이고 크기만 한 칸 줄인다 — 링크 배너의 `slim` 과 같은 처방이다.
 * 🔴 **문이 «없을 때만» 안 눌린다** (2026-09-10 · 112차 요청 9번 — 옛 줄은 「안 눌린다 · 카드 전체가 이미 글을 여는
 *    버튼이라 눌리는 것처럼 보이면 안 된다」였다). 이 기본값의 `pointer-events: none` 은 이제 **지워짐·가려짐**인
 *    목록판에만 남는다 — 갈 곳이 없으니 탭이 뒤의 카드(그 글)로 떨어진다. 문이 있으면 아래 `.b-quote-slim.has-go` 가
 *    되살린다. (`.b-cmtquote` 는 여전히 막는다 — 거기는 이미 그 글 안이다.)
 * 🔴 제목은 **한 줄로 자른다** — 목록에서 인용이 두세 줄을 먹으면 그 글의 발췌보다
 *    남의 제목이 더 커진다(카드의 주인공이 바뀐다). */
/* ── 🟢 **게시글 투표 카드** (2026-10-12 · board/poll.js `pollCardHTML`·`openPollSheet`) — 생김새는 인용 카드(`.b-quote`)와 한 벌이고 축 색은 글 색(`--q-post`): */
.b-vt { position: relative; display: block; background: var(--surface-2); border: 1px solid var(--line); border-left: 3px solid var(--q-post);
  border-radius: 10px; padding: 9px 12px; margin-bottom: 10px; font-size: 13px; }
.b-vt .b-quote-tag { color: var(--q-post); }
.b-vt-slim { padding-right: 30px; }
.b-vt-prev .vt-opt { cursor: default; }
.b-vt-slim { pointer-events: none; }
.vt-opts { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.vt-opt { position: relative; display: flex; align-items: center; gap: 8px; width: 100%; min-height: 36px; padding: 6px 10px; border: 1px solid var(--line);
  border-radius: 8px; background: var(--surface); color: var(--text); font-size: 13px; text-align: left; overflow: hidden; }
.vt-opt .vt-bar { position: absolute; left: 0; top: 0; bottom: 0; width: var(--w, 0%); background: var(--q-post); opacity: .16; transition: width .35s ease-out; pointer-events: none; }
.vt-opt .vt-txt { position: relative; flex: 1; min-width: 0; overflow-wrap: anywhere; }
.vt-opt .vt-pct { position: relative; flex: none; font-variant-numeric: tabular-nums; font-size: 12px; color: var(--text-mut); }
.vt-opt.on { border-color: var(--q-post); font-weight: 700; }
.vt-opt.on .vt-pct { color: var(--q-post); }
.vt-opt.sel { border-color: var(--brand-500); box-shadow: inset 0 0 0 1px var(--brand-500); }
.vt-opt[aria-disabled="true"] { opacity: .75; }
.vt-foot { display: flex; align-items: center; gap: 10px; margin-top: 8px; font-size: 11.5px; color: var(--text-mut); }
.vt-foot .vt-go { margin-left: auto; }
.vt-form { display: flex; flex-direction: column; gap: 8px; }
.vt-form .b-titlein { width: 100%; }
.vt-row { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.vt-lab { color: var(--text-mut); flex: none; }
.vt-durs { flex-wrap: wrap; }
/* 🟡 투표 기간 세그먼트 — 코인스쿱 손익 게시판의 세그(`.pnl-seg`)를 빌려 쓰던 것을 이름째 옮겼다(2026-10-22 · 그 규칙을 걷으면서 이 칸이 맨 버튼으로 남아 있었다) */
.vt-seg { display: flex; gap: 3px; padding: 3px; background: var(--line-2); border-radius: 9px; flex: none; }
.vt-segbtn {
  padding: 7px 10px; border: 0; border-radius: 7px; background: transparent;
  color: var(--text-mut); font-size: 12.5px; font-weight: 700; font-family: inherit;
}
.vt-segbtn.on { background: var(--surface); color: var(--text); }
.vt-check { color: var(--text); }
.vt-actions { display: flex; gap: 8px; margin-top: 4px; }
.vt-actions > .btn { flex: 1; width: auto; min-width: 0; }   /* `.b-submit` 의 전체 폭을 두 칸으로 나눈다 — [빼기][붙이기]가 한 줄 */
.vt-add { align-self: flex-start; }
/* 🟢 카드 머리 — 인트로 화면의 그림(`GM_ART` · 바탕은 `.gmi-art` 와 같은 어두운 판)을 작게 + 이름·설명 (2026-10-14 · 사용자 「오프닝 화면의 아이콘으로 꾸며」) */
.b-gm-head { display: flex; align-items: center; gap: 10px; }
.b-gm-head[data-bgm-play] { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.b-gm-head[data-bgm-play]:active .b-gm-art { transform: scale(.96); }   /* 눌림 — 머리도 [플레이]다 (board/game.js `gameCardHTML`) */
.b-gm-art { flex: none; width: 76px; aspect-ratio: 220 / 150; border-radius: 10px; overflow: hidden;
  background: radial-gradient(120% 90% at 50% 0%, #232a38 0%, #12151c 70%); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06); }
.b-gm-art svg { display: block; width: 100%; height: 100%; }
.b-gm-art.sm { width: 40px; border-radius: 7px; }
.b-gm-art.sm * { animation: none !important; }   /* 한 줄판(목록·말풍선)은 멈춘 그림 — 카드가 여럿이면 산만하다 */
@media (prefers-reduced-motion: reduce) { .b-gm-art * { animation: none !important; } }
.b-gm-ti { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.b-gm-name { font-size: 16px; font-weight: 800; letter-spacing: -.2px; color: var(--text); }
.b-gm-desc { font-size: 12px; color: var(--text-mut); line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.b-gm-slim { display: flex; align-items: center; gap: 8px; }
.b-gm-sl { display: flex; align-items: baseline; gap: 6px; min-width: 0; flex-wrap: wrap; }
/* 상위 셋 — 줄마다 [왕관 + 순위 · 네임드와 같은 조각 `.tr-crown`][이름][점수] */
.b-gm .gm-top { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; }
.gm-rank { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 8px; background: var(--surface); }
.gm-rank .tr-no { flex: none; font-style: normal; font-size: 11px; font-weight: 700; border-radius: 999px; min-width: 30px; padding-top: 1px; padding-bottom: 1px; }
.gm-rank-n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.gm-rank-v { flex: none; font-size: 13px; font-variant-numeric: tabular-nums; }
/* 🟢 대회 상금 상자의 순위 메달 (`board/game.js showCupReward` — 상자·덮개는 플러스 선물 `.lvup.gift` 를 그대로 쓴다) */
.cup-medal { width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 30px; font-weight: 800; border: 4px solid #fff8d6; box-sizing: border-box; }
.cup-medal.r1 { background: #ffd24a; color: #6b4600; border-color: #fff3b0; }
.cup-medal.r2 { background: #d5dbe3; color: #3f4854; border-color: #f4f7fb; }
.cup-medal.r3 { background: #d9955a; color: #4f2a0c; border-color: #f3cfa8; }
/* 🟢 대회 줄 (2026-10-17 · `board/game.js cupRowHTML`) — 상금 / 정산 대기 / [보상 받기] */
.b-gm .gm-cup { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 7px 10px; border-radius: 10px; background: color-mix(in srgb, #f0b90b 14%, var(--surface)); border: 1px solid color-mix(in srgb, #f0b90b 40%, transparent); font-size: 12px; }
.gm-cup-ti { flex: none; display: inline-flex; align-items: center; gap: 4px; font-weight: 800; color: #f0b90b; }
.gm-cup-ti svg { width: 15px; height: 15px; flex: none; }   /* 트로피·메달 SVG(board/game.js `CUP_IC`·`MEDAL_IC`) */
.gm-cup-tx { flex: 1; min-width: 0; color: var(--text); line-height: 1.35; }
/* 🟢 [보상 받기] — 글자만 있던 `.b-mini` 라 «눌러야 하나» 헷갈렸다(2026-10-21 · 실기기). 금빛 알약 버튼 + 숨 쉬는 빛으로 «눌러서 받는 것»임을 보인다. 받을 게 있는 줄은 테두리도 한 단계 진하게 */
.b-gm .gm-cup:has(.gm-claim) { padding: 8px 10px 8px 12px; border-color: #f0b90b; background: color-mix(in srgb, #f0b90b 20%, var(--surface)); }
.gm-claim { margin-left: auto; flex: none; padding: 8px 14px; border-radius: 999px; background: #f0b90b; color: #1a1300; font-size: 13px; font-weight: 800; box-shadow: 0 0 0 0 color-mix(in srgb, #f0b90b 55%, transparent); animation: gm-claim-glow 1.8s ease-out infinite; }
@keyframes gm-claim-glow { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, #f0b90b 55%, transparent); } 70%, 100% { box-shadow: 0 0 0 9px transparent; } }
@media (prefers-reduced-motion: reduce) { .gm-claim { animation: none; } }
/* 🟢 [플레이] — 회색 글자뿐인 `.b-mini` 라 카드에서 안 보였다(2026-10-23 · 사용자 「플레이 버튼이 회색이라 눈에 안 띈다」 · 10-15 「잘 안 보인다」 뒤 머리를 누르게 한 데 이어 버튼 자체를 고쳤다).
   브랜드색 알약 + ▶ + 숨 쉬는 빛 — [보상 받기](`.gm-claim`)와 같은 «눌러서 하는 것» 문법이되 색은 테마의 브랜드색(`.btn.primary` 와 같은 글자색). 마감된 판엔 버튼이 없다 */
.b-gm .vt-foot .vt-go:not(.gm-claim) { display: inline-flex; align-items: center; gap: 6px; flex: none; padding: 8px 16px; border-radius: 999px; background: var(--brand-500); color: #0d1117; font-size: 13px; font-weight: 800; letter-spacing: -.1px;
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand-500) 55%, transparent); animation: gm-play-glow 2.2s ease-out infinite; }
.b-gm .vt-foot .vt-go:not(.gm-claim)::before { content: ''; width: 9px; height: 10px; background: currentColor; clip-path: polygon(0 0, 100% 50%, 0 100%); }
.b-gm .vt-foot .vt-go:not(.gm-claim):active { transform: scale(.96); }
@keyframes gm-play-glow { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand-500) 55%, transparent); } 70%, 100% { box-shadow: 0 0 0 8px transparent; } }
@media (prefers-reduced-motion: reduce) { .b-gm .vt-foot .vt-go:not(.gm-claim) { animation: none; } }
.b-gm .vt-foot { flex-wrap: wrap; row-gap: 6px; }   /* 버튼이 커진 만큼 좁은 폭에서 줄이 넘치지 않게 */
.gm-rank-empty { margin: 0; padding: 6px 8px; font-size: 12px; color: var(--text-mut); }
.gm-mine { color: var(--q-post); font-weight: 700; margin-left: auto; }
/* 🟢 게임 고르기 — 두 칸 격자 (2026-10-18 · 그림이 위, 이름·설명이 아래) */
.gm-opts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.gm-opt { display: flex; flex-direction: column; align-items: stretch; gap: 6px; width: 100%; min-width: 0; padding: 8px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2); color: var(--text); text-align: left; cursor: pointer; }
.gm-opt.on { border-color: var(--q-post); box-shadow: inset 0 0 0 1px var(--q-post); }
.gm-opt .gm-opt-art { width: 100%; border-radius: 8px; }   /* 카드·인트로와 같은 `GM_ART`(멈춘 판 `.sm`) — 칸 폭에 맞춘다 */
.gm-opt .gm-nm { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.gm-opt .gm-nm b { font-size: 14px; }
.gm-opt small { color: var(--text-mut); font-size: 11px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* 머리줄 — [제목] …… [기간 칩][빼기][붙이기] · 스크롤해도 머리줄은 남는다 */
/* 🔴 `top: -9px` — 시트의 윗 패딩(8px)은 스크롤 영역 «안»이라 `top: 0` 이면 머리줄 위 틈으로 아래 내용이 비친다(2026-10-18 실기기) */
.sheet-head.gm-head { position: sticky; top: -9px; z-index: 2; background: var(--surface); padding: 11px 0 8px; margin: -9px 0 0; gap: 6px; }
.gm-dur { display: inline-flex; align-items: center; gap: 4px; height: 36px; padding: 0 10px; border: 1px solid var(--line); border-radius: 18px; background: var(--surface-2); color: var(--text); font-size: 13px; font-weight: 700; cursor: pointer; }
.gm-dur svg { width: 16px; height: 16px; display: block; }
.gm-ibtn { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; padding: 0; border: 1px solid var(--line); border-radius: 50%; background: var(--surface-2); color: var(--text); cursor: pointer; }
.gm-ibtn svg { width: 18px; height: 18px; display: block; }
.gm-ibtn.go { border-color: transparent; background: var(--brand-500); color: #0d1117; }
.gm-bar .gm-bar-art { width: 34px; border-radius: 6px; }
.gm-hint { margin: 0; font-size: 12px; color: var(--text-mut); }
/* ── 🟢 **게임 만들기 — 게임 스테이션** (2026-10-28 · 코인스쿱 같은 날 판 이식 · board/game.js `openUgcStation` · `docs/ugc-game.md`) — 게임 시트와 같은 재료(`.vt-form`·`.vt-seg`·`.gm-hint`)에 몇 줄만 더한다(🟡 코인스쿱은 `.pnl-seg` — 이 앱의 같은 규칙 이름은 `.vt-seg` · board/game.js `ugcSegHTML` 머리).
   판(iframe · `games/ugc.js`)은 게임판 고유색(다크) 바탕 — 밝은 테마에서도 판만 어둡다(엔진 판과 같은 규칙). */
.ugc-entry { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--brand-500) 55%, var(--line)); border-radius: 12px;
  background: color-mix(in srgb, var(--brand-500) 10%, var(--surface-2)); color: var(--text); text-align: left; cursor: pointer; }
.ugc-entry .gm-nm { display: flex; flex-direction: column; flex: 1; min-width: 0; line-height: 1.3; }
.ugc-entry .gm-nm b { font-size: 14px; }
.ugc-entry small { color: var(--text-mut); font-size: 11.5px; }
.ugc-entry i { font-style: normal; font-size: 20px; color: var(--text-mut); }
.ugc-entry:active { transform: scale(.99); }
.ugc-lab { margin: 6px 0 0; font-size: 13px; font-weight: 700; color: var(--text); }
.ugc-genres { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.ugc-genre { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding: 8px 10px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2); color: var(--text); text-align: left; cursor: pointer; }
.ugc-genre b { font-size: 14px; }
.ugc-genre small { color: var(--text-mut); font-size: 11px; line-height: 1.3; }
.ugc-genre.on { border-color: var(--q-post); box-shadow: inset 0 0 0 1px var(--q-post); }
.ugc-seg { align-self: stretch; }
.ugc-seg .vt-segbtn { flex: 1; min-width: 0; }
.vt-seg.ugc-seg-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }   /* 고를 값이 넷을 넘으면(테마) 4열 두 줄 */
.ugc-ta { min-height: 96px; resize: vertical; font: inherit; line-height: 1.45; }
.ugc-count { align-self: flex-end; margin-top: -4px; font-size: 11px; color: var(--text-mut); font-variant-numeric: tabular-nums; }
.ugc-left { margin: 4px 0 0; font-size: 12.5px; font-weight: 700; color: var(--q-post); }
.ugc-left:empty { display: none; }
.ugc-note { text-align: center; }
.ugc-go[disabled] { opacity: .5; }
.ugc-busy { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 36px 12px 44px; text-align: center; }
.ugc-busy b { font-size: 15px; }
.ugc-busy small { color: var(--text-mut); font-size: 12px; line-height: 1.4; }
.ugc-spin { width: 34px; height: 34px; border-radius: 50%; border: 3px solid var(--line); border-top-color: var(--brand-500); animation: ugc-spin 1s linear infinite; }
@keyframes ugc-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ugc-spin { animation-duration: 3s; } }
.ugc-prev-ti { margin: 0; font-size: 16px; font-weight: 800; }
.ugc-stage { position: relative; height: min(56vh, 480px); border-radius: 12px; overflow: hidden; background: #12151c; }
.ugc-howto { margin: 0; font-size: 12.5px; color: var(--text-mut); line-height: 1.45; white-space: pre-line; }
.ugc-keep { align-items: center; }
.ugc-end { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background: rgba(10, 12, 16, .72); color: #e8eaf0; }
.ugc-end[hidden] { display: none; }
.ugc-end b { font-size: 22px; font-variant-numeric: tabular-nums; }
/* 판 — 칸을 꽉 채우는 iframe (덮개의 `.gm-host` · 미리보기의 `.ugc-stage` 둘 다) · 덮개에서는 엔진 캔버스(`.gm-cv`)와 같은 여백·모서리 */
.gm-ugc { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; background: #12151c; }
.gm-host .gm-ugc { inset: 10px; width: calc(100% - 20px); height: calc(100% - 20px); border-radius: 12px; }
.gm-ov { position: fixed; inset: 0; z-index: 960; display: flex; flex-direction: column; background: var(--bg); color: var(--text);
  /* 🟢 위에 배너 자리를 둔 풀화면 (2026-10-14 · 사용자 「미니게임 화면도 통합 검색·마켓 검색처럼 풀화면으로」) — 위쪽 `--cv-gap` 은 상단 AdMob 배너의 자리(공통 규격 · `:root` 아래 문단)이고
     띠는 앱 바탕색이다. 🟡 10-13 첫 판은 «시트 모양»(띠에 딤 `--cv-dim` · 패널 둥근 윗모서리 · 그림자)이었다 — 검색 화면 둘과 같은 날 같은 이유로 걷었다(「가짜 시트」). */
  padding-top: var(--cv-gap); }
.gm-panel { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--bg);
  overflow: hidden; padding-bottom: env(safe-area-inset-bottom, 0px); }
/* 🟢 시트 모양 — 광고 없는 사람(플러스 · 2026-10-20 · board/game.js `openGamePlay` 의 `sheetLook`): 위 띠가 앱 바탕색 빈 칸이 아니라 «뒤 화면이 비치는 딤»이고, 패널은 둥근 윗모서리로 아래에서 올라온다.
   🔴 광고가 있는 사람은 이 클래스가 없다 — 배너가 그 띠에 앉으니 딤·모서리를 칠하지 않는다(10-14 에 «가짜 시트» 를 걷은 이유 그대로). */
.gm-ov.gm-sheet { background: var(--scrim, rgba(0, 0, 0, .45)); }
.gm-ov.gm-sheet .gm-panel { border-radius: var(--r-lg) var(--r-lg) 0 0; box-shadow: 0 -6px 24px rgba(0, 0, 0, .28);
  animation: gm-sheet-up .3s cubic-bezier(.22, .8, .3, 1) both; }
@keyframes gm-sheet-up { from { transform: translateY(100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) { .gm-ov.gm-sheet .gm-panel { animation: none; } }
.gm-body { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* 아래 캐러셀 — 조작부와 손가락 하나 이상 띄운다(실수 클릭) · 비면(플러스 등) 자리 없음 */
.gm-promo { flex: none; margin: 14px 0 8px; }
.gm-promo:empty { display: none; }
.gm-bar { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-bottom: 1px solid var(--line); font-weight: 700; font-size: 15px; }
.gm-x { width: 34px; height: 34px; border: 0; background: transparent; color: var(--text); font-size: 18px; border-radius: 8px; flex: none; }
.gm-try { margin-left: auto; font-size: 12px; color: var(--text-mut); font-weight: 500; }
.gm-hud { display: flex; justify-content: space-between; gap: 8px; padding: 8px 14px; font-size: 12px; color: var(--text-mut); border-bottom: 1px solid var(--line); }
.gm-hud b { font-size: 18px; color: var(--text); font-variant-numeric: tabular-nums; }
.gm-host { position: relative; isolation: isolate; flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; padding: 10px; }
/* 🔴 `isolation: isolate` — 엔진 안의 z-index(2048 타일 `.g2048-tile` 의 1 · 합쳐짐 2)가 이 칸 «밖»으로 새면 결과 화면(`.gm-res` · z-index 없음)보다 위에 떠서 판이 끝난 뒤
   타일이 결과 카드를 덮는다(2026-10-15 · 사용자 실기기 「2048 게임 끝났을 때 겹쳐서 나온다」). 엔진이 늘어도 같은 사고가 안 나게 칸 하나에 가둔다. */
.gm-host .gm-cv { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; touch-action: none; user-select: none; -webkit-user-select: none; border-radius: 12px; }
.g2048 { position: relative; display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding: 12px; width: min(100%, 360px); box-sizing: border-box; background: #1e222b; border-radius: 12px; touch-action: none; user-select: none; -webkit-user-select: none; cursor: grab; }
.g2048-cell { aspect-ratio: 1; border-radius: 8px; background: #242934; }
.g2048-tile { position: absolute; left: 0; top: 0; display: flex; align-items: center; justify-content: center; border-radius: 8px; font-weight: 700; font-size: 18px; color: #1a1200; pointer-events: none; font-variant-numeric: tabular-nums;
  /* 🟢 자리는 translate 속성으로 — 손짓마다 미끄러진다(games/g2048.js `SLIDE_MS` 와 같은 값 · 2026-10-13) */
  transition: translate .12s ease-in-out; will-change: translate; z-index: 1; }
.g2048-tile.g2048-ghost { z-index: 0; }
@media (prefers-reduced-motion: reduce) { .g2048-tile { transition: none; } .g2048-tile.g2048-pop, .g2048-tile.g2048-merge { animation: none; } }
.g2048-tile.g2048-pop { animation: g2048pop .18s cubic-bezier(.3, 1.4, .5, 1) .11s both; }   /* 🔴 `.pop` 으로 되돌리지 않는다 — 전역 팝업 클래스와 겹쳐 타일이 투명해진다 */
/* 🔴 타일의 자리가 `translate` 속성이라 크기 애니메이션은 `scale` 속성으로 한다(둘은 따로 합성된다 · 자리를 transform 에 두면 배율이 이동까지 곱해진다) */
@keyframes g2048pop { from { scale: 0; opacity: 0; } to { scale: 1; opacity: 1; } }
.g2048-tile.g2048-merge { animation: g2048merge .26s cubic-bezier(.3, 1.5, .5, 1); z-index: 2; }
@keyframes g2048merge { 0% { scale: 1; filter: brightness(1); } 40% { scale: 1.24; filter: brightness(1.35); box-shadow: 0 0 22px rgba(255, 215, 0, .65); } 100% { scale: 1; filter: brightness(1); } }
.g2048-tile.v1 { background: #e9e4d6; } .g2048-tile.v2 { background: #f0d9a8; } .g2048-tile.v3 { background: #f2b83c; } .g2048-tile.v4 { background: #f29a3c; }
.g2048-tile.v5 { background: #f0783c; } .g2048-tile.v6 { background: #f04f5c; color: #fff; } .g2048-tile.v7 { background: #4f8cff; color: #fff; }
.g2048-tile.v8 { background: #3b6fd6; color: #fff; } .g2048-tile.v9 { background: #22c47a; color: #fff; } .g2048-tile.v10 { background: #139a5c; color: #fff; font-size: 15px; }
.g2048-tile.v11 { background: #ffd700; color: #1a1200; font-size: 15px; box-shadow: 0 0 18px rgba(255, 215, 0, .5); }
/* 🟢 인트로 한 장 (2026-10-13 · `board/game.js gmIntroHTML`) — 덮개를 열면 먼저 선다 · 그동안 점수 줄·게임판 자리는 숨긴다 · [플레이]를 누르면 걷히고 엔진이 그 자리에 붙는다.
   그림은 게임판 고유색(다크)이라 둥근 판 안에 둔다 — 밝은 테마에서도 판만 어둡다(게임판과 같은 규칙). 움직임은 은은한 둘(떠오름·빛)뿐이고 reduced-motion 이면 멈춘다. */
.gm-ov-intro .gm-hud, .gm-ov-intro .gm-host { display: none; }
.gm-intro { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 20px 24px 28px; text-align: center; }
.gmi-art { position: relative; width: min(100%, 300px); aspect-ratio: 220 / 150; border-radius: 20px; overflow: hidden;
  background: radial-gradient(120% 90% at 50% 0%, #232a38 0%, #12151c 70%); box-shadow: 0 10px 30px rgba(0, 0, 0, .35), inset 0 0 0 1px rgba(255, 255, 255, .06); }
.gmi-art::after { content: ""; position: absolute; inset: 0; background: linear-gradient(115deg, transparent 35%, rgba(255, 255, 255, .10) 50%, transparent 65%); transform: translateX(-100%); animation: gmi-shine 3.2s ease-in-out 0.4s infinite; pointer-events: none; }
.gmi-art svg { display: block; width: 100%; height: 100%; }
.gmi-bob { animation: gmi-bob 1.6s ease-in-out infinite; }
.gmi-scroll { animation: gmi-scroll 6s linear infinite; }
.gmi-glow { animation: gmi-glow 1.8s ease-in-out infinite; }
.gmi-pulse { animation: gmi-glow 1.2s ease-in-out infinite; }
@keyframes gmi-bob { 0%, 100% { transform: translateY(6px); } 50% { transform: translateY(-8px); } }
@keyframes gmi-scroll { from { transform: translateX(0); } to { transform: translateX(-40px); } }
@keyframes gmi-glow { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
.gmi-slide-iso { animation: gmi-slide-iso 2.4s ease-in-out infinite alternate; }
@keyframes gmi-slide-iso { from { transform: translate(-30px, -15px); } to { transform: translate(30px, 15px); } }
@keyframes gmi-shine { 0% { transform: translateX(-100%); } 45%, 100% { transform: translateX(100%); } }
.gmi-name { margin: 8px 0 0; font-size: 26px; font-weight: 900; letter-spacing: -.5px; }
.gmi-desc { margin: 0; max-width: 30ch; font-size: 14px; line-height: 1.5; color: var(--text-sub); }
.gmi-help { display: inline-flex; align-items: center; gap: 8px; height: 34px; padding: 0 14px 0 8px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); color: var(--text-sub); font-size: 13px; font-weight: 700; cursor: pointer; }
.gmi-help i { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; background: var(--q-post); color: #1a1200; font-style: normal; font-size: 13px; font-weight: 900; }
.gmi-help:active { transform: scale(.96); }
/* 🟢 「플레이 방법」 팝업의 줄(`board/game.js openGameHelp`) — 앱 팝업 카드 안에서 왼쪽 정렬 · 줄마다 간격 */
.gm-help { margin: 10px 0 4px; padding-left: 18px; text-align: left; font-size: 14px; line-height: 1.55; color: var(--text); }
.gm-help li { margin-bottom: 8px; }
.gm-help li:last-child { margin-bottom: 0; }
.gmi-stats { display: flex; gap: 8px; margin-top: 6px; width: min(100%, 320px); }
.gmi-stats > span { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; padding: 8px 6px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }
.gmi-stats small { font-size: 11px; color: var(--text-mut); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gmi-stats b { font-size: 17px; font-variant-numeric: tabular-nums; }
.gmi-play { margin-top: 10px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; width: min(100%, 260px); height: 56px; border: 0; border-radius: 999px;
  background: linear-gradient(180deg, #ffd34d, #f2a93c); color: #1a1200; font-size: 18px; font-weight: 900; letter-spacing: .2px; cursor: pointer;
  box-shadow: 0 8px 22px rgba(242, 169, 60, .4), inset 0 -3px 0 rgba(0, 0, 0, .12); animation: gmi-breathe 1.8s ease-in-out infinite; }
.gmi-play svg { width: 22px; height: 22px; }
.gmi-play:active { transform: scale(.96); animation: none; }
@keyframes gmi-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.035); } }
.gmi-try { margin: 0; font-size: 12px; color: var(--text-mut); }
@media (prefers-reduced-motion: reduce) { .gmi-art::after, .gmi-art *, .gmi-play { animation: none !important; } }
.gm-res { position: absolute; inset: 0; z-index: 3; overflow: auto; background: var(--bg); padding: 12px 16px 24px; display: flex; flex-direction: column; gap: 12px; }
.gm-res[hidden] { display: none; }
.gm-score { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 10px 0 4px; text-align: center; }
.gm-score small { color: var(--text-mut); font-size: 12px; }
.gm-score b { font-size: 44px; line-height: 1.1; color: var(--q-post); font-variant-numeric: tabular-nums; }
.gm-score span { font-size: 14px; }
.gm-score em { font-style: normal; font-size: 12px; color: var(--text-mut); }
.gm-acts { display: flex; gap: 8px; }
.gm-acts > .btn { flex: 1; width: auto; min-width: 0; }
.gm-lb { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--surface); }
.gm-lb h4 { margin: 0; padding: 9px 14px; font-size: 13px; font-weight: 700; color: var(--text-mut); display: flex; justify-content: space-between; gap: 8px; border-bottom: 1px solid var(--line); }
.gm-lb ol { list-style: none; margin: 0; padding: 4px 0; }
.gm-lb li { display: grid; grid-template-columns: 26px 1fr auto; gap: 8px; padding: 6px 14px; align-items: center; font-variant-numeric: tabular-nums; }
.gm-lb li.me { background: rgba(242, 184, 60, .12); }
.gm-lb li i { font-style: normal; color: var(--text-mut); font-weight: 700; }
.gm-lb li b { font-size: 15px; }

.b-quote-slim {
  margin: 6px 0 0; padding: 6px 10px; font-size: 12px;
  border-radius: 8px; pointer-events: none;
  /* 🔴 자식이 전부 `inline` 이라 이 상자 하나가 한 줄 자르기를 맡는다(옛 `.b-qline` 과 같다) */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.b-quote-slim .b-quote-tag { display: inline; font-size: 11px; }
.b-quote-slim .b-quote-title {
  display: inline; margin-top: 0; color: var(--text-sub);
}
.b-quote-slim .b-quote-tag::after { content: ' · '; color: var(--text-mut); font-weight: 400; }
.b-quote-slim .b-quote-src { display: none; }
.b-quote-slim .b-quote-del { display: inline; margin-top: 0; }
.b-quote-slim .b-quote-del::before { content: ' '; }

/* ══════════════════════════════════════════════════════════════════════════
   🔴 **부각판 — 채널 말풍선에서만** (2026-09-08 · 101차 요청 · board/quote.js `quoteCardHTML` 의 다섯째 축)
   ══════════════════════════════════════════════════════════════════════════
 * 🔴 **`.b-quote-slim` 위에 덧입힌다** — 그 클래스의 문 규칙(문이 있을 때만 눌린다 · `.has-go`)을 그대로 두고
 *    크기·줄 수·출처 줄만 되살린다. slim 을 끄면 그 규칙이 같이 뒤집힌다.
 * 🔴 **출처(매체명)를 되살린다** — `.b-quote-slim` 의 `display: none` 은 «한 줄 자르기» 전용 규칙이었다.
 *    `.has-go`)도 같은 이유로 반드시 남는다(🟡 옛 줄은 「화살표(원문으로 가는 문)」였다 — 2026-09-11 · 113차 요청 11번에 걷었다).
 * 🔴 **제목은 두 줄로 자른다** — non-slim 에는 클램프가 없어서(가로 페이저에만 있다) 그대로 풀면 120자 제목이
 *    320px 화면에서 아홉 줄이 된다. 그리고 `overflow-wrap: anywhere` 와 **세트**다(긴 주소가 상자를 뚫는다).
 * 🔴 **글자 크기는 글 제목(`.chbub-t` 15px)보다 작다** — 부각의 주역은 «그림»이지 남의 제목이 아니다.
 *    특히 댓글 인용은 «제목 자리»가 남의 댓글 120자라, 여기서 커지면 그 말풍선의 주인공이 바뀐다.
 * 🔴 **높이는 로드 «전»에 확정된다**(그림 칸이 고정 높이) — 채널 화면은 「들어오면 끝으로」와 「위로 자라며
 *    scrollTop 되갚기」 둘 다 그리는 «직후»에 재므로, `height: auto`·`aspect-ratio` 로 두면 착지 지점이 어긋난다. */
.b-quote-hero { padding: 8px 10px; white-space: normal; overflow: hidden; font-size: 12.5px; }
/* 🔴 눌린 동안 카드가 말풍선에 녹지 않게 — `.chbub:active` 와 `.b-quote` 배경이 같은 토큰이라,
   부각 뒤에는 누르는 순간 «가장 큰 상자»가 통째로 사라지고 왼쪽 3px 선만 남는다. */
.chbub:active .b-quote-hero { background: var(--surface); }
/* 그림은 상자 끝까지 — 음수 마진은 padding 만 먹고 왼쪽 브랜드 선(3px)은 안 덮는다(그 선이 「인용이다」의 신호다) */
.b-quote-im { display: block; margin: -8px -10px 8px; background: var(--surface); }
.b-quote-im img { display: block; width: 100%; height: 150px; object-fit: cover; }
.b-quote-hero .b-quote-tag { display: block; font-size: 10.5px; }
.b-quote-hero .b-quote-tag::after { content: none; }
.b-quote-hero .b-quote-title {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  margin-top: 2px; font-size: 14px; line-height: 1.35; color: var(--text); overflow-wrap: anywhere;
}
.b-quote-hero .b-quote-src { display: block; margin-top: 3px; font-size: 11px; }
.b-quote-hero .b-quote-del { display: block; margin-top: 3px; }
.b-quote-hero .b-quote-del::before { content: none; }

/* ── 🔴 **인용 상자 = 인용 대상으로 가는 문** (2026-09-08 · 100차 요청 5번 → 2026-09-10 · 112차 요청 9번 · `quoteCardHTML` 의 `openable`) ──
 * 🔴 **상자 «전체»가 문이다**(게시판 목록 카드도 같다 · board/quote.js `quoteCardHTML` 의 `door`).
 *    규칙은 «인용 상자 = 인용 대상 · 그 밖 = 그 글»이고, 두 번 열리지 않는 것은 클릭 위임이 `closest` 하나라서다.
 *    눌린 동안은 테두리가 브랜드 색이 된다 — 뒤의 카드·말풍선도 같이 `:active` 라, 무엇을 눌렀는지를 그 선이 말한다.
 * 🔴 **화살표는 없다** (2026-09-11 · 113차 요청 11번 · 사용자 「나는 단지 그 인용을 누르면 이동되게 해달라고 했을 뿐」).
 *    100차 판의 «유일한 문»이던 오른쪽 끝 화살표(옛 `.b-quote-go`)를 112차가 문을 넓히고도 «표시»로 남겨 목록 카드에까지
 *    세웠다 — 요청에 없던 그림이었다. 걷으면서 그 그림의 자리도 같이 걷었다(이 규칙의 오른쪽 여백 · 부각판의 아래 42px ·
 *    부각판 매체명 줄의 오른쪽 여백).
 * 🔴 **여기에 오른쪽 여백을 되살리지 않는다** — 클래스가 둘이라 부각판의 `padding` 을 이겨 썸네일이 카드 폭을 못 채운다
 *    (화살표가 있던 동안은 그것을 되갚는 클래스 셋짜리 덮어쓰기가 따로 있었다). */
.b-quote-slim.has-go { pointer-events: auto; cursor: pointer; }
.b-quote-slim.has-go:active { border-color: var(--brand-500); }

/* 🔴 **오른쪽 끝으로 민다** — 공감 둘과 「읽고 나서 하는 행동」을 갈라 놓는 여백이다.
      공간을 **나눠 갖는다** — 화면에는 셋이 제각각 흩어진 줄로 보인다(2026-09-01 후속
      요청 1번의 그 스크린샷). 그래서 **첫 번째만** 민다: 뒤엣것은 바로 옆에 붙는다. */
/* 🟢 **글자 없이 그림 하나** (2026-09-15 · 사용자 요청 6번) — 인용·답글(댓글 그림) 버튼 전부. 좌우 여백만 공감 버튼과 맞춘다. */
.reactbar .quotebtn { margin-left: auto; padding: 0 9px; }
.reactbar .quotebtn + .quotebtn { margin-left: 0; }
/* 🟢 뉴스·포인트 상세의 북마크 — 오른쪽 끝, 인용 바로 왼쪽 (2026-10-25 · 코인스쿱 b889c2a 그대로 · `mediareact.js`) — 줄에서 «첫 번째로 미는 것»은 북마크고 인용은 바로 옆에 붙는다.
   게시글 상세 줄(`.bx`)은 space-between 이라 따로 둔다. */
.reactbar:not(.bx) .bmbtn { margin-left: auto; }
.reactbar:not(.bx) .bmbtn + .quotebtn { margin-left: 0; }
/* 상세 본문과 원문 카드 사이 — 코인스쿱 `.detail .reactbar` 와 같은 숨통 */
.pdetail .reactbar.mreact { margin: 12px 0 4px; }

.bseg { display: flex; gap: 4px; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 3px; }
.bseg-btn { background: none; border: 0; border-radius: 8px; padding: 6px 12px; font-size: 13px; color: var(--text-sub); }
/* ── 🔴 글쓰기 화면 **아래 여유** (2026-08-20 · 요청 1번 · board/compose.js `renderBoardCompose`) ── */
.b-composepromo { margin-top: 40px; }
.b-composetail { height: 60px; }
.bseg-btn.on { background: var(--surface-2); color: var(--text); font-weight: 700; }
.bhead { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }

.bhot { margin-bottom: 14px; }
/* 🟢 인기글(커뮤니티) 아래 캐러셀 (2026-10-23) — HOT 섹션 **안 끝**이라 목록 갱신에 안 지워지고, 카드와 같은 간격이다(코인스쿱 `#bhot-promo .promo-slot`). 인기글이 없으면 섹션째 없어 광고만 남는 모양이 안 난다. */
#bhot-promo .promo-slot { margin-top: 10px; }
/* 🟢 피드 HOT 상자 끝의 캐러셀 (2026-10-23 · `duel.js paintHot`) — 위 `#bhot-promo` 와 같은 간격이다. 🔴 두 값은 세트 — 한쪽만 바꾸면 두 탭의 간격이 다시 갈린다(코인스쿱 `.promo-hot`). */
.promo-hot { margin-top: 10px; }
.bhot-row {
  display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x mandatory;
  padding-bottom: 2px;
}
.bhot-row::-webkit-scrollbar { display: none; }
/* 🔴 인기글도 같이 키웠다 (요청 3번) — 62% → 76% 폭, 패딩 10/12 → 13/15.
   좁은 카드에 제목 2줄 + 이름줄이 들어가 글자가 계단처럼 끊겨 보였다. */
.bhotcard {
  flex: 0 0 76%; max-width: 300px; scroll-snap-align: start; background: var(--surface);
  border: 1px solid var(--line); border-radius: 14px; padding: 13px 15px;
  display: flex; flex-direction: column; gap: 7px;
}
.bhotcard:active { background: var(--surface-2); }
.bhot-top { display: flex; align-items: center; gap: 6px; }
.bhot-rank {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 5px; background: var(--brand-weak);
  color: var(--brand-500); font-size: 11px; font-weight: 800;
}
.bhot-title {
  font-size: 14.5px; font-weight: 700; line-height: 1.45; margin: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* 🔴 **카드 최하단에 고정한다** (2026-08-27 · 35차 요청 10번). `.bhot-row` 의 flex 가
   카드 높이를 줄에서 가장 큰 카드에 맞춰 늘리는데(align-items 기본 stretch), 제목이
   1줄인 카드는 내용이 짧아 이 메타줄이 제목을 따라 **위로 올라와** 카드 아래가 비어
   보였다. 카드가 이미 flex column 이라(`.bhotcard`) `margin-top: auto` 하나면 된다 —
   🔴 셀렉터를 `.bhotcard` 로 좁힌다: `.bhot-foot` 은 지금 인기글 카드에만 있지만,
      이름이 같은 줄을 다른 카드가 나중에 쓰게 되면 그 카드의 배치까지 끌어내린다. */
/* 🔴 인기글 카드의 발췌는 목록보다 한 칸 작다 (2026-08-31 · 57차 요청 10번) — 이 카드의
   제목이 14.5px·메타줄이 11.5px 라, 목록의 13px 을 그대로 쓰면 발췌가 제목만 해진다. */
.bhotcard .b-ex { margin: 0; font-size: 12.5px; }
.bhot-foot { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-sub); }
.bhotcard .bhot-foot { margin-top: auto; }
.bhot-foot .bhot-tk { min-width: 0; overflow: hidden; display: inline-flex; }
.bhot-foot .bhot-n { margin-left: auto; flex: none; }
.bhot-top .bhot-who { flex: 1 1 auto; min-width: 0; font-size: 11.5px; color: var(--text-sub); }   /* 🔴 글자 크기·색은 옛 아랫줄(`.bhot-foot`)의 것을 그대로 — 위 줄은 상속이 16px 라 안 적으면 이름이 커진다 */

/* 글쓰기 FAB — 🔴 떠 있는 버튼이다. 하단 탭 **위**에 두되 탭을 가리지 않는다.
   `.tabbar` 가 absolute 이므로(app.css 그쪽 주석) 여기서도 같은 축으로 띄운다. */
.bfab {
  position: fixed; right: 16px; z-index: 40;
  /* 🔴 `--sab` 가 아니라 **`--safe-bot`** 이다 (2026-08-18 · 실기기 「탭과 부딪힌다」).
     탭바 높이 계산이 전부 `--safe-bot` 을 쓰는데(`.tabbar` · `.scroll` 패딩) 여기만
     `--sab` 였다 — 그 변수는 이 파일에 정의가 없어 `0px` 폴백으로 떨어졌고, 그래서
     노치 있는 기기에서 FAB 이 홈 인디케이터 높이만큼 **탭 쪽으로 내려앉았다.**
     🔴 여백도 18 → 26 으로 올린다. 떠 있는 버튼과 탭 사이에는 손가락 하나가 들어가야
        오탭이 안 난다 — 둘 다 누르기 위한 것이라 붙어 있으면 서로를 가린다. */
  bottom: calc(var(--tab-h, 56px) + var(--safe-bot, 0px) + 26px);
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--brand-500); color: var(--on-brand); border: 0; border-radius: 999px;
  padding: 12px 17px; font-size: 14px; font-weight: 800; box-shadow: var(--sh-3);
}
/* 🔴 태블릿: `position: fixed` 라 기준이 **뷰포트**다 — 그냥 두면 FAB 이 가운데 컬럼을
   벗어나 화면 오른쪽 끝, 아무것도 없는 배경 위에 떠 있는다(`#inapp` 이 `--maxw` 로
   자기를 묶는 것과 같은 이유). 컬럼 오른쪽 가장자리로 끌어온다. */
@media (min-width: 700px) {
  .bfab { right: max(16px, calc(50vw - var(--maxw) / 2 + 16px)); }
}
.bfab:active { transform: scale(.96); }
/* ── 🔴 **모두 읽음 FAB — 글쓰기 FAB «위»** (2026-08-30 · 55차 요청 4번) ────────────────────
 * 🔴 자리를 **여기서** 계산한다(마크업이 아니라). 글쓰기 FAB 의 높이(패딩 12+12 + 글자
 *    줄 ≈ 44px)에 간격 10px 을 더해 그만큼 올린다 — 마크업에서 계산하면 FAB 패딩을
 *    바꾸는 날 이 값이 조용히 낡는다.
 * 🔴 **원형이다**(글쓰기는 글자가 있어 알약). 같은 모양이면 둘이 한 버튼의 두 부분처럼
 *    보이는데, 하는 일이 아예 다르다(하나는 쓰기, 하나는 지우기).
 * 🔴 **색이 다르다** — 글쓰기는 브랜드색(주된 행동), 이쪽은 표면색이다. 떠 있는 버튼이
 *    둘 다 브랜드색이면 무엇이 주된 행동인지가 사라진다.
 *
 * ── 🔴 **누를 것처럼 보이게** (2026-08-30 · 55차 요청 4번 · 같은 날 2판) ──────────────
 * 첫 판은 `background: var(--surface-2)` + `1px solid var(--line)` 이었다. 그건 목록의
 * 카드와 **정확히 같은 재료**라(카드도 surface-2 + line), 목록 위에 떠 있는데도 카드의
 * 일부처럼 납작하게 읽혔다 — 「눌러야 할 것 같지 않다」는 관찰이 그것이다.
 * 그래서 셋을 바꾼다:
 *   ① 바탕을 `--surface`(카드보다 «위»의 면) + 브랜드 테두리 — 목록에서 떨어져 나온다.
 *   ② 아이콘 색을 브랜드색으로 — 44px 원 안에서 그림이 먼저 눈에 든다.
 *   ③ 그림자를 글쓰기 FAB 과 **같은 `--sh-3`** 으로 — 높이(z)가 같아야 둘이 한 층에
 *      떠 있는 것으로 읽힌다. 첫 판은 `.bfab` 의 그림자를 물려받으면서도 테두리가
 *      그것을 눌러 평면으로 보이게 하고 있었다.
 * 🔴 그래도 **바탕을 브랜드색으로 채우지는 않는다** — 채우면 글쓰기 FAB 과 같은 무게가
 *    되어 「무엇이 주된 행동인가」가 다시 사라진다. 위 셋은 «떠 있음»만 올린다.
 *
 * ── 🔴 **오른쪽 위의 점** (2026-08-30 · 55차 요청 4번) ──────────────────────────────
 * 🔴 카드의 점(`.bcard-dot`)과 **같은 재료**다(`--hot` · 원). 같은 색이어야 「저 카드들의
 *    점을 지우는 버튼」이라는 연결이 그림만으로 읽힌다 — 다른 색이면 새 뜻이 하나 는다.
 * 🔴 원 **바깥으로 내민다**(top/right 음수). 안쪽에 두면 44px 원의 테두리 획과 겹쳐
 *    둘 다 못 읽는다(`.b-cmti::after` 가 같은 이유로 반 칸 나가 있다).
 * 🔴 그래서 `.bfab-read` 에 `overflow: hidden` 을 **주지 않는다** — 주는 순간 이 점이
 *    반달로 잘린다(칩의 점이 그 규칙에 한 번 물렸다 · `.bseg-dot` 주석). */
.bfab-read {
  bottom: calc(var(--tab-h, 56px) + var(--safe-bot, 0px) + 26px + 54px);
  padding: 0; width: 40px; height: 40px; justify-content: center;
  background: var(--surface); color: var(--brand-500);
  border: 1.5px solid var(--brand-500); box-shadow: var(--sh-3);
}
/* 🔴 **버튼만 줄인다 — 아이콘은 그대로다** (2026-08-31 · 57차 요청 4번 · 44 → 40px).
   🔴 그래서 `.bfab-read svg` 를 **안 건드린다.** 25px 를 유지하면 40px 원 안에서 좌우
      여백이 7.5px 씩이라 아직 「버튼 안의 아이콘」으로 읽힌다 — 여기서 원을 더 줄이면
      그림이 테두리(1.5px)에 붙어 잘린 것처럼 보이므로, 40px 이 이 조합의 바닥이다. */
.bfab-read svg { width: 25px; height: 25px; }
/* 🔴 **활동 화면(= 채널)에도 같은 버튼이 선다** (2026-09-06 · 85차·F 후속 · board/activity.js `renderUser`) — 그 화면은 `no-tabs`
   라 아래 `html.no-tabs .bfab` 규칙이 이 원의 `bottom` 을 덮어 글쓰기 FAB 과 **같은 자리**가 된다. 그래서 여기서도 글쓰기
   FAB 높이만큼 올린다(값은 위 `.bfab-read` 와 같은 54px · 세트). `solo` 는 글쓰기 FAB 이 없는 남의 화면 — 그 자리에 선다. */
html.no-tabs .bfab-read { bottom: calc(var(--safe-bot, 0px) + 26px + 54px); }
html.no-tabs .bfab-read.solo { bottom: calc(var(--safe-bot, 0px) + 26px); }
.bfab-read::after {
  content: ''; position: absolute; top: -3px; right: -3px;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--hot); border: 2px solid var(--surface);
}
.bfab svg { width: 17px; height: 17px; }

.b-chip.on { background: var(--brand-500); color: var(--on-brand); }

.bcard .b-foot { justify-content: flex-start; }
.bcard .b-stats { margin-left: auto; display: flex; gap: 8px; font-size: 12px; color: var(--text-sub); }
.bcard .reactbar { margin: 0; }
.bpost .reactbar { margin: 4px 0 12px; }
.b-views { font-size: 12px; color: var(--text-mut); display: inline-flex; align-items: center; gap: 3px; }
/* 🟢 조회수 눈동자 (2026-09-17 · 134차·B · board/list.js `viewCountHTML`) — 댓글 수 그림(`.b-cmtn svg` 13px)과 같은 크기 */
.b-views svg { width: 13px; height: 13px; flex: none; }
.b-views i { font-style: normal; }
/* 🟢 조회수가 오르는 순간 눈 깜빡임 (2026-09-17 · 134차·B · 골격 `countroll.js` 가 넘기는 동안 부모에 `data-cnt-roll` 을 단다) —
   숫자 넘김과 같은 박자라 따로 타이머가 없다 · 움직임 줄이기면 넘김 자체가 없어 표식도 안 선다 */
.b-views[data-cnt-roll="up"] svg { animation: eyeBlink var(--cnt-blink-ms, 420ms) ease-in-out; transform-origin: 50% 50%; }
@keyframes eyeBlink { 0%, 100% { transform: scaleY(1); } 35% { transform: scaleY(.1); } 60% { transform: scaleY(1); } }
/* 🟢 **공감·댓글·채널 반응 그림도 숫자가 바뀔 때 살짝** (2026-09-17 · 134차·B · 같은 `data-cnt-roll` 표식) —
   오르면 톡 튀고(공감 · 반응) 또는 까딱(댓글), 줄면 살짝 움츠린다. 크기는 1.15 안팎 — 목록에서 여러 줄이 동시에 바뀌어도 소란스럽지 않게.
   🔴 내가 누른 공감(`.rpop`)은 이미 제 튐이 있어 겹치지 않게 뺀다 · 그림(`svg`·`.emo-e`)만 움직이고 숫자 창은 건드리지 않는다. */
.reactbtn:not(.rpop)[data-cnt-roll="up"] > b svg,
.emopill[data-cnt-roll="up"] > .emo-e { animation: cntIcUp .38s cubic-bezier(.34, 1.56, .64, 1); transform-origin: 50% 70%; }
.reactbtn:not(.rpop)[data-cnt-roll="dn"] > b svg,
.emopill[data-cnt-roll="dn"] > .emo-e,
.b-cmtn[data-cnt-roll="dn"] .b-cmti svg,
.bhot-cmt[data-cnt-roll="dn"] .b-cmti svg { animation: cntIcDn .32s ease-out; transform-origin: 50% 50%; }
.b-cmtn[data-cnt-roll="up"] .b-cmti svg,
.bhot-cmt[data-cnt-roll="up"] .b-cmti svg { animation: cntIcNod .42s ease-in-out; transform-origin: 30% 90%; }
@keyframes cntIcUp { 0%, 100% { transform: none; } 45% { transform: translateY(-1.5px) scale(1.16); } }
@keyframes cntIcDn { 0%, 100% { transform: none; } 45% { transform: scale(.86); } }
@keyframes cntIcNod { 0%, 100% { transform: none; } 30% { transform: rotate(-12deg) scale(1.1); } 65% { transform: rotate(6deg); } }

/* 검색어 하이라이트 (2026-08-14 / 2026-08-19 뒤집음 · 요청 1번)
   🔴 **배경을 안 칠한다. 글자색만 브랜드색으로 바꾼다.** 옛 판은 반대였는데(배경만),
      검색어가 흔한 낱말이면 목록 전체가 노란 블록으로 덮여 제목이 안 읽혔다 —
      찾으라고 만든 표시가 읽기를 방해했다.
   🔴 `--brand-500` 은 다크·라이트 **둘 다** 본문색과 대비가 확보된 값이다(팔레트 블록
      머리 주석 — 밝은 판 테마는 바탕에 묻지 않게 한 단계 진한 브랜드색을 쓴다).
      그래서 여기서 색을 직접 적지 않는다. 굵기는 남긴다 — 색만으로는 색각 이상에서
      표시가 통째로 사라진다. */
mark.hl {
  background: none; color: var(--brand-500); font-weight: 700;
}

/* ── 🔴 키워드 후보의 **매칭 별칭** (2026-08-19 · 요청 4번) ────────────────────
   한국어 화면에서 `elon` 을 치면 「일론 머스크」가 나오는데, 그 이유가 화면에 없으면
   목록이 엉뚱한 걸 줬다고 읽힌다. 걸린 별칭을 괄호로 붙이고 그 안의 매칭 부분만
   `mark.hl` 로 칠한다.
   🔴 이름보다 **약하게** 보여야 한다(작게·흐리게) — 이름표는 어디까지나 제목 쪽이다.
      괄호 안의 강조는 `mark.hl` 이 이미 하므로 여기서 색을 또 주지 않는다. */
.ent-alias { margin-left: 6px; font-size: 12.5px; font-weight: 600; color: var(--text-mut); }

/* ── 커뮤니티 3차 (2026-08-14) — 실시간 인기 랭킹 · 게시판 검색 ─────────────── */
/* 🟢 흐르는 줄은 골격 `common/autoflow.js` 가 창의 scrollLeft 로 민다 (2026-09-17 · 140차) — 손으로 밀 수 있어야 «손대면 멈춤»이 성립한다.
   🔴 옛 판(CSS 애니메이션)의 장점은 골격이 지킨다: 화면 밖·문서 숨김이면 프레임을 안 잡는다. */
.btrend { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; min-width: 0; }
.btrend-tag {
  flex: none; font-size: 11px; font-weight: 800; color: var(--brand-500);
  background: var(--brand-weak); border-radius: 6px; padding: 4px 7px;
}
.btrend-view { flex: 1; min-width: 0; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; -webkit-mask-image: linear-gradient(90deg, transparent, #000 16px, #000 calc(100% - 16px), transparent); mask-image: linear-gradient(90deg, transparent, #000 16px, #000 calc(100% - 16px), transparent); }
.btrend-view::-webkit-scrollbar { display: none; }
/* 🔴 되감기 폭 = 첫 항목 ~ 복제 첫 항목 사이 — 그래서 간격은 `gap` 이 아니라 항목의 오른쪽 여백으로 준다(마지막 원본과 첫 복제 사이도 같은 간격) */
.btrend-track { display: flex; width: max-content; }
.btrend-track > .btrend-item { margin-right: 8px; }
/* 🔴 **눌러도 안 멈춘다** (2026-08-20 · 요청 5번 · 실기기). 옛 판은 `:hover`·`:active` 에서
   `animation-play-state: paused` 였는데, 터치 기기에서 그건 「잠깐 멈춘다」가 아니다:
   ① `:active` 는 손가락이 닿는 내내 참이고 ② 모바일 웹뷰는 탭한 요소에 `:hover` 를
   **그대로 붙여 둔다**(다른 곳을 누를 때까지). 그래서 칩을 한 번 누르면 줄이 **영영**
   멈춘 것처럼 보였다 — 다시 그려도 `:hover` 가 새 노드로 옮겨 붙으면 또 멈춘다. */
.btrend-item {
  flex: none; display: inline-flex; align-items: center; gap: 5px; background: none; border: 0;
  color: var(--text); font-size: 12.5px; font-weight: 700; padding: 4px 2px;
}
.btrend-rank { color: var(--text-mut); font-size: 11px; font-weight: 800; }

.srch-seg { margin: 0 0 12px; }
/* 🔴 **넷이 되면서 폭을 «글자만큼»으로** (2026-09-11 · 113차 후속 5 · 375px 실측 · 건수 「309+」·「30+」) — 같은 폭(`flex: 1`)이면 칸이 83px 라
   한국어 「게시글 12+」부터 두 줄로 꺾였고(행 44px) 스페인어·포르투갈어는 옆으로 넘쳤다(373·354 / 349px). 글자만큼 + 남는 폭 나누기 · 12px ·
   좌우 4px 이면 한국어·일본어·중국어·영어가 한 줄이다 · 스페인어·포르투갈어는 라벨을 짧게 해서 한 줄이다(`strings.js search_posts`·`search_cmts` — 긴 라벨이면 두 줄). 🔴 좌우 6px 이면 영어 「Comments 30+」가,
   13px 이면 영어 전체가 다시 꺾인다(첫 커밋 bb6cc0a 가 6px 로 넣고 «영어도 한 줄»이라 적었는데 실제 렌더에서 영어가 두 줄이었다). */
.srch-seg .bseg-btn { flex: 1 1 auto; padding: 6px 4px; font-size: 12px; text-align: center; }

/* ── 커뮤니티 4차 (2026-08-14) — 레벨 · 묘비 · 대댓글 · 발췌 ─────────────────── */
/* 레벨 배지 — 닉네임 앞. 🔴 999까지 3자리라 폭을 고정하지 않는다(잘리면 안 된다) */
/* 🔴 레벨은 **이름 뒤**다 (2026-08-17 · 요청 4번) — 여백도 그 방향으로 */
.b-lv {
  display: inline-block; margin-left: 1px; padding: 1px 5px; border-radius: 5px;
  background: var(--brand-weak); color: var(--brand-500);
  font-size: 10.5px; font-weight: 800; letter-spacing: -.2px;
}
/* ── 🔴 **운영 배지** (2026-08-19 4차 · 요청 8번 · board/people.js `admBadgeHTML`) ──────────
   레벨 배지 **자리를 대신 차지한다**(둘을 나란히 두지 않는다 — 그쪽 주석).
   🔴 등급 색 열 개 중 **어느 것과도 안 겹치는** 색이어야 한다. 그래서 레벨이 절대 못
      갖는 무늬를 쓴다: **꽉 찬 배경 + 테두리**. 레벨 배지는 전부 옅은 바탕(16%)이라
      멀리서도 이 하나만 도드라진다. */
.b-lv.b-adm {
  background: var(--hot); color: #fff; border: 1px solid var(--hot);
  letter-spacing: 0;
}
/* ── 🔴 레벨 등급 색 — 10레벨마다 (2026-08-19 · 요청 15번) ─────────────────────
   흰(회) → 노랑 → 초록 → 파랑 → 빨강 → 보라 → 주황. 판정은 board/people.js `lvClass` 다.
   🔴 **글자색과 옅은 바탕이 세트다.** 글자만 바꾸면 이 크기(10.5px)에서 색이 안 읽히고,
      바탕만 바꾸면 색각 이상에서 숫자가 묻힌다.
   🔴 값은 팔레트 토큰을 **안 쓴다.** `--up`·`--hot` 은 "오름·내림" 이라는 뜻이 이미
      박힌 색이라, 레벨 배지에 그대로 쓰면 30레벨 배지가 하락으로 읽힌다.
      대신 다크·라이트 **양쪽에서 읽히는 중간 채도**로 직접 고른다.
   🔴 바탕은 `color-mix` 가 아니라 rgba 다 — 구형 WKWebView 폴백이 필요 없고,
      같은 파일 안의 `--*-weak` 토큰들과 표기가 맞는다. */
/* 🔴 lvt0 은 **흰색(본문색)** 이다 — `--text-sub` 가 아니다. 인기글 카드 아랫줄처럼
   주변 글자가 이미 `--text-sub` 인 자리가 있어서, 거기서는 배지가 배경에 녹아 사라진다.
   대다수 사용자가 이 등급에 머무르므로 여기가 가장 자주 보이는 배지다.
   ── 🟢 **눈금이 둘이다 — 99 까지는 10단위, 그 위는 100단위** (2026-09-19 · 145차·D 요청 2번 · board/people.js `lvClass`) ──
   🔴 색 값은 열 개 그대로이고 늘어난 것은 **칠하는 방식**(`.lvx`) 하나다 — 아래 링 규칙 한 줄이 그 전부다.
   🟢 **순서를 다시 폈다** — 옛 판은 회색 «바로 다음»이 금색이라(10~19) 맨 위 칸(90~99 밝은 금)과 다퉜고,
      색상환을 한 바퀴 돈다는 설명과 달리 «가장 귀한 색»이 두 번 나왔다. 이제 **차가운 색 → 따뜻한 색 → 금** 한 방향이고
      금색 둘이 맨 위 두 칸에 나란히 선다(끝이 곧 정점으로 읽힌다).
   🔴 **색 값은 하나도 새로 만들지 않았다** — 열 색 그대로를 자리만 옮겼다(두 테마에서 이미 검증된 값들이다).
      인접한 두 등급이 여전히 다른 계열이라 목록에서 배지 둘이 붙어도 안 뭉친다.
   🔴 `board-ui.js` 의 `LV_TIER`(=100)·`LV_TIERS`(=9) 와 **세트다.** 여기만 늘리면 쓰이지 않는 규칙이
      되고, 저기만 늘리면 색 없는 클래스가 붙어 배지가 기본 회색으로 떨어진다. */
.b-lv.lvt0 { background: rgba(139, 148, 158, .16); color: var(--text); }
.b-lv.lvt1 { background: rgba(63, 185, 80, .16); color: #3fb950; }
.b-lv.lvt2 { background: rgba(45, 212, 191, .16); color: #2dd4bf; }
.b-lv.lvt3 { background: rgba(88, 166, 255, .16); color: #58a6ff; }
.b-lv.lvt4 { background: rgba(129, 140, 248, .16); color: #818cf8; }
.b-lv.lvt5 { background: rgba(178, 128, 255, .16); color: #b280ff; }
.b-lv.lvt6 { background: rgba(244, 114, 182, .16); color: #f472b6; }
.b-lv.lvt7 { background: rgba(255, 138, 46, .16); color: #ff8a2e; }
.b-lv.lvt8 { background: rgba(240, 185, 11, .16); color: #f0b90b; }
.b-lv.lvt9 { background: rgba(255, 215, 90, .16); color: #ffd75a; }
:root[data-tone="light"] .b-lv.lvt1 { color: #2f7d4a; }
:root[data-tone="light"] .b-lv.lvt2 { color: #0f766e; }
:root[data-tone="light"] .b-lv.lvt3 { color: #1f6feb; }
:root[data-tone="light"] .b-lv.lvt4 { color: #4f46e5; }
:root[data-tone="light"] .b-lv.lvt5 { color: #7c4dff; }
:root[data-tone="light"] .b-lv.lvt6 { color: #be1d64; }
:root[data-tone="light"] .b-lv.lvt7 { color: #c05a12; }
:root[data-tone="light"] .b-lv.lvt8 { color: #a4790a; }
:root[data-tone="light"] .b-lv.lvt9 { color: #8a6d1b; }
/* ── 🟢 **`lvx` — 100 위의 «격»** (2026-09-19 · 145차·D 요청 2번 · board/people.js `lvClass`) ────────────────────────
   같은 색의 다른 등급이므로 **색을 새로 만들지 않고 칠하는 방식만 바꾼다**: 옅은 판 → 그 색의 링.
   🔴 `box-shadow: inset` 이다(테두리가 아니라) — 테두리를 주면 배지 폭이 커져 목록 줄이 통째로 흔들린다.
      inset 링은 **자리를 한 픽셀도 안 밀어낸다.**
   🔴 `currentColor` 라 등급 색·테마를 저절로 따른다(색 값을 여기 적지 않는다). */
.b-lv.lvx, .lvup-badge.lvx { box-shadow: inset 0 0 0 1.5px currentColor; }

/* 🔴 축하 팝업의 큰 배지도 같은 색을 받는다 — `.lvup-badge` 는 아래에서 브랜드색을
   직접 주므로, 등급 클래스가 그것을 **덮어야** 한다(선택자를 겹쳐 가중치를 올린다). */
.lvup-badge.lvt0 { background: rgba(139, 148, 158, .16); color: var(--text); }
.lvup-badge.lvt1 { background: rgba(63, 185, 80, .16); color: #3fb950; }
.lvup-badge.lvt2 { background: rgba(45, 212, 191, .16); color: #2dd4bf; }
.lvup-badge.lvt3 { background: rgba(88, 166, 255, .16); color: #58a6ff; }
.lvup-badge.lvt4 { background: rgba(129, 140, 248, .16); color: #818cf8; }
.lvup-badge.lvt5 { background: rgba(178, 128, 255, .16); color: #b280ff; }
.lvup-badge.lvt6 { background: rgba(244, 114, 182, .16); color: #f472b6; }
.lvup-badge.lvt7 { background: rgba(255, 138, 46, .16); color: #ff8a2e; }
.lvup-badge.lvt8 { background: rgba(240, 185, 11, .16); color: #f0b90b; }
.lvup-badge.lvt9 { background: rgba(255, 215, 90, .16); color: #ffd75a; }
/* 🔴 밝은 바탕 보정(`data-tone="light"` · 2026-10-27 테마 이름 목록에서 옮김)은 **두 배지 모두**에 있어야 한다. 축하 팝업만 빠뜨리면 밝은 배경에서
   노랑·주황이 묻히는데, 하필 그 배지가 이 앱에서 가장 크게 뜨는 레벨 표시다. */
:root[data-tone="light"] .lvup-badge.lvt1 { color: #2f7d4a; }
:root[data-tone="light"] .lvup-badge.lvt2 { color: #0f766e; }
:root[data-tone="light"] .lvup-badge.lvt3 { color: #1f6feb; }
:root[data-tone="light"] .lvup-badge.lvt4 { color: #4f46e5; }
:root[data-tone="light"] .lvup-badge.lvt5 { color: #7c4dff; }
:root[data-tone="light"] .lvup-badge.lvt6 { color: #be1d64; }
:root[data-tone="light"] .lvup-badge.lvt7 { color: #c05a12; }
:root[data-tone="light"] .lvup-badge.lvt8 { color: #a4790a; }
:root[data-tone="light"] .lvup-badge.lvt9 { color: #8a6d1b; }
/* 🔴 테두리는 글자색을 따른다 — 아래 `.lvup-badge` 기본이 브랜드 노랑으로 못박아 두는데,
   등급 색을 받은 배지에 노란 테두리가 남으면 색이 둘로 보인다. */
.lvup-badge[class*="lvt"] { border-color: currentColor; }
/* 목록 카드의 본문 발췌 (서버가 30자만 준다) */
.b-ex {
  margin: 3px 0 0; font-size: 13px; line-height: 1.5; color: var(--text-sub);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 🟢 채널 글 검색 카드의 본문 (2026-10-16 · board/list.js `chResultCardHTML`) — 제목이 없는 말풍선 글이라 본문이 주인공이다: 줄을 지키고 세 줄까지 · 글자는 제목만큼 진하게 */
.b-ex.b-chex {
  margin-top: 7px; font-size: 14.5px; color: var(--text);
  white-space: pre-line; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3;
}
/* 묘비 — 🔴 **누를 수 없다**(요청 8번). button 이 아니라 div 이고 포인터 이벤트도 없다.
   🔴 **글 카드보다 얇다** (2026-08-20 · 요청 3번). 여기는 「없다」는 사실 한 줄이지
      글이 아니다 — 같은 높이를 주면 목록에서 지워진 글이 살아 있는 글만큼 눈에 띈다. */
.btomb {
  color: var(--text-mut); font-size: 12px; text-align: center; padding: 7px 12px;
  background: var(--surface); border: 1px dashed var(--line-2); border-radius: 10px;
  margin-bottom: 8px;
  pointer-events: none;
}
/* 🟢 지운·블라인드·검토 중 글의 묘비는 **눌린다** — 댓글이 살아 있다 (2026-10-03 · board/cards.js `tombOpen`·`renderTombPost`). 댓글 수가 오른쪽에 선다.
   상세 머리의 묘비(`.btomb-head`)는 글 본문 자리라 조금 두껍다. */
.btomb.btomb-open { pointer-events: auto; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 10px; }
.btomb.btomb-open .b-cmtn { margin-left: 0; }
.bpost-del .btomb-head { padding: 16px 12px; font-size: 13.5px; margin: 10px 0 14px; }

/* ── 🔴 **차단·뮤트로 가린 자리** (2026-08-20 · 요청 3번 · board/activity.js `blkRowHTML`) ────────
   🔴 묘비(`.btomb`)를 그대로 쓰지 않는다. 묘비는 `pointer-events: none` 인데, 이 줄은
      **이름을 눌러 활동 화면으로 가야 한다** — 차단을 푸는 곳이 거기다.
   🔴 그래서 모양은 묘비를 닮되(점선·흐린 글씨) 누를 것 둘만 살아 있다: 이름, 「보기」. */
.b-blkrow {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; margin-bottom: 8px;
  background: var(--surface); border: 1px dashed var(--line-2); border-radius: 10px;
}
.b-blkrow .b-meta { flex: 0 0 auto; margin: 0; }
/* 🔴 문구는 **한 줄에서 줄어든다** — 두 줄이 되면 가린 줄이 안 가린 카드보다 커진다 */
.b-blktext {
  flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--text-mut);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.b-blkshow {
  flex: 0 0 auto; padding: 4px 10px; border: 1px solid var(--line-2); border-radius: var(--r-pill);
  background: none; color: var(--text-mut); font-size: 11.5px; font-weight: 700; cursor: pointer;
}
.b-blkshow:active { opacity: .6; }
/* 🔴 상세가 통째로 가려진 자리의 「차단 해제」 — 목록 줄과 달리 옆에 아무것도 없어서
   가운데로 놓는다(`renderBoardPost` 의 차단 관문 · 2026-09-01 · 요청 2번) */
.b-blkundo { display: flex; justify-content: center; margin-top: 4px; }
/* 🟢 **비공감 접기** (2026-09-17 · 133차 요청 1번 · board/activity.js `downFolded`) — 줄은 뮤트 자리(`.b-blkrow`) 그대로이고,
   글 상세는 접지 않고 머리에 옅은 한 줄만 둔다(판정이 아니라 반응의 사실이라 경고색을 안 쓴다) */
.b-downnote {
  margin: 6px 0 2px; padding: 6px 10px; border-radius: 8px;
  background: var(--surface); color: var(--text-mut); font-size: 12px;
}
.b-cmt.btomb { text-align: left; padding: 10px 2px; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: none; }
/* 답글 — 깊이는 무한(2026-10-03)이지만 글 상세에는 바로 달린 답글 미리보기 한 건만 서므로(2026-10-04 · board/comments.js `cmtRepliesPreviewHTML`) 들여쓰기 한 칸이면 충분하다 */
.b-reply { margin-left: 18px; border-left: 2px solid var(--line-2); padding-left: 10px; }
/* 🟢 「댓글 N개 더 보기」 — 미리보기 답글과 같은 세로 줄에 이어 선다 (2026-10-04 · board/comments.js `cmtRepliesPreviewHTML`) */
.b-repmore {
  display: block; width: auto; margin: 0 0 10px 18px; padding: 6px 0 6px 10px;
  border: 0; border-left: 2px solid var(--line-2); border-radius: 0; background: none;
  color: var(--brand-500); font-size: 12.5px; font-weight: 600; text-align: left; cursor: pointer;
}
.b-repmore:active { opacity: .6; }
.b-cmt .reactbar { margin-top: 6px; }
.b-edited { font-size: 11.5px; color: var(--text-mut); margin: -6px 0 10px; }
.b-body.loading { color: var(--text-mut); }

/* ── 커뮤니티 5차 (2026-08-14) — 첨부 이미지 · @멘션 ─────────────────────────── */
.b-imgrow { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.b-imgth { position: relative; width: 76px; height: 76px; border-radius: 10px; overflow: hidden; cursor: pointer; }
.b-imgth img { width: 100%; height: 100%; object-fit: cover; }
/* 🔴 누르면 커서 자리로 옮긴다(board/media.js `moveImgToken`) — 눌리는 것처럼 보여야 한다 */
.b-imgth:active { transform: scale(.95); }
/* 🔴 **번호**를 찍는다. 본문의 표식이 `[[img:2]]` 라, 그 2 가 어느 사진인지 화면에
   없으면 표식이 무슨 뜻인지 알 길이 없다 — 표식과 썸네일을 잇는 유일한 끈이다. */
.b-imgn {
  position: absolute; left: 3px; bottom: 3px; min-width: 16px; height: 16px; padding: 0 4px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; background: rgba(0, 0, 0, .6); color: #fff;
  font-size: 10.5px; font-weight: 700; line-height: 1; pointer-events: none;
}
/* 힌트는 줄을 통째로 차지한다 — 썸네일 옆에 끼면 사진이 한 칸씩 밀린다 */
.b-imghint { flex: 1 0 100%; margin-top: 2px; }
.b-imgx {
  position: absolute; top: 3px; right: 3px; width: 20px; height: 20px; border: 0;
  border-radius: 50%; background: rgba(0, 0, 0, .6); color: #fff; font-size: 11px; line-height: 1;
}
.b-imgrow:empty { display: none; }   /* 🗄 줄 끝의 [＋ 사진 첨부](`.b-imgadd`)는 2026-10-01 에 걷었다 — 첨부는 도구줄의 [카메라][앨범] */
/* ── 🔴 **유튜브는 카드이고, 누르면 그 자리가 플레이어가 된다** (2026-08-25 → 2026-09-02 ·
   board/embeds.js `ytEmbedHTML`·`mountYt`) ──────────────────────────────────────────────────
   웹뷰 안에서 유튜브 iframe 을 곧장 열면 오류 153(Referer)이라, 누른 뒤에 **우리 호스팅의
   중계 페이지**(`public/yt.html`)를 iframe 으로 연다 — 그 페이지가 유튜브를 연다.
   🔴 `.b-ytbox` 는 카드와 **같은 상자**(16:9 · 같은 여백)에 iframe 만 채운 것이다 — 누르는
      순간 본문이 한 픽셀도 안 움직여야 한다.
   🔴 자리(16:9)는 옛 플레이어와 **같다** — 본문 흐름이 안 바뀐다.
   🔴 썸네일이 못 오면 `onerror` 가 그림만 걷는다: 그때도 카드·라벨은 남아야 누를 수 있다
      (그래서 배경색이 `.b-ytcard` 에 있고 `.b-ytthumb` 에 있지 않다). */
.b-ytcard {
  position: relative; display: block; width: 100%; aspect-ratio: 16 / 9;
  margin: 10px 0 12px; border-radius: 12px; overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--line-2);
  padding: 0; cursor: pointer;
}
.b-ytthumb { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.b-ytbox { background: #000; cursor: default; }
.b-ytframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
/* 🟢 X 게시물 카드 (2026-10-01 · board/embeds.js `xCardHTML`·`mountX`) — 누르기 전은 유튜브 카드와 같은 상자(`.b-ytcard`)이고,
   누른 뒤에는 높이가 게시물마다 달라 상자가 «키를 안 정한다»: iframe 의 키는 중계 페이지가 알려 준 값으로 JS 가 맞춘다.
   알려 주기 전의 키(360)는 영상 붙은 짧은 게시물의 어림값이다 — 0 이면 누른 순간 본문이 접혔다 펴진다. */
.b-xbox { margin: 10px 0 12px; }
.b-xframe { display: block; width: 100%; height: 360px; border: 0; background: transparent; }
.b-ytplay {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 56px; height: 56px; pointer-events: none;
}
.b-ytplay svg { width: 100%; height: 100%; display: block; }
/* 🔴 아래쪽 띠 — 썸네일 위 글씨는 사진에 따라 안 보인다. 어두운 띠가 대비를 보장한다 */
.b-ytlabel {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 7px 10px; font-size: 12px; font-weight: 600; color: #fff; text-align: left;
  background: linear-gradient(transparent, rgba(0, 0, 0, .72));
}
/* 🟢 **댓글의 유튜브·X 카드 — 작은 가로 카드** (2026-10-04 · board/embeds.js `cmtBodyHTML`·`cmtLinkCardHTML`) — 본문 카드(16:9 · 화면 폭)는 댓글 줄 사이에서
   너무 커서 썸네일(16:9 · 112px) + 글자 두 줄로 줄였다. 누르면 그 자리가 플레이어다(`.b-ytbox`·`.b-xbox` — 댓글 폭). X 는 썸네일이 없어 빈 칸에 재생 기호만. */
.b-cmtlink {
  display: flex; align-items: center; gap: 10px; width: 100%; max-width: 340px; margin: 6px 0 4px; padding: 6px;
  border: 1px solid var(--line-2); border-radius: 10px; background: var(--surface-2); color: var(--text); text-align: left; cursor: pointer;
}
.b-cmtlink-th { position: relative; flex: none; width: 112px; aspect-ratio: 16 / 9; border-radius: 6px; overflow: hidden; background: var(--surface); }
.b-cmtlink-th img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.b-cmtlink-th .b-ytplay { width: 30px; height: 30px; }
.b-cmtlink-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.b-cmtlink-tx b { font-size: 13px; font-weight: 600; }
.b-cmtlink-tx span { font-size: 12px; color: var(--text-sub); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.b-cmtlink-on { margin: 6px 0 4px; }
/* 🟢 **링크 미리보기 줄** (2026-10-04 · board/embeds.js `paintLinkPrev`) — 쓰는 칸 «아래»(글쓰기·댓글 쓰기) · 바닥 입력줄 «위»(스레드·내 채널 방). 칩은 보기만 한다
   (누름 없음 · 커서 기본). 비면 자리를 안 먹는다(`:empty`). 입력줄 안에서는 좌우 여백을 입력 알약과 맞춘다. */
.lnkprev { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; margin: 8px 0 4px; }
.lnkprev:empty { display: none; }
.chth-bar .lnkprev { margin: 2px 4px 6px; }
.lnkprev-c { display: flex; align-items: center; gap: 8px; max-width: 100%; padding: 4px 10px 4px 4px; border: 1px solid var(--line-2); border-radius: 10px; background: var(--surface-2); }
.lnkprev-th { position: relative; flex: none; width: 56px; height: 32px; border-radius: 6px; overflow: hidden; background: var(--surface); }
.lnkprev-th img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.lnkprev-th .b-ytplay { width: 20px; height: 20px; }
.lnkprev-tx { display: flex; flex-direction: column; min-width: 0; font-size: 12px; line-height: 1.35; }
.lnkprev-tx b { font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lnkprev-tx span { color: var(--text-sub); }
.lnkprev-hint { font-size: 12px; color: var(--text-mut); padding: 0 2px; }

/* ── 🔴 **본문 링크** (2026-08-25 · 요청 5번 · board/body.js `mentionHTML`) ────────────────
   🔴 `@닉`·`#태그`(`.b-at`)와 **다른 색**이다: 저건 «앱 안»으로 가는 길이고 이건 앱을
      떠나는 길이라, 같은 모양이면 누르는 사람이 그 차이를 모른다.
   🔴 밑줄을 둔다 — 색만으로 링크를 표시하면 색각 이상에서 그냥 글자로 보인다. */
.b-link {
  border: 0; background: none; padding: 0; margin: 0; font: inherit; cursor: pointer;
  color: var(--brand-500); text-decoration: underline; text-underline-offset: 2px;
  word-break: break-all; text-align: left;
}
.b-link:active { opacity: .6; }
/* 🟢 **글자 링크** `[라벨](주소)` (2026-10-17 · board/body.js `mentionHTML` · `services/mdlink.js`) — 라벨은 말이라 낱말 단위로 줄을 바꾼다(맨 주소의 `break-all` 을 안 쓴다) */
.b-link.b-tlink { word-break: normal; overflow-wrap: anywhere; }
/* 🟢 **인용 블록** `> ` 줄 (2026-10-17 · board/body.js `bodyBlocksHTML`) — 녹색 왼쪽 막대 + 옅은 녹색 바탕. 색은 오른 값의 두 토큰(`--up`·`--up-weak`)이라
   다크·라이트가 각자 맞는 녹색을 쓴다. 문단(`.b-body`)과 같은 글자 크기·줄 간격을 물려받고 여백만 줄인다(세 겹 — `.bpost .b-body` 의 margin 을 이긴다). */
.b-body.b-quote, .bpost .b-body.b-quote {
  margin: 8px 0 12px; padding: 8px 12px; border-left: 3px solid var(--up); border-radius: 0 8px 8px 0;
  background: var(--up-weak); white-space: pre-wrap;
}

/* ── 🔴 **첨부는 작게, 누르면 크게** (2026-08-20 · 요청 5번 · board/embeds.js `imagesHTML`) ──
   본문 안의 그림은 「무엇이 붙어 있나」를 말하는 자리지 감상하는 자리가 아니다 —
   화면을 꽉 채우면 글이 두 번째 장으로 밀린다. 폭을 **절반 수준**으로 줄이고, 크게
   보는 것은 팝업(`services/imgview.js` · 두 손가락 확대)이 맡는다.
   🔴 `max-height` 가 세트다: 세로로 긴 사진(스크린샷)은 폭을 줄여도 화면을 넘긴다.
   🔴 `object-fit: cover` 를 쓰지 않는다 — 잘라 보여 주면 「이 그림이 전부」로 읽힌다.
      맞춰 넣고(contain) 실제 그림은 팝업에서 본다. */
/* 🔴 폭 56% → **70%** (2026-08-20 · 요청 6번 — 「절반이 아니라 30% 축소」).
   본문 안에 놓이면서 글의 일부가 됐으므로, 너무 작으면 무엇이 붙었는지 안 보인다. */
.b-img {
  display: block; width: 70%; max-width: 300px; max-height: 260px;
  height: auto; object-fit: contain; object-position: left center;
  border-radius: 10px; margin: 4px 0 12px; cursor: pointer;
}
/* 🔴 본문 **안**에 놓인 사진은 앞뒤 글과 간격이 같아야 한다 — 묶음(아래)과 달리
   여기서는 그림이 문장 사이에 끼는 것이라 여백이 크면 글이 두 동강 난다. */
.b-img-inline { margin: 8px 0 10px; }
.b-img:active { opacity: .8; }
.b-imgpager .b-img { width: 100%; max-width: none; max-height: 260px; }

/* ── 🔴 첨부 미리보기 팝업 (2026-08-20 · 요청 5번 · services/imgview.js) ──────────────
   🔴 `.crop-ov` 와 **같은 z-index 층**이다(시트 스크림보다 위) — 아래에 두면 바깥을
      눌렀을 때 팝업이 아니라 그 아래 시트가 닫힌다(2026-08-19 에 실제로 물린 함정).
   🔴 `touch-action: none` 이 있어야 두 손가락 확대가 웹뷰의 기본 줌에 안 뺏긴다. */
.imgview {
  position: fixed; inset: 0; z-index: 960;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .9);
  opacity: 0; transition: opacity .15s;
  touch-action: none; overflow: hidden;
}
.imgview.show { opacity: 1; }
.imgview-img {
  max-width: 100%; max-height: 100%; object-fit: contain;
  transform-origin: center center; will-change: transform;
}
/* 🔴 **되돌아가는 동안에만** 전이를 켠다 (2026-08-25 · 요청 11번 · services/imgview.js).
   상시로 켜면 손가락을 따라가는 것이 한 박자 늦어 「끈적하다」는 느낌이 된다 —
   확대·팬도 같이 굼떠진다. 그래서 임계값을 못 넘겨 제자리로 갈 때만 붙였다 뗀다. */
.imgview-img.snap { transition: transform .18s ease-out; }
/* 🔴 첨부 **자리표** (2026-08-19 · 요청 10번 · board/embeds.js `paintImages`). 첨부가 없거나
   "무언가 안 뜬 것" 으로 읽힌다. 자리표 자체는 늘 있어야 나중에 끼워 넣을 수 있다. */
#b-imgs:empty { display: none; }
/* @멘션 — 누를 수는 없다(프로필 화면이 아직 없다). 눈에만 띄게 */
.b-at { color: var(--brand-500); font-weight: 700; }
.hint.ok { color: var(--up); }
/* 🔴 규칙을 어긴 입력 (2026-08-19 · 요청 1번) — 닉네임 시트가 타자마다 켠다
   (`board/profile.js paintNickHint`). 문장은 그대로 두고 **색만** 바꾼다: 다른 문장을 띄우면
   "무엇이 되는지" 를 말하는 유일한 안내가 그 순간 화면에서 사라진다. */
.hint.bad { color: var(--hot); }

/* @멘션 자동완성 — 이 스레드 참여자만 (요청이 안 나간다) */
.b-mentions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.b-mentions:empty { display: none; }

/* ── 🔴 캐럿 위치 자동완성 팝업 (2026-08-27 · 34차 요청 10번) ────────────────────────────
   입력칸 아래 고정 띠 대신 `@`·`$`·`#` 를 «친 자리»에 뜬다 — 좌표(left·top)는 board-ui
   `placeSuggAtCaret` 가 키 입력마다 인라인으로 다시 잰다. 이 클래스는 생김새만 맡는다.
   🔴 z-index 는 시트 스크림(940)보다 아래 — 시트가 열리면 그쪽이 덮는 것이 맞다. */
/* 🔴 겹 선택자 — `.sugg-inline`(아래쪽 정의)의 margin·max-height 를 순서와 무관하게 이긴다 */
.sugg-caret, .sugg-inline.sugg-caret, .b-mentions.sugg-caret {
  position: fixed; z-index: 930; margin: 0; padding: 5px;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .38);
  max-height: 34vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: 3px; flex-wrap: nowrap;
}
.sugg-caret:empty { display: none; }

/* ── 프로필·계정 카드 (2026-08-14 · 요청 4·6·7번) ─────────────────────────────
   🔴 `.profcard` 정의는 **아래(옛 프로필 화면 구획)에 하나**만 남겼다 (2026-08-27 ·
   28차 감사). 여기와 아래에 두 번 정의돼 있었고 뒤엣것이 이겨서, 이 자리의 값
   (radius 14px 등)은 처음부터 죽은 채 화면은 아래 값으로 칠해지고 있었다 —
   `.mk-tools` 중복 정의 사고와 같은 유형. 지금 보이는 모양을 정본으로 굳혔다. */
.prof-txt { flex: 1; min-width: 0; }
.prof-hi { font-size: 15px; font-weight: 700; }
.prof-mail { font-size: 12px; color: var(--text-sub); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 🔴 로그아웃은 카드 **바깥** 오른쪽 아래의 밑줄 텍스트다 — 카드 안에 두면 프로필을
   누르려다 로그아웃되거나 그 반대가 된다(한 상자에 두 동작을 넣지 않는다). */
.signout-row { display: flex; justify-content: flex-end; padding: 8px 4px 0; }
.linkbtn {
  background: none; border: 0; color: var(--text-mut); font-size: 12.5px;
  text-decoration: underline; text-underline-offset: 3px; padding: 4px 2px;
}
/* ── 🔴 계정 카드의 **유일한** `.profcard` (위 구획의 정정 주석 참조 · 2026-08-27) ─────
   쓰는 곳은 더보기의 계정 카드 하나다(app.js `data-act="profile"` — 안은
   `.profcard-av` + `.prof-txt`). 옛 프로필 화면(2026-08-18 판)의 `-body/-name/-nick/
   -need` 하위 선택자들은 참조 0 이라 걷었다 — 카드 전체가 버튼이라는 성질은 그대로다. */
.profcard {
  width: 100%; display: flex; align-items: center; gap: 12px; text-align: left;
  margin: 14px 0 6px; padding: 14px; cursor: pointer;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
  box-shadow: var(--sh-1); color: var(--text); font-family: inherit;
}
.profcard:active { background: var(--surface-2); }
.profcard-av {
  flex: none; width: 54px; height: 54px; border-radius: 50%;
  background-size: cover; background-position: center; background-color: var(--surface-2);
}
/* 편집 시트의 큰 사진 — 가운데 하나 */
/* 🟢 번들 캐릭터가 열셋이라(2026-09-12) 한 줄에 안 든다 — 줄바꿈 · 폰 폭에서 6~7개씩 두 줄 */
.profedit-av { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin: 6px 0 2px; }

/* 이 글에서 찾기 — 제목 오른쪽 (추가 비용 0 · 이미 받은 댓글에서 거른다) */
.b-titlerow { display: flex; align-items: flex-start; gap: 8px; }
.b-titlerow .b-title { flex: 1; min-width: 0; }
.b-find { flex: none; background: none; border: 0; color: var(--text-mut); padding: 4px; }
.b-find svg { width: 18px; height: 18px; }
.b-findbar { display: flex; gap: 8px; margin: 6px 0 10px; }
.b-findbar input {
  flex: 1; background: var(--surface); border: 1px solid var(--line-2); border-radius: 10px;
  padding: 8px 11px; color: var(--text); font-size: 13.5px;
}
/* ── 🔴 **찾는 동안만 아래에 자리를 만든다** (2026-09-02 · 65차·A 요청 5번 · board/comments.js `findPadHTML`) ──
 * 사고: 돋보기를 누르면 키보드가 올라오는데, 걸린 댓글이 몇 개뿐이면 그 결과가 **키보드
 * 뒤에 깔려** 보이지 않았다. `#scroll` 의 아래 여백에 `--kb` 가 이미 있지만(그쪽 주석)
 * 그건 「입력칸이 가려지지 않을」 만큼이고, 그 아래의 결과를 화면 위로 끌어올릴 만큼은
 * 아니다 — 걸린 줄이 적으면 문서가 짧아져 스크롤 여지가 그만큼 없어진다.
 * 🔴 그래서 열려 있는 동안만 **키보드 높이 + 한 화면의 40%** 를 뒤에 붙인다. 그 자리가
 *    입력줄을 화면 맨 위까지 올릴 여지이고, 결과가 서는 자리다(board/comments.js `scrollFindBarUp`).
 *    클래스만 남거나 사라진다(`findBarHTML` 이 `hidden` 을 상태로 들고 있는 것과 세트다).
 * 🔴 접히면 높이가 **0** 이다 — 안 찾는 동안 빈 여백이 남으면 「목록이 끝났나」로 읽힌다. */
.b-findpad { height: 0; }
#b-findbar:not([hidden]) ~ .b-findpad { height: calc(var(--kb, 0px) + 40vh); }

/* ── 스크롤바 숨김 (2026-08-17 · 요청 3번) ──────────────────────────────────
   🔴 **모든 화면**이다. 네이티브 웹뷰에서 오버레이 스크롤바가 목록 오른쪽에 잠깐씩
      번쩍이는데, 앱 안에서는 그게 웹페이지처럼 보이는 신호다.
   🔴 스크롤 자체는 그대로다 — 막대만 안 그린다(`overflow` 는 안 건드린다). */
* { scrollbar-width: none; -ms-overflow-style: none; }
*::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* ── 프로필 화면 — 사진·닉네임 (2026-08-17 · 요청 12번 · 체크비 이식) ────────
   🔴 큰 원 = 지금 사진. 누르면 앨범이 열린다(자르기 → 8KB 로 줄이기 → 업로드).
   🔴 그 아래 격자가 **번들 캐릭터 + ＋** 다(개수는 `services/avatar.js AVATARS` · 여기
      숫자를 안 적는다). 🔴 **고른 순간이 저장**이라 「저장」 버튼이 없다 — 초안(옛
      `pendAvatar`)과 머리줄 저장 버튼은 2026-08-19 3차에 걷었다(board/profile.js `avatarSheet`). */
.prof-av {
  position: relative; width: 84px; height: 84px; border-radius: 50%; border: 0; padding: 0;
  background: var(--surface-2) center/cover no-repeat; cursor: pointer;
}
.prof-av-cam {
  position: absolute; right: -2px; bottom: -2px; width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center; background: var(--brand-500); color: var(--on-brand);
  border: 2px solid var(--bg);
}
.prof-av-cam svg { width: 15px; height: 15px; }
/* ── 🔴 **격자다 — 한 줄에 다 밀어 넣지 않는다** (2026-09-12 · 실기기 「썸네일이 찌그러진다」) ──
   옛 판은 `display: flex` 에 줄바꿈이 없어서 캐릭터 열셋 + ＋ 가 한 줄을 다퉜다 — flex 가
   **폭만** 줄이고 높이(46px)는 그대로라 동그라미가 세로로 늘어난 **타원**이 됐다.
   🔴 그래서 칸 수를 고정하고(`repeat(7, …)`) 모양은 `aspect-ratio` 가 지킨다 — 좁은
      화면에서는 지름이 같이 줄 뿐 찌그러지지 않는다(`minmax(0, 46px)` 이 그 여지다).
   🔴 캐릭터를 늘리면 **줄이 는다** — 그게 이 규칙의 요점이다(개수의 정본은
      `services/avatar.js AVATARS` 이고 여기 숫자를 안 적는다). */
.av-row {
  display: grid; grid-template-columns: repeat(7, minmax(0, 46px));
  gap: 10px; justify-content: center; margin-top: 12px;
}
.av-pick {
  /* 🔴 `height` 를 되살리지 말 것 — 폭이 줄어드는 격자에서 높이를 박으면 타원이 된다
     (`.av-row` 주석). 정사각은 `aspect-ratio` 가 지킨다. */
  width: 100%; aspect-ratio: 1 / 1; border-radius: 50%; padding: 0; cursor: pointer;
  background: var(--surface-2) center/cover no-repeat;
  border: 2px solid transparent; box-shadow: 0 0 0 1px var(--line);
}
.av-pick.on { border-color: var(--brand-500); }
.av-add {
  display: grid; place-items: center; font-size: 22px; font-weight: 700;
  color: var(--brand-500); background: var(--brand-weak); border: 2px dashed var(--line-2);
}

/* ── 이미지 자르기 오버레이 (services/avatar.js · 체크비 cropImage 이식) ──────
   🔴 화면을 통째로 덮는다 — 앱바·탭바 위다(z-index). 자르는 동안 다른 곳을 누르면
      상태가 갈린다.
   🔴 손잡이의 **터치 판정은 44px**, 눈에 보이는 점은 16px 이다. 작게 그리고 크게 잡는다. */
/* 🔴 **z-index 는 시트(940)·로딩(950)보다 위여야 한다** (2026-08-19 운영 사고 · 요청
   4·9번). 200 이었다 — 그런데 자르기를 여는 곳이 **썸네일 변경 시트 안**이라, 크롭
   화면이 열려도 `#sheet-scrim`(common.css · 940) 밑에 통째로 깔렸다. 그 결과가 사용자가
   본 두 증상이다:
     · 「완료·취소가 아무 반응이 없다」 — 탭이 스크림에 먼저 닿는다.
     · 「썸네일을 누르면 시트가 닫힌다」 — 그 탭을 스크림이 "바깥 클릭" 으로 읽는다.
   🔴 자르는 동안은 **로딩(950)보다도 위**다. 업로드 스피너는 크롭이 끝난 뒤에 뜨므로
      가릴 일이 없고, 팝업(970·980)·스플래시(99999)보다는 아래로 둔다. */
.crop-ov {
  position: fixed; inset: 0; z-index: 960; background: #000;
  display: flex; flex-direction: column; touch-action: none;
  /* 🟢 위는 상단 배너 자리 (2026-10-15 · `avatar.js cropImage` 의 `cv-sheet` 문단) — 배너가 사진·손잡이를 덮지 않는다 */
  padding-top: var(--cv-gap);
}
/* 🟢 둘레 16px (2026-10-25 · 변 손잡이를 넣으며) — 그림이 무대를 꽉 채우면 처음 상자(그림 전체)의 변·모서리 손잡이 바깥 절반이 무대 밖에 잘려 잡기 어려웠다 ·
   상자 좌표는 무대의 테두리 상자 기준이라(avatar.js `measure`) 안쪽 여백이 계산을 안 바꾼다.
   🔴 칸은 `minmax(0, 1fr)` 로 못 박는다 (같은 날 · 375×812 에서 잰 값) — 옛 판은 칸이 내용 크기(`auto`)라 그림의 `max-height: 100%` 가 풀리지 않았고,
      세로로 긴 그림(스크린샷 — 이 화면의 주 손님)은 폭에 맞춰 811px 로 서서 무대(723px) 아래가 잘렸다 — 아래 변·모서리 손잡이가 버튼줄 밑에 깔려 아래를 못 잘랐다.
      이제 그림이 무대 안에 «다» 들어온다(같은 그림 319×691). */
.crop-stage {
  position: relative; flex: 1; min-height: 0; display: grid; place-items: center; overflow: hidden; padding: 16px;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
}
.crop-img { max-width: 100%; max-height: 100%; object-fit: contain; user-select: none; -webkit-user-drag: none; }
.crop-box { position: absolute; border: 2px solid #fff; box-shadow: 0 0 0 9999px rgba(0, 0, 0, .55); }
/* 🟢 끄는 동안의 3등분 격자 (2026-10-25 · 아이폰 자르기처럼 · avatar.js `down`·`up` 이 `.on` 을 켜고 끈다) */
.crop-box::before, .crop-box::after { content: ""; position: absolute; pointer-events: none; opacity: 0; transition: opacity .15s; border: 0 solid rgba(255, 255, 255, .55); }
.crop-box::before { top: 0; bottom: 0; left: 33.333%; right: 33.333%; border-left-width: 1px; border-right-width: 1px; }
.crop-box::after { left: 0; right: 0; top: 33.333%; bottom: 33.333%; border-top-width: 1px; border-bottom-width: 1px; }
.crop-box.on::before, .crop-box.on::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .crop-box::before, .crop-box::after { transition: none; } }
.crop-h {
  position: absolute; width: 44px; height: 44px; display: block;
  background: no-repeat center/16px 16px radial-gradient(circle, #fff 0 7px, transparent 7px);
}
.crop-h[data-h="nw"] { left: -22px; top: -22px; }
.crop-h[data-h="ne"] { right: -22px; top: -22px; }
.crop-h[data-h="sw"] { left: -22px; bottom: -22px; }
.crop-h[data-h="se"] { right: -22px; bottom: -22px; }
/* 🟢 자유 자르기의 네 변 손잡이 (2026-10-25 · avatar.js 머리 문단) — 잡히는 자리는 모서리 둘 사이의 변 전체 · 보이는 것은 변 위 가운데 짧은 막대.
   🔴 띠는 «바깥으로» 치우친다(안쪽 12px · 바깥 32px) — 가운데에 두면 작게 자른 상자의 안쪽이 거의 다 변 손잡이라 상자를 옮기려던 손이 크기를 바꿨다.
   🔴 모서리 손잡이보다 «앞»에 그린다(DOM 순서) — 겹치는 끝자리는 모서리가 이긴다 */
.crop-e { background: no-repeat center/26px 4px linear-gradient(#fff, #fff); }
.crop-e[data-h="n"], .crop-e[data-h="s"] { left: 22px; right: 22px; width: auto; }
.crop-e[data-h="w"], .crop-e[data-h="e"] { top: 22px; bottom: 22px; height: auto; background-size: 4px 26px; }
.crop-e[data-h="n"] { top: -32px; background-position: center 31px; }
.crop-e[data-h="s"] { bottom: -32px; background-position: center 9px; }
.crop-e[data-h="w"] { left: -32px; background-position: 31px center; }
.crop-e[data-h="e"] { right: -32px; background-position: 9px center; }
.crop-bar {
  flex: none; display: flex; gap: 10px; padding: 14px 16px calc(14px + var(--safe-bot));
  background: #000;
}
.crop-bar button {
  flex: 1; border: 0; border-radius: 12px; padding: 13px 0; font-size: 15px; font-weight: 700; cursor: pointer;
}
.crop-cancel { background: #23272e; color: #e6edf3; }
.crop-apply { background: var(--brand-500); color: var(--on-brand); }

/* ── 게시판 머리의 검색 아이콘 (2026-08-17 · 요청 5번) ──────────────────────
   전용 검색칸을 없앤 자리다. 누르면 **통합 검색 화면**이 커뮤니티 세그먼트로 열린다. */
.bhead { align-items: center; }
/* 🔴 **공지 줄로 옮겼다** (2026-08-28 · 47차 요청 1번 · board/notice.js `searchIcHTML` 문단).
   자리 이력: 칩 줄(폭을 뺏었다) → 머리줄(탭마다 있고 없다) → **공지 줄**(언제나 같은 자리). */
.bsearch-ic {
  margin-left: auto; background: none; border: 0; padding: 6px; cursor: pointer;
  color: var(--text-sub); display: inline-flex; flex: 0 0 auto;
}
.bsearch-ic svg { width: 20px; height: 20px; }

.sugg-inline { margin-top: 6px; max-height: 46vh; overflow-y: auto; }
.sugg-inline .opt { text-align: left; }

/* ── 탭 줄 하나로 (2026-08-17 3차 · 요청 1번) ──────────────────────────────── */
.bseg-scroll {
  flex: 1; min-width: 0; overflow-x: auto; overscroll-behavior-x: contain;
}
.bseg-scroll .bseg { display: inline-flex; gap: 6px; width: max-content; }
.bseg-pick {
  position: relative; flex: none; display: inline-flex; align-items: stretch;
  border-radius: 9px; border: 1px solid var(--line); background: var(--surface-2);
  color: var(--text-sub); font-size: 12.5px; font-weight: 700;
}
.bseg-pick.on, .bseg-pick.open { border-color: var(--brand-500); }
.bseg-pick.on { background: var(--brand-weak); color: var(--brand-500); }
.bseg-pick button { display: inline-flex; align-items: center; gap: 5px; border: 0; background: none; color: inherit; font: inherit; cursor: pointer; }
.bseg-pick-main { padding: 7px 4px 7px 9px; border-radius: 8px 0 0 8px; }
.bseg-pick-menu { padding: 7px 10px 7px 9px; border-radius: 0 8px 8px 0; }
/* ▾ 만 있는 쪽은 앞 칩과 붙어 있되 누르는 면적은 넉넉히 — 손가락이 칩 본체와 안 헷갈리게 */
.bseg-pick-only { padding: 7px 9px 7px 6px; }
.bseg-pick-solo { border-radius: 8px; padding: 7px 10px 7px 12px; }

.bseg-caret {
  width: 0; height: 0; margin-top: 1px; flex: none;
  border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid currentColor;
}
.bseg-pick.open .bseg-caret { transform: rotate(180deg); }

/* ── 인기글 카드 (2026-08-19 · 요청 7번 — 첫 판 배치로 복원) ──────────────────
   🔴 8/17~8/18 의 네 요청이 이 카드를 네 줄로 불려 놨던 흔적(`.bhot-foot2`·`.bhot-v`·
      `.bhot-time`·`.bhot-top .bhot-title`)은 **지웠다.** 안 쓰는 규칙을 남겨 두면
      다음에 이 블록을 읽는 사람이 지금 배치를 마크업이 아니라 CSS 로 추측하게 된다. */
.bhot-top { align-items: center; gap: 7px; }
/* ── 조회용 공감 — **목록 카드 · 인기글 카드** (2026-08-18 · 요청 1번) ──────────
      배경도 없다. 누를 수 없는 것이 눌리는 것처럼 보이면 안 된다.
   🔴 옛 `.reactview*` 클래스는 지웠다 — 같은 것을 두 벌로 그리면 한쪽만 고치게 된다.
   🔴 선택자를 `.bcard`/`.bpost`/`.bhotcard` 로 **좁혀 가중치를 올린다**.
      `.reactbtn.ro`(0,2,0) 는 `.reactbtn.bull.on`(0,3,0) 에 진다 — HOT 카드에서
      한 번 겪은 것과 같은 함정이라 여기도 같은 방식으로 못을 박는다. */
.bcard .reactbtn.ro, .bcard .reactbtn.ro.on,
.bhotcard .reactbtn.ro, .bhotcard .reactbtn.ro.on {
  cursor: default; background: none; border-color: transparent;
  color: var(--text-mut); height: auto; padding: 0 2px;
}
.bcard .reactbar.ro, .bhotcard .reactbar.ro { margin: 0; gap: 5px; }
.bcard .reactbtn.ro b svg, .bhotcard .reactbtn.ro b svg { width: 13px; height: 13px; }
.bcard .reactbtn.ro .rcount, .bhotcard .reactbtn.ro .rcount { font-size: 11.5px; }

/* 🔴 숫자는 **기울이지 않는다** (2026-08-17 · 요청 5번). `<i>` 를 쓴 자리가 몇 군데라
   기본체로 못박는다 — 카드마다 숫자만 기울어 있으면 다른 뜻처럼 읽힌다. */
.b-cmtn i, .bhot-cmt i, .bhot-n i, .rcount { font-style: normal; }

/* ── 레벨업 축하 팝업 (2026-08-18 · 요청 2번 · board/xp.js `showLevelUp`) ────────
   🔴 `position: fixed` + `body` 직속이다 — 셸(`#app`) 안에 넣으면 광고 배너 오프셋
      (`--ad-h`)만큼 내려가 "가운데"가 가운데가 아니게 된다(온보딩 오버레이와 같은 이유).
   🔴 z-index 는 시트(`#sheet-scrim`)보다 위, 스플래시(99999)보다 아래.
   🔴 자동으로 안 닫는다 — 확인 버튼이나 스크림을 눌러야 닫힌다(요청). 그래서 토스트와
      달리 `pointer-events` 를 살려 둔다. */
.lvup {
  position: fixed; inset: 0; z-index: 980;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(0, 0, 0, .55);
  opacity: 0; transition: opacity .18s ease;
}
.lvup.show { opacity: 1; }
.lvup-card {
  width: 100%; max-width: 320px; text-align: center;
  padding: 26px 22px 18px; border-radius: 18px;
  background: var(--surface); border: 1px solid var(--line);
  transform: scale(.92); transition: transform .22s cubic-bezier(.2, 1.3, .4, 1);
}
.lvup.show .lvup-card { transform: scale(1); }
/* 🔴 배지는 이 화면에서 **유일한 브랜드색**이다 — 눈이 숫자로 먼저 가야 한다 */
.lvup-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 76px; height: 76px; margin-bottom: 14px; padding: 0 12px;
  border-radius: 50%; background: var(--brand-weak); border: 2px solid var(--brand-500);
  color: var(--brand-500); font-size: 20px; font-weight: 800; letter-spacing: -.5px;
  font-variant-numeric: tabular-nums;
}
.lvup-card h3 { margin: 0 0 6px; font-size: 19px; font-weight: 800; letter-spacing: -.3px; }
.lvup-card p { margin: 0; font-size: 14px; color: var(--text-sub); }
.lvup-ok { margin-top: 18px; }

/* ── 프로필 레벨·경험치 (board/profile.js `levelCardHTML`) ─────────────────────────────
   🔴 막대의 뜻은 「**다음 레벨까지 얼마나 왔나**」다(누적이 아니다). 레벨 구간이
      제곱으로 넓어져서, 누적으로 그리면 상위 레벨에서 막대가 안 움직이는 것처럼 보인다. */
/* ── 🔴 프로필의 **레벨 섹션** (2026-08-19 · 요청 4번) ────────────────────────────
   카드 하나에 [배지 …… 경험치 n/n] / [막대] / [설명 한 줄]. 옛 판은 프로필 카드 **안**의
   한 줄이었고 가운데 정렬이었다 — 이제 자기 섹션이라 목록 행들과 **왼쪽을 맞춘다**.
   🔴 경험치 숫자는 오른쪽 끝이다(`margin-left: auto`). 배지 옆에 붙이면 「Lv.3 12/16」이
      한 덩어리로 읽혀서, 12가 레벨의 일부처럼 보인다.
   🔴 설명 줄은 지우지 말 것 — 숫자를 되살린 전제가 이 줄이다(strings `prof_lv_help`). */
.prof-lvcard {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 14px; margin-bottom: 6px;
}
.prof-lvcard .prof-lv-top { justify-content: flex-start; }
.prof-lvcard .prof-lv-bar { margin: 10px 0 8px; }
.prof-xp { margin-left: auto; font-size: 12.5px; font-weight: 700; color: var(--text-sub); font-variant-numeric: tabular-nums; }
/* 🔴 `.hint` 의 아래 패딩(14px)을 걷는다 — 안 걷으면 카드 안쪽 여백(14)과 겹쳐
   설명 줄 밑에만 28px 이 남는다. 카드 안에서는 카드가 여백의 주인이다. */
.prof-lv-help { margin: 0; padding: 0; }
/* ── 🟢 오늘의 미션 화면 (140차·H · board/missions.js `renderMissions` · 옛 카드 안 구획 둘 `missionsHTML`·`xpTodayHTML` 을 옮겼다) ──
   🔴 계열 색 셋은 계열 램프(`:root` 의 `--sv-1..3`)의 앞 세 칸이다 — 도넛은 빈 조각이 빠지며 아무 두 색이 맞닿으므로
      «모든 쌍» 검증을 통과하는 것이 그 셋뿐이다(`validate_palette.js --pairs all` · 어둠 바탕 #161b22 · 밝은 바탕 테마는 밝은 단계(`data-tone` 줄)).
   🔴 글자는 글자 색(`--text`·`--text-sub`)이다 — 계열 색은 표식(조각·점·막대)에만 쓴다. 상태 색은 아이콘 + 글자와 같이만 쓴다. */
.mv { --mv-1: var(--sv-1); --mv-2: var(--sv-2); --mv-3: var(--sv-3); --mv-good: #0ca30c; --mv-track: var(--surface-2);
  display: flex; flex-direction: column; gap: 12px; padding: 12px 0 8px; }

.mv-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md, 14px); padding: 14px; }
.mv-h { display: flex; align-items: baseline; gap: 8px; margin: 0 0 10px; font-size: 14px; font-weight: 800; color: var(--text); }
.mv-h small { margin-left: auto; font-size: 11.5px; font-weight: 600; color: var(--text-mut); }
.mv-note { margin: 8px 0 0; padding: 0; }
/* ── 🟢 **머리 카드 — 「나」가 먼저, 「오늘의 한도」가 그 아래** (2026-09-21 · 145차·H 요청 1·2번 · board/missions.js `renderMissions` 의 그 문단) ──
 * 층 둘이 한 카드 안에 있고 사이를 실선이 가른다(`.mv2-sep` — 카드 여백만큼 좌우로 흘러넘쳐 «칸이 둘»로 읽힌다).
 * 🔴 위층의 큰 숫자는 «내 경험치», 아래층의 큰 숫자는 «오늘의 한도»다 — 둘 다 오른쪽 끝에 서서 세로로 읽힌다(`tabular-nums`).
 * 🔴 한도 막대(`.mv2-tbar`)만 두껍다(10px) — 이 화면에서 «오늘 얼마나 찼나»가 한눈에 보여야 하는 값이라 레벨 막대(6px)보다 앞선다. */
.mv-donutbox { position: relative; flex: none; display: grid; place-items: center; }
.mv-ringtxt { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1.1; }
.mv-ringtxt b { font-size: 24px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
.mv-ringtxt small { font-size: 11px; font-weight: 600; color: var(--text-mut); }
.mv2-lvrow { display: flex; align-items: center; gap: 8px; }
.mv2-lvl { font-size: 12.5px; color: var(--text-sub); }
.mv2-lvv { margin-left: auto; font-size: 16px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
.mv2-lvv small { font-size: 12px; font-weight: 700; color: var(--text-mut); }
.mv2-lvbar { margin-top: 9px; }
.mv2-sub { margin: 7px 0 0; font-size: 11.5px; color: var(--text-mut); }
.mv2-sub b { color: var(--text-sub); font-variant-numeric: tabular-nums; }
/* 🔴 좌우 `-14px` 은 카드 안쪽 여백(`.mv-card` 의 padding)과 **세트다** — 값이 갈리면 선이 카드보다 짧거나 길어진다 */
.mv2-sep { height: 1px; background: var(--line); margin: 13px -14px; }
.mv2-todayh { display: flex; align-items: baseline; gap: 8px; }
.mv2-tl { font-size: 12.5px; font-weight: 700; color: var(--text-sub); }
/* 구독 중이라는 표식 — 색은 이 앱에서 「구독으로 열린 것」을 가리키는 브랜드 계열이다(`.prof-boost-x` 와 같은 축) */
.mv2-plus { margin-left: 6px; padding: 1px 6px; border-radius: 6px; font-size: 10px; font-weight: 800; color: var(--brand-500); background: var(--brand-weak); }
.mv2-cap { margin-left: auto; font-size: 22px; font-weight: 800; letter-spacing: -.02em; color: var(--text); font-variant-numeric: tabular-nums; }
.mv2-cap small { font-size: 12px; font-weight: 700; color: var(--text-mut); }
/* 🔴 한도 막대만 두껍고 **색이 다르다**(`.mv-s-good` — 아래 계열 색 줄에 이미 있다): 레벨 막대는 브랜드색이라,
   같은 색이면 두 막대가 같은 것을 센다고 읽힌다. */
.mv2-tbar { height: 10px; margin-top: 9px; border-radius: 6px; }
.mv2-tbar > i { border-radius: 6px; }
.mv2-tfoot { display: flex; align-items: center; gap: 12px; margin-top: 8px; font-size: 11.5px; color: var(--text-mut); flex-wrap: wrap; }
.mv2-tfoot b { color: var(--text); font-variant-numeric: tabular-nums; }
/* 점 둘이 막대의 두 몫(받음·남음)을 가리킨다 — 글자만 있으면 어느 색이 무엇인지 말할 자리가 없다 */
.mv2-dot { display: inline-block; width: 7px; height: 7px; margin-right: 5px; border-radius: 50%; background: var(--mv-track); }
.mv2-dot.on { background: var(--mv-good); }
.mv2-reset { margin-left: auto; }
.mv2-mis { display: flex; align-items: center; gap: 8px; margin-top: 11px; padding-top: 11px; border-top: 1px solid var(--line); font-size: 12.5px; color: var(--text-sub); }
.mv2-mis b { color: var(--text); font-variant-numeric: tabular-nums; }
.mv2-mis .mv-steps { flex: 1; }

/* 게이지 — 막대(끝 4px 둥글게) · 칸(목표가 작은 정수) */
.mv-bar { display: block; height: 6px; border-radius: 4px; background: var(--mv-track); overflow: hidden; }
.mv-bar > i { display: block; height: 100%; border-radius: 4px; background: var(--c, var(--brand-500)); transition: width .6s cubic-bezier(.2, .7, .3, 1); }
.mv-bar-thin { height: 4px; grid-column: 1 / -1; }
.mv-steps { display: flex; gap: 2px; }
.mv-steps > i { flex: 1; height: 8px; border-radius: 4px; background: var(--mv-track); }
.mv-steps > i.on { background: var(--c, var(--brand-500)); }
.mv-s-brand { --c: var(--brand-500); }
.mv-s1 { --c: var(--mv-1); } .mv-s2 { --c: var(--mv-2); } .mv-s3 { --c: var(--mv-3); } .mv-s-good { --c: var(--mv-good); }
/* 미션 줄 */
.mv-mis { display: grid; grid-template-columns: 20px 1fr auto; align-items: center; column-gap: 8px; row-gap: 6px; padding: 8px 0; border-top: 1px solid var(--line); }
.mv-mis:first-of-type { border-top: 0; padding-top: 0; }
.mv-mis-ck { width: 20px; height: 20px; border-radius: 50%; border: 1.5px solid var(--line-2); display: grid; place-items: center; font-size: 11px; font-weight: 900; color: #fff; }
.mv-mis.is-done .mv-mis-ck { background: var(--brand-500); border-color: var(--brand-500); }
.mv-mis-t { min-width: 0; font-size: 13.5px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mv-mis.is-done .mv-mis-t { color: var(--text-sub); }
/* 보상 — 달성 전에는 흐린 테두리(받을 것) · 달성하면 채운 배지(받은 것) (141차·M 요청 4번) */
.mv-mis-p { font-size: 12px; font-variant-numeric: tabular-nums; padding: 2px 7px; border-radius: 999px; border: 1px solid var(--line-2); color: var(--text-mut); }
.mv-mis.is-done .mv-mis-p { background: var(--brand-500); border-color: var(--brand-500); color: var(--on-brand); font-weight: 800; }   /* 글자색은 .btn.primary 와 같다 — 노란 브랜드에서 흰 글자는 안 읽힌다 */
.mv-mis-g { grid-column: 2 / -1; display: flex; align-items: center; gap: 10px; }
.mv-mis-g > .mv-steps, .mv-mis-g > .mv-bar { flex: 1; }
.mv-mis-n { flex: none; min-width: 34px; text-align: right; font-size: 12px; color: var(--text-sub); font-variant-numeric: tabular-nums; }
/* 도넛 + 범례(표) */
.mv-mix { display: flex; align-items: flex-start; gap: 14px; }
.mv-seg { stroke: var(--c); }
.mv-seg.mv-s1 { stroke: var(--mv-1); } .mv-seg.mv-s2 { stroke: var(--mv-2); } .mv-seg.mv-s3 { stroke: var(--mv-3); }
.mv-legend { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.mv-lg-h { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; color: var(--text); }
.mv-lg-h b { margin-left: auto; font-variant-numeric: tabular-nums; }
.mv-lg-h small { min-width: 32px; text-align: right; font-size: 11.5px; color: var(--text-mut); font-variant-numeric: tabular-nums; }
.mv-dotk { flex: none; width: 10px; height: 10px; border-radius: 3px; background: var(--c); }
/* ── 🟢 **오늘의 획득 경험치 — 방법별 줄** (2026-10-11 · board/profile.js `renderXpDay`) ──
   묶음 머리(색 점 = 도넛 조각) 아래 줄마다 [이름 · 상태 칩] / 한 줄 설명 / [게이지 · 오늘 값].
   칩 색이 상태를 말한다: 다 받음·완료 = 초록 · 더 받을 수 있음 = 브랜드 · 아직·오늘 한도 끝 = 흐림 (색만으로 말하지 않는다 — 칩 글자가 같은 말을 한다) */
.xpd-grp + .xpd-grp { margin-top: 14px; }
.xpd-gh { display: flex; align-items: center; gap: 6px; margin: 0 0 4px; font-size: 12.5px; font-weight: 800; color: var(--text-sub); }
.xpd-row { padding: 10px 0; border-top: 1px solid var(--line); }
.xpd-gh + .xpd-row { border-top: 0; }
.xpd-top { display: flex; align-items: center; gap: 8px; }
.xpd-name { min-width: 0; font-size: 14px; font-weight: 800; color: var(--text); }
.xpd-chip { margin-left: auto; flex: none; padding: 3px 9px; border-radius: 999px; font-style: normal; font-size: 11.5px; font-weight: 800;
  color: var(--text-mut); background: var(--mv-track); font-variant-numeric: tabular-nums; }
.xpd-row.is-full .xpd-chip { color: #fff; background: var(--mv-good); }
.xpd-row.is-go .xpd-chip { color: var(--brand-500); background: color-mix(in srgb, var(--brand-500) 16%, transparent); }
.xpd-why { margin: 4px 0 8px; font-size: 12.5px; line-height: 1.5; color: var(--text-sub); }
.xpd-meter { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 10px; }
.xpd-meter > .mv-bar, .xpd-meter > .mv-steps { grid-column: auto; }
.xpd-v { font-size: 12px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.xpd-row.is-none .xpd-v, .xpd-row.is-cap .xpd-v { color: var(--text-mut); }
.mv-lg-row { display: grid; grid-template-columns: 1fr auto auto; align-items: center; column-gap: 8px; row-gap: 3px; padding: 4px 0 0 16px; font-size: 12px; color: var(--text-sub); }
.mv-lg-l { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mv-lg-c { font-variant-numeric: tabular-nums; }
.mv-lg-v { min-width: 52px; text-align: right; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.mv-lg-v small { font-weight: 600; color: var(--text-mut); }
@media (max-width: 360px) { .mv-mix { flex-direction: column; align-items: center; } .mv-legend { width: 100%; } }
/* 선 그래프 — 2px 선 · 8px 점 · 옅은 눈금 · 십자선 + 말풍선 */
.mv-linebox { position: relative; }
.mv-line { display: block; width: 100%; height: auto; touch-action: pan-y; }
.mv-grid { stroke: var(--line); stroke-width: 1; }
.mv-axis { fill: var(--text-mut); font-size: 10px; font-variant-numeric: tabular-nums; }
.mv-path { stroke: var(--mv-1); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.mv-dot { fill: var(--surface); stroke: var(--mv-1); stroke-width: 2; vector-effect: non-scaling-stroke; }
.mv-dot.on { fill: var(--mv-1); }
.mv-cross { stroke: var(--text-mut); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.mv-tip { position: absolute; top: -6px; transform: translateY(-100%); display: flex; flex-direction: column; gap: 2px; padding: 6px 9px; border-radius: 8px;
  background: var(--surface-2); border: 1px solid var(--line-2); box-shadow: 0 4px 12px rgba(0, 0, 0, .25); pointer-events: none; white-space: nowrap; }
.mv-tip b { font-size: 12.5px; color: var(--text); font-variant-numeric: tabular-nums; }
.mv-tip span { font-size: 11px; color: var(--text-sub); }
/* 봇 시간표 — 열 칸 · 상태는 아이콘 + 글자(색만으로 말하지 않는다) */
.mv-slots { display: grid; grid-template-columns: repeat(10, 1fr); gap: 4px; }
.mv-slot { position: relative; display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 4px 0 6px; border-radius: 8px; background: var(--surface-2); border: 1.5px solid transparent; }
.mv-slot img { width: 26px; height: 26px; }
/* 🟢 **얼굴 각도** (2026-09-19 · 145차·E 요청 2번 · 슬롯마다 씨앗이 고른다 · `mascot.js catchPlan` 의 `tilt`) —
   🔴 **도 단위는 여기 하나다**: 판정(±1)은 JS, 각도는 CSS 다. 그림이 26px 라 8° 를 넘기면 얼굴이 기울어 보이는 게 */
.mv-slot img.mv-tilt-l { transform: rotate(-8deg); transform-origin: 50% 100%; }
.mv-slot img.mv-tilt-r { transform: rotate(8deg); transform-origin: 50% 100%; }
/* 🟢 시각 줄 — 시각 + ★(미션 봇) (2026-09-21 · 145차·F 요청 9번 · board/missions.js `renderMissions`) */
.mv-slot-h {
  display: flex; align-items: center; justify-content: center; gap: 2px;
  font-size: 10px; font-weight: 700; color: var(--text-mut); font-variant-numeric: tabular-nums;
}
/* ── 🟢 **상태 배지는 얼굴 오른쪽 아래** (2026-09-21 · 145차·I 요청 1번 · board/missions.js `renderMissions` 의 `ST` 문단) ──────────
 *    같은 자리에 맨 ✓·✕ 를 두었던 판이 바로 그 이유로 시각 줄로 쫓겨났었다(위 문단) — 자리가 아니라 모양이 원인이었다.
 * 🔴 글자는 **흰색 고정**이다: 바탕이 녹색·빨강·브랜드색으로 바뀌므로 `--text` 를 쓰면 밝은 테마에서 대비가 무너진다.
 * 🔴 링 색은 칸의 바탕(`--surface-2`)과 **세트다** — 칸 배경을 바꾸면 여기도 같이 바꾼다(안 바꾸면 배지 둘레에 헌 색 테가 남는다). */
.mv-slot-f { position: relative; display: block; line-height: 0; }
.mv-slot-st {
  position: absolute; right: -3px; bottom: -3px;
  display: grid; place-items: center; width: 14px; height: 14px; border-radius: 50%;
  font-size: 9px; font-weight: 900; line-height: 1; font-style: normal;
  color: #fff; background: var(--text-mut); box-shadow: 0 0 0 2px var(--surface-2);
}
.mv-slot.is-caught { border-color: var(--mv-good); } .mv-slot.is-caught .mv-slot-st { background: var(--mv-good); }
/* 🔴 놓친 칸은 **붉은 테 + 붉은 X** 다 (요청 1번) — 흐린 얼굴만으로는 「아직 안 왔다」와 구별이 안 됐다. */
.mv-slot.is-missed { border-color: color-mix(in srgb, var(--hot) 55%, transparent); }
.mv-slot.is-missed img { opacity: .45; filter: grayscale(1); } .mv-slot.is-missed .mv-slot-st { background: var(--hot); }
.mv-slot.is-live { border-color: var(--brand-500); animation: mv-live 1.4s ease-in-out infinite; } .mv-slot.is-live .mv-slot-st { background: var(--brand-500); }
.mv-slot.is-soon img { opacity: .7; }
/* 미션 봇 ★ — 시각 줄, 상태 기호 «앞» (141차·M 에 칸 아래 노란 점에서 ★ 로 · 그 점은 범례의 「● 지금 숨어 있음」과 색·모양이 같았다 ·
   자리는 2026-09-21 에 그림 모서리에서 이 줄로 올라왔다 · board/missions.js `renderMissions` 의 그 문단) */
.mv-slot-mis { font-size: 9px; line-height: 1; font-style: normal; color: var(--brand-500); }
@keyframes mv-live { 50% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-500) 30%, transparent); } }
@media (prefers-reduced-motion: reduce) { .mv-slot.is-live { animation: none; } .mv-bar > i { transition: none; } }
.mv-keys { display: flex; flex-wrap: wrap; gap: 10px; margin: 10px 0 4px; font-size: 11.5px; color: var(--text-sub); }
.mv-key { display: inline-flex; align-items: center; gap: 4px; }
/* 🔴 범례 기호는 **칸의 배지와 같은 모양**이라야 한다 (2026-09-21 · 145차·I) — 범례가 맨 글자면 칸의 배지를 설명하는 말로 안 읽힌다 */
.mv-key i { display: grid; place-items: center; width: 14px; height: 14px; border-radius: 50%;
  font-style: normal; font-weight: 900; font-size: 9px; line-height: 1; color: #fff; }
.mv-key.k0 i { background: var(--mv-good); } .mv-key.k1 i { background: var(--hot); } .mv-key.k2 i { background: var(--brand-500); }
/* 「예정」은 기호가 없는 상태다 — 빈 동그라미(테두리만)로 그 «없음»을 그린다 */
.mv-key.k3 i { width: 10px; height: 10px; background: none; border: 1.5px solid var(--text-mut); }
.mv-key.kmis i { width: auto; height: auto; background: none; }
.mv-key.kmis i { color: var(--brand-500); font-size: 11px; }
/* 봇 출현 알림 스위치 — 카드 안의 한 줄 (144차 · board/missions.js `renderMissions`) */
.mv-alertcard { padding: 0; overflow: hidden; }
.mv-alertcard .row { border: 0; background: none; }
.mv-bots { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.mv-bot { display: grid; grid-template-columns: 24px 88px 1fr auto; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text); }
.mv-bot img { width: 24px; height: 24px; }
.mv-bot em { margin-left: 6px; font-style: normal; font-size: 10.5px; font-weight: 800; color: var(--brand-500); }
.mv-bot b { font-variant-numeric: tabular-nums; min-width: 28px; text-align: right; }
/* ── 경험치 획득 내역 (2026-08-19 · 요청 7번) ────────────────────────────────
   🔴 카드 **안쪽**의 마지막 줄이라 카드 여백(14px)을 좌우로 되밀어 구분선이 카드 폭을
      꽉 채우게 한다 — 안 밀면 선이 짧아 "잘린 줄" 로 보인다. */
.prof-lv-log {
  display: flex; align-items: center; width: calc(100% + 28px);
  margin: 12px -14px -14px; padding: 12px 14px;
  border: 0; border-top: 1px solid var(--line); background: none; cursor: pointer;
  color: var(--text); font-size: 14px; font-weight: 600; text-align: left;
}
/* ── 🔴 **아래로 흘러넘치는 것은 «맨 끝 줄» 하나뿐이다** (2026-09-18 · 145차 요청 2번 · 사용자 「레벨업 부스터 배너가 겹쳐 보이는 현상」) ──
 * 이 카드의 줄들(`.prof-lv-log` — 오늘의 획득 경험치·광고 XP·오늘의 미션·업적 · `.prof-boost` — 레벨업 부스터 배너 · 🟡 옛 「획득 내역」 줄은 2026-10-12 에 걷었다)은 **전부** 아래 마진이
 * `-14px` 이었다. 카드 «바닥까지» 흘러넘치라고 준 값인데, 그 값이 쓰여진 2026-08-21 에는 부스터 줄이 **실제로 마지막 줄**이었다.
 * 그 뒤 광고 XP(08-23)·획득 내역·오늘의 미션(140차·H)이 그 아래에 붙으면서 전제가 조용히 깨졌다:
 *   인접 형제의 마진은 합쳐지므로(`12px` 와 `-14px`) 줄마다 **2~4px 씩 겹친다.**
 *   글자 줄끼리는 티가 안 나지만 부스터 배너는 **바탕색이 있어서**(`--brand-weak`) 위아래 줄의 구분선을 덮는다 — 그게 「겹쳐 보임」이다.
 * 🔴 그래서 흘러넘침을 **마지막 줄에만** 준다. 줄끼리는 딱 붙고(위 마진 0 · 구분선이 경계다) 카드 바닥은 그대로 꽉 찬다.
 * 🔴 `:last-child` 를 카드 자식 «전부»에 걸지 않는다 — 줄이 하나도 없는 카드에서는 마지막 자식이 설명 줄(`.prof-lv-help`)이라
 *    그때 카드 바닥이 14px 말려 올라간다. 두 클래스에만 건다.
 * 🔴 **카드에 새 줄을 더하면 그 클래스를 여기 세 규칙에 같이 적는다** — 안 적으면 그 줄이 이 사고를 그대로 되살린다. */
.prof-lvcard > .prof-lv-log, .prof-lvcard > .prof-boost { margin-bottom: 0; }
.prof-lvcard > .prof-lv-log + .prof-lv-log, .prof-lvcard > .prof-lv-log + .prof-boost,
.prof-lvcard > .prof-boost + .prof-lv-log, .prof-lvcard > .prof-boost + .prof-boost { margin-top: 0; }
.prof-lvcard > .prof-lv-log:last-child, .prof-lvcard > .prof-boost:last-child { margin-bottom: -14px; }
/* ── 🔴 **맨 «위» 줄도 카드 끝까지 간다** (2026-09-21 · 145차·F 요청 1번 · 사용자 「레벨업 부스터 배너가 위로 붙어야 할 것 같은데 어긋난 거지?」) ──
 * 위 문단이 «아래»만 고쳤다. 부스터 줄은 141차·M 에 **카드 맨 위**로 올라갔는데 위 마진이 `10px` 그대로라,
 * 카드 안쪽 여백(14) + 10 = **24px 짜리 빈 띠**가 배너 위에 남았다(줄이 하나도 없는 카드를 위해 준 값이 첫 줄에서 헛돈다).
 * 🔴 그리고 그 배너는 좌우로 카드 밖(-14px)까지 흘러넘치므로 위로 붙이면 **카드의 둥근 모서리**를 덮는다 —
 *    그래서 첫 줄일 때만 위 모서리를 카드와 같은 반지름으로 깎고, 위 구분선도 지운다(위에 아무것도 없는데 선만 뜬다).
 * 🔴 `.prof-boost-off`(누를 수 있는 반대 상태)도 같은 자리라 같이 건다 — 둘은 같은 줄의 두 얼굴이다. */
/* 🔴 **그리고 아래에는 여백이 있어야 한다** (2026-09-21 · 145차·I 요청 2번 · 사용자 「위에 붙긴 했는데 아래 레벨 영역과 여백이 없다」) ──
 * 위 `.prof-lvcard > .prof-boost { margin-bottom: 0 }` 는 **줄과 줄이 구분선으로 맞닿는** 경우를 위한 값이다(그 문단).
 * 첫 줄인 부스터 배너 아래에 오는 것은 줄이 아니라 **카드 본문**(레벨 배지·막대)이라 구분선이 없고, 그래서 배너가 본문에 그냥 붙었다.
 * 🔴 값은 카드 안쪽 여백(14px)과 **같다** — 배너 «위»가 카드 테두리에 딱 붙었으므로, 아래는 본문이 카드 좌우에서 갖는 것과 같은 간격이어야
 *    배너가 「카드 머리띠」로 읽힌다. 🔴 배너가 첫 줄이 아닌 판(줄 사이에 낀 경우)에는 안 건다 — 거기선 구분선이 경계다. */
.prof-lvcard > .prof-boost:first-child {
  margin-top: -14px; margin-bottom: 14px; border-top: 0;
  border-radius: calc(var(--r-sm) - 1px) calc(var(--r-sm) - 1px) 0 0;
}
.prof-lv-log .chev { margin-left: auto; width: 16px; height: 16px; color: var(--text-mut); }
.prof-lv-log .chev svg { width: 100%; height: 100%; }
/* ── 🔴 **줄 앞 아이콘** (2026-08-24 · 요청 5번 · board/profile.js `ICON_ADXP`·`ICON_MISSION`·`ICON_ACH`·`ICON_XPDAY` · 🗄 `ICON_XPLOG` 는 2026-10-12 에 그 줄과 함께 걷었다) ────
   🔴 크기는 **여기 한 곳**이 정한다 — SVG 에 px 를 적으면 값이 두 곳에 살아서 갈린다
      (코인스쿱에서 아이콘 그림에 15px 을 따로 적어 아이콘과 이니셜이 어긋난 사고가 있었다).
   🔴 색은 안 정한다 — `currentColor` 라 줄의 색(브랜드 · `.is-done` 회색)을 그대로 따른다.
   🔴 `flex: none` 이 세트다: 옆 글자가 길어질 때 아이콘이 눌려 찌그러지면 안 된다. */
.prof-lvi { flex: 0 0 auto; display: grid; place-items: center; width: 17px; height: 17px; margin-right: 8px; }
.prof-lvi svg { width: 100%; height: 100%; }
/* 🔴 라벨은 **줄어들 수 있어야 한다** — 오른쪽 「오늘 남은 횟수 3/5회」가 길어지면
   좁은 기기에서 줄이 밀린다. 여기가 말줄임을 먹고 숫자는 안 밀린다(그쪽이 `flex: none`). */
.prof-lvt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 시트 안의 목록 — [무엇 …… 언제] [+점수]. 🔴 점수는 오른쪽 끝에 **폭 고정**으로 둔다
   (`tabular-nums`) — 자리가 흔들리면 줄마다 눈이 다시 찾아야 한다. */
.xp-list { display: flex; flex-direction: column; }
.xp-row {
  display: flex; align-items: baseline; gap: 8px;
  padding: 11px 4px; border-bottom: 1px solid var(--line);
}
.xp-row:last-child { border-bottom: 0; }
.xp-what { font-size: 14px; }
.xp-when { font-size: 12px; color: var(--text-mut); }
.xp-pt { margin-left: auto; color: var(--brand-500); font-variant-numeric: tabular-nums; }
/* ── 🔴 **점수 한 줄의 「왜」** (2026-08-25 · 요청 8·9번 · board/missions.js `xpWhySheet`) ─────────
   🔴 물음표는 **작지만 과녁은 크다** — 44px 짜리 손가락에 12px 글자를 그대로 두면
      「눌러도 안 눌린다」가 된다(칩 ✕ 에서 이미 겪었다). 글자만 작게, 패딩으로 넓힌다.
   🔴 `×2` 배지는 **그 줄에 적힌 배수**다(지금 구독 상태가 아니다) — 색은 부스터 줄
      (`.prof-boost-x`)과 같은 축이라야 「같은 것」으로 읽힌다. */
.xp-x2 {
  display: inline-block; margin-left: 5px; padding: 1px 5px; border-radius: 5px;
  font-size: 10.5px; font-weight: 800; color: var(--brand-500); background: var(--brand-weak);
  vertical-align: 1px;
}
.xp-why-btn {
  flex: 0 0 auto; margin-left: 6px; padding: 4px 9px; border: 0; border-radius: 999px;
  font-size: 12px; font-weight: 800; line-height: 1;
  color: var(--text-mut); background: var(--surface-2); cursor: pointer;
}
.xp-why-top { display: flex; align-items: baseline; gap: 8px; padding: 4px 4px 12px; }
.xp-why-calc {
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px 12px; border-radius: 10px; background: var(--surface-2); margin-bottom: 12px;
}
.xp-why-step { display: flex; justify-content: space-between; font-size: 13.5px; }
/* 🔴 **깎인 단계는 붉게, 늘어난 단계는 푸르게** (2026-08-25 · 요청 12번) — 계산 줄에서
   눈이 먼저 찾는 것은 「어디서 줄었나」다. 색이 그 답을 문장보다 먼저 준다. */
.xp-why-step b.dn { color: var(--hot); }
.xp-why-step b.up { color: var(--up); }
/* 기대값과 실제의 차이 한 줄 — 🟡 힌트보다 진하다(이 화면의 결론이다) */
.xp-why-gap {
  margin: 8px 2px 2px; font-size: 12.5px; font-weight: 700; color: var(--text-sub);
  font-variant-numeric: tabular-nums;
}
.xp-why-step > b { font-variant-numeric: tabular-nums; }
/* 🔴 합계 줄은 위와 **선으로** 갈린다 — 글씨만 굵게 하면 그냥 한 항목으로 읽힌다 */
.xp-why-sum {
  margin-top: 6px; padding-top: 7px; border-top: 1px solid var(--line);
  color: var(--brand-500); font-weight: 800;
}
.xp-why .hint { margin: 0 4px 8px; }
/* 계정 섹션의 두 행 — 오른쪽에 **지금 값**을 같이 보여 준다 (요청 4번).
   🔴 썸네일은 그림 자체가 그 값이라 작은 원으로 놓는다. 글자로 "설정됨" 이라 적으면
      정작 어떤 그림인지는 시트를 열어야 안다. */
.row-av {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  background-size: cover; background-position: center; background-color: var(--surface-2);
}
/* 닉네임을 아직 안 정했으면 값 자리가 **안내**가 된다 — 비워 두면 고칠 게 있는 줄 모른다 */
.row-need { color: var(--text-mut); font-style: italic; }

.prof-lv-top { display: flex; align-items: center; justify-content: center; gap: 6px; }
.prof-lv-bar {
  height: 6px; margin: 7px 0 5px; border-radius: 3px; overflow: hidden;
  background: var(--surface-2);
}
.prof-lv-bar i { display: block; height: 100%; background: var(--brand-500); transition: width .3s ease; }
/* 🟢 **경험치가 «차오른다»** (2026-10-08 · 리워드 광고 · board/xp.js `playXpBars`) — 게이지 둘레가 금빛으로 번지고 · 채움이 옛 폭에서 새 폭으로 천천히 ·
   그 위로 빛 두 줄기. 레벨 카드(`.prof-lv-bar`)와 미션 화면의 레벨·오늘 게이지(`.mv2-lvbar`·`.mv2-tbar`)가 같다. */
.xp-gain { position: relative; animation: xpg-pulse 1.4s ease-out 1; }
.xp-gain > i { position: relative; overflow: hidden; transition: width 1.1s cubic-bezier(.2, .8, .3, 1) !important;
  background-image: linear-gradient(90deg, transparent 40%, rgba(255, 216, 77, .55)); }
.xp-gain > i::after { content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 45%; pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .9), transparent); transform: translateX(-120%); animation: xpg-shine .8s ease-out .3s 2 both; }
@keyframes xpg-pulse {
  0% { box-shadow: 0 0 0 0 rgba(240, 185, 11, 0); }
  25% { box-shadow: 0 0 0 3px rgba(240, 185, 11, .35), 0 0 16px rgba(240, 185, 11, .7); }
  100% { box-shadow: 0 0 0 0 rgba(240, 185, 11, 0); }
}
@keyframes xpg-shine { to { transform: translateX(330%); } }
@media (prefers-reduced-motion: reduce) { .xp-gain, .xp-gain > i::after { animation: none !important; } }

/* ── 🔴 **레벨업 부스터** (2026-08-21 · 요청 2번 · board/profile.js `levelCardHTML`) ──────────────
   혜택 셋 중 이것만 화면에 흔적이 없었다 — 경험치는 숫자가 두 배로 오를 뿐이라
   구독자가 「받고 있나?」를 확인할 길이 없었다.
   🔴 **누를 수 없게 보이게** 만든다(`div` · cursor 기본). 여기서 켜고 끄는 게 아니라
      지금 상태를 말하는 줄이라, 버튼처럼 보이면 눌러 보고 아무 일이 없다.
   🔴 색은 `--brand-*` 다 — 이 앱에서 「구독으로 열린 것」을 가리키는 색이 그것이다
      (`plus.js` 의 팝업과 같은 계열). 금색·보라 같은 새 색을 여기서 만들지 않는다. */
.prof-boost {
  display: flex; align-items: center; gap: 10px;
  margin: 10px -14px -14px; padding: 11px 14px;
  border-top: 1px solid var(--line);
  background: var(--brand-weak);
}
.prof-boost-x {
  flex: 0 0 auto; font-size: 13px; font-weight: 800; letter-spacing: -.02em;
  color: var(--brand-500); background: var(--surface);
  border: 1px solid var(--brand-500); border-radius: 8px; padding: 2px 7px;
}
.prof-boost .grow { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.prof-boost b { font-size: 13px; font-weight: 700; color: var(--text); }
.prof-boost i { font-style: normal; font-size: 11.5px; color: var(--text-sub); }
/* ── 🔴 **기기와 서버의 답이 갈렸을 때** (2026-08-29 · 53차 요청 7번) ────────────────────
   같은 자리에 «반대 상태»가 온다: 기기는 구독 중인데 서버 장부에 아직 안 붙은 상태다.
   🔴 그래서 **누를 수 있다** — 바로 위 줄과 정반대의 규칙인데, 이유가 다르기 때문이다:
      위는 「지금 이렇다」를 말하는 줄이고 여기는 「고칠 수 있다」를 말하는 줄이다.
   🔴 **브랜드 색을 안 쓴다** — 그 색은 이 앱에서 「구독으로 열린 것」을 가리키므로, 안
      열린 상태에 쓰면 위 줄과 같은 뜻으로 읽힌다. 🟡 경고 전용 토큰은 이 팔레트에 없다
      (테마 넷을 다 채워야 하는 값이라 한 자리를 위해 만들지 않는다) — 이미 있는
      `--down`(하락·경고 계열)을 쓴다. */
.prof-boost-off {
  width: calc(100% + 28px); text-align: left; cursor: pointer;
  background: var(--surface-2); border-top: 1px solid var(--line);
}
.prof-boost-off .prof-boost-x { color: var(--down); border-color: var(--down); }
.prof-boost-off .chev { flex: 0 0 auto; color: var(--text-sub); display: flex; }

/* 신고 결과 배지 — 🔴 색은 **단계가 올라갈수록만** 세진다. 「접수됨」은 중립이어야 한다:
   신고했다는 사실 자체가 잘못을 뜻하지는 않는다. */
.rep-st { margin-left: auto; font-size: 11px; padding: 1px 6px; border-radius: 5px; }
.rep-r_open { background: var(--surface-2); color: var(--text-mut); }
.rep-r_dim { background: var(--surface-2); color: var(--text-sub); }
.rep-r_hide { background: var(--brand-weak); color: var(--brand-500); }
/* 🔴 `--hot`(빨강)이다 — 이 팔레트에 `--down` 은 없다(등락은 `--up`/`--hot` 두 축이다) */
.rep-r_block { background: var(--hot-weak); color: var(--hot); }

/* 🔴 가려진 글의 자리 (2026-08-18 · 요청 5번) — 묘비와 같은 모양이되 조금 더 밝다.
   "지워진 것"과 "검토 중인 것"은 다른 상태이고, 그 차이를 말하려고 만든 줄이다. */
.bcard.bhid { color: var(--text-sub); }

/* 가림·블라인드 줄의 **사유** (2026-08-18 · 요청 2번). 🔴 본문과 같은 크기로 두지 않는다 —
   이건 안내지 내용이 아니다. 이유 없이 사라진 자리는 검열로 읽힌다. */
.b-why { color: var(--text-mut); font-size: 12px; }

.b-kwfield { position: relative; }

.b-kwfield .sugg-inline {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 20;
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: var(--sh-2); max-height: 220px; overflow-y: auto;
}
.b-kwfield .sugg-inline:empty { display: none; }
/* 🟢 키보드가 떠 있으면 입력칸 위로 (2026-10-22 · board/picker.js `placeKwRowSugg`) */
.b-kwfield .sugg-inline.up { top: auto; bottom: 100%; }

/* ── 🔴 공지 배너 (2026-08-18 · 요청 2번) — **한 줄**이다 ───────────────────────
   🔴 제목을 말줄임으로 자른다(`nowrap` + `ellipsis`). 두 줄로 흐르게 두면 공지 길이에
      따라 화면 맨 위 높이가 매번 달라져, 그 아래 목록이 들쭉날쭉해 보인다.
   🔴 배지는 브랜드색이되 **띠 전체는 아니다** — 배너가 화면에서 가장 센 색이면
      정작 읽어야 할 목록보다 먼저 눈에 든다. */
.bnotice {
  display: flex; align-items: center; gap: 8px; width: 100%; margin-bottom: 10px;
  padding: 9px 11px; border-radius: 10px; cursor: pointer; text-align: left;
  background: var(--surface); border: 1px solid var(--line);
}
/* ── 🔴 **공지 줄 = [배너][통합검색]** (2026-08-28 · 47차 요청 1번 · board/notice.js `noticeRowHTML`) ──
   🔴 아래 여백을 **줄이 갖는다** — 배너에 남겨 두면 공지가 0건인 날(배너가 없다) 줄이
      아이콘만 남으면서 아래 여백까지 같이 사라져 목록이 위로 붙는다.
   🔴 `flex: 1; min-width: 0` 이 「공지 영역을 그만큼 줄인다」의 전부다 — 폭을 JS 에서
      계산하지 않는다. `min-width: 0` 이 없으면 flex 자식이 내용 폭 아래로 안 줄어들어
      긴 공지 제목이 아이콘을 화면 밖으로 밀어낸다(말줄임이 그때부터 안 먹는다).
   🔴 아이콘의 세로 여백을 죽인다 — 아이콘(32px)이 배너(약 38px)보다 작아 그냥 두면
      줄 높이는 그대로지만, 배너가 없는 날에는 아이콘이 줄 높이를 혼자 정하게 된다. */
.bnotice-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.bnotice-row .bnotice { flex: 1; min-width: 0; margin-bottom: 0; }
.bnotice-row .bsearch-ic { margin: -6px 0 -6px auto; }
.bnotice:active { background: var(--surface-2); }
.bnotice-tag {
  flex: none; padding: 2px 7px; border-radius: 5px; font-size: 11px; font-weight: 800;
  background: var(--brand-weak); color: var(--brand-500);
}
/* 🔴 분류 태그마다 색을 가른다 (2026-08-27 · 41차 요청 2번 · board/notice.js `ntTagHTML`) —
   클래스 이름이 곧 서버의 태그 이름(`tg`)이라, 태그를 늘리면 여기 한 줄만 더한다.
   🔴 **새 색을 만들지 않는다**: 테마가 여덟이라 하드코딩한 값은 어딘가에서 튄다.
      모든 테마에 있는 등락 팔레트를 빌린다(압정 배지가 세운 그 규칙). */
.bnotice-tag.guide { background: var(--up-weak); color: var(--up); }
/* 🔴 긴급 = 푸시로도 나간 공지 (2026-09-04 · 76차 요청 5번) — 「주요」 배지와 같은 빨강(모든 테마에 있다) */
.bnotice-tag.urgent { background: var(--hot-weak); color: var(--hot); }
/* 🔴 고정 공지 배지 (2026-08-22 밤 · 요청 3번) — 「공지」와 색을 갈라야 배지 둘이
   한 덩어리로 안 읽힌다. 등락 팔레트(--up-weak)를 빌린다(새 색을 만들지 않는다). */
.bnotice-card { position: relative; }
/* ── 🔴 **공지 카드는 게시글 카드보다 슬림하다** (2026-08-31 · 57차 요청 9번) ──────────────
 * `.bcard` 의 여백(15/16 · 아래 10)은 **게시글 카드**가 정한 값이다 — 거기는 썸네일·발췌·
 * 두 줄뿐이라 같은 여백을 쓰면 내용보다 빈칸이 넓어 목록이 성기게 보인다.
 * 🔴 셀렉터를 `.bcard.bnotice-card` 로 **좁힌다**: 같은 `.bnotice-card` 클래스를 «상세»
 *    화면도 쓴다(`renderNoticeOne` — 거기는 `.bpost` 라 여백 축이 다르다). 안 좁히면
 *    상세 본문까지 같이 조여진다.
 * 🔴 제목 여백도 세트다 — 카드만 조이고 `.b-title` 의 `7px 0 3px` 을 두면 줄어든 만큼이
 *    제목 마진으로 되돌아온다(둘을 따로 고치면 다음 사람이 「안 줄었다」로 읽는다). */
.bcard.bnotice-card { padding: 10px 16px; margin-bottom: 7px; }
.bcard.bnotice-card .b-title { margin: 4px 0 0; }
/* 🔴 압정도 같이 올라간다 — 위 패딩을 줄였는데 압정만 10px 에 두면 시각 줄과 어긋난다 */
.bcard.bnotice-card .bnotice-pin { top: 7px; }
.bnotice-pin {
  position: absolute; top: 10px; right: 12px;
  display: grid; place-items: center; width: 18px; height: 18px;
  color: var(--text-mut); pointer-events: none;
}
.bnotice-pin svg { width: 100%; height: 100%; }
/* 공지 본문의 이동 버튼 (2026-08-27 · 38차 요청 1번 · board/notice.js `ntBodyHTML`의 [[go:…]]) */
.nt-go {
  display: block; margin: 10px 0 2px; padding: 10px 14px; cursor: pointer;
  border-radius: 10px; border: 1px solid var(--brand-500); background: none;
  color: var(--brand-500); font: inherit; font-size: 13.5px; font-weight: 800;
}
.nt-go:active { transform: scale(.98); }
/* 🔴 공지 화면 첫 카드가 타이틀바에 붙어 보였다 (2026-08-22 밤 · 요청 2번) */
.nt-gap { margin-top: 10px; }

/* ── 쪽지 (2026-08-22 밤 · 요청 3번 · board-ui renderDmBox/renderDmChat) ─────────────
   말풍선은 메신저의 공통 문법을 따른다: 내 것 오른쪽·브랜드 약색, 상대 왼쪽·표면색.
   🔴 입력줄은 화면 맨 아래 고정이 아니라 **내용의 끝**이다 — 이 앱의 스크롤 상자는
      #scroll 하나라(SHELL), fixed 로 띄우면 키보드 회피(--kb)와 싸운다. */
/* ── 🔴 대화방 리뉴얼 (2026-08-27 · 32차 요청 3번 · board/dm-room.js renderDmChat 머리 주석) ──
   카카오톡·텔레그램의 공통 문법: 상대 [아바타][말풍선][시각] · 내 것 [시각][말풍선] 우측.
   🔴 입력줄은 fixed 가 아니라 **sticky** 다 — 스크롤 상자(#scroll) 안에서 바닥에 붙고,
      키보드 회피(--kb)와 안 싸운다(옛 제약 그대로). 상대 이름 줄은 앱바가 한다(중복 제거). */
/* ── 🔴 **바닥 여백은 «상쇄»가 아니라 «안 먹기»다** (2026-09-02 · 65차 요청 7번 · 실측) ──────
 * 증상(요청): 「입력창 아래 여백이 너무 많은데, 왜 이렇게 떠 있는 거지?」
 * 실측(하니스 · `--safe-bot: 34px`): 입력칸 밑으로 **92px** 이 비어 있었고 두 겹이었다 —
 *   ① `.dmbar` 자신의 아래 패딩 42px(8 + 홈 인디케이터) · **의도한 값이다**
 *   ② `html.no-tabs #scroll` 의 아래 패딩 50px(`--tab-bot` + 16) · **이쪽이 사고다**
 * 🔴 sticky 상자는 컨테이닝 블록(= 이 컨테이너)을 벗어날 수 없고, 이 컨테이너는 스크롤
 *    상자의 **콘텐츠 상자 안**에서 끝난다 — 그 패딩만큼 입력줄이 화면 바닥에서 뜬다.
 * 🔴 2026-08-31(57차 요청 9번)의 「음수 마진을 컨테이너로 옮긴다」는 **절반만 맞았다**:
 *    그것은 `scrollHeight` 의 유령 구간을 없앴을 뿐이고(그건 실제로 고쳐졌다), 자식이
 *    콘텐츠 상자 안에서 끝나는 사실은 안 바뀐다. 실측이 `gapBarToScreen: 50` 이었다.
 * 🔴 그래서 그 패딩을 **이 화면에서 아예 안 먹는다**(`html.dm-room #scroll` · app.js 가
 *    `no-tabs` 와 같은 자리에서 클래스를 건다). 그러면 상쇄가 필요 없어지므로
 *    **음수 마진과 `min-height` 보정을 같이 걷는다** — 남겨 두면 이제 컨테이너가 그만큼
 *    «짧아져» 말풍선이 적은 방에서 입력줄이 위로 뜬다(그 둘은 언제나 세트였다). */
.dmchat { display: flex; flex-direction: column; padding-top: 10px; min-height: 100%; }
/* 🔴 **대화방만 바닥 패딩이 0 이다** — 위 문단 ②. `--kb` 는 남긴다: 키보드가 뜨면 그만큼
   스크롤 영역이 짧아져야 마지막 말풍선이 키보드 뒤로 안 들어가고, sticky 의 `bottom`
   (`.dmbar`)이 같은 항을 쓰므로 **둘이 같은 자리에서 만난다**(값이 갈리면 바가 뜬다). */
html.dm-room #scroll, html.dm-room #scroll2 { padding-bottom: var(--kb, 0px); }
.dm-msgs { display: flex; flex-direction: column; gap: 7px; flex: 1; min-height: 120px; padding-bottom: 8px; }
.dm-day { display: flex; justify-content: center; margin: 10px 0 4px; }
.dm-day span {
  font-size: 11px; color: var(--text-mut); background: var(--surface-2);
  border-radius: 999px; padding: 3px 12px;
}
.dmrow-b { display: flex; align-items: flex-end; gap: 6px; max-width: 100%; }
.dmrow-b.mine { flex-direction: row-reverse; }
.dm-av {
  flex: none; width: 32px; height: 32px; border-radius: 50%; border: 0; cursor: pointer;
  background-color: var(--surface-2); background-size: cover; background-position: center;
  align-self: flex-end;   /* 🟢 얼굴은 무리의 마지막 말풍선 옆 «바닥»에 (2026-10-12 · 텔레그램 · board/dm-room.js `renderDmChat` 의 `tail`) — 옛 판은 머리 말풍선 위쪽이었다 */
}
.dmb {
  max-width: 72%;
  padding: 8px 12px; border-radius: 4px 14px 14px 14px;
  background: var(--surface); border: 1px solid var(--line);
  font-size: 14px; line-height: 1.45; color: var(--text); white-space: pre-wrap; word-break: break-word;
}
.dmrow-b.mine .dmb {
  border-radius: 14px 4px 14px 14px;
  background: var(--brand-weak); border-color: transparent;
}
/* 🔴 시각은 말풍선 **밖** 바닥 정렬 — 안에 품으면 짧은 말이 시각 폭만큼 늘어난다 */
.dmb-t { flex: none; font-size: 10.5px; color: var(--text-mut); padding-bottom: 2px; }
/* 「읽음」 — 시각 «위» 한 줄 · 상대가 읽은 내 말마다 (2026-09-07 · 94차·B 요청 1번 · board-ui `isRead`) · 시각 칸을 세로로 세운다 */
.dmb-t { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
.dmrow-b:not(.mine) .dmb-t { align-items: flex-start; }
.dmb-rd { font-style: normal; font-size: 10px; font-weight: 700; color: var(--accent); line-height: 1; }
.dmbar {
  /* 🔴 대화방은 스택 화면이라 탭 알약이 없다(`no-tabs` · app.js renderScreen) — sticky 의
     바닥은 실제 화면 바닥이고, 홈 인디케이터 영역(--tab-bot)은 **안쪽 패딩**으로 먹는다.
     `--kb` 로 키보드가 뜨면 같이 올라온다(#scroll 패딩과 같은 항 · keyboard.js). */
  /* ── 🔴 **`bottom` 은 0 이다 — `--kb` 를 여기 «또» 넣지 않는다** (2026-09-02 · 65차 요청 7번) ──
   * 실측으로 알아낸 사실: 이 웹뷰(Chromium)는 sticky 의 `bottom` 을 스크롤 상자의
   * **콘텐츠 상자** 기준으로 잰다(패딩 상자가 아니다). 그래서 키보드 높이를 **스크롤
   * 상자의 아래 패딩과 sticky 의 `bottom` 양쪽에** 넣으면 **두 번 세어져** 바가
   * `2 × --kb` 만큼 떠오른다(하니스 실측: `--kb: 300px` 에서 바 아래가 600px).
   * 🔴 그래서 키보드 몫은 **패딩 한 곳**이 먹는다(`html.dm-room #scroll`) — 그러면 이
   *    `bottom: 0` 이 정확히 그 패딩 위, 즉 키보드 윗변에 바를 세운다. 그리고 스크롤
   *    영역도 그만큼 짧아져 **마지막 말풍선이 키보드 뒤로 안 들어간다**(옛 판은
   *    `no-tabs` 패딩에 `--kb` 항이 없어서 실제로 들어갔다). */
  position: sticky; bottom: 0; z-index: 5;
  display: flex; gap: 8px; align-items: flex-end;
  /* ── 🔴 **바닥 여백은 «홈 인디케이터»만큼이다** (2026-08-30 · 56차 요청 9번) ─────────────
   * 옛 판은 `--tab-bot`(= `max(--safe-bot, --tab-min-bot)`)을 안쪽 패딩으로 먹었다.
   * 그 변수의 일은 **떠 있는 탭 알약이 바닥에 붙지 않게** 하는 것(최소 12px)인데,
   * 대화방 입력줄은 떠 있는 알약이 아니라 **바닥에 붙는 바**다 — 메신저의 그 문법이다.
   * 그래서 그 최소값이 그대로 「입력칸이 바닥에서 떠 있다」로 보였다.
   * 🔴 여기서 먹어야 하는 것은 **기기가 알려 준 사실**(`--safe-bot` · 홈 인디케이터)뿐이고,
   *    없는 기기(안드로이드 3버튼)에서는 0 이 맞다 — 바가 바닥에 붙는 것이 정상이다.
   * 🔴 **2026-08-31(57차 요청 9번) 정정 — 음수 마진을 «컨테이너»(`.dmchat`)로 옮겼다.**
   *    여기(마지막 자식)에 두면 상쇄가 **눈에만** 일어나고 `scrollHeight` 에는 그만큼이
   *    남아, 들어오자마자 그 유령 구간으로 스크롤되면서 바 아래에 빈 띠가 생겼다.
   *    이유 전문은 `.dmchat` 위 문단에 있다 — 여기는 이제 좌우로만 펼친다. */
  margin: 4px -12px 0;
  padding: 8px 12px calc(8px + var(--safe-bot, 0px));
  background: var(--bg); border-top: 1px solid var(--line);
}
/* 🔴 **`min-width: 0` 이 이 줄의 전부다** (2026-08-27 · 35차 요청 7번 · 실사고).
   textarea 는 기본 `cols=20` 이라 **내용 최소 폭**이 20글자쯤인데, flex 항목의
   `min-width` 기본값이 `auto` 라 `flex: 1` 을 줘도 그 아래로 안 줄어든다 — 그래서
   입력줄이 컨테이너를 넘어 「화면 밖으로 길게」 나갔다(`#scroll` 의 `overflow-x: hidden`
   이 잘라서 원인이 안 보였다). 🔴 `width: 100%` 는 그 최소 폭을 한 번 더 못 박는다. */
.dm-in {
  flex: 1; min-width: 0; width: 100%;
  min-height: 42px; max-height: 120px; padding: 10px 12px; resize: none;
  border-radius: 12px; border: 1px solid var(--line); background: var(--surface);
  color: var(--text); font: inherit; font-size: 14px;
}
@font-face { font-family: 'JwStk'; src: url(../fonts/stk.woff2) format('woff2'); unicode-range: U+E100-E363; font-display: block; }
/* 🟢 U+E301~E363 은 글쓰기 본문의 사진 표식(`[[img:n]]` → 글자 하나 · 2026-10-04 · 폭 2.6em · board/body.js `bodyToField` · 그 자리에 썸네일 `.stk-inlay-img`) */
.dm-in, .b-bodyin { font-family: 'JwStk', var(--font); }
/* 🔴 **보내기는 아이콘 하나다** (2026-08-27 · 35차 요청 7번). 글자 라벨은 언어마다 폭이
   달라 위 함정과 겹치면 줄이 넘친다 — 동그란 아이콘 버튼은 폭이 언어와 무관하다.
   🔴 `align-self: flex-end` — 입력칸이 여러 줄로 자라도 버튼은 **바닥**에 남는다
      (`.dmbar` 가 `align-items: flex-end` 라 이미 그렇지만, 명시해 둔다). */
.dm-send {
  flex: none; align-self: flex-end;
  width: 42px; height: 42px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  background: var(--brand-500); color: var(--on-brand);
}
.dm-send svg { width: 19px; height: 19px; }
.dm-send:disabled { opacity: .5; }
/* ── 🟢 **채널 글 상세 = 스레드** (2026-09-30 · board/thread.js `paintThread` · 사용자 요청 「텔레그램의 스레드처럼」) ──────────────────
   맨 위에 그 글의 말풍선, 아래로 댓글 말풍선(남 = 왼쪽 · 나 = 오른쪽), 바닥에 입력줄. 입력줄은 쪽지(`.dmbar`)와 같은 sticky 이고
   `#scroll` 의 바닥 패딩도 그 화면과 같이 0 이다(app.js 의 `dm-room` — 패딩을 먹으면 입력줄이 그만큼 뜬다 · 그 문단).
   🔴 몸통(`.chth-post` 안)은 게시판 상세와 같은 조각이라 그쪽 규칙(`.bpost .b-title`·`.b-body`·`#b-imgs`)을 그대로 물려받는다 —
      여기서는 «말풍선 안에 들어가서 달라지는 것»만 적는다. */
.board.chth { display: flex; flex-direction: column; min-height: 100%; padding: 10px 0 0; }
/* 🟢 글 말풍선은 왼쪽에 붙는다 — 목록·방의 말풍선과 같은 쪽 · 같은 꼬리 (2026-10-01 · 사용자 정정 「우측이 아니라 좌측」 · 첫 판은 오른쪽이었다) · 폭은 92% */
.chbub.chth-post { max-width: 92%; margin: 0 0 4px; padding: 10px 12px 8px; cursor: auto; }
.chth > .chbub-day { margin-top: 2px; }
.chth-acts { display: flex; justify-content: flex-start; gap: 4px; margin: 0 2px 4px; }
.chth-acts .b-mini { font-size: 11.5px; color: var(--text-mut); }
.chth-post .chbub-foot .b-views:empty { display: none; }
.chbub.chth-post:active { background: var(--surface); }
.chth-post > .b-meta { margin-top: 0; }
.chth-post .b-titlerule { margin: 6px 0 8px; }
.chth-post .b-title { font-size: 16.5px; }
.chth-post .reactbar { margin-bottom: 4px; }
.chth-post #b-imgs:empty { display: none; }
.chth > #b-promo:not(:empty) { margin-top: 8px; padding-bottom: 6px; border-bottom: 0; }
/* 「댓글 N」 — 대화가 시작되는 자리의 알약(날짜 알약 `.chbub-day` 와 같은 결) · 오른쪽 끝에 찾기 */
.b-cmthead.chth-head { position: relative; justify-content: center; margin-top: 14px; padding: 0; }
.chth-head .b-cmtlbl { font-size: 11.5px; font-weight: 600; color: var(--text-mut); background: var(--surface-2); border-radius: 999px; padding: 3px 12px; }
.chth-head .b-cmttools { position: absolute; right: 0; top: 50%; transform: translateY(-50%); }
/* 🔴 `contain: paint` — 줄 오른쪽 밖에 숨겨 둔 답글 그림(`.thc::after`)이 가로 스크롤 폭을 만들지 않게 자른다(밀면 빈 자리 안으로 들어온다).
   좌우 2px 은 밝힘 링(`hl-ring`)이 잘리지 않을 틈이다. */
.chth-msgs { display: flex; flex-direction: column; flex: 1; gap: 10px; padding: 12px 2px 10px; margin: 0 -2px; min-height: 96px; contain: paint; }
.chth-msgs > .b-cmtmore { align-self: center; margin: 0 0 2px; }
.chth-none { padding: 22px 12px; text-align: center; color: var(--text-mut); font-size: 13px; }
/* 댓글 한 줄 = [얼굴][말풍선 + 아랫줄] */
.thc { display: flex; align-items: flex-start; gap: 7px; max-width: 100%; }
.thc.mine { flex-direction: row-reverse; }
.thc-face { flex: none; display: inline-flex; }
.thc-face .b-avlink { padding: 0; border: 0; background: none; display: inline-flex; }
.bpost .thc-face .b-av { width: 32px; height: 32px; }
.thc-col { min-width: 0; max-width: calc(100% - 39px); display: flex; flex-direction: column; align-items: flex-start; }
.thc.mine .thc-col { align-items: flex-end; max-width: 86%; }
.b-cmt.thc-bub {
  max-width: 100%; padding: 7px 12px 8px; border: 1px solid var(--line); border-radius: 4px 14px 14px 14px;
  background: var(--surface); font-size: 14px; word-break: break-word;
}
.thc.mine .b-cmt.thc-bub { border-radius: 14px 4px 14px 14px; background: var(--brand-weak); border-color: transparent; }
.thc-bub p { margin: 0; line-height: 1.5; }
.thc-bub .b-xfrom { margin: 0 0 2px; }
.thc-who { --nm-gap: 5px; display: flex; align-items: center; gap: var(--nm-gap); min-width: 0; margin: 0 0 2px; font-size: 12px; line-height: 1.35; }
.thc-who .b-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700; color: var(--accent); background: none; border: 0; padding: 0; font-size: inherit; }
.thc-who .b-lv { flex: none; }
/* 답글 머리 — 원 댓글의 이름 + 첫 줄 (누르면 그 자리로) */
.thc-re {
  display: flex; flex-direction: column; align-items: flex-start; gap: 0; width: 100%; min-width: 0; margin: 2px 0 5px; padding: 2px 8px;
  border: 0; border-left: 2.5px solid var(--brand-500); border-radius: 3px; background: color-mix(in srgb, var(--brand-500) 9%, transparent);
  font: inherit; font-size: 12px; line-height: 1.4; color: var(--text-sub); text-align: left; cursor: pointer;
}
.thc-re b { font-weight: 700; color: var(--text); }
.thc-re b, .thc-re span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 말풍선 아래 한 줄 — 시각 + [차단][신고](내 말이면 [삭제]) (2026-09-30 · 사용자 결정 — 공감·답글·인용 버튼은 걷었다: 반응은 길게 누르기, 답글은 밀기) */
.thc-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 0 6px; margin: 1px 2px 0; min-height: 20px; font-size: 11px; color: var(--text-mut); }
.thc.mine .thc-acts { justify-content: flex-end; }
.thc-time { flex: none; font-size: 10.5px; }
.thc-acts .chbub-bm { margin-left: auto; }   /* 🟢 채널 스레드 댓글의 북마크 — 시각 오른쪽 끝, 차단·신고는 그 뒤 (코인스쿱 b0498f0) */
.thc.mine .thc-acts .chbub-bm { margin-left: 0; }
.thc-more { display: inline-flex; align-items: center; gap: 2px; }
.thc-more .b-mini { font-size: 11px; padding: 2px 3px; }
/* 반응 알약 — 말풍선 안 아랫줄(텔레그램) · 글의 알약(`.emopill`)과 같은 조각을 한 치수 작게 */
.thc-bub .emorow.thc-emo { margin-top: 6px; gap: 4px; }
.thc-bub .thc-emo .emopill { height: 24px; padding: 0 7px 0 5px; }
/* 길게 누르기(반응 팝오버)가 글자 선택·iOS 돋보기와 안 싸우게 — 채널 말풍선(`.chbub[data-emo]`)과 같은 처방 */
.thc-bub[data-emo] { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
/* ── 왼쪽으로 밀면 답글 (board-ui 「말풍선을 왼쪽으로 밀면」 구획) — 세로는 브라우저가 스크롤하고 가로는 우리가 받는다 ── */
.thc { position: relative; touch-action: pan-y; }
.thc::after {
  content: ""; position: absolute; right: -44px; top: 50%; width: 30px; height: 30px; margin-top: -15px; border-radius: 50%;
  background: var(--surface-2); opacity: 0; transform: scale(.6); transition: opacity .12s, transform .12s, background-color .12s; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b949e' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 14 4 9l5-5'/%3E%3Cpath d='M4 9h9a7 7 0 0 1 7 7v3'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: center; background-size: 16px 16px;
}
.thc.thc-sw::after { opacity: .85; transform: scale(.85); }
.thc.thc-go::after { opacity: 1; transform: scale(1); background-color: var(--brand-weak); }
/* 실시간으로 붙은 말풍선의 등장 (board/thread.js `paintThreadList`) */
@keyframes thc-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.thc.thc-in { animation: thc-in .22s ease-out; }
@media (prefers-reduced-motion: reduce) { .thc.thc-in { animation: none; } }
/* 🟢 **말풍선** (2026-10-08 · 사용자 「기본안으로」 · `uifx.js bubbleFx`) — 들어올 때 최신 말부터 아래에서 차례로 · 새 상대 말은 꼬리에서 톡 · 내가 보낸 말은 입력줄에서 떠올라 제자리로 · 늘 움직이는 것은 없다 */
.fx-bub-e { transform-origin: 50% 100%; animation: ufx-bub .5s cubic-bezier(.2, 1.3, .4, 1) calc(var(--bi, 0) * 45ms) 1 backwards; }
.fx-bub-n { transform-origin: 0% 100%; animation: bub-tail .42s cubic-bezier(.3, 1.6, .5, 1) 1 backwards; }
.dmrow-b.mine .fx-bub-n, .thc.mine .fx-bub-n { transform-origin: 100% 100%; }
@keyframes bub-tail { 0% { opacity: 0; transform: scale(.5); } 60% { opacity: 1; transform: scale(1.06); } 100% { opacity: 1; transform: none; } }
.fx-bub-s { animation: bub-send .46s cubic-bezier(.2, .9, .3, 1) 1 backwards; }
@keyframes bub-send { 0% { opacity: .35; transform: translateY(var(--fy, 40px)) scale(.92); } 100% { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .fx-bub-e, .fx-bub-n, .fx-bub-s { animation: none; } }
/* 「맨 아래로」 원 — 끝에 없을 때 새 댓글이 오면 (채널 방의 `.chroom-down` 조각). 🔴 입력줄(`.chth-bar` · sticky)에 매달아 그 «바로 위»다 —
   화면 기준(fixed)으로 두면 답글 줄·여러 줄 입력으로 입력줄이 자랄 때 그 위에 겹친다. 키보드가 뜨면 입력줄과 같이 오른다. */
.chth-bar .chroom-down.chth-down, .chth-join .chroom-down.chth-down { position: absolute; right: 12px; top: -52px; bottom: auto; }
.chth-down .chroom-down-n:empty { display: none; }
.thc.hl .thc-bub { animation: hl-ring 1.6s ease-out; }
.thc-sys { display: flex; justify-content: center; }
.thc-sys span { font-size: 11.5px; color: var(--text-mut); background: var(--surface-2); border-radius: 999px; padding: 3px 12px; }
.b-cmt.thc-fold { border-bottom: 0; margin: 0; align-self: stretch; }
.b-cmt.thc-fold.bdim { padding: 8px 12px; border: 1px dashed var(--line-2); border-radius: 10px; }
/* 바닥 입력줄 — `.dmbar` 와 같은 자리·같은 값(sticky `bottom: 0` · 홈 인디케이터만큼의 안쪽 패딩 · 그 문단들) */
.chth-bar {
  position: sticky; bottom: 0; z-index: 5; margin: 4px -12px 0; padding: 8px 12px calc(8px + var(--safe-bot, 0px));
  background: var(--bg); border-top: 1px solid var(--line);
}
.chth-row { display: flex; gap: 8px; align-items: flex-end; }
.chth-bar .b-count { margin: 2px 50px 0 0; }
.chth-re { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; padding: 3px 4px 3px 10px; border-left: 2.5px solid var(--brand-500); }
.chth-re[hidden] { display: none; }
.chth-re-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; font-size: 12px; line-height: 1.4; }
.chth-re-to { color: var(--text-sub); }
.chth-re-to b { color: var(--accent); font-weight: 700; }
.chth-re-x2 { color: var(--text-mut); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chth-re-x { flex: none; width: 28px; height: 28px; padding: 0; border: 0; background: none; color: var(--text-mut); font-size: 14px; cursor: pointer; }
html.kb-acc-on .chth-bar { padding-bottom: 8px; }
/* 🟢 스레드의 «자리에서 수정» (board/compose.js `startThreadEdit`) — 「메시지 수정」 줄의 이름에 연필 · 보내기는 체크 */
.chth-ed-to { display: inline-flex; align-items: center; gap: 5px; color: var(--brand-500); font-weight: 700; }
.chth-ed-to svg { width: 14px; height: 14px; flex: none; }
/* 채널 글 입력줄(내 채널 방의 바닥 · board/channel-compose.js `chComposeBarHTML`) — 위에서부터 [첨부 미리보기] → [인용] → [입력칸 · 보내기] */
.chth-atts { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 8px; }
.chth-atts:empty { display: none; }
.chth-atts .chth-attth { width: 64px; height: 64px; cursor: default; }
.chth-qs { margin: 0 0 8px; }
.chth-qs:empty { display: none; }
.chth-qs .b-quote { margin: 0; }
/* ── 🟢 **입력줄 = 떠 있는 알약** (2026-10-01 · 사용자 요청 「입력바도 하단 탭바처럼 FAB 느낌 · 쪽지 대화도 같은 공통 UI」 · board-ui `.inpill`) ──
   채널 글 스레드·채널 글쓰기·쪽지 대화의 입력줄이 한 조각이다: 바(`.chth-bar`·`.dmbar`)는 자리만 잡는 투명한 띠가 되고(내용이 그 뒤로 비친다),
   🔴 띠는 `pointer-events: none` 이고 그 자식만 눌린다 — 알약 옆의 빈 자리로 뒤의 내용이 눌린다(탭 알약과 같은 규칙).
   🔴 알약 위에 얹히는 줄(답글 대상·첨부·인용 칸)은 띠가 투명해져 바탕이 없으므로 각자 같은 유리 카드를 입는다. */
.chth-bar, .dmbar { background: none; border-top: 0; padding: 6px 10px calc(8px + var(--safe-bot, 0px)); pointer-events: none; }
.chth-bar > *, .dmbar > * { pointer-events: auto; }
.dmbar { display: block; }
.inpill {
  display: flex; align-items: flex-end; gap: 4px; padding: 4px; border-radius: 25px;
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(18px) saturate(180%); backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-3), inset 0 1px 0 var(--glass-top);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .inpill, .chth-bar .chth-re, .chth-bar .chth-atts, .chth-bar .chth-qs, .dmbar .chth-atts, .bform .chth-atts { background: var(--bar-bg); }
}
.inpill .dm-in { min-height: 40px; padding: 10px 6px 10px 13px; border: 0; border-radius: 0; background: none; }
.inpill .dm-in:focus, .inpill .dm-in:focus-visible { outline: none; }
.inpill .dm-inwrap .dm-in, html.kb-acc-on .inpill .dm-inwrap .dm-in { padding-right: 34px; }.inpill .dm-send { width: 40px; height: 40px; }
.chth-bar .chth-re, .chth-bar .chth-atts, .chth-bar .chth-qs:not(:empty) {
  margin: 0 0 6px; padding: 7px 8px 7px 12px; border-radius: 14px;
  background: var(--glass-bg); -webkit-backdrop-filter: blur(18px) saturate(180%); backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid var(--glass-line);
}
.chth-bar .chth-re { border-left: 2.5px solid var(--brand-500); }
.chth-bar .chth-atts:empty { display: none; }
.chth-bar .b-count { margin: 2px 54px 0 0; }
.dmbar .chth-atts, .bform .chth-atts {
  margin: 0 0 6px; padding: 7px 8px 7px 12px; border-radius: 14px;
  background: var(--glass-bg); -webkit-backdrop-filter: blur(18px) saturate(180%); backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid var(--glass-line);
}
.dmbar .chth-atts:empty, .bform .chth-atts:empty { display: none; }
.stk-leadth img { object-fit: contain; background: var(--surface-2); }
.stk-inlay { position: absolute; overflow: hidden; pointer-events: none; z-index: 1; }
.stk-inlay:empty { display: none; }
.stk-inlay-c { position: absolute; display: flex; align-items: center; justify-content: center; box-sizing: border-box; }
.stk-inlay-c img { display: block; pointer-events: none; }
/* 🟢 사진 칸 — 자리(2.6em × 줄 높이)를 채우는 썸네일 + 번호(첨부 줄 `.b-imgn` 과 같은 번호 · 2026-10-04) · 사진이 없는 번호는 빈 칸만 */
/* 🟢 투표 칩 — 본문 칸의 `[[poll]]` 글자 자리(제 줄) · 글자 칩이라 바탕·테두리가 있다(그림 칸과 달리 가릴 글자가 없는 줄이다 · `stkinlay.js POLL_MARK`) */
.stk-inlay-poll { justify-content: flex-start; width: auto; padding: 0 9px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--q-post);
  color: var(--q-post); font-size: 12px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; line-height: 1; }
.stk-inlay-poll { display: flex; }
.stk-inlay-game { border-color: var(--q-post); }   /* 게임 칩 — 투표 칩과 같은 모양 (2026-10-12 · `stkinlay.js GAME_MARK`) */
.stk-inlay-img { padding: 2px 1px; }
.stk-inlay-img img { width: 100%; height: 100%; object-fit: cover; border-radius: 4px; }
.stk-inlay-img b { position: absolute; right: 3px; bottom: 3px; min-width: 12px; padding: 0 3px; border-radius: 6px; background: rgba(0,0,0,.6); color: #fff; font-size: 9px; line-height: 12px; font-weight: 700; text-align: center; }
.stk-inlay-img.none::before { content: ''; position: absolute; inset: 2px 1px; border: 1px dashed var(--text-mut); border-radius: 4px; }
html.sheet-dock #sheet .stk-body { display: flex; flex-direction: column; flex: 1; min-height: 0; }
html.sheet-dock #sheet .stk-scroll { max-height: none; flex: 1; min-height: 0; }

/* ══ 🟢 **머리 카드 한 벌 — `.hc`** (2026-09-17 · 137차·H 요청 1·2·3번 · app.js·`mkhead.js hcardHTML`) ═══════════════════════
 * 🔴 폭은 «화면에 세 장 반»이다(`--hc-w`) — 옛 ①의 「내용만큼」은 키가 고정되며 뜻을 잃었고, 반 장이 걸쳐 있어야 «더 있다»가 읽힌다
 *    (위 ③ 스크롤바와 같은 신호 · 둘 다 둔다). 상한 150px — 태블릿 좁은 창에서 카드가 늘어지지 않게.
 * 🔴 값이 길면(원화 환산·긴 이름) 말줄임이다 — 줄바꿈하면 키가 깨진다. 라벨도 한 줄.
 * 🔴 다섯 단계 색(`.fg1`~`.fg5` · 파랑↔주황 · 「등락 색을 안 쓴다」)은 값 글자와 게이지 조각이 같은 값을 쓴다(`--fg1`~`--fg5`). */

/* 그림의 색조 — 옅은 원 + 진한 선 (종류를 말할 뿐 · 등락이 아니다) */

.iconbtn.on { color: var(--brand-500); }

/* ── 🟢 **봇 얼굴 — 지우(좌지우지 진행자 캐릭터)의 색 셋** (2026-10-22 · 그림 새로 그리기 · 2026-10-25 좌지·우지·중립으로 갈았다 · 봇 표 `services/config.js BOTS` 의 `av`) ─────
      중립(`.b-av-bot`) = 양손 기준형(금색 · 저울 더듬이 · 웃는 입) · 왼손(`.b-av-jwaji`) = 같은 얼굴의 파랑 · 오른손(`.b-av-uji`) = 같은 얼굴의 빨강.
      🟡 2026-10-25 이전에는 «봇은 중립이라 왼손(파랑)·오른손(빨강)의 색을 봇 얼굴에 쓰지 않는다»였다 — 봇 자체가 좌지·우지가 되면서 그 규칙을 거꾸로 했다.
         옛 정리봇(`.b-av-brief` · 안경)·질문봇(`.b-av-ask` · 물음표)의 얼굴은 걷었다 — 그 봇의 글은 DB 에 남지만 얼굴은 기본(`.b-av-bot`)으로 떨어진다(부르는 곳 0 을 grep 으로 확인).
   🔴 파일이 아니라 **인라인 SVG** 다 — 요청 0 · 실패해서 얼굴이 깨질 일이 없고 · 그림을 바꿀 때 올라간 글을 안 고친다(글에 주소가 없다).
   🔴 **작은 크기가 설계 조건이다**(목록 18px · 상세 22px · 활동 화면 84px) — 요소가 더듬이·몸·눈·입·볼(+ 안경)뿐이다. 더 넣으면 18px 에서 뭉갠다.
   🔴 프레임이 없으므로 `.b-av` 의 원형 자르기·바탕색을 이 셋에서만 끈다(`span.b-av.b-av-*` — `.b-av.sm` 같은 두 클래스 규칙보다 세다). */
.b-av-bot { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2064%2064%27%3E%3Cg%20class%3D%27ant%27%3E%3Cline%20x1%3D%2732%27%20y1%3D%2718%27%20x2%3D%2732%27%20y2%3D%276%27%20stroke%3D%27%23B88A00%27%20stroke-width%3D%272.2%27%20stroke-linecap%3D%27round%27%2F%3E%3Cline%20x1%3D%2722%27%20y1%3D%277%27%20x2%3D%2742%27%20y2%3D%277%27%20stroke%3D%27%23B88A00%27%20stroke-width%3D%272.2%27%20stroke-linecap%3D%27round%27%2F%3E%3Cpath%20d%3D%27M22%207v3M42%207v3%27%20stroke%3D%27%23B88A00%27%20stroke-width%3D%271.4%27%2F%3E%3Cpath%20d%3D%27M18%2010h8a4%204%200%200%201-8%200z%27%20fill%3D%27%23FFE07A%27%20stroke%3D%27%23B88A00%27%20stroke-width%3D%271.4%27%20stroke-linejoin%3D%27round%27%2F%3E%3Cpath%20d%3D%27M38%2010h8a4%204%200%200%201-8%200z%27%20fill%3D%27%23FFE07A%27%20stroke%3D%27%23B88A00%27%20stroke-width%3D%271.4%27%20stroke-linejoin%3D%27round%27%2F%3E%3Ccircle%20cx%3D%2732%27%20cy%3D%275.5%27%20r%3D%272%27%20fill%3D%27%23B88A00%27%2F%3E%3C%2Fg%3E%3Cellipse%20cx%3D%2732%27%20cy%3D%2739%27%20rx%3D%2723%27%20ry%3D%2720%27%20fill%3D%27%23F2C230%27%20stroke%3D%27%23B88A00%27%20stroke-width%3D%272.5%27%2F%3E%3Cellipse%20cx%3D%2732%27%20cy%3D%2747%27%20rx%3D%2713%27%20ry%3D%279%27%20fill%3D%27%23FFF6DA%27%2F%3E%3Cellipse%20cx%3D%2717%27%20cy%3D%2741%27%20rx%3D%273.2%27%20ry%3D%272.1%27%20fill%3D%27%23FF8FA3%27%20opacity%3D%27.8%27%2F%3E%3Cellipse%20cx%3D%2747%27%20cy%3D%2741%27%20rx%3D%273.2%27%20ry%3D%272.1%27%20fill%3D%27%23FF8FA3%27%20opacity%3D%27.8%27%2F%3E%3Ccircle%20cx%3D%2724%27%20cy%3D%2734%27%20r%3D%274.2%27%20fill%3D%27%231b1f24%27%2F%3E%3Ccircle%20cx%3D%2725.4%27%20cy%3D%2732.5%27%20r%3D%271.4%27%20fill%3D%27%23fff%27%2F%3E%3Ccircle%20cx%3D%2740%27%20cy%3D%2734%27%20r%3D%274.2%27%20fill%3D%27%231b1f24%27%2F%3E%3Ccircle%20cx%3D%2741.4%27%20cy%3D%2732.5%27%20r%3D%271.4%27%20fill%3D%27%23fff%27%2F%3E%3Cpath%20d%3D%27M26%2041h12q0%208-6%208q-6%200-6-8z%27%20fill%3D%27%236b2230%27%2F%3E%3Cpath%20d%3D%27M28.6%2046.2q3.4-2.2%206.8%200q-1.2%202.8-3.4%202.8q-2.2%200-3.4-2.8z%27%20fill%3D%27%23FF7A8A%27%2F%3E%3C%2Fsvg%3E"); }
.b-av-jwaji { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2064%2064%27%3E%3Cg%20class%3D%27ant%27%3E%3Cline%20x1%3D%2732%27%20y1%3D%2718%27%20x2%3D%2732%27%20y2%3D%276%27%20stroke%3D%27%232F5BB5%27%20stroke-width%3D%272.2%27%20stroke-linecap%3D%27round%27%2F%3E%3Cline%20x1%3D%2722%27%20y1%3D%277%27%20x2%3D%2742%27%20y2%3D%277%27%20stroke%3D%27%232F5BB5%27%20stroke-width%3D%272.2%27%20stroke-linecap%3D%27round%27%2F%3E%3Cpath%20d%3D%27M22%207v3M42%207v3%27%20stroke%3D%27%232F5BB5%27%20stroke-width%3D%271.4%27%2F%3E%3Cpath%20d%3D%27M18%2010h8a4%204%200%200%201-8%200z%27%20fill%3D%27%23BFD4FF%27%20stroke%3D%27%232F5BB5%27%20stroke-width%3D%271.4%27%20stroke-linejoin%3D%27round%27%2F%3E%3Cpath%20d%3D%27M38%2010h8a4%204%200%200%201-8%200z%27%20fill%3D%27%23BFD4FF%27%20stroke%3D%27%232F5BB5%27%20stroke-width%3D%271.4%27%20stroke-linejoin%3D%27round%27%2F%3E%3Ccircle%20cx%3D%2732%27%20cy%3D%275.5%27%20r%3D%272%27%20fill%3D%27%232F5BB5%27%2F%3E%3C%2Fg%3E%3Cellipse%20cx%3D%2732%27%20cy%3D%2739%27%20rx%3D%2723%27%20ry%3D%2720%27%20fill%3D%27%235B8DEF%27%20stroke%3D%27%232F5BB5%27%20stroke-width%3D%272.5%27%2F%3E%3Cellipse%20cx%3D%2732%27%20cy%3D%2747%27%20rx%3D%2713%27%20ry%3D%279%27%20fill%3D%27%23FFF6DA%27%2F%3E%3Cellipse%20cx%3D%2717%27%20cy%3D%2741%27%20rx%3D%273.2%27%20ry%3D%272.1%27%20fill%3D%27%23FF8FA3%27%20opacity%3D%27.8%27%2F%3E%3Cellipse%20cx%3D%2747%27%20cy%3D%2741%27%20rx%3D%273.2%27%20ry%3D%272.1%27%20fill%3D%27%23FF8FA3%27%20opacity%3D%27.8%27%2F%3E%3Ccircle%20cx%3D%2724%27%20cy%3D%2734%27%20r%3D%274.2%27%20fill%3D%27%231b1f24%27%2F%3E%3Ccircle%20cx%3D%2725.4%27%20cy%3D%2732.5%27%20r%3D%271.4%27%20fill%3D%27%23fff%27%2F%3E%3Ccircle%20cx%3D%2740%27%20cy%3D%2734%27%20r%3D%274.2%27%20fill%3D%27%231b1f24%27%2F%3E%3Ccircle%20cx%3D%2741.4%27%20cy%3D%2732.5%27%20r%3D%271.4%27%20fill%3D%27%23fff%27%2F%3E%3Cpath%20d%3D%27M26%2041h12q0%208-6%208q-6%200-6-8z%27%20fill%3D%27%236b2230%27%2F%3E%3Cpath%20d%3D%27M28.6%2046.2q3.4-2.2%206.8%200q-1.2%202.8-3.4%202.8q-2.2%200-3.4-2.8z%27%20fill%3D%27%23FF7A8A%27%2F%3E%3C%2Fsvg%3E"); }
.b-av-uji { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2064%2064%27%3E%3Cg%20class%3D%27ant%27%3E%3Cline%20x1%3D%2732%27%20y1%3D%2718%27%20x2%3D%2732%27%20y2%3D%276%27%20stroke%3D%27%23B3342D%27%20stroke-width%3D%272.2%27%20stroke-linecap%3D%27round%27%2F%3E%3Cline%20x1%3D%2722%27%20y1%3D%277%27%20x2%3D%2742%27%20y2%3D%277%27%20stroke%3D%27%23B3342D%27%20stroke-width%3D%272.2%27%20stroke-linecap%3D%27round%27%2F%3E%3Cpath%20d%3D%27M22%207v3M42%207v3%27%20stroke%3D%27%23B3342D%27%20stroke-width%3D%271.4%27%2F%3E%3Cpath%20d%3D%27M18%2010h8a4%204%200%200%201-8%200z%27%20fill%3D%27%23FFC9C4%27%20stroke%3D%27%23B3342D%27%20stroke-width%3D%271.4%27%20stroke-linejoin%3D%27round%27%2F%3E%3Cpath%20d%3D%27M38%2010h8a4%204%200%200%201-8%200z%27%20fill%3D%27%23FFC9C4%27%20stroke%3D%27%23B3342D%27%20stroke-width%3D%271.4%27%20stroke-linejoin%3D%27round%27%2F%3E%3Ccircle%20cx%3D%2732%27%20cy%3D%275.5%27%20r%3D%272%27%20fill%3D%27%23B3342D%27%2F%3E%3C%2Fg%3E%3Cellipse%20cx%3D%2732%27%20cy%3D%2739%27%20rx%3D%2723%27%20ry%3D%2720%27%20fill%3D%27%23EF6B63%27%20stroke%3D%27%23B3342D%27%20stroke-width%3D%272.5%27%2F%3E%3Cellipse%20cx%3D%2732%27%20cy%3D%2747%27%20rx%3D%2713%27%20ry%3D%279%27%20fill%3D%27%23FFF6DA%27%2F%3E%3Cellipse%20cx%3D%2717%27%20cy%3D%2741%27%20rx%3D%273.2%27%20ry%3D%272.1%27%20fill%3D%27%23FF8FA3%27%20opacity%3D%27.8%27%2F%3E%3Cellipse%20cx%3D%2747%27%20cy%3D%2741%27%20rx%3D%273.2%27%20ry%3D%272.1%27%20fill%3D%27%23FF8FA3%27%20opacity%3D%27.8%27%2F%3E%3Ccircle%20cx%3D%2724%27%20cy%3D%2734%27%20r%3D%274.2%27%20fill%3D%27%231b1f24%27%2F%3E%3Ccircle%20cx%3D%2725.4%27%20cy%3D%2732.5%27%20r%3D%271.4%27%20fill%3D%27%23fff%27%2F%3E%3Ccircle%20cx%3D%2740%27%20cy%3D%2734%27%20r%3D%274.2%27%20fill%3D%27%231b1f24%27%2F%3E%3Ccircle%20cx%3D%2741.4%27%20cy%3D%2732.5%27%20r%3D%271.4%27%20fill%3D%27%23fff%27%2F%3E%3Cpath%20d%3D%27M26%2041h12q0%208-6%208q-6%200-6-8z%27%20fill%3D%27%236b2230%27%2F%3E%3Cpath%20d%3D%27M28.6%2046.2q3.4-2.2%206.8%200q-1.2%202.8-3.4%202.8q-2.2%200-3.4-2.8z%27%20fill%3D%27%23FF7A8A%27%2F%3E%3C%2Fsvg%3E"); }
span.b-av.b-av-bot, span.b-av.b-av-jwaji, span.b-av.b-av-uji {
  border-radius: 0; background-color: transparent; background-size: contain; background-position: center; background-repeat: no-repeat;
}

/* 시트 안의 소제목 (2026-08-23 · 요청 10번 · 테마 시트는 2026-10-27 부터 «기본과 사계절»·«무드» 두 묶음) */
.sheet-grp {
  margin: 12px 4px 4px; font-size: 12px; font-weight: 800; color: var(--text-mut);
  letter-spacing: .02em;
}
.sheet-grp:first-of-type { margin-top: 4px; }

/* 🗄 홍보 태그(`.pnl-tag.promo`)·구독자 전용 태그·잠금 줄·맛보기·잠긴 상세(`.pnl-tag.so`·`.b-lock`·`.b-lockex`·`.b-locked`·`.b-lockbtn`)는
   2026-09-30 에 그 기능과 함께 걷었다(board-ui 의 그 🗄 문단 · git 이력). */
/* 🟢 **닉네임↔배지는 1px** (2026-09-14 · 125차 요청 4번 · 사용자 「닉네임+배지 간격은 거의 가까이 붙이고」) — 부모 flex 줄의 gap 을
      `--nm-gap` 으로 받아 그만큼 빼서 자리(카드·인기글·말풍선·방 앱바·채널 머리)마다 gap 이 달라도 결과가 같다. inline 흐름(멘션 후보·
      구독 띠)은 변수가 없어 fallback 0 → 1px. 🟡 옛 판은 margin-left 3px(sm 2px) + gap 6 이라 카드에서 9px 이었다(닉↔Lv 7px 보다 넓었다 · 실측).
   🔴 `--nm-gap` 을 여는 컨테이너는 다섯이고 각자 `gap: var(--nm-gap)` 이다 — 새 이름 줄을 만들면 그 둘을 같이 적는다(안 적으면 gap 만큼 벌어진다). */
.b-plusbadge { display: inline-block; width: 14px; height: 14px; margin-left: calc(1px - var(--nm-gap, 0px)); vertical-align: -2px; flex: 0 0 auto; }
.b-plusbadge.sm { width: 11px; height: 11px; vertical-align: -1px; }
.b-plusbadge svg { display: block; width: 100%; height: 100%; }
.b-plusbadge .pb-bg { fill: var(--brand-500); }
.b-plusbadge .pb-ck { fill: none; stroke: #111; }
.b-plusbadge.b-offbadge .pb-bg { fill: #829aab; }
.b-plusbadge.b-offbadge .pb-ck { stroke: #fff; }
/* 🟢 **닉네임 + 배지 + 태그** (2026-09-13 후속 · 사용자 요청 2번) — 태그(AI·운영·Lv)의 앞 간격은 **5px** 다(2026-09-14 · 125차 요청 4번 ·
      사용자 「태그는 위의 닉네임+태그 간격 정도, 그보다 1~2 더 붙여도」 — 옛 카드의 닉↔Lv 7px 에서 2px 뺐다). 배지 뒤든 이름 바로 뒤든 같다.
   🔴 값은 `calc(5px - var(--nm-gap, 0px))` — 부모 gap 을 빼는 무늬가 위 `.b-plusbadge` 와 같다. 🟡 옛 판은 `:where()` 로 특이도를 낮춰
      말풍선의 `margin: 0` 에 지게 두었는데(그래서 말풍선만 4px), 이제 그 규칙이 가로 margin 을 안 건드린다.
   🔴 **`:is()` 로 묶지 않는다** — 옛 안드로이드 웹뷰가 `:is()` 를 모르면 쉼표로 묶인 규칙 «전체»가 버려져 배지↔태그 규칙까지 같이 죽는다.
   🔴 이름 클래스를 늘리면 여기 한 줄 — `.b-lv` 단독 자리(레벨 카드 `.prof-lv-top`)는 형제가 없어 안 걸린다. */
.b-plusbadge + .b-lv,
.b-name + .b-lv, .chbub-fnm + .b-lv, .barch-nm + .b-lv, .buser-name + .b-lv, .chl-nm + .b-lv {
  margin-left: calc(5px - var(--nm-gap, 0px));
}
/* 자동 해시태그 줄 (요청 2번) — 본문 태그 칩과 같은 무늬 */
.b-autotags { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 2px; }

/* ── 🔴 **검색 결과의 «걸린» 해시태그** (2026-09-02 · 65차·A 요청 7번 · board/list.js `hashHitsHTML`) ──
 * 발췌(`.b-ex`)가 앞에서 잘려 키워드가 한 글자도 안 보이는 경우의 근거 한 줄이다.
 * 🔴 **발췌 «아래»**에 선다: 위에 두면 제목과 발췌 사이를 갈라 카드가 세 덩어리로 읽힌다.
 * 🔴 색은 본문 태그(`.b-at`)와 **같은 브랜드색**이다 — 같은 것을 가리키는 같은 몸짓
 *    (누르면 그 태그로 다시 검색한다)이라, 다르게 칠하면 다른 기능으로 배운다. */
.b-hashes { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.b-hash {
  flex: none; border: 0; background: none; padding: 0; cursor: pointer;
  color: var(--tagc); font-size: 12.5px; font-weight: 700;   /* 금색 고정 — `:root` 의 `--tagc` 머리 문단 */
}
.b-hash:active { opacity: .6; }

/* 🔴 활동 봇 배지 (2026-08-23 · 요청 11번) — 운영 배지(`b-adm`)와 **같은 무늬·다른 색**.
   같은 색이면 「AI 가 운영한다」로 읽힌다(둘은 배타이고 뜻도 다르다). */
.b-lv.b-ai { color: #7c4dff; background: rgba(124, 77, 255, .14); }

/* 광고 보고 XP 버튼 (요청 15번) — 획득 내역 줄과 같은 무늬, 색만 브랜드 */
.prof-adxp { color: var(--brand-500); font-weight: 700; }
/* 🔴 남은 횟수 (2026-08-23 · 요청 8번) — 라벨과 화살표 **사이**에 오른쪽 정렬로 선다.
   `tabular-nums` 인 이유: 5/5 → 4/5 로 줄 때 폭이 바뀌면 줄 전체가 한 칸 떨린다. */
.prof-adxp-n {
  flex: 0 0 auto; margin-left: auto; padding-left: 8px; white-space: nowrap;
  font-size: 12.5px; font-weight: 800; font-variant-numeric: tabular-nums;
}
/* 🔴 **숫자는 화살표 바로 앞에 붙는다** (2026-09-18 · 144차 요청 2번 「오늘의 미션 값과 아래 오늘 남은 횟수의 오른쪽 끝을 맞춰 달라」) —
   숫자와 화살표(`.chev`)가 둘 다 `margin-left: auto` 라 남는 폭을 반씩 나눠 가져서, 숫자의 오른쪽 끝이 글자 길이마다 달랐다. */
.prof-adxp-n + .chev { margin-left: 6px; }
/* 다 쓴 줄은 **눌리지 않는다**(disabled) — 그 사실을 색으로도 말한다 */
.prof-adxp.is-done { color: var(--text-mut); }
.prof-adxp.is-done .prof-adxp-n { color: var(--text-mut); }

/* ── 🔴 「이 글은 없습니다」 화면 (2026-08-23 · 요청 10번 · app.js renderGone) ──────────
   🔴 **화면 가운데**다 — 목록 맨 위에 붙는 빈 상태(`.empty`)와 다른 자리인 이유는,
      여기는 「지금 이 화면이 통째로 그 얘기」이기 때문이다(요청의 「화면 가운데 표시」).
   🔴 높이를 `min-height` 로 잡는다 — 앱바·탭바가 있는 화면이라 `100vh` 면 넘친다. */
.gone {
  min-height: 62vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center;
  gap: 10px; padding: 24px 26px;
}
.gone-ic { width: 56px; height: 56px; color: var(--text-mut); opacity: .55; }
.gone-ic svg { width: 100%; height: 100%; }
.gone h2 { font-size: 17px; font-weight: 800; margin: 4px 0 0; }
.gone p { font-size: 13.5px; line-height: 1.55; color: var(--text-sub); margin: 0; max-width: 30em; }
.gone-acts { display: flex; flex-direction: column; gap: 8px; width: 100%; max-width: 320px; margin-top: 10px; }

/* ── 🟢 **스크린샷 시트** (2026-10-19 · app.js `openShotSheet` — OS 스크린샷을 찍으면 뜬다 · 흔한 «캡처 공유» 시트 모양 · 🗄 옛 상세 인용 FAB `.qfab` 자리) ───────────
   찍힌 화면을 테두리 액자 안에 크게 · 아래는 가로로 미는 네모 아이콘 줄(선 테두리 · 아래 이름). 맨 앞 둘(`.main`)이 대표 — 브랜드 색 채움.
   🔴 액자 높이에 상한 — 세로로 긴 화면을 그대로 두면 아이콘 줄이 시트 밖으로 밀린다 · 줄은 넘치면 옆으로 민다(스크롤바 없음). */
.shotsheet { display: flex; flex-direction: column; gap: 18px; padding: 6px 0 4px; }
/* 🔴 **이 시트만 다른 덮개 위다** (2026-10-19) — 바텀시트(940)는 사진·게임(960)·팝업·레벨업(980)·그림 그리기(985) 아래라, 그것들이 떠 있을 때 찍으면
   시트가 밑에 깔려 안 보였다. 웹 게이트(990)·토스트(996)보다는 아래다. 클래스는 app.js `initShell` 의 `onSheet` 가 시트마다 다시 판정한다
   (`:has()` 를 안 쓰는 이 파일의 규칙 — 웹뷰 버전마다 지원이 갈린다). */
html.shot-on #sheet-scrim { z-index: 987; }
.shot-frame { align-self: center; line-height: 0; padding: 10px; border-radius: 18px; border: 1px solid var(--line-2); background: var(--surface-2); }
.shot-frame img { display: block; max-height: min(58vh, 520px); max-width: 100%; width: auto; border-radius: 10px; }
/* ── 🟢 **시트 안에서 바로 자르기** (2026-10-25 · 사용자 「편집 화면으로 가지 말고 캡처 시트에서 바로 조절 · 거기서 바로 첨부·공유」 · app.js `bindShotCrop`) ──
   그림 위에 상자가 늘 떠 있다(처음엔 그림 전체) — 네 모서리의 ㄱ자 꺾쇠 + 네 변 가운데 막대 · 끄는 동안 3등분 격자 · 바깥은 어둡게.
   상자 자리는 그림 기준 «안쪽 여백» 비율 넷(`--cl`·`--ct`·`--cr`·`--cb`)이고, 어둠(`.shot-hole`)과 손잡이 상자(`.shot-cbox`)가 같은 값을 읽는다.
   🔴 어둠은 따로 잘린 층이다(`.shot-dim` 의 `overflow: hidden`) — 감싸개를 자르면 그림 끝에 선 손잡이의 바깥 절반이 잘려 잡기 어렵다.
   🔴 `touch-action: none` — 상자를 끄는 손이 시트 스크롤이 되지 않게(시트 끌어 닫기는 손잡이 줄 `.sheet-grab` 에서만이라 안 겹친다). */
.shot-crop { position: relative; touch-action: none; -webkit-user-select: none; user-select: none; --cl: 0; --ct: 0; --cr: 0; --cb: 0; }
.shot-crop img { -webkit-user-drag: none; pointer-events: none; }
.shot-dim { position: absolute; inset: 0; overflow: hidden; border-radius: 10px; pointer-events: none; }
.shot-hole, .shot-cbox {
  position: absolute; left: calc(var(--cl) * 100%); top: calc(var(--ct) * 100%); right: calc(var(--cr) * 100%); bottom: calc(var(--cb) * 100%);
}
.shot-hole { box-shadow: 0 0 0 9999px rgba(0, 0, 0, .55); }
.shot-cbox { outline: 1px solid rgba(255, 255, 255, .85); }
.shot-cbox::before, .shot-cbox::after { content: ""; position: absolute; pointer-events: none; opacity: 0; transition: opacity .15s; border: 0 solid rgba(255, 255, 255, .6); }
.shot-cbox::before { top: 0; bottom: 0; left: 33.333%; right: 33.333%; border-left-width: 1px; border-right-width: 1px; }
.shot-cbox::after { left: 0; right: 0; top: 33.333%; bottom: 33.333%; border-top-width: 1px; border-bottom-width: 1px; }
.shot-cbox.on::before, .shot-cbox.on::after { opacity: 1; }
/* 손잡이 — 잡히는 자리는 넉넉히(모서리 40px · 변은 모서리 둘 사이 전체), 보이는 것은 꺾쇠·막대. 변 띠는 «바깥으로» 치우친다(avatar.js `.crop-e` 와 같은 이유 —
   작게 자른 상자의 안쪽이 다 변 손잡이면 옮기려던 손이 크기를 바꾼다) · 모서리를 뒤에 그려 겹치는 끝자리는 모서리가 이긴다 */
.shot-ch { position: absolute; display: block; }
.shot-ch::after { content: ""; position: absolute; border: 0 solid #fff; filter: drop-shadow(0 0 1px rgba(0, 0, 0, .6)); }
.shot-ch[data-h="nw"], .shot-ch[data-h="ne"], .shot-ch[data-h="sw"], .shot-ch[data-h="se"] { width: 40px; height: 40px; }
.shot-ch[data-h="nw"] { left: -20px; top: -20px; } .shot-ch[data-h="ne"] { right: -20px; top: -20px; }
.shot-ch[data-h="sw"] { left: -20px; bottom: -20px; } .shot-ch[data-h="se"] { right: -20px; bottom: -20px; }
.shot-ch[data-h="nw"]::after { left: 17px; top: 17px; width: 18px; height: 18px; border-left-width: 3px; border-top-width: 3px; }
.shot-ch[data-h="ne"]::after { right: 17px; top: 17px; width: 18px; height: 18px; border-right-width: 3px; border-top-width: 3px; }
.shot-ch[data-h="sw"]::after { left: 17px; bottom: 17px; width: 18px; height: 18px; border-left-width: 3px; border-bottom-width: 3px; }
.shot-ch[data-h="se"]::after { right: 17px; bottom: 17px; width: 18px; height: 18px; border-right-width: 3px; border-bottom-width: 3px; }
.shot-ch[data-h="n"], .shot-ch[data-h="s"] { left: 20px; right: 20px; height: 32px; }
.shot-ch[data-h="w"], .shot-ch[data-h="e"] { top: 20px; bottom: 20px; width: 32px; }
.shot-ch[data-h="n"] { top: -22px; } .shot-ch[data-h="s"] { bottom: -22px; }
.shot-ch[data-h="w"] { left: -22px; } .shot-ch[data-h="e"] { right: -22px; }
.shot-ch[data-h="n"]::after, .shot-ch[data-h="s"]::after { left: 50%; width: 22px; margin-left: -11px; border-top-width: 3px; }
.shot-ch[data-h="n"]::after { top: 19px; } .shot-ch[data-h="s"]::after { bottom: 19px; }
.shot-ch[data-h="w"]::after, .shot-ch[data-h="e"]::after { top: 50%; height: 22px; margin-top: -11px; border-left-width: 3px; }
.shot-ch[data-h="w"]::after { left: 19px; } .shot-ch[data-h="e"]::after { right: 19px; }
@media (prefers-reduced-motion: reduce) { .shot-cbox::before, .shot-cbox::after { transition: none; } }
.shot-row { display: flex; gap: 14px; overflow-x: auto; scrollbar-width: none; padding: 2px 2px 6px; margin: 0 -2px; overscroll-behavior-x: contain; }
.shot-row::-webkit-scrollbar { display: none; }
/* 🟢 자동 정렬 (2026-10-27 · 사용자 «코인스쿱처럼 버튼이 적으면 가운데 · 많아지면 왼쪽부터 가로 스크롤» · 코인스쿱 app.css `.shot-row` 문단) — 칸이 줄 안에 들어가면 가운데, 넘치면 왼쪽부터 옆으로 밀기.
   `justify-content:center` 는 넘칠 때 왼쪽이 잘려 못 미니, 양끝 칸의 auto 여백으로 «들어가면 가운데 · 넘치면 왼쪽부터 밀기» 를 얻는다(플랫폼 구분 없음). */
.shot-row > .shot-tile:first-child { margin-left: auto; }
.shot-row > .shot-tile:last-child { margin-right: auto; }
.shot-tile { flex: 0 0 64px; display: flex; flex-direction: column; align-items: center; gap: 7px; padding: 0; border: 0; background: none; color: var(--text); font: inherit; cursor: pointer; }
.shot-tile i { width: 60px; height: 60px; display: grid; place-items: center; border-radius: 18px; border: 1px solid var(--line-2); background: var(--surface); color: var(--text); }
.shot-tile i svg { width: 26px; height: 26px; }
.shot-tile.app i svg { width: 32px; height: 32px; }
.shot-tile.main i { background: var(--brand-500); border-color: transparent; color: var(--on-brand); }
.shot-tile span { font-size: 11.5px; font-weight: 700; line-height: 1.25; text-align: center; color: var(--text-sub); max-width: 68px; word-break: keep-all; overflow-wrap: break-word; }
.shot-tile.main span { color: var(--text); }
.shot-tile:active i { transform: scale(.94); }
@media (prefers-reduced-motion: reduce) { .shot-tile:active i { transform: none; } }

/* ── 🟢 **첨부 그림 위에 그리기** (2026-10-07 · board/channel-compose.js `openImgDraw`) — 화면 덮개 · 가운데에 그림 · 바닥에 도구줄(`.imgdraw-bar` · 버튼은 `.kx-tool`) ── */
.imgdraw { position: fixed; inset: 0; z-index: 985; background: rgba(0, 0, 0, .92);
  display: flex; flex-direction: column; padding: calc(var(--safe-top, 0px) + 16px) 16px calc(var(--safe-bot, 0px) + 84px); }
.imgdraw-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; }
.imgdraw-host { position: relative; display: inline-block; line-height: 0; }
.imgdraw-host img { display: block; max-width: calc(100vw - 32px); max-height: calc(100vh - var(--safe-top, 0px) - var(--safe-bot, 0px) - 116px);
  border-radius: 6px; user-select: none; -webkit-user-drag: none; }
/* 🔴 도구줄은 덮개의 자식 · 덮개 기준 바닥(넓은 화면의 사이드바·두 열과 무관하게 가운데 · 옛 `.kx-fab` 의 자리 규칙을 안 탄다 — 2026-10-07 리뷰) */
.imgdraw-bar { position: absolute; left: 12px; right: 12px; bottom: calc(var(--safe-bot, 0px) + 16px); z-index: 3;
  display: flex; align-items: center; gap: 8px; max-width: 520px; margin: 0 auto;
  padding: 8px; border-radius: var(--r-pill, 999px); background: var(--surface); border: 1px solid var(--line); box-shadow: var(--sh-3); }

.imgdraw-pick { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; padding: 4px 2px 12px; }
.imgdraw-pick button { position: relative; padding: 0; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--surface-2); aspect-ratio: 1; }
.imgdraw-pick img { width: 100%; height: 100%; object-fit: cover; display: block; }
.imgdraw-pick b { position: absolute; left: 6px; top: 6px; min-width: 20px; height: 20px; border-radius: 10px; font-size: 12px; line-height: 20px;
  text-align: center; color: var(--on-brand); background: var(--brand-500); }

/* ── 🔴 **테마 고르기 — 버튼이 그 테마처럼 보인다** (2026-08-23 · 요청 17번 · 2026-10-27 세 칸 타일) ──────────
   각 타일의 배경·글자색이 **그 테마의 실제 `--bg`·`--text`** 이고, 위에 그 테마의 카드 두 줄(`--mini-card`)·강조색(`--mini-acc`)·
   날짜 아이콘(`--mini-art`)이 얹힌다 — 목록이 곧 미리보기다.
   🔴 값은 위 팔레트(`:root[data-theme=…]`)에서 그대로 가져온 것이다 — **팔레트를 고치면 여기도 같이 고친다.**
      시트를 그릴 때 그 테마는 아직 적용 전이라 변수로 못 읽는다(두 벌인 이유) · `scripts/check-themes.mjs` 가 바탕·글자색을 대조한다.
   🔴 고른 타일은 테두리(브랜드색)로만 말한다 — 바탕은 전부 다르기 때문이다. */
.theme-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 0 0 14px; }
.theme-opt { display: flex; flex-direction: column; min-width: 0; padding: 0; border: 1.5px solid var(--line-2); border-radius: 14px;
  overflow: hidden; cursor: pointer; font: inherit; text-align: left; }
.theme-opt[data-swatch="dark"]   { background: #0d1117; color: #e6edf3; --mini-card: #161b22; --mini-acc: #f0b90b; }
.theme-opt[data-swatch="pastel"] { background: #f7f2fc; color: #3b3550; --mini-card: #ffffff; --mini-acc: #8a63d9; --mini-art: var(--ic-pastel); }
.theme-opt[data-swatch="spring"] { background: #fdf4f3; color: #3d2a2e; --mini-card: #fffdfc; --mini-acc: #d6466c; --mini-art: var(--ic-spring); }
.theme-opt[data-swatch="summer"] { background: #edf8fb; color: #102f3a; --mini-card: #ffffff; --mini-acc: #0a86a8; --mini-art: var(--ic-summer); }
.theme-opt[data-swatch="autumn"] { background: #f4eadb; color: #3a291b; --mini-card: #fffaf1; --mini-acc: #bd4f1c; --mini-art: var(--ic-autumn); }
.theme-opt[data-swatch="winter"] { background: #0d1626; color: #e9f1fb; --mini-card: #162236; --mini-acc: #8fd0ff; --mini-art: var(--ic-winter); }
.theme-opt[data-swatch="news"] { background: #efebe2; color: #1d1b17; --mini-card: #faf8f3; --mini-acc: #1d1b17; --mini-art: var(--ic-news); }
.theme-opt[data-swatch="neon"] { background: #0b0817; color: #efeaff; --mini-card: #140f26; --mini-acc: #2ef2d0; --mini-art: var(--ic-neon); }
.theme-opt[data-swatch="space"] { background: #110b22; color: #eee8ff; --mini-card: #1a1333; --mini-acc: #c58bff; --mini-art: var(--ic-space); }
.theme-opt[data-swatch="coffee"] { background: #241b14; color: #f0e6d9; --mini-card: #30251c; --mini-acc: #d9a26d; --mini-art: var(--ic-coffee); }
.theme-opt[data-swatch="rain"] { background: #e6ebef; color: #1e2a35; --mini-card: #f7f9fa; --mini-acc: #36658f; --mini-art: var(--ic-rain); }
.theme-opt[data-swatch="ink"] { background: #f2ede2; color: #1f1d1a; --mini-card: #fbf8f1; --mini-acc: #a92d27; --mini-art: var(--ic-ink); }
.theme-mini { position: relative; display: block; height: 58px; background: var(--mini-art, none) right 7px top 6px / 18px 18px no-repeat; }
.theme-mini::before { content: ""; position: absolute; left: 8px; right: 8px; top: 28px; height: 9px; border-radius: 3px;
  background: var(--mini-card); box-shadow: 0 13px 0 var(--mini-card); }
.theme-mini::after { content: ""; position: absolute; left: 8px; top: 28px; width: 16px; height: 9px; border-radius: 3px; background: var(--mini-acc); }
.theme-opt > span { padding: 6px 9px 8px; font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.theme-opt[aria-selected="true"] { border-color: var(--brand-500); box-shadow: 0 0 0 1.5px var(--brand-500); }

/* ── 🔴 알림종 — **더보기 맨 위 오른쪽의 자기 줄** (2026-09-18 · 141차 요청 2번 · app.js `MoreScreen`) ──
   🔴 `.more-top + .sect` 의 윗 여백을 줄이는 이유: `.sect` 기본(18px)은 «앞 섹션과의 사이»라 종 줄 바로 뒤에서는 과하다 —
      겹침은 없고 머리가 종에서 조금만 떨어지면 된다.
   🔴 넓은 폭(`html.wide`)은 `#scroll > *` 읽기 폭 상한 규칙이 이 줄에도 걸려 종이 읽기 열의 오른쪽 끝(= 계정 카드 오른쪽 끝)에 선다
      — 가로 마진을 여기서 주지 않는 이유다(그 규칙의 `margin-left/right: auto` 와 싸운다). */
.more-top { display: flex; justify-content: flex-end; padding-top: 6px; }
.more-top + .sect { padding-top: 6px; }
/* 🟢 계정 머리와 카드 사이는 다른 섹션(머리 → 행)과 같게 (138차 요청 2번) — `.profcard` 기본 윗 마진(14px)을 여기서만 끈다 */
.sect-acct + .profcard, .sect-acct + .logincard { margin-top: 0; }

/* ── 🟢 **더보기 — 섹션마다 카드 하나** (2026-10-16 · 사용자 「B안으로 구현」 · app.js `MoreScreen` 의 `.more-group`) ──
   옛 판은 줄마다 따로 떠 있는 카드(`.row` 의 테두리 + 아래 8px)라 섹션의 경계가 머리 글자 하나뿐이었다. 이제 한 섹션의 줄들이
   한 상자 안에 서고 줄 사이는 가는 선이다 — 메뉴 구성·순서·조건부 줄은 그대로다(줄을 만드는 `navRow` 는 안 바꿨다).
   🔴 `.row` 기본 규칙(테두리·둥근 모서리·아래 여백)은 앱 정보·관리자 화면도 쓰므로 이 상자 «안»에서만 끈다.
   🔴 조건부 줄(알림 허용 · 화면 배치)이 빠져도 `+` 선택자라 남은 줄 사이에만 선이 선다 — 첫 줄 위에 선이 생기지 않는다. */
.more-group {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
  box-shadow: var(--sh-1); overflow: hidden;
}
.logincard + .more-group { margin-top: 10px; }

.more-group > .row { border: 0; border-radius: 0; margin: 0; background: transparent; padding: 12px 14px; }
.more-group > .row:active { background: var(--surface-2); }
.more-group > .row + .row { border-top: 1px solid var(--line); }

.more-top ~ .sect .sect-ic { color: var(--brand-500); opacity: 1; }
/* 프로필 섹션 머리(board/profile.js `renderProfile`)도 같은 브랜드색 */
.sect-pf .sect-ic { color: var(--brand-500); opacity: 1; }

/* 글쓰기 제목 줄 — [수익|손실 세그][제목 입력] 한 줄 */
.b-titlerow { display: flex; align-items: center; gap: 8px; }
.b-titlerow .b-titlein { flex: 1; min-width: 0; }
.b-qtag { flex: none; padding: 7px 11px; border-radius: 9px; background: var(--brand-weak); border: 1px solid var(--brand-500); color: var(--brand-500); font-size: 12.5px; font-weight: 700; }

/* 게시판 머리줄의 「＋추가」 칩 (요청 10번) — 도미넌스 칩과 같은 줄, 색으로 행동을 가른다 */
.badd-chip {
  border: 1px dashed var(--brand-500); color: var(--brand-500); background: none;
  font-size: 11px; padding: 2px 8px; border-radius: 999px; cursor: pointer; flex: none;
}

/* ── 알림종 (2026-08-22 밤 · 요청 3번 · MoreScreen · renderBell) ── */
.bellbtn {
  position: relative; width: 38px; height: 38px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--line); color: var(--text-sub); cursor: pointer;
}
.bellbtn svg { width: 19px; height: 19px; }
.bellbtn.dot::after {
  content: ""; position: absolute; top: 7px; right: 8px; width: 7px; height: 7px;
  border-radius: 50%; background: var(--hot);
}
.bellrow .bell-meta { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; }
.bellrow .bell-body {
  margin-top: 2px; font-size: 12.5px; color: var(--text-sub);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* ══════════════════════════════════════════════════════════════════════════
 * 🔴 그리는 함수(board-ui `premiumHTML`·`fxBoxHTML`)와 **같은 커밋**에서 지웠다 — 한쪽만
 *    남으면 읽는 곳이 없는 죽은 규칙이 되고, 되살릴 때 무엇이 한 벌인지 알 수 없다
 *    (전체 시총 칩을 걷을 때 세운 그 규칙 그대로).
 *    의 바닥값이 그 사실을 따라가야 한다(요청 10번 · 그쪽 주석). */
/* 🔴 작성자 이름 링크 (2026-08-20 · 요청 1번 · board/people.js `nameHTML`).
   `<button>` 이지만 **글자처럼 보여야 한다** — 머리줄에서 이름만 버튼 모양이면
   그 줄이 두 덩이로 읽힌다. 색·굵기는 `.b-name` 이 그대로 정한다. */
/* ── 🔴 사용자 활동 화면 (2026-08-20 · 요청 1번 · board/activity.js `renderUser`) ──────────────
   머리는 [얼굴][이름 / 구독자·구독중][쪽지·팔로우]. 🟡 이름 아래의 식별자(uh 열두 자 · 눌러서 복사)는 2026-09-14 · 128차
   요청 4번에 걷었다 — 사람이 읽을 수 없는 값이라 「이름이 다른 옛 글이 왜 여기 있나」의 답이 되지 못했다(축은 그대로 uh). */
/* 🔴 **카드다** (2026-08-22 밤 · 요청 5번 — 「전문적인 느낌」). 옛 판은 여백 위에 그냥
   떠 있어서 목록과 경계가 없었다 — 프로필의 레벨 카드(.prof-lvcard)와 같은 표면을 쓴다. */
.buser-head {
  /* 🔴 세로 구조다 (2026-08-22 심야2 · 요청 4·5번) — 위 [얼굴][이름·이력·uh], 아래
     동작 칩 줄. 아바타를 84px 로 키우면서 버튼을 옆에 세울 폭이 없어졌고, 아랫줄이
     손가락에도 넓다. */
  /* 🔴 `relative` 는 우측 상단 버튼 묶음(`.buser-tr` — 쪽지 아이콘)의 기준점이다 — 지우면 버튼이 화면(뷰포트) 오른쪽 위로 날아간다. */
  position: relative;
  display: flex; flex-direction: column; gap: 12px;
  margin: 12px 0 10px; padding: 16px 14px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
}
/* ── 우측 상단 — 쪽지 아이콘 하나 (`.buser-dmbtn`) · 🗄 옆의 [구독] 알약·알림 종(`.buser-folbtn`)은 2026-10-01 에 걷었다(구독은 채널 방에서 한다).
   🔴 묶음(`.buser-tr`)만 absolute 다. 이름이 아주 길면 그 밑으로 들어가므로 `.buser-id` 에 오른쪽 여백을 준다(아래). */
.buser-tr { position: absolute; top: 12px; right: 12px; z-index: 1; display: flex; align-items: center; gap: 6px; }
.buser-dmbtn:active { opacity: .75; }
.buser-dmbtn {
  border: 0; border-radius: 999px; cursor: pointer;
  background: var(--surface-2); color: var(--brand-500);
  padding: 9px; display: inline-flex;
}
.buser-dmbtn svg { width: 18px; height: 18px; display: block; }
/* 🟢 차단 아이콘 — 쪽지 아이콘 «오른쪽» (2026-10-19 · board/channel-admin.js `userHeadHTML` 의 `.buser-blkbtn`) · 평소엔 차분한 색, 차단·뮤트가 걸려 있으면(`.on`) 빨갛게 */
.buser-blkbtn:active { opacity: .75; }
.buser-blkbtn {
  border: 0; border-radius: 999px; cursor: pointer;
  background: var(--surface-2); color: var(--text-mut);
  padding: 9px; display: inline-flex;
}
.buser-blkbtn.on { color: var(--hot); }
.buser-blkbtn svg { width: 18px; height: 18px; display: block; }
/* 🔴 이름·요약 줄이 우측 상단 쪽지 아이콘 밑으로 파고들지 않게 — 그 폭(아이콘 36 + 여유)만큼 비워 둔다.
   🔴 **묶음이 있을 때만**이다 (2026-09-07 · 93차·B 요청 1번 · board/channel-admin.js `userHeadHTML` 의 `has-tr`). 옛 규칙은 이 폭을
      언제나 비웠고 주석은 「버튼이 없는 내 화면에도 걸리지만 어차피 여백이라 해가 없다」고 적었는데 — 한마디가 한 줄
      말줄임이던 동안에는 참이었다. 접히기 시작한 순간 그 104px 는 **내 채널에서만 한마디를 반으로 자르는 값**이 됐다. */
.buser-head.has-tr .buser-id { padding-right: 88px; }   /* 🟢 아이콘 둘(쪽지 + 차단 · 36px × 2 + 간격)만큼 */.buser-top { display: flex; align-items: center; gap: 14px; }
/* ── 🔴 **머리 아바타가 아예 안 보이던 것** (2026-08-25 · 요청 8번 · 실사고) ────────────────
   `.b-av` 는 `display` 를 안 정한다 — 즉 **인라인**이다. 목록·댓글에서는 부모가 flex 라
   («flex 항목은 블록화된다») 폭·높이가 듣지만, 이 머리에서는 `.b-av` 의 부모가
   `.buser-av` 라는 **그냥 span** 이었다. 그래서 `width/height: 84px` 가 통째로 무시되고
   얼굴이 화면에서 **사라져 있었다**(CSS 는 오류를 안 내므로 조용히 없어진다).
   🔴 고치는 자리는 여기다: `.b-av` 에 `display` 를 주면 이 앱의 **모든** 얼굴 자리가
      같이 바뀐다(인라인으로 흐르던 곳까지). 껍데기 하나만 flex 로 만든다. */
.buser-av { display: flex; flex: none; }
/* 이름 줄 — flex 는 말줄임(min-width:0)을 위해 유지한다.
   🟢 이제 이름 뒤에 배지·AI 태그가 선다(2026-09-12~13) — 간격은 `--nm-gap` 규칙(`.b-plusbadge` 문단 · 2026-09-14 · 125차 요청 4번).
   🔴 이름은 `flex: 0 1 auto` 다(줄어들기만 · 늘어나지 않는다) — 🟡 옛 `1 1 auto` 는 이름이 남는 폭을 다 먹어 배지가 이름에서 떨어져
      줄 오른쪽 끝에 붙었다(글자 끝부터 약 40px · 실측). 긴 이름은 여전히 이름만 말줄임한다. */
.buser-nameline { --nm-gap: 6px; display: flex; align-items: center; gap: var(--nm-gap); }
.buser-nameline .buser-name { flex: 0 1 auto; min-width: 0; }
/* 🔴 **한마디** (2026-08-25 · 요청 9번) — 🔴 **얼굴·이름 줄 아래, 카드 폭 전부** (2026-09-07 · 94차·B 후속 8 · 요청 4·5번 · 사용자
      「구독자·구독중을 닉네임 아래에 · 한마디를 아래로 · 우측 끝까지 길게」). 옛 자리(이름 바로 아래 · `.buser-id` 안)는 얼굴 84px 과
      우측 상단 버튼 묶음(`padding-right`) 사이의 좁은 칸이었다. 이제 `.buser-head`(세로 flex)의 둘째 자식이라 카드 안쪽 폭을 통째로 쓴다.
   🔴 **줄바꿈이 없는 것과 «한 줄에 그리는 것»은 다른 말이다** (2026-09-07 · 93차·B 요청 1번 · 실기기 — 「안녕하세요.
      `white-space: nowrap` 을 더해 **접힐 수 있는 글을 잘라 버렸다** — 이 카드는 그 사람이 자기를 소개하는 유일한
      자리인데 첫 문장에서 끊겼다.
   🔴 그래도 **높이는 갇혀 있어야 한다**(옛 규칙이 지키려던 것) — 서버 상한이 `BIO_MAX` 40자라 이 폭에서 두 줄이면 다 들어가고,
      클램프(넉 줄)는 상한을 늘렸을 때의 천장으로 남긴다(상한을 늘리면 이 줄이 자동으로 따라오지 않는다는 뜻이다 · 숫자를 여기 안 적는다). */
.buser-bio {
  margin-top: -4px; font-size: 12.5px; line-height: 1.45; color: var(--text-sub);
  overflow: hidden; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4;
}
/* ── 🔴 **닉네임 이력 표** (2026-08-25 · 요청 7번 · board/channel-admin.js `histHTML`) ────────────────
   머리의 한 줄(`.buser-prev`)을 대신한다 — 이름이 둘만 돼도 넘쳤고 시각을 적을 자리가
   없었다. 🔴 **목록 카드가 아니다**: 누를 것이 없는 표라, 카드 무늬(테두리·그림자)를
   쓰면 눌리는 척하는 줄이 된다(이 파일의 묘비 규칙과 같은 판단). */
.bhist { padding: 4px 2px 0; }
.bhist-row {
  display: flex; align-items: baseline; gap: 8px;
  padding: 11px 2px; border-bottom: 1px solid var(--line);
}
.bhist-n { flex: 1 1 auto; min-width: 0; font-size: 14px; font-weight: 700; color: var(--text-sub);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bhist-row.now .bhist-n { color: var(--text); }
.bhist-tag {
  flex: none; padding: 1px 7px; border-radius: 999px;
  background: var(--brand-weak); color: var(--brand-500);
  font-size: 10.5px; font-weight: 800;
}
.bhist-at { flex: none; font-size: 11.5px; color: var(--text-mut); font-variant-numeric: tabular-nums; }
/* 카드·댓글의 아바타 링크 (요청 9번) — 이름 버튼과 같은 문 */
.b-avlink { padding: 0; border: 0; background: none; display: inline-flex; cursor: pointer; }
.b-avlink:active { opacity: .6; }
.buser-stats { margin-top: 3px; font-size: 12px; color: var(--text-sub); }
.buser-stats b { color: var(--text); font-weight: 700; }
.buser-stats .dot { margin: 0 4px; color: var(--text-mut); }
/* 활동 줄의 미니 얼굴·이름·종류 배지 (renderUser rowMeta) */
.b-av.sm { width: 20px; height: 20px; border-radius: 50%; flex: none; background-size: cover; }
.b-meta .b-nick { font-size: 12px; font-weight: 700; color: var(--text-sub); }
.tag.usr-kind { background: var(--surface-2); color: var(--text-mut); }
/* 🔴 **프로필 설정(.prof-av 84px)과 같은 값이다** (2026-08-22 심야2 · 요청 5번 —
   옛 56 에서 상향). 원본이 128px(`services/avatar.js` THUMB_EDGE)라 DPR 3 에서는
   흐릿함이 생기지만, 자기 얼굴을 84 로 보여 주면서 남의 얼굴만 56 인 것이 더 어색하다 —
   얼굴을 크게 그리는 두 자리는 **같은 값**이어야 한다(둘을 같이 바꿀 것). */
.buser-head .b-av { width: 84px; height: 84px; flex: none; }
.buser-id { flex: 1; min-width: 0; }
.buser-name {
  font-size: 15px; font-weight: 800; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 팔로잉 목록 — 줄 전체가 활동 화면으로 가고, 오른쪽 작은 버튼이 알림 모드다 */
.folrow { gap: 8px; }
.folrow-go {
  display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0;
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left; color: inherit;
}
.folrow-go .grow { display: flex; flex-direction: column; min-width: 0; }
.folrow-uh { font-size: 10.5px; color: var(--text-mut); }
/* 🟢 저장한 글 목록 (2026-10-12 · board/profile.js `renderBookmarks`) — 줄을 누르면 상세 · 오른쪽 책갈피를 누르면 저장 취소 */
.bmrow { gap: 8px; padding: 12px 10px 12px 14px; }
.bmrow-go {
  display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0;
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left; color: inherit; font: inherit;
}
.bmrow-go .row-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.4; }
.bmrow-go .row-desc { margin-top: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bmrow-x {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px;
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--brand-500);
}
.bmrow-x svg { width: 20px; height: 20px; }
.bm-cap { text-align: center; margin: 14px 0 6px; }
.b-namelink {
  padding: 0; background: none; border: 0; cursor: pointer;
  font: inherit; color: inherit; line-height: inherit;
  max-width: 10em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.b-namelink:active { opacity: .6; }
/* ── 🔴 게시판 목록 로딩 (2026-08-20 · 요청 2번 · board/list.js `listHTML`) ──────────────
   🔴 화면을 **덮지 않는다**(`withLoad` 를 안 쓴다) — 읽던 자리·스크롤을 지켜야 한다.
      뼈대(`.bskel`)는 첫 진입에만 나오고, 이 스피너는 갱신 경로에도 나온다. */
.bloading { display: flex; justify-content: center; padding: 14px 0 6px; }
.bspin {
  width: 20px; height: 20px; border-radius: 50%;
  border: 2.5px solid var(--line-2); border-top-color: var(--brand-500);
  animation: spin .8s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .bspin { animation-duration: 2.4s; } }

/* ── 🔴 공지 **롤링** (2026-08-20 · board/notice.js `noticeBannerHTML`) ────────────────────
   최근 몇 건이 한 줄 안에서 **머물렀다 미끄러지듯** 올라간다. `+N` 배지는 없앴다 —
   숫자만으로는 무슨 공지가 더 있는지 알 수 없어 누를 이유가 안 생겼다.
   🔴 **`steps()` 를 쓰지 않는다** (2026-08-20 · 「버벅거리고 끊긴다」). 옛 판은
      `steps(n)` 이라 줄이 **순간이동**했다 — 그건 애니메이션이 아니라 점프라서,
      화면에서는 깜빡임으로 보인다. 이제 구간마다 **머무름(정지) → 미끄러짐(ease)** 을
      키프레임에 직접 적는다.
   🔴 창(`.bnotice-roll`)은 **한 줄 높이로 고정**이어야 한다. 안 그러면 트랙이 다 보여
      배너가 목록만큼 길어진다 — 이 배너의 전제가 「한 줄」이다.
   🔴 트랙 끝에는 **첫 줄이 한 번 더** 붙어 있다(board-ui). 그래서 마지막 미끄러짐이
      끝난 자리가 첫 줄과 같은 그림이고, 애니메이션이 처음으로 되감겨도 안 보인다.
   🔴 건수마다 키프레임이 다르다(`roll2`~`roll5`) — CSS 는 변수로 키프레임을 만들 수
      없다. board-ui 가 건수에 맞는 클래스를 붙인다. 🟡 6건 이상은 안 만든다
      (`ROLL_MAX` 가 5다 — 그 상수를 올리면 여기도 같이 늘릴 것). */
.bnotice-t {
  display: block; min-width: 0; height: 20px; line-height: 20px;
  font-size: 13px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bnotice-roll { flex: 1; min-width: 0; height: 20px; overflow: hidden; }
.bnotice-track { display: block; }
.bnotice-track.on { animation-duration: var(--roll-dur, 12s); animation-iteration-count: infinite; }
/* 🔴 한 구간 = 머무름 + 미끄러짐. 미끄러짐은 전체의 12% 남짓(0.5초 안팎)이라
   눈이 따라갈 수 있고, 나머지는 읽는 시간이다. */
.bnotice-track.roll2 { animation-name: bnroll2; }
.bnotice-track.roll3 { animation-name: bnroll3; }
.bnotice-track.roll4 { animation-name: bnroll4; }
.bnotice-track.roll5 { animation-name: bnroll5; }
@keyframes bnroll2 {
  0%, 44% { transform: translateY(0); }
  50%, 94% { transform: translateY(-20px); }
  100% { transform: translateY(-40px); }
}
@keyframes bnroll3 {
  0%, 29% { transform: translateY(0); }
  33%, 62% { transform: translateY(-20px); }
  66%, 96% { transform: translateY(-40px); }
  100% { transform: translateY(-60px); }
}
@keyframes bnroll4 {
  0%, 22% { transform: translateY(0); }
  25%, 47% { transform: translateY(-20px); }
  50%, 72% { transform: translateY(-40px); }
  75%, 97% { transform: translateY(-60px); }
  100% { transform: translateY(-80px); }
}
@keyframes bnroll5 {
  0%, 17% { transform: translateY(0); }
  20%, 37% { transform: translateY(-20px); }
  40%, 57% { transform: translateY(-40px); }
  60%, 77% { transform: translateY(-60px); }
  80%, 97% { transform: translateY(-80px); }
  100% { transform: translateY(-100px); }
}
@media (prefers-reduced-motion: reduce) {
  .bnotice-track.on { animation: none; }
}
/* 관리자 FAB — 공지 쓰기. 🔴 색을 바꿔 **일반 글쓰기와 구별**한다: 같은 자리 같은 모양의
   버튼이 다른 일을 하면, 관리자 계정으로 로그인한 채 잡담을 쓰려다 공지를 올리게 된다. */

/* ── 🔴 닉네임 입력 (2026-08-18 · 요청 12번) ─────────────────────────────────
   `.b-titlein` 의 바탕·테두리는 `.bform` 안에서만 걸려 있었다. 프로필은 `.bform` 이
   아니라 **아무 꾸밈이 없는 맨 input** 이 어두운 배경에 놓여 사실상 안 보였다. */
/* 🔴 선택자가 `#sheet` 로 넓어졌다 (2026-08-19 · 요청 13번) — 입력칸을 감싸던
   `.prof-nickedit` 줄이 없어졌기 때문이다. 시트 안의 맨 input 은 전부 이 꾸밈을 받는다. */
#sheet .b-titlein {
  width: 100%; box-sizing: border-box; margin: 0;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: 10px;
  padding: 11px 12px; color: var(--text); font-size: 15px;
}
/* ── 🔴 **확대는 «번들 캐릭터일 때만»** (2026-08-25 · 요청 10번 · 실사고) ──────────────────
   폴백 캐릭터 4종은 원본 여백이 넓어 그대로 두면 얼굴이 동그라미 한가운데 작게 뜬다 —
   그래서 128% + 위쪽 정렬로 얼굴이 원을 채우게 했다(2026-08-18 · 요청 12번).
   🔴 그런데 옛 선택자는 **자리**(`.prof-av`·`.av-pick`)에 걸려 있었다. 그 자리에는
      사용자가 **직접 자른 사진**도 온다 — 방금 맞춘 영역이 128% 로 확대되고 22% 지점으로
      밀려서, 화면에서는 「크롭이 위쪽으로 잡혔다」로 보였다(저장된 파일은 멀쩡했다).
   🔴 그래서 판정을 **그림의 정체**로 옮긴다: `.av-char` 는 `avatarZoomClass()` 가
      붙인다(services/avatar.js — 판정이 한 곳이다).
   🔴 사용자 사진에는 아무 변형도 걸지 않는다 — 자른 그대로가 정답이다. */
.av-pick.av-char, .prof-av.av-char { background-size: 128% auto; background-position: center 22%; }
.av-add { background-size: auto; }

/* ── 🟢 게시판 드롭다운 (board/picker.js `pickPopHTML`·`placePick`) ──────────────────────────
      기준 상자는 `.bhead-pick` 이고 `top` 은 JS 가 입력칸 아래로 잰다. 🔴 덮는다, 밀어내지 않는다(2026-09-02 · 68차 요청 12번 · 실기기).
   🔴 패널은 `.bseg-scroll`(overflow-x: auto) 의 «밖»이다 — 안에 두면 아래로 뜨는 패널이 잘린다(2026-08-18 의 「입력해도 반응 없음」).
   🔴 `left`·`top` 은 JS 가 칩의 자리를 재서 넣는다 — 칩이 스크롤 상자 안이라 CSS 로는 못 잡는다. */
.bhead-pick { position: relative; }
.bpick-pop {
  position: absolute; top: 100%; left: 0; z-index: 6;
  width: max-content; max-width: 100%; padding: 8px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--sh-3);
}
.bpick-in { display: flex; }
.bpick-in .bpick-addin { width: 164px; }
.bpick-addin {
  flex: 1; min-width: 0; padding: 9px 12px; border-radius: 10px; font-size: 14px;
  background: var(--surface-2); border: 1px dashed var(--brand-500); color: var(--text);
}
.bpick-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; max-height: min(46vh, 320px); overflow-y: auto; overscroll-behavior: contain; }
.bpick-none { margin: 4px 2px; font-size: 12.5px; max-width: 164px; }
/* 옛 가로 칩과 같은 모양 — 세로로 쌓고 폭은 목록에서 가장 긴 칩에 맞춘다(`stretch`) */
.bpick-chip {
  position: relative; display: flex; align-items: center; gap: 5px; cursor: pointer;
  padding: 7px 12px 7px 9px; border-radius: 9px;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text-sub); font-size: 12.5px; font-weight: 700;
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
.bpick-chip.on { background: var(--brand-weak); border-color: var(--brand-500); color: var(--brand-500); }
.bpick-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 🟢 줄 오른쪽 끝의 끌기 손잡이(≡ — 줄 셋) · 눌리는 자리는 보이는 것보다 넓다(음수 마진 — 줄 높이는 그대로) · board/picker.js `pickDragAt` 문단 (2026-10-23 · 코인스쿱 10-22 이식) */
.bpick-grip {
  flex: none; margin: -12px -9px -12px auto; padding: 12px 9px; box-sizing: content-box; width: 14px; height: 12px; align-self: center;
  background: repeating-linear-gradient(to bottom, currentColor 0 2px, transparent 2px 5px); background-clip: content-box;
  opacity: .45; touch-action: none; cursor: grab;
}
.bpick-chip > .bpick-name { margin-right: 10px; }
.bpick-chip.drag { background: var(--surface); box-shadow: var(--sh-3); border-color: var(--brand-500); }
.bpick-chip.drag .bpick-grip { opacity: 1; cursor: grabbing; }
.bpick-hint { margin: 8px 2px 0; max-width: 164px; font-size: 11px; line-height: 1.35; color: var(--text-mut); }
.bpick-kwic {
  flex: none; display: inline-flex; align-items: center; justify-content: center; width: 15px; height: 15px; border-radius: 50%;
  background: var(--brand-weak); color: var(--brand-500); font-size: 10px; font-style: normal; font-weight: 800; line-height: 1;
}
.bpick-chip { max-width: 220px; }
.bpick-sec { margin: 10px 2px 0; font-size: 11px; font-weight: 700; color: var(--text-mut); }
.bpick-act { margin-top: 6px; }
.bkw-opt {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 11px 12px; margin-bottom: 2px; border-radius: 10px; text-align: left;
  background: var(--brand-weak); color: var(--brand-500); font-size: 13.5px; font-weight: 700;
}
.bpick-chip .coin-ic, .bseg-pick-main .coin-ic { width: 17px; height: 17px; font-size: 10px; }
.bkw-opt .bpick-kwic { width: 18px; height: 18px; font-size: 11px; background: var(--surface); }
.bkw-opt span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.b-chip-kw { color: var(--brand-500); max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bkw-head .b-chip-kw { font-size: 14px; }
.bpick-sugg {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 7;
  margin: 0; padding: 4px; max-height: min(56vh, 380px); overflow-y: auto; overscroll-behavior: contain;
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--sh-3);
}
.bpick-sugg:empty { display: none; }

.b-kwrow .b-chip { display: inline-flex; align-items: center; gap: 5px; }
.b-kwx {
  border: 0; background: none; padding: 0 0 0 3px; cursor: pointer;
  color: var(--text-mut); font-size: 11px; line-height: 1;
}

/* ── 2026-08-18 3차 실기기 손보기 ─────────────────────────────────────────── */
/* 🔴 레벨업 팝업 버튼 — `.btn` 은 **크기만** 정하고 색이 없다. 그래서 밝은 바탕에
   밝은 글자가 됐다(첨부 사진). 여기서 색을 직접 준다. */
.lvup-ok {
  width: 100%; height: 48px; margin-top: 18px; border: 0; border-radius: var(--r-sm);
  background: var(--brand-500); color: var(--on-brand); font-size: 15px; font-weight: 800;
}
.lvup-ok:active { transform: scale(.975); }
.prof-lv-top { position: relative; }
/* 탈퇴한 사용자 이름 (2026-08-18 밤 · 요청 11번) — 🔴 사람 이름이 아니라 **상태**라
   기울여 흐리게 쓴다. 이름처럼 보이면 그 문자열을 닉으로 오해한다. */
.b-gone-u { color: var(--text-mut); font-style: italic; font-weight: 500; }
/* 시트 안의 항목 목록 — 🔴 이름이 `.lv-help-list` 였는데 그 시트가 없어졌다.
   지금 쓰는 곳은 신고 안내 시트다(board/moderation.js `reportSheet`). */
.sheet-list { margin: 8px 0 4px; padding-left: 18px; color: var(--text-sub); font-size: 14px; line-height: 1.9; }

/* 본문이 오는 동안 — 🔴 **요약문을 안 그린다**(요청 6번). 자리도 안 변하고 상태도 분명하다 */
.b-bodywait { display: grid; place-items: center; min-height: 90px; }

/* 첨부 여러 장 — 가로 페이징 (요청 3번). 🔴 스냅은 CSS 가 한다(JS 없음) */
.b-imgpager {
  display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory;
  margin: 10px 0 14px; padding-bottom: 2px;
}
.b-imgpager::-webkit-scrollbar { display: none; }
.b-imgpage { flex: 0 0 88%; scroll-snap-align: center; }
.b-imgpage .b-img { width: 100%; margin: 0; }

/* 공지 목록의 「공지」 태그는 제목 **앞**이다 (요청 9번) */
.bnotice-tag { flex: none; }

/* @·#·$ 태그 — 🔴 **누를 수 있는 것**이다(요청 8번). 버튼이지만 글 안에 흐르므로
   `display: inline` 을 유지한다 — inline-block 이면 줄바꿈에서 통째로 다음 줄로 내려간다. */
.b-at {
  display: inline; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--brand-500); font: inherit;
}
.b-at:active { opacity: .6; }
.b-at-tag { color: var(--tagc); }

/* 🔴 **글의 소속 게시판 칩** (코인스쿱 2026-08-28 · board/list.js `boardChipHTML`).
   🔴 누를 수 없다는 것이 보여야 한다: 눌림 효과를 안 붙인다(🗄 코인스쿱의 «누르면 코인 상세» 칩 `.b-tick-link` 는 걷었다).

   🔴 **글자만 옅게 두는 것으로는 안 됐다** (2026-08-28 · 47차 · 「칙칙하다」).
      옛 판은 `color` 하나만 `--text-mut` 으로 덮었는데, **배경은 `.b-chip` 의
      `--brand-weak`(브랜드색 틴트)가 그대로 남아** 있었다. 노란 틴트 위의 흐린 회색이라
      두 색의 «색조»가 어긋났고, 그건 옅은 것이 아니라 **탁한** 것이다.
   🔴 그래서 축을 바꾼다: **색이 아니라 «배경»으로 구별한다.** 배경을 중립(`--surface-2`)
      으로 내리면 브랜드색 틴트가 사라져 「누를 수 있는 것」과 한눈에 갈리고, 그러면 글자를
      굳이 흐리게 둘 이유가 없다 — 읽으라고 적은 글자다.
   🔴 여덟 테마 전부에서 도는 이유: 둘 다 팔레트 변수라 테마마다 같이 움직인다
      (하드코딩한 색은 어느 테마에서 반드시 튄다 · board/notice.js `ntTagHTML` 주석). */
.b-chip-board { color: var(--text); background: var(--surface-2); font-weight: 700; }

/* 🔴 **쿨다운 중인 버튼** (2026-08-29 · 53차 요청 — 「입력이 지워지지 않게, 쿨다운을
   그 자리에 표시」). 아이콘 버튼(대화 전송)은 글자를 넣을 자리가 없어 «흐리게 + 남은 초는
   aria-label/title» 로 말한다 — 종이비행기 자리에 숫자를 쓰면 아이콘이 사라진다.
   🔴 `disabled` 만으로 안 하는 이유: 버튼이 그냥 죽은 것처럼 보여 「고장」과 구별이 안 된다.
      맥동이 「기다리는 중」이라는 뜻을 만든다. */
[data-cd] { opacity: .55; animation: cdPulse 1s ease-in-out infinite; }
@keyframes cdPulse { 50% { opacity: .3 } }
@media (prefers-reduced-motion: reduce) { [data-cd] { animation: none } }

/* ── 🟢 **대화방 131차·B** (2026-09-17 · 요청 1~9번 · board/dm-room.js `renderDmChat`) ──────────────────────────────────── */
/* 번역 줄 — 말풍선 «안» 맨 위 · 게시글 `.b-xfrom` 의 잔글씨 그대로(노란 한 칸 · 밑줄 버튼) */
.dmb .dm-xline { display: block; margin: 0 0 3px; white-space: normal; font-size: 11.5px; }
.dmb .dm-xline .b-xorig { padding: 2px 0; }
.dmb .dm-xline.wait { font-style: italic; animation: cdPulse 1.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .dmb .dm-xline.wait { animation: none } }
/* 상대 말 — [얼굴][이름 줄 / 말풍선] · 얼굴·이름은 연속 말의 머리에만 */
.dm-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.dm-line { display: flex; align-items: flex-end; gap: 6px; min-width: 0; }
.dm-line .dmb { max-width: 82%; }
.dm-who { display: flex; align-items: center; min-width: 0; font-size: 12px; line-height: 1.3; }
.dm-who .b-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700; color: var(--text-sub); }
.dm-who .b-lv { flex: none; }
span.dm-av.dm-av-gap { visibility: hidden; cursor: default; }
.dmrow-b:not(.head) { margin-top: -3px; }
/* 인용 — 말풍선 안의 카드(글과 같은 조각) · 줄바꿈 규칙은 카드의 것 */
.dmb.has-q { width: 82%; }
.dmrow-b.mine .dmb.has-q { width: 72%; }
.dmb-q { white-space: normal; margin: 0 0 4px; }
.dmb-q .b-quote, .dmb-q .b-qpager { margin-bottom: 0; }
.dmb-q .b-quote { background: var(--bg); }
/* 입력줄 위의 인용 칸 — 🟢 2026-10-12 부터 바닥에 붙는 입력줄(`.dmbar`) «안»의 맨 위다(대화와 같이 스크롤되지 않는다 · 스레드의 `.chth-qs` 와 같은 유리 카드) */
.dmbar .dm-quoteslot {
  margin: 0 0 6px; padding: 6px; border-radius: 14px;
  background: var(--glass-bg); -webkit-backdrop-filter: blur(18px) saturate(180%); backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid var(--glass-line);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dmbar .dm-quoteslot { background: var(--bar-bg); } }
.dm-quoteslot[hidden] { display: none; }
.dm-quoteslot .b-quote, .dm-quoteslot .b-qpager { margin-bottom: 0; }
/* 🟢 보낸 편지함 (2026-10-12 · board/dm-room.js `dmOutbox`) — 보내는 중이면 시각 자리에 작은 시계 · 못 보냈으면 빨간 느낌표(누르면 [다시 보내기][지우기]) */
.dmb.pend { opacity: .72; }
.dmb.fail { opacity: .85; }
.dmb-pend { justify-content: flex-end; color: var(--text-mut); }
.dmb-pend svg { width: 13px; height: 13px; display: block; }
.dmb-fail {
  flex: none; align-self: center; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  background: var(--hot); color: #fff; font-size: 14px; font-weight: 800; line-height: 22px; text-align: center;
}
/* 길게 누르기(메뉴 · `openDmPop`)가 글자 선택·iOS 돋보기와 안 싸우게 — 채널 말풍선(`.chbub[data-emo]`)과 같은 처방 · 복사는 메뉴가 한다 */
.dmb[data-dmk] { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
/* 키보드 위 도구줄이 선 대화방 — 입력줄이 그 줄 «위»에 선다(`paintKbAcc` 가 스크롤을 같은 만큼 되갚는다) */
html.kb-acc-on.dm-room #scroll, html.kb-acc-on.dm-room #scroll2 { padding-bottom: calc(var(--kb, 0px) + 46px); }
html.kb-acc-on .dm-inwrap .dm-in { padding-right: 12px; }
html.kb-acc-on .dmbar { padding-bottom: 8px; }
.stk-sugg {
  position: fixed; z-index: 930; display: flex; gap: 4px; padding: 6px; max-width: calc(100vw - 16px); box-sizing: border-box;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: 14px; box-shadow: 0 8px 24px rgba(0, 0, 0, .38);
}
.stk-sugg[hidden] { display: none; }
.stk-sugg::-webkit-scrollbar { display: none; }
.stk-sugg-b { flex: none; width: 60px; height: 60px; padding: 0; border: 0; border-radius: 10px; background: none; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.stk-sugg-b:active { background: var(--surface-2); }
.stk-sugg-b img { width: 54px; height: 54px; display: block; margin: 0 auto; pointer-events: none; }

/* 🔴 **검색 「더 찾기」 자리** (2026-08-31 · 57차 3차 · 요청 1번 · `paintSearchFoot`).
   버튼 «위»에 지금 범위를 한 줄로 적는다 — 버튼만 있으면 「몇 번을 눌러야 하나」가
   화면에 없고, 그 없음이 사용자가 물은 그 결함이었다. */
.search-foot { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.search-scope { margin: 6px 0 0; font-size: 12px; color: var(--text-mut); }
.search-foot .more { margin-top: 0; }

/* ── 🔴 채널 구독자 관리 — 통계 줄 · 퇴장 (2026-09-07 · 86차 · `docs/channel.md` §8) ──
   내 활동 화면의 통계 줄은 «버튼»이다(누르면 구독자·구독 화면) — 기본 버튼 모양을 걷고 글줄처럼 둔다. */
/* 🔴 `font: inherit` 는 축약이라 위 `.buser-stats` 의 `font-size: 12px` 를 **되돌린다** (2026-09-07 · 91차 요청 7번 · 실기기 — 내 화면(button)의
   구독자 줄만 남의 화면(div)보다 크게 찍혔다). 축약 «뒤»에 크기를 다시 적는다. */
button.buser-stats { background: none; border: 0; padding: 0; text-align: left; font: inherit; font-size: 12px; cursor: pointer; }
button.buser-stats:active { opacity: .7; }
.buser-note { margin-top: 6px; font-size: 12px; color: var(--text-mut); line-height: 1.5; }
/* 구독자 화면 — 퇴장 고지 한 줄 · 퇴장 목록 머리 · 10건씩 늘리는 표식(높이만 있고 보이지 않는다)
   (🗄 구독자 상한의 상한 줄·대기 버튼 규칙은 2026-09-30 에 상한과 함께 걷었다) */
.fol-note { padding: 2px 14px 8px; }
.fol-kx { margin-top: 14px; }
.folmore { height: 1px; }
.b-mini.primary { color: var(--accent); font-weight: 700; }
.b-mini.danger { color: var(--hot); }

/* ══════════════════════════════════════════════════════════════════════════
   🟢 **넓은 폭 레이어 — `html.wide`** (2026-09-12 · 119차 · `docs/web.md` §3-A 1단계)
   ══════════════════════════════════════════════════════════════════════════
 * 폴드(펼침)·태블릿·웹 브라우저가 **같은 레이어**를 쓴다. 켜는 것은 골격(`common/shell.js
 * bindWidthLayer` · 폭 `WIDE_PX` 하나)이고 이 구획은 그 클래스 «아래»에서만 다르게 그린다 —
 * 🔴 여기에 `@media (min-width: …)` 를 적지 않는다(폭의 정본은 그 상수 하나다).
 * 🔴 폰(narrow)은 한 글자도 안 바뀐다 — 모든 규칙이 `html.wide`·`html.side`·`html.aside`(넓은 폭에서만 붙는 클래스)로 시작한다.
 * 자리마다 무엇이 다른가(§3-A ②):
 *   · 하단 탭바 → **왼쪽 세로 사이드바**(아이콘만 · 이름은 `title` 호버 풍선 · `--side-w`) · 서브 화면에서도 남는다
 *     (`no-tabs` 의 내려가기를 되돌린다 — 큰 화면의 «뒤로»는 앱바 버튼이고 탭은 늘 보이는 것이 웹의 규칙)
 *   · 앱바·본문·당김 표시등은 사이드바 폭만큼 오른쪽
 *   · 바텀시트 → 가운데 팝업(스크림·닫기 규약 그대로 · 손잡이는 뜻이 없어 숨긴다)
 *   · 탭 알약의 유리 재질은 사이드바에는 없다 · 스크롤하면 숨는 바도 넓은 폭에서는 안 숨는다(「스크롤하면 숨는 바」 문단의 `:not(.wide)`)
 * 🟢 두 열(`split` → `html.aside`)의 규칙은 바로 아래 「두 열」 구획이다.
 * 🟢 **사이드바는 `html.side` 다** (2026-09-30 · 사용자 요청 「더보기 → 화면 배치 — 탭을 좌측 세로 또는 하단」 · 골격 `shell.js setLayout`).
 *    `side` = 넓은 폭 + 사용자가 «아래»를 안 골랐다. 탭바·사이드바 폭·사이드바 전제의 바닥 값(바닥 여백·토스트·떠 있는 버튼)은 `html.side`,
 *    탭과 무관한 넓은 폭 규칙(읽는 폭·팝업 시트·두 열)은 `html.wide` 그대로다. «아래»를 고르면 탭바는 폰 규칙(알약 · 서브 화면에서
 *    내려감)을 그대로 타고 `--side-w` 가 0 이라 사이드바 폭을 쓰던 식이 전부 저절로 맞는다. */
html.wide { --side-w: 0px; --read-w: 720px; }
html.side { --side-w: 76px; }

/* 사이드바 */
html.side .tabbar {
  left: 0; right: auto; top: 0; bottom: 0; width: var(--side-w); height: auto;
  flex-direction: column; justify-content: flex-start; align-items: stretch;
  padding: calc(var(--safe-top) + 10px) 8px calc(var(--safe-bot) + 10px);
  background: var(--bar-bg); border-right: 1px solid var(--line);
  pointer-events: auto; transform: none; opacity: 1; visibility: visible;
}
/* 서브 화면에서도 남는다 — 사이드바이거나, 하단 탭이어도 두 열(`html.aside`)이면 왼쪽 목록의 탭이라 남는다(한 열 서브 화면의 하단 탭은 폰처럼 내려간다) */
html.side.no-tabs .tabbar, html.aside.no-tabs .tabbar { transform: none; opacity: 1; visibility: visible; }
html.side .tabpill {
  flex-direction: column; align-items: stretch; gap: 4px; padding: 0; width: 100%;
  /* 🟢 바로가기 셋 + ＋ 까지 서면 낮은 창에서 사이드바를 넘는다 (125차) — 알약만 세로로 스크롤한다 */
  min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  /* 🔴 **모서리 0** (2026-09-30 · 사용자 실기기 「마지막 끝 버튼을 누르면 하이라이팅이 약간 일그러져」) — 폰 알약의 `--r-pill`(999px)이
     여기까지 내려와 있었는데, 바로 위 `overflow-y: auto` 가 그 둥근 모서리로 **안을 오려 낸다**. 폭 59px 에 999px 이면 위·아래 끝이
     모서리가 하는 일이 그 오려 내기뿐이다. */
  border-radius: 0;
  background: none; border: 0; box-shadow: none;
  -webkit-backdrop-filter: none; backdrop-filter: none;
  transform: none;
}
/* 🟢 **아이콘만** (2026-09-14 · 요청 5번 · 사용자 「넓은 폭도 탭 제목 빼고 아이콘만」) — 옛 판은 아이콘 + 라벨(`.tablbl`)이라
   스페인어·포르투갈어 이름이 76px 칸에서 잘렸다. 이름은 app.js `renderTabbar` 의 `title`(마우스 호버 풍선)·`aria-label` 이 말한다.
   칸 높이 48px — 폰 알약(44)보다 조금 크게: 마우스 과녁이자 세로로 쌓이는 칸 사이의 숨. */
html.side .tabbtn {
  width: 100%; height: 48px; padding: 0; border-radius: var(--r-md);
}

/* 🟢 편집 모드 — 안내 줄은 사이드바 «안»이 아니라 **사이드바 오른쪽의 말풍선**이다 (2026-09-14 · 요청 3번 · 사용자 스크린샷 —
   옛 판은 76px 칸 안에 세로로 욱여넣어 안내·완료·초기화가 찌그러졌다). 🔴 DOM 은 그대로 `#tabbar` 의 자식이라
   바깥 누름 판정(app.js `_tabEditOutside` 의 `closest('#tabbar')`)과 전역 위임(`data-tabdone`·`data-tabreset`)이 한 글자도 안 바뀐다.
   🔴 폭은 창 오른쪽을 넘지 않는다(`100vw − 사이드바 − 여백`) · 색은 폰 줄과 같은 유리 토큰이라 라이트·다크가 따라온다.
   꼬리(`::before`)는 사이드바를 가리키는 작은 마름모 — 테두리 두 변만 그어 말풍선 테두리와 이어진다. */
html.side .tabbar.editing { height: auto; flex-direction: column; gap: 0; }
html.side .tabbar.editing .tabpill { align-self: stretch; transform: none; }

/* 앱바·본문·당김 표시등·토스트는 사이드바 오른쪽 */
html.wide .appbar, html.wide #scroll { margin-left: var(--side-w); }
html.wide #ptr { left: var(--side-w); }
html.side #scroll,
html.side.no-tabs #scroll { padding-bottom: calc(var(--tab-bot) + 16px + var(--kb, 0px)); }
html.side #toast,
html.side.no-tabs #toast { left: calc(50% + var(--side-w) / 2); bottom: calc(var(--safe-bot) + 18px); }
/* 떠 있는 버튼은 바닥 탭이 없으니 서브 화면과 같은 높이 · 도구줄은 사이드바를 피한다 */
html.side .bfab { bottom: calc(var(--safe-bot, 0px) + 26px); }
html.side .bfab-read { bottom: calc(var(--safe-bot, 0px) + 26px + 54px); }
html.side .bfab-read.solo { bottom: calc(var(--safe-bot, 0px) + 26px); }
/* 🟢 **하단 탭 + 두 열** (2026-09-30 · 위 `html.side` 문단) — 서브 화면(`no-tabs`)인데도 탭바가 남으므로(바로 위 사이드바 구획의 그 줄),
   폰의 `no-tabs` 가 내려 둔 바닥 값들을 «탭이 있는 화면»의 값으로 되돌린다. 오른쪽 열(`#scroll2`)도 탭바 뒤로 안 숨게 같은 여백을 준다. */
html.aside.no-tabs:not(.side) #scroll,
html.aside:not(.side) #scroll2 { padding-bottom: calc(var(--tab-h) + var(--tab-bot) + 16px + var(--kb, 0px)); }
html.aside.no-tabs:not(.side) #toast { bottom: calc(var(--tab-h) + var(--safe-bot) + 18px); }
/* 🟢 **웹 도구줄과 넓은 화면** (2026-10-01 · board/toolbar.js `paintKbAcc` 의 「웹에서는 고정」 문단 · 독립 리뷰가 찾은 두 겹침) —
   ① 하단 탭 + 두 열: 탭 알약이 남아 있어 도구줄이 화면 바닥에 서면 알약을 덮는다 → 알약 «위»에 세우고 그만큼 오른쪽 열을 비운다
   ② 사이드바(한 열·두 열): `html.side #scroll` 의 바닥 여백이 도구줄을 모른다(같은 무게에 뒤에 있어 대화방 규칙을 이긴다) → 도구줄 높이를 더한다 */
html.aside:not(.side) .kb-acc.web { bottom: calc(var(--tab-h) + var(--tab-bot)); }
html.kb-acc-on.aside.no-tabs:not(.side) #scroll,
html.kb-acc-on.aside:not(.side) #scroll2 { padding-bottom: calc(var(--tab-h) + var(--tab-bot) + 16px + var(--kb, 0px) + 46px); }
html.kb-acc-on.side #scroll,
html.kb-acc-on.side.no-tabs #scroll,
html.kb-acc-on.side #scroll2 { padding-bottom: calc(var(--tab-bot) + 16px + var(--kb, 0px) + 46px); }
/* 🟢 **대화방(스레드·쪽지·채널 글쓰기)은 입력줄이 바닥 — 도구줄이 서면 그 줄 바로 위 — 에 붙는다** (2026-10-01 · 감사) — 바로 위 사이드바·두 열의
   바닥 여백(탭 자리 16px + 홈 인디케이터)이 대화방 규칙(`html.dm-room #scroll` · `html.kb-acc-on.dm-room #scroll`)과 무게가 같거나 무거워서
   입력줄이 도구줄·바닥에서 떠 있었다(그 틈으로 말풍선이 비쳤다 · 두 열은 웹 도구줄을 붙인 날부터 · 사이드바 한 열은 그 전부터). 무게를 하나 더 얹는다.
   하단 탭 + 두 열은 둘로 갈린다: 웹은 도구줄이 알약 위(`html.aside:not(.side) .kb-acc.web`)라 그 줄 위 · 앱은 도구줄이 키보드 위(알약은 키보드 뒤)라
   키보드 + 줄 높이만(`kb-acc-web` — board/toolbar.js `paintKbAcc` 가 웹에서만 켠다). */
html.dm-room.side #scroll, html.dm-room.side #scroll2 { padding-bottom: var(--kb, 0px); }
html.kb-acc-on.dm-room.side #scroll, html.kb-acc-on.dm-room.side #scroll2 { padding-bottom: calc(var(--kb, 0px) + 46px); }
html.kb-acc-on.kb-acc-web.dm-room.aside:not(.side) #scroll2 { padding-bottom: calc(var(--tab-h) + var(--tab-bot) + 46px); }
html.kb-acc-on.dm-room.aside:not(.side):not(.kb-acc-web) #scroll2 { padding-bottom: calc(var(--kb, 0px) + 46px); }
/* 웹 도구줄 위로 — 토스트·떠 있는 글쓰기 버튼이 도구줄(화면 바닥 46px)을 덮지 않게(두 열에서는 왼쪽 목록의 버튼이 오른쪽 칸의 줄과 겹친다) */
html.kb-acc-web #toast, html.kb-acc-web .bfab, html.kb-acc-web .bfab-read { translate: 0 -46px; }
html.aside.no-tabs:not(.side) .bfab { bottom: calc(var(--tab-h, 56px) + var(--safe-bot, 0px) + 26px); }
html.aside.no-tabs:not(.side) .bfab-read { bottom: calc(var(--tab-h, 56px) + var(--safe-bot, 0px) + 26px + 54px); }
html.aside.no-tabs:not(.side) .bfab-read.solo { bottom: calc(var(--tab-h, 56px) + var(--safe-bot, 0px) + 26px); }

html.wide:not(.wide-full) #scroll > :not(.bfab):not(.bfab-read),
html.wide:not(.wide-full) #scroll2 > :not(.bfab):not(.bfab-read) {
  max-width: var(--read-w); margin-left: auto; margin-right: auto;
}

/* 시트 → 가운데 팝업 (스크림·`pop-cancel`·Escape 는 그대로) */
html.wide #sheet-scrim { align-items: center; }
html.wide #sheet {
  max-width: 480px; max-height: min(80%, calc(100% - 2 * (var(--safe-top) + var(--ad-room))));   /* 가운데 팝업이라 위아래로 같은 만큼 — 맨 위의 광고 배너 자리 (common.css `#sheet`) */
  border-radius: var(--r-lg); padding: 18px var(--pad) 20px;
  transform: scale(.96);
}
html.wide #sheet-scrim.show #sheet { transform: none; }
html.wide .sheet-grab { display: none; }
html.wide .pop-card { max-width: 400px; }

/* ══════════════════════════════════════════════════════════════════════════
   🟢 **두 열 — `html.aside`** (2026-09-12 · 119차·I · `docs/web.md` §3-A 3단계)
   ══════════════════════════════════════════════════════════════════════════
 * `split`(폭 ≥ `SPLIT_PX` 또는 사용자 배치 「2분할」 · `shell.js syncWidthLayer`)이고 스택 깊이가 2 이상이면 골격(`shell.js syncAside`)이 `html.aside` 를 켜고 `#pane2` 의 hidden 을 푼다.
 * 왼쪽 열 = 뿌리(`#appbar`+`#scroll` · 탭의 목록) · 오른쪽 열 = 위 화면(`#appbar2`+`#scroll2` · 상세·글·방·설정 하위 화면).
 * 🔴 `#pane2` 의 보임은 hidden 속성이 정한다(`[hidden]` 이 !important) — 여기서 display 를 켜지 않는다.
 * 🔴 사이드바(`.tabbar`)는 `#app` 의 왼쪽 패딩 안에 선다(absolute · left 0) — 두 열은 그 패딩 오른쪽의 격자다. */
html.aside #app {
  display: grid;
  grid-template-columns: minmax(340px, 36%) minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  padding-left: var(--side-w);
}
html.aside #appbar { grid-column: 1; grid-row: 1; margin-left: 0; }
html.aside #scroll { grid-column: 1; grid-row: 2; margin-left: 0; }
html.aside #pane2 {
  grid-column: 2; grid-row: 1 / 3; min-width: 0; min-height: 0;
  display: flex; flex-direction: column;
  border-left: 1px solid var(--line);
}
html.wide #appbar2 { margin-left: 0; }
/* 둘째 자리의 본문 — `#scroll` 의 그 규칙(스크롤·가로 넘침 막기·바닥 여백) */
#scroll2 {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 0 12px calc(var(--tab-bot) + 16px + var(--kb, 0px));
  overscroll-behavior-y: contain; overflow-x: hidden;
}
/* 당김 표시등은 왼쪽 열 폭만 */
html.aside #ptr { left: var(--side-w); right: auto; width: max(340px, calc((100% - var(--side-w)) * .36)); }

/* ══════════════════════════════════════════════════════════════════════════
   🟢 **마우스·키보드 동선** (2026-09-12 · 119차·J · `docs/web.md` §3-A 4단계)
   ══════════════════════════════════════════════════════════════════════════
 * 🔴 축이 «폭»이 아니라 **«가리키는 장치»**다 — `@media (hover: hover) and (pointer: fine)`.
 *    폭으로 가르면 태블릿(넓지만 손가락)에 호버가 붙어 **한 번 누른 자리가 계속 눌린 것처럼**
 *    남는다(터치는 hover 를 «떼는» 이벤트가 없다). 폴드·태블릿은 `wide` 지만 여기 안 든다.
 * 🔴 **초점 테두리는 장치를 안 가린다** — `:focus-visible` 은 브라우저가 «키보드로 왔을 때»만
 *    켜므로 터치·마우스 클릭에서는 안 뜬다. 그래서 미디어 쿼리 밖이다.
 *    🔴 이것이 없으면 키보드로 Tab 을 눌러도 «지금 어디»가 화면에 없다 — 이 앱은
 *       `outline: none`(`.input:focus`)까지 걸어 두어서 더 그렇다. 그 규칙보다 **뒤에** 있어야
 *       같은 특정도에서 이긴다(이 구획을 파일 위로 옮기지 말 것).
 * 🔴 호버는 **배경·글자색만** 바꾼다. 크기·여백을 건드리면 마우스가 스칠 때마다 줄이 움직인다. */
:where(button, a, summary, input, textarea, select, [tabindex], [role="button"]):focus-visible {
  outline: 2px solid var(--brand-500);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

@media (hover: hover) and (pointer: fine) {
  /* 누를 수 있는 줄 — 밑에 옅은 판을 깐다(카드·표·후보 줄이 전부 같은 재료를 쓴다) */
  .bcard:hover,
.row:hover,
.more:hover { background: var(--surface-2); }
  /* 꺼져 있는 칩·탭만 밝아진다 — 켜진 것은 이미 브랜드색이라 건드리면 오히려 흐려진다 */
  .chip:hover:not(.on), .tabbtn:hover:not(.active), .iconbtn:hover { color: var(--text); }
  .btn:hover, .pop-btn:hover, .b-mini:hover { filter: brightness(1.08); }
  /* 🔴 손가락이 없으면 **커서가 유일한 «누를 수 있다»는 신호**다 */
  button,
[role="button"],
a,
.bcard,
.row,
.chip { cursor: pointer; }
  button[disabled], [aria-disabled="true"] { cursor: default; }

  /* ── 🔴 **읽는 글은 마우스에서 «고를 수 있어야» 한다** ─────────────────────────────────
   * `body { user-select: none }`(이 파일 머리)은 **웹뷰를 앱처럼 보이게** 하려고 건 것이고
   * 그 근거는 「길게 누르면 시스템 복사 툴팁이 뜬다」였다 — 마우스에는 그 동작이 없다.
   * 🔴 **읽는 표면에만** 연다(제목·본문·요약·말풍선·댓글) — 목록 카드까지 열면 드래그가
   *    글자 선택으로 새어 스크롤·끌기와 싸운다. */
  .b-body,
.b-title,
.chbub-t,
.chbub-x,
.b-cmt p { user-select: text; -webkit-user-select: text; }
}

/* ── 🟢 **레벨 고르기 피커** (2026-09-14 · 126차 요청 6번 · board/dm-room.js `dmLevelPicker`) ─────────────────────────
   세로 스크롤 스냅 휠 — 가운데 줄이 고른 값. 🔴 줄 높이 40px 은 board/dm-room.js `LVPICK_ROW` 와 세트(갈리면 한 칸 어긋난다) ·
   위아래 여백 2줄(80px)이 첫·끝 값을 가운데로 올린다 · 가운데 띠는 배경 그라디언트 하나(요소를 안 늘린다). */
.lvpick {
  position: relative; height: 200px; margin: 6px 0 14px; overflow-y: auto;
  scroll-snap-type: y mandatory; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  scrollbar-width: none; border-radius: 12px;
  background:
    linear-gradient(var(--surface-2, rgba(127,127,127,.12)), var(--surface-2, rgba(127,127,127,.12))) center / 100% 40px no-repeat scroll,
    transparent;
  -webkit-mask-image: linear-gradient(transparent, #000 30%, #000 70%, transparent);
  mask-image: linear-gradient(transparent, #000 30%, #000 70%, transparent);
}
.lvpick::-webkit-scrollbar { display: none; }
.lvpick-pad { height: 80px; }
.lvpick-it {
  height: 40px; line-height: 40px; text-align: center; scroll-snap-align: center;
  font-size: 17px; font-weight: 600; color: var(--text-sub); cursor: pointer; user-select: none;
}
.lvpick-it.on { color: var(--text); font-size: 20px; font-weight: 800; }

/* 🟢 글쓰기 인용 시트 (2026-09-15 · 사용자 요청 3번 · board/game.js `openQuoteSheet`) — 탭 칩 한 줄 + 검색 + 목록(체크) + 완료 · 여러 개를 고른다 */
/* 🟢 머리줄 = [제목] …… [N/3] [완료] (2026-09-16 · 사용자 요청 4번 · `.sheet-head` 규약을 그대로 쓴다) */
.qs-head { align-items: center; }
.qs-count { flex: none; font-size: 13px; font-weight: 800; color: var(--text-mut); font-variant-numeric: tabular-nums; }
.qs-tabs { margin: 2px 0 8px; overflow-x: auto; flex-wrap: nowrap; }
.qs-q { width: 100%; margin: 0 0 8px; }
.qs-list { display: flex; flex-direction: column; max-height: 52vh; overflow-y: auto; overscroll-behavior: contain; }
.qs-row {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 2px; text-align: left;
  background: none; border: 0; border-bottom: 1px solid var(--line); color: inherit; font: inherit; cursor: pointer;
}
.qs-row:active { opacity: .7; }
.qs-im { flex: none; width: 40px; height: 40px; border-radius: 8px; object-fit: cover; background: var(--surface-2); }
.qs-t { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.qs-t b { font-size: 13.5px; font-weight: 700; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.qs-t small { font-size: 11.5px; color: var(--text-mut); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.qs-ck {
  flex: none; width: 22px; height: 22px; border-radius: 50%; border: 1.5px solid var(--line-2);
  display: grid; place-items: center; font-style: normal; font-size: 13px; font-weight: 900; color: #fff;
}
.qs-row.on .qs-ck { background: var(--brand-500); border-color: var(--brand-500); }
/* 🔴 완료 버튼은 `.btn` 이 아니라 `.sheet-save` 다 — 그 클래스는 `width: 100%` 라 머리줄에서 통째로 자란다(`.sheet-head` 주석). */

/* ══════════════════════════════════════════════════════════════════════════
   🟢 **«새 것» 표시는 앱 전체가 빨간 원 안의 «N»** (2026-09-17 · 132차·B 후속 2번 · 사용자 「빨콩은 모두 공통으로 말한 거야」)
   ══════════════════════════════════════════════════════════════════════════
 * 옛 판은 자리마다 6~11px 빨간 점이었다(탭 · 게시판 카드 · 게시판 칩 · 댓글 아이콘 · 채널 띠 얼굴 · 채널 말풍선 · 쪽지함 줄 ·
 * (옛 `.dm-dot` 주석의 그 규칙 · 🟡 `.dm-dot` 은 139차·D 에 쪽지함 줄·프로필 쪽지 행과 함께 걷었다 — 쪽지 목록은 개수 알약 `.chl-n` 이다).
 * 🔴 글자는 언어와 무관한 N 한 자다 — **개수가 아니다**(점은 세지 않는다 · app.js `_tabDot` 머리 주석). 뜻은 각 자리의 `aria-label` 이 말한다.
 * 🔴 **자리(top·right)는 각 규칙이 그대로 정한다** — 여기는 모양만이다. 크기는 둘: 넓은 자리 15px · 좁은 자리(카드 · 말풍선 · 공지) 13px — 🟢 게시판 칩과 카드의 댓글 아이콘은 2026-10-01 에 작은 점으로 돌아갔다(아래 `.bseg-dot-s` 문단) (🟡 채널 띠 `.chsub-dot` 은 139차에 띠와 함께 걷었다 — 구독 목록은 개수 알약 `.chl-n` 이다). 칩 안쪽은 부모가 가로 스크롤이라 바깥으로 못 내민다(`.bseg-dot` 주석) — 그래서 모서리에 바짝 붙인다.
 * 🔴 테두리는 `border` 가 아니라 `box-shadow` 고리다 — `border` 는 상자 높이를 먹어 글자가 세로 가운데에서 어긋난다.
 * 🔴 빈 요소(`<i class="bcard-dot">`)는 `::before` 가 글자를 든다 · 가상 요소 점(`::after`)은 `content` 가 곧 글자다.
 * ── 🟢 **N 은 «줄»이 아니라 «글자»가 가운데여야 한다** (2026-09-19 · 145차·E 요청 4번 · 사용자 「N 이 중앙에 배치되지 않았다」) ──
 * `line-height: 15px` 는 **줄 상자**를 가운데 놓는다. 그런데 대문자 N 은 베이스라인 «위»에만 있고 그 아래는 디센더의
 * 빈자리라, 줄이 가운데면 글자는 언제나 그 빈자리의 절반만큼 **위로 뜬다**(실측: 15px 배지에서 0.25px · 13px 에서 0.19px —
 * 화면 배율 3배 기기에서는 0.75px 라 눈에 보인다). 🔴 `flex`·`grid` 로 가운데 놓아도 **똑같다** — 베이스라인 자리는
 * 글꼴이 정하지 상자가 정하지 않는다. 그래서 `line-height` 에 그 몫의 **두 배**를 더해 베이스라인을 그만큼 내린다
 * (줄 상자만 커지고 배지 크기·모양은 안 바뀐다). 🔴 숫자를 쓰는 배지(`.chip-n`·`.chl-n`)도 같은 자리가 어긋난다. */
.tabbtn.tabdot::after, .bfab-read::after, .bellbtn.dot::after,
.bcard-dot, .bseg-dot, .chbub-new {
  box-sizing: border-box; min-width: 15px; width: auto; height: 15px; padding: 0 3px;
  border: 0; border-radius: 8px; box-shadow: 0 0 0 2px var(--bg);
  background: var(--hot); color: #fff;
  font: 800 9.5px/15.5px system-ui, -apple-system, "Segoe UI", sans-serif; font-style: normal;
  text-align: center; letter-spacing: 0; white-space: nowrap;
}
.tabbtn.tabdot::after, .bfab-read::after, .bellbtn.dot::after { content: 'N'; }
.bcard-dot::before, .bseg-dot::before, .chbub-new::before { content: 'N'; }
.bfab-read::after { box-shadow: 0 0 0 2px var(--surface); top: -4px; right: -4px; }
.bellbtn.dot::after { top: 2px; right: 2px; }
/* 좁은 자리 */
.bcard-dot, .bseg-dot, .chbub-new {
  min-width: 13px; height: 13px; padding: 0 2px; border-radius: 7px; font-size: 8.5px; line-height: 13.4px;
}
/* 🔴 칩의 N 은 모서리가 아니라 **글자 뒤 한 칸**이다 — 칩 안쪽 모서리에 두면 N 이 칩 글자를 덮는다(실측 · 옛 6px 점은 여백에 들어갔다). */
.bseg-dot { position: static; display: inline-block; vertical-align: 1px; box-shadow: none; }
/* 🟢 공지의 N (2026-09-21 · 공지 목록 제목 뒤 · 커뮤니티 공지바의 「공지」 태그 뒤 · board/core.js `NT_NEW_HTML`) — 같은 모양에 간격만 */
.nt-new { flex: none; margin-left: 5px; }
.bnotice > .nt-new { margin-left: -3px; }   /* 공지바는 flex gap 8px 이 이미 있다 */
.bseg-btn .bseg-dot { margin-left: 4px; }
.bcard-dot { top: 7px; right: 8px; }
.chbub-new { top: 6px; right: 6px; }
.bseg-dot.bseg-dot-s {
  position: absolute; top: 3px; right: 4px; display: block; min-width: 0; width: 6px; height: 6px; margin: 0; padding: 0; border-radius: 50%;
}
.bseg-dot.bseg-dot-s::before { content: none; }

/* ── 🟢 봇 잡기 (2026-09-18 · 140차·B · `www/js/mascot.js`) — 시트(940)·팝업보다 아래 · 콘텐츠보다 위 ── */
.mascot {
  position: fixed; z-index: 930; width: 76px; height: 76px; padding: 0; border: 0; background: none; cursor: pointer;
  -webkit-tap-highlight-color: transparent; transform: scale(0); opacity: 0;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, .35));
}
.mascot img { width: 100%; height: 100%; display: block; pointer-events: none; user-select: none; -webkit-user-drag: none; }
.mascot.flip img { transform: scaleX(-1); }
.mascot.in { animation: mascot-in .5s cubic-bezier(.34, 1.56, .64, 1) forwards, mascot-bob 1.6s ease-in-out .5s infinite; opacity: 1; }
/* 🟢 등장 (2026-10-10 · `hitfx.js spawnFx` 와 세트) — 빛기둥 안으로 위에서 늘어진 채 떨어져 «착지»(50% ≈ 0.31초 · 먼지·고리 시각)하고 눌렸다 튕긴 뒤 둥실 · 말풍선은 착지 뒤에 */
.mascot.in.drop { animation: mascot-drop .62s linear forwards, mascot-bob 1.6s ease-in-out .62s infinite; }
.mascot.in.drop .mascot-bubble { animation: mascot-bubble-in .3s ease-out .5s both, mascot-plead 1.1s ease-in-out .8s infinite; }
@keyframes mascot-drop {
  0% { transform: translateY(-90px) scale(.7, 1.35); opacity: 0; animation-timing-function: cubic-bezier(.5, 0, .9, .5); }
  12% { opacity: 1; }
  50% { transform: translateY(0) scale(.88, 1.15); animation-timing-function: ease-out; }
  62% { transform: translateY(4px) scale(1.28, .72); animation-timing-function: ease-out; }
  76% { transform: translateY(-6px) scale(.94, 1.08); }
  88% { transform: translateY(0) scale(1.03, .97); }
  100% { transform: none; opacity: 1; }
}
@keyframes mascot-bubble-in { 0% { opacity: 0; transform: translateX(-50%) translateY(6px) scale(.6); } 100% { opacity: 1; transform: translateX(-50%) scale(1); } }
.mascot-bubble {
  position: absolute; left: 50%; bottom: calc(100% - 4px); transform: translateX(-50%);
  padding: 3px 9px; border-radius: 999px; white-space: nowrap; font-size: 11.5px; font-weight: 800;
  color: #1a1300; background: #ffd84d; box-shadow: 0 2px 6px rgba(0, 0, 0, .3);
  animation: mascot-plead 1.1s ease-in-out infinite;
}
.mascot-bubble::after {
  content: ''; position: absolute; left: 50%; top: 100%; margin-left: -4px;
  border: 4px solid transparent; border-top-color: #ffd84d;
}
/* 🟢 잡는 순간 (2026-10-10 «게임 느낌» 판 · `hitfx.js catchHit`) — 맞고 하얗게 질려 눌린 채 «멈춤»(히트스톱) → 늘어났다 → 부풀어 사라진다(그 순간 그림 조각이 날아간다) ·
   길이는 `hitfx.js CATCH_POP_MS`(0.26초)와 세트. 🗄 옛 판의 여덟 점 반짝이(`::before`·`::after`)는 조각·색종이로 대신해 걷었다 */
.mascot.caught { animation: mascot-hit .26s linear forwards; }
.mascot.caught .mascot-bubble { display: none; }
.mascot.caught img { animation: mascot-white .26s steps(1) forwards; }
@keyframes mascot-hit {
  0% { transform: scale(1); opacity: 1; animation-timing-function: ease-out; }
  14% { transform: translateY(6px) scale(1.3, .68) rotate(-4deg); animation-timing-function: steps(1); }
  42% { transform: translateY(6px) scale(1.3, .68) rotate(-4deg); animation-timing-function: cubic-bezier(.2, .8, .4, 1); }
  66% { transform: translateY(-4px) scale(.86, 1.24) rotate(5deg); animation-timing-function: ease-in; }
  92% { transform: scale(1.5) rotate(-3deg); opacity: 1; }
  100% { transform: scale(1.62); opacity: 0; }
}
@keyframes mascot-white { 0% { filter: brightness(0) invert(1); } 42% { filter: none; } 100% { filter: none; } }
@keyframes mascot-fade { to { opacity: 0; } }
@keyframes mascot-in { 0% { transform: scale(0) rotate(-12deg); opacity: 0; } 100% { transform: scale(1) rotate(0); opacity: 1; } }
@keyframes mascot-bob {
  0%, 100% { transform: translateY(0) rotate(0); }
  25% { transform: translateY(-7px) rotate(-5deg); }
@keyframes mascot-pop { 0% { transform: scale(1); opacity: 1; } 40% { transform: scale(1.3); opacity: 1; } 100% { transform: scale(.2); opacity: 0; } }
@keyframes mascot-spark { 0% { opacity: 1; transform: scale(.3); } 100% { opacity: 0; transform: scale(1.35); } }
  50% { transform: translateY(0) rotate(0); }
  75% { transform: translateY(-4px) rotate(5deg); }
}
@keyframes mascot-plead { 0%, 100% { transform: translateX(-50%) scale(1); } 50% { transform: translateX(-50%) scale(1.08); } }
@media (prefers-reduced-motion: reduce) {
  .mascot.in { animation: none; transform: none; }
  .mascot-bubble { animation: none; }
}

/* ── 🟢 봇 숨바꼭질 길 표식 (140차·E · `mascot.js paintPins`) — 가는 길의 버튼(탭 아이콘·칩·행·목록 줄) 오른쪽 아래의 작은 봇 얼굴.
   빨간 점(`.tabdot`)과 같은 자리 감각 · 누르기를 막지 않는다 · 🟡 140차·D 의 하단 탭 위 띠는 걷었다(사용자 요청 5번 — 계속 떠 있어 거슬린다) ── */
  .mascot.caught { animation: mascot-fade .2s ease-out forwards; }
  .mascot.caught img { animation: none; }
.mascot-pinned { position: relative; }
.mascot-pin {
  position: absolute; right: -2px; bottom: -2px; z-index: 2; width: 20px; height: 20px;
  pointer-events: none; user-select: none; -webkit-user-drag: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45)); animation: mascot-pin-bob 1.4s ease-in-out infinite;
}
.tabbtn > .mascot-pin { right: 0; bottom: 2px; }
/* 칩 줄은 가로 스크롤 상자라 세로로 넘친 것이 잘린다 — 칩 안쪽에 둔다 */
.chip > .mascot-pin, .bseg-btn > .mascot-pin { right: -4px; bottom: 0; width: 16px; height: 16px; }
/* 더보기 행·목록 줄은 테두리 안쪽 모서리에 — 바깥으로 걸치면 아래 행과의 틈에 떠 보인다 */
.row > .mascot-pin { right: 6px; bottom: 4px; }
@keyframes mascot-pin-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } }
@media (prefers-reduced-motion: reduce) { .mascot-pin { animation: none; } }

/* ── 🟢 미션 완료 팝업 (140차·B · board/xp.js `showMissionDone`) — 레벨업 축하(`.lvup`)와 같은 덮개 · 카드 안쪽만 다르다 ── */
.lvup .mis-face { width: 84px; height: 84px; margin: 0 auto 4px; display: block; }
.lvup .mis-list { margin: 6px 0 12px; display: flex; flex-direction: column; gap: 6px; }
.lvup .mis-line { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 14px; font-weight: 700; }
.lvup .mis-line b { color: var(--brand-500); font-variant-numeric: tabular-nums; }
.lvup .mis-plus {
  margin: 4px 0 12px; padding: 10px 12px; border-radius: 12px; font-size: 13px; line-height: 1.45; text-align: left;
  background: color-mix(in srgb, var(--brand-500) 14%, transparent); color: var(--text);
}
.lvup .mis-plus-go {
  display: block; width: 100%; margin: 0 0 8px; padding: 11px 0; border: 1px solid var(--brand-500); border-radius: 12px;
  background: none; color: var(--brand-500); font: inherit; font-weight: 800; cursor: pointer;
}

/* ── 🟢 **말풍선 꼬리 — 옆구리의 작은 삼각형** (2026-10-01 · 사용자 「최하단에 딱 붙이지 말고 살짝 위로 · 작은 삼각형을 붙인 느낌 · 댓글 말풍선에도」) ──────
     · 채널 글 말풍선(목록·방·스레드) — 왼쪽, 바닥에서 조금 위(얼굴이 바닥에 맞춰 선다)
     · 댓글 말풍선 — 남의 말은 왼쪽 · 내 말은 오른쪽, 꼭대기에서 조금 아래(얼굴이 꼭대기에 맞춰 선다)
   두 겹이다 — 뒤(`::before`)가 테두리 색, 앞(`::after`)이 바탕색이고 앞이 말풍선 쪽으로 1.5px 들어와 있다: 빗변에는 1px 쯤의 윤곽이 남고, 말풍선과
   만나는 변에서는 앞 겹이 말풍선의 테두리를 덮어 이음매가 없다. 자리는 말풍선의 패딩 상자 기준이다(테두리 1px 은 그 바깥 · 뒤 겹의 안쪽 변 = 패딩 상자의 변).
   🔴 내 댓글 말풍선은 바탕이 반투명(`--brand-weak`)이고 테두리가 없어 한 겹만 쓰고 겹치지 않게 바깥에 댄다(겹치면 그 자리가 진해진다). */
.chbub:not(.chbub-dim) { border-radius: 16px; }
.chbub:not(.chbub-dim)::before, .chbub:not(.chbub-dim)::after,
.b-cmt.thc-bub::before, .b-cmt.thc-bub::after { content: ''; position: absolute; pointer-events: none; width: 8px; height: 13px; clip-path: polygon(100% 0, 0 62%, 100% 100%); }
.chbub:not(.chbub-dim)::before { left: -8px; bottom: 9px; background: var(--line); }
.chbub:not(.chbub-dim)::after { left: -6.5px; bottom: 9px; background: var(--surface); }
.chbub:not(.chbub-dim):active::after { background: var(--surface-2); }
.chbub.chth-post:active::after { background: var(--surface); }
.chbub-wrap { gap: 10px; }
.chbub-wrap .chbub { max-width: calc(100% - 50px); }   /* 얼굴 40 + 틈 10 */
.b-cmt.thc-bub { position: relative; border-radius: 14px; }
.thc.mine .b-cmt.thc-bub { border-radius: 14px; }
.b-cmt.thc-bub::before, .b-cmt.thc-bub::after { clip-path: polygon(100% 0, 0 38%, 100% 100%); }
.b-cmt.thc-bub::before { left: -8px; top: 12px; background: var(--line); }
.b-cmt.thc-bub::after { left: -6.5px; top: 12px; background: var(--surface); }
.thc.mine .b-cmt.thc-bub::before { display: none; }
.thc.mine .b-cmt.thc-bub::after { left: auto; right: -8px; top: 12px; background: var(--brand-weak); clip-path: polygon(0 0, 100% 38%, 0 100%); }
.thc { gap: 10px; }
.thc-col { max-width: calc(100% - 42px); }
.b-md { font-weight: 800; color: var(--text); }
/* 말풍선 발췌의 첫머리 굵은 글씨 = 제목 줄 (`board/cards.js mdLead`) */
.b-md-lead { display: block; margin-bottom: 2px; }
.b-md-s { text-decoration: line-through; text-decoration-thickness: 1.5px; opacity: .75; }
/* ── 🟢 말풍선의 인용 카드는 «두 줄» — 「인용한 ○○」 + 그 제목 한 줄 (2026-10-01 · 사용자 요청 · 같은 날의 한 줄 판(slim)·옛 부각판(hero)을 대신한다) ──
   목록 말풍선(카드 하나)·스레드(여럿이면 가로 페이징 `quotesHTML` 그대로)가 같은 모양이다. 재게시의 원글 카드(`.b-rt-orig`)는 그 글의 몸통이라 그대로. */
.chbub .b-quote:not(.b-rt-orig) { padding: 7px 12px 8px; margin-bottom: 8px; font-size: 13px; white-space: normal; }
.chbub .b-quote:not(.b-rt-orig) .b-quote-tag { display: block; font-size: 11px; }
.chbub .b-quote:not(.b-rt-orig) .b-quote-tag::after { content: none; }
.chbub .b-quote:not(.b-rt-orig) .b-quote-title { display: block; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -webkit-line-clamp: unset; }
.chbub .b-quote:not(.b-rt-orig) .b-quote-src { display: none; }
.chbub .b-qpager { margin-bottom: 8px; }
.chbub .b-qpager > .b-quote { margin-bottom: 0; }
/* ── 🟢 고정 글 배너 = 떠 있는 알약 (2026-10-01 · board/channel-room.js `pinBarHTML`) — 입력줄 알약(`.inpill`)·탭 알약과 같은 유리 재질. 붙박이 띠는 자리만 잡는다 ── */
.chroom-sticky { background: none; border-bottom: 0; padding: 6px 10px 2px; margin: 0 -12px; pointer-events: none; }
.chroom-pinbar {
  pointer-events: auto; gap: 10px; padding: 7px 12px 7px 10px; border-radius: 22px; min-height: 46px;
  background: var(--glass-bg); -webkit-backdrop-filter: blur(18px) saturate(180%); backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid var(--glass-line); box-shadow: var(--sh-3), inset 0 1px 0 var(--glass-top);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .chroom-pinbar { background: var(--bar-bg); } }
.chroom-pinsegs { flex: none; align-self: stretch; display: flex; flex-direction: column; gap: 2px; width: 2.5px; margin: 1px 0; }
.chroom-pinsegs i { flex: 1; border-radius: 2px; background: var(--text-mut); opacity: .45; }
.chroom-pinsegs i.on { background: var(--text); opacity: 1; }
.chroom-pinbody { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.chroom-pintag { font-size: 12px; font-weight: 700; color: var(--text); }
.chroom-pint { flex: none; font-size: 13px; color: var(--text-sub); }
.chroom-pinic { flex: none; display: inline-flex; color: var(--text-sub); }
.chroom-pinic svg { width: 19px; height: 19px; display: block; }
/* 스레드의 그림 앨범이 말풍선의 첫 줄이면 위 모서리까지 채운다 (목록 말풍선의 `alb-top` 과 같은 모양) */
.chth-post > #b-imgs:first-child .alb { margin-top: -10px; border-radius: 15px 15px 0 0; }
.chth-post > #b-imgs:empty { display: none; }
.chth-post > .b-quote { margin: 0 0 6px; }


/* ══════════════════════════════════════════════════════════════════════════
   🟢 **맞대결** — 대결 피드 · 대결 화면(VS · 반반 실시간 댓글) · 영상 화면 (2026-10-22 · 코인스쿱 골격으로 옮김)
   ══════════════════════════════════════════════════════════════════════════
   마크업은 `www/js/duel.js` 의 맞대결 화면들이다. 본문은 골격의 `#scroll`(가로 패딩 12px) 안에 그려지고
   앱바는 `#appbar` 다 — 화면마다 자체 앱바(옛 `.bar`)를 두지 않는다.
   🔴 성향은 색(`--l` 파랑 · `--r` 빨강)과 「좌파 성향」·「우파 성향」 두 낱말뿐이다 — 이 구획 밖에서 성향을 칠하지 않는다.
   🔴 플레이어(`.player`) 위에는 아무것도 얹지 않는다 — 덮개·배지·그라데이션·배너 전부(RMF · 오클릭). 두 영상을 한 화면에서 같이 틀지 않는다(플레이어는 영상 화면에 하나).
   🔴 신고·가리기·지우기(`.cm-a .ca`)는 댓글마다 «보이게» 둔다 — 숨기거나 흐리게 줄이지 않는다(App Store 1.2). */

/* ── 성향 표시 — 두 낱말뿐 (`.lean.l` 좌파 성향 · `.lean.r` 우파 성향) ──
   🔴 상단 칩(`.chip`)과 다른 이름이다 — 누를 수 있는 칩처럼 보이면 안 된다. */
.lean {
  display: inline-block; flex: none;
  padding: 2px 7px; border-radius: var(--r-pill);
  font-size: 11px; font-weight: 700; line-height: 1.35; white-space: nowrap;
}
.lean.l { color: var(--l); background: var(--l-soft); }
.lean.r { color: var(--r); background: var(--r-soft); }

/* ── 대결 피드 — 카드 = [좌 썸네일] VS [우 썸네일] (커뮤니티 카드 `.bcard` 와 같은 면) ── */
.feed { display: flex; flex-direction: column; gap: 10px; padding: 0; }
/* 🟢 날짜 머리와 첫 카드·HOT 과 머리 사이를 코인스쿱과 같게 (2026-10-28 · 사용자 «'오늘' 섹션제목과 첫카드 여백이 큰데 코인스쿱과 동일하게»).
   🟡 정정: 옛 판은 `.feed` 의 위 패딩 · 카드 사이 gap · 머리의 아래 마진이 겹쳐 머리 → 첫 카드, HOT → 머리 간격이 코인스쿱(카드 margin-bottom 한 겹)보다 컸다.
   지금은 머리 아래는 gap 에서 마진을 덜어 코인스쿱의 한 겹 값이 되게, 위 여백은 «머리가 아닌 첫 칸»만 갖게 옮겼다(값의 정본은 아래 규칙들).
   🔴 머리의 위 여백은 margin 이 아니라 padding 이다(`.date-sect` 문단의 sticky 사고) — 그래서 첫 머리는 위 padding(`.date-sect:first-child`)만 갖는다. */
.feed > .date-sect { margin-bottom: -2px; }
.feed > :first-child:not(.date-sect) { margin-top: 12px; }
/* 토픽 바·HOT 이 둘 다 없을 때의 첫 날짜 머리 — 여백이 padding 이라 padding 을 걷는다(코인스쿱과 같은 규칙) */
#appbar.has-chips ~ #scroll > #feed-topic:empty ~ #feed-hot:empty ~ #feed-list > .date-sect:first-child { padding-top: 0; }
.feed .hint { text-align: center; padding: 48px 16px 8px; }
.feed .hint .btn { width: auto; height: 42px; padding: 0 18px; margin: 0 auto; }
.feed .card {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--line); border-radius: 14px; padding: 14px 14px 12px;
}
.feed .card:active { background: var(--surface-2); }
.card-h { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 10px; }
.card-h b { font-size: 16px; font-weight: 800; line-height: 1.35; min-width: 0; }
.card-h span { color: var(--text-mut); font-size: 12px; white-space: nowrap; }
.duel { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 8px; align-items: start; }
.duel .vs { align-self: center; font-weight: 900; font-size: 13px; letter-spacing: .5px; color: var(--text-mut); }
.duel .side { min-width: 0; }
.duel .side .th {
  position: relative; aspect-ratio: 16 / 9; border-radius: 10px; overflow: hidden;
  background: var(--surface-2); border: 2px solid transparent;
}
.duel .side.l .th { border-color: var(--l); }
.duel .side.r .th { border-color: var(--r); }
.duel .side .th img { width: 100%; height: 100%; object-fit: cover; display: block; }
.duel .side .th.none {
  display: flex; align-items: center; justify-content: center; text-align: center; padding: 4px;
  font-size: 12px; color: var(--text-mut); border-style: dashed; border-color: var(--line-2);
}
.duel .side .meta { display: flex; align-items: center; gap: 4px; margin-top: 6px; min-width: 0; }
.duel .side .cn { font-size: 12px; color: var(--text-sub); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.duel .side .vt {
  font-size: 13px; line-height: 1.4; margin-top: 4px; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* ── 대결 화면 · 영상 화면의 틀 ──
   🔴 둘 다 «입력줄이 바닥에 붙는 화면»이라 앱이 `html.dm-room` 을 건다 — 그러면 `#scroll` 의 바닥 패딩이 키보드 높이(`--kb`) 하나가 되고,
      이 틀은 `#scroll` 의 높이를 그대로 채운다(`height: 100%`). 칸 안의 댓글 상자만 스크롤한다 — `#scroll` 은 구르지 않는다.
   🔴 가로는 `#scroll` 의 12px 패딩을 되먹는다(`margin: 0 -12px`) — 반반 칸이 화면 끝까지 가야 두 칸이 같은 폭으로 읽힌다. */
.vs-page, .video-page { height: 100%; min-height: 0; display: flex; flex-direction: column; margin: 0 -12px; }

/* 머리 — VS 썸네일 둘. 🔴 눌러야 영상 화면으로 간다(재생은 거기서 하나만) */
.vs-head { flex: 0 0 auto; display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 8px; align-items: start; padding: 10px 12px 10px; }
.vs-badge { align-self: center; font-weight: 900; font-size: 13px; color: var(--text-mut); }
.vs-th { position: relative; display: flex; flex-direction: column; min-width: 0; text-align: left; color: var(--text); cursor: pointer; border: 0; background: none; padding: 0; font: inherit; }   /* 🟡 단추 기본 테두리가 썸네일 둘레에 회색 네모로 남았다(2026-10-22 미리보기) */
.vs-th img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; border-radius: 10px; border: 2px solid transparent; background: var(--surface-2); }
.vs-th.l img { border-color: var(--l); }
.vs-th.r img { border-color: var(--r); }
.vs-th:active img { opacity: .82; }
.vs-th .play {
  position: absolute; top: 0; left: 0; right: 0; aspect-ratio: 16 / 9;
  display: flex; align-items: center; justify-content: center; pointer-events: none;
  color: #fff;
}
.vs-th .meta { display: flex; align-items: center; gap: 4px; margin-top: 6px; min-width: 0; }
.vs-th .cn { font-size: 12px; color: var(--text-sub); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vs-th .vt { font-size: 12.5px; line-height: 1.4; margin-top: 3px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.vs-th.none {
  aspect-ratio: 16 / 9; cursor: default; align-items: center; justify-content: center; gap: 6px;
  border: 2px dashed var(--line-2); border-radius: 10px; color: var(--text-mut); font-size: 12px; text-align: center;
}

/* 반반 두 칸 — 칸마다 그 영상의 댓글이 실시간으로 쌓인다 */
.cols { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); border-top: 1px solid var(--line); }
.col { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.col.l { border-right: 1px solid var(--line); background: linear-gradient(var(--l-soft), transparent 44px); }
.col.r { background: linear-gradient(var(--r-soft), transparent 44px); }
.col-h { flex: 0 0 auto; display: flex; align-items: center; gap: 5px; padding: 8px 8px 6px; font-size: 12px; color: var(--text-sub); min-width: 0; }
.col-h span:last-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.col-box { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-y: contain; padding: 0 6px 10px; }
.col-box.full { padding: 0 12px 12px; }
.older {
  display: block; width: 100%; margin: 4px 0 8px; padding: 8px; border-radius: var(--r-sm);
  font-size: 12px; font-weight: 600; color: var(--text-sub); background: var(--surface-2);
}
.older:active { opacity: .7; }

/* 댓글 — 말풍선 카드 하나 */
.cms { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.cm { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 7px 9px 5px; min-width: 0; }
.cm.mine { border-color: var(--line-2); background: var(--surface-2); }
.cm-h { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-mut); min-width: 0; }
.cm-h .nick { color: var(--text-sub); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.cm-h .t { flex: none; }
.cm .b { margin: 3px 0 0; font-size: 13.5px; line-height: 1.5; color: var(--text); white-space: pre-wrap; word-break: keep-all; overflow-wrap: anywhere; }
.col-box.full .cm .b { font-size: 14.5px; }
.cm .b.clamp { display: -webkit-box; -webkit-line-clamp: var(--lines, 3); -webkit-box-orient: vertical; overflow: hidden; cursor: pointer; }
.cm .b.blur { filter: blur(5px); cursor: pointer; -webkit-user-select: none; user-select: none; }
.cm .blur-hint, .cm .gone { margin: 3px 0 0; padding: 0; font-size: 12px; color: var(--text-mut); text-align: left; }
/* 🔴 신고·가리기·지우기 — 댓글마다 늘 보인다(머리 문단). 손가락 자리는 글자보다 넓게(위아래 패딩) */
/* 🟢 영상방 말풍선 길게 누르기 → 답장·복사 (2026-10-28 · 사용자 «채널 말풍선 답장 방식처럼») — 누르는 글자 선택·콜아웃은 막는다(복사는 메뉴 줄) */
.cms .cm { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
.cms .cm.flash { animation: cm-flash 1.4s ease-out; }
@keyframes cm-flash { 0%, 40% { background: var(--brand-weak); } 100% { background: var(--surface); } }
.cm-rp { display: block; width: 100%; margin: 4px 0 0; padding: 3px 8px; border: 0; border-left: 2px solid var(--line-2); border-radius: 2px; background: none; text-align: left; font: inherit; font-size: 11.5px; line-height: 1.4; color: var(--text-mut); cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cm-rp b { color: var(--text-sub); font-weight: 700; margin-right: 4px; }
.cm-pop { position: fixed; z-index: 960; animation: emo-pop-in .14s ease-out; }
.cp-reply { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; padding: 6px 10px; border-left: 2px solid var(--brand-500); border-radius: 2px; background: var(--surface-2); font-size: 12px; color: var(--text-sub); }
.cp-reply[hidden] { display: none; }
.cp-reply span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cp-reply b { color: var(--text); }
.cp-reply button { flex: none; border: 0; background: none; color: var(--text-mut); font-size: 16px; line-height: 1; padding: 2px 4px; }
.cm-a { display: flex; gap: 12px; justify-content: flex-end; margin-top: 2px; }
.cm-a .ca { font-size: 11.5px; font-weight: 600; color: var(--text-mut); padding: 5px 0 3px; background: none; border: 0; }
.cm-a .ca:active { color: var(--hot); }
.cms .empty { padding: 18px 4px; font-size: 12px; line-height: 1.5; color: var(--text-mut); text-align: center; }

/* ── 글쓰기 — 바닥의 떠 있는 알약 (쪽지·채널 입력줄 `.inpill` 과 같은 유리 재질) ──
   🔴 키보드 높이는 `html.dm-room #scroll` 의 바닥 패딩(`--kb`)이 먹는다 — 여기서 또 더하지 않는다.
      홈 인디케이터 몫만 키보드가 없을 때 남긴다(`max(0, safe-bot − kb)`). */
.composer {
  flex: 0 0 auto; position: relative; z-index: 2;
  padding: 8px 10px calc(8px + max(0px, var(--safe-bot, 0px) - var(--kb, 0px)));
  background: var(--bg); border-top: 1px solid var(--line);
}
.composer .seg { display: flex; gap: 6px; margin: 0 0 8px; padding: 0; background: none; border-radius: 0; }
.composer .seg button {
  flex: 1 1 0; min-width: 0; padding: 7px 4px; border-radius: var(--r-pill);
  font-size: 12.5px; font-weight: 700; color: var(--text-sub);
  background: var(--surface); border: 1px solid var(--line);
}
.composer .seg button.l.on { color: #fff; background: var(--l); border-color: var(--l); }
.composer .seg button.r.on { color: #fff; background: var(--r); border-color: var(--r); }
.composer .row {
  display: flex; align-items: flex-end; gap: 4px; width: 100%; cursor: default;
  margin: 0; padding: 4px; border-radius: 25px;
  background: var(--glass-bg); border: 1px solid var(--glass-line);
  -webkit-backdrop-filter: blur(18px) saturate(180%); backdrop-filter: blur(18px) saturate(180%);
  box-shadow: inset 0 1px 0 var(--glass-top);
}
.composer .row:active { background: var(--glass-bg); }
.composer textarea {
  flex: 1 1 auto; min-width: 0; width: 100%; min-height: 40px; max-height: 120px; resize: none;
  padding: 10px 6px 10px 13px; border: 0; border-radius: 0; background: none; outline: none;
  color: var(--text); font: inherit; font-size: 15px; line-height: 1.35;
}
.composer textarea::placeholder { color: var(--text-mut); }
.composer .send {
  flex: none; align-self: flex-end; height: 40px; min-width: 40px; padding: 0 14px; border: 0; border-radius: 20px;
  background: var(--brand-500); color: var(--on-brand); font-size: 14px; font-weight: 800; cursor: pointer;
}
.composer .send:disabled { opacity: .5; }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .composer .row { background: var(--bar-bg); }
}

/* ── 영상 화면 ──
   🔴 맨 위 배너(네이티브 · 웹뷰 위에 얹힌다)는 앱바 «위»에 선다 — 앱이 이 화면에 있는 동안 `html.ad-top` 을 걸면 앱바가
      배너 높이(`--ad-h` · common/ads.js 가 채운다)만큼 내려와 배너와 겹치지 않는다. 배너와 플레이어 사이에는 앱바가 있다(오클릭 간격).
   🟡 1단계는 화면 맨 위에 빈 상자(`.ad-space`)를 두었다 — 골격이 앱바를 `#scroll` 밖에 두므로 그 상자는 앱바 «아래»가 되어 쓸모가 없다(그래서 숨긴다). */
html.ad-top .appbar { min-height: calc(var(--bar-h) + var(--safe-top) + var(--ad-h, 0px)); padding-top: calc(var(--safe-top) + var(--ad-h, 0px)); }
.ad-space { display: none; }
.player { flex: 0 0 auto; position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000; }
.player iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.v-info { padding: 4px 0 6px; }   /* 🟢 댓글 상자 안의 맨 위 — 댓글과 같이 구른다(2026-10-27 · 사용자 «댓글 영역이 너무 좁아») */
.v-info h2 { font-size: 16px; font-weight: 800; line-height: 1.4; margin: 0 0 6px; }
/* 영상 요약 — 🔴 요약만 낸다(자막 전문 금지 · CLAUDE.md 「절대 깨면 안 되는 것」 3번) */
.sum { margin: 4px 0 8px; padding: 10px 12px; border-radius: var(--r-sm); background: var(--surface); border: 1px solid var(--line); }
.sum b { font-size: 12px; font-weight: 800; color: var(--text); }
.sum span { font-size: 11px; color: var(--text-mut); margin-left: 6px; }
.sum p { margin: 6px 0 0; font-size: 13.5px; line-height: 1.55; color: var(--text-sub); white-space: pre-wrap; }
.video-page .col-box.full { padding-top: 8px; }

/* ── 유튜버 성향 목록 (채널 목록 화면 · 1단계 마크업 `.page`) ── */
.page { padding: 12px 2px 24px; }   /* 🟡 2026-10-28: 4px → 2px — 다른 상세(`.pdetail`)와 같은 좌우 (사용자 «좌우 여백 피드처럼 다 맞춰줘») */
.page > .sub { font-size: 13.5px; line-height: 1.6; color: var(--text-sub); margin: 4px 0 8px; }
.page h3 { margin: 18px 0 6px; font-size: 14px; }
.page .chlist { list-style: none; display: block; margin: 0; padding: 0; background: var(--surface); border: 1px solid var(--line); border-radius: 14px; overflow: hidden; }
.page .chlist li { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--line); font-size: 14.5px; }
.page .chlist li:last-child { border-bottom: 0; }
.page .chlist .dot { flex: none; width: 10px; height: 10px; border-radius: 50%; margin: 0; }
.page .chlist .dot.l { background: var(--l); }
.page .chlist .dot.r { background: var(--r); }
.page .chlist .link { margin-left: auto; font-size: 13px; font-weight: 600; color: var(--brand-500); background: none; border: 0; }
.page .fine, .sheet-b .fine { font-size: 12px; line-height: 1.6; color: var(--text-mut); margin-top: 14px; }
.page .fine a, .sheet-b .fine a { color: var(--text-sub); }

/* ── 정당 목록 · 정당 상세 — 코인스쿱 마켓탭 목록 + 코인 상세를 옮겼다 (2026-10-23 · 사용자 「마켓탭+암호화폐칩+코인상세 UI 그대로 · 정당 아이콘을 이름 앞에 · 코인상세처럼 예쁘게」 · `parties.js`) ──
   🔴 코인스쿱 `app.css` 의 `.coin-ic`·`.mkrow`·`.mk-*`·`.chead`·`.cprice`·`.cchg`·`.csecbar`·`.mcards`·`.cstat`·`.linkcard` 를 «이 화면에 필요한 만큼만» 복사해 이 저장소 소유로 둔다(동기화하지 않는다).
   값 열(가격 반짝임·정렬 머리·열 폭 재기·스파크라인)은 정당에 없는 개념이라 안 옮겼다. 색은 `--up`(초록)·`--hot`(빨강) 을 «좋다/나쁘다»로 쓰지 않는다 — 정당 화면의 숫자는 중립(브랜드색 하나)이다. */
:root { --mk-ic: 26px; --mk-ic-mr: 8px; --mk-rank-w: 22px; --mk-row-py: 11px; --list-rank: var(--text-mut); --list-sub: var(--text-mut); }
/* 아이콘 — 로고(위키미디어 썸네일)가 있으면 그림 · 없거나 못 받으면 첫 글자(그림이 `onerror` 로 스스로 빠진다) */
.coin-ic { flex: 0 0 auto; width: 15px; height: 15px; border-radius: 50%; overflow: hidden; display: grid; place-items: center; grid-template: 100% / 100%;
  background: var(--surface-2); color: var(--brand-500); font-size: 9px; font-weight: 800; font-style: normal; line-height: 1; }
.coin-ic > * { grid-area: 1 / 1; }
.coin-ic img { width: 100%; height: 100%; object-fit: contain; display: block; position: relative; background: #fff; padding: 2px; box-sizing: border-box; }
.mkrow .coin-ic { width: var(--mk-ic); height: var(--mk-ic); font-size: 12px; margin-right: var(--mk-ic-mr); }
.bar-sym { display: flex; align-items: center; min-width: 0; }
.bar-sym h1 { display: flex; align-items: center; gap: 8px; min-width: 0; margin: 0; }
.bar-sym .coin-ic { width: 22px; height: 22px; font-size: 11px; }
.barsym-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 목록 — 마켓탭의 표(순번 · 아이콘 · 이름/부제 · 값 칸들) */
.mkwrap { margin-top: 6px; }
.mklist { display: flex; flex-direction: column; width: 100%; }
.mkrow { display: flex; align-items: center; gap: 4px; width: 100%; padding: var(--mk-row-py) 0 var(--mk-row-py) 3px; border: none; background: none; text-align: left; color: var(--text); font: inherit; border-bottom: 1px solid var(--line); }
button.mkrow { cursor: pointer; }
.mk-head { padding: 4px 0 4px 3px; font-size: 11px; color: var(--text-mut); }
.mk-head .mk-name, .mk-head .mk-cell { font-size: 11px; font-weight: 700; color: var(--text-mut); }
.mk-ic-gap { flex: none; width: calc(var(--mk-ic) + var(--mk-ic-mr)); }
.mk-rank { flex: none; width: var(--mk-rank-w); font-size: 12px; font-weight: 500; color: var(--list-rank); text-align: left; font-variant-numeric: tabular-nums; }
.mk-name { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
.mk-name b { font-size: 14px; font-weight: 700; line-height: 19px; letter-spacing: -.2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mk-name small { font-size: 11.5px; font-weight: 500; line-height: 15px; color: var(--list-sub); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mk-cell { flex: none; width: 56px; text-align: right; white-space: nowrap; font-size: 13px; font-weight: 500; font-variant-numeric: tabular-nums; color: var(--text); }
.mk-cell.mk-dim { color: var(--text-mut); }
.mk-note { padding: 14px 4px; }
/* 🗄 검색칸(`.pty-search`)은 2026-10-26 걷었다(사용자 «검색바는 빼도 돼») · 목록 칩은 앱바의 공통 칩 바로 올라갔다(`common/chipbar.js`) */
.pty-count { margin: 12px 2px 0; font-size: 12px; color: var(--text-mut); }
/* ── 🟢 정당 목록 머리 — 코인스쿱 «암호화폐» 칩의 머리 카드 줄(`.hc-row`·`.hc` · 코인스쿱 app.css «머리 카드 한 벌»)을 이 화면 몫만 옮겼다 (2026-10-26 · parties.js `headHTML`) ──
   🔴 폭은 «화면에 세 장 반»(`--hc-w` · 반 장이 걸쳐 있어야 «더 있다»가 읽힌다) · 키 고정 112px(그림 없는 카드와 한 줄에 서도 안 울퉁불퉁) · 값·라벨은 한 줄 말줄임.
   🔴 색은 브랜드색 하나 — 정당에 좌·우 색을 칠하지 않는다(parties.js 머리 «중립»). */
.hc-row {
  --hc-w: clamp(100px, calc((100vw - 2 * var(--pad, 14px) - 3 * 8px) / 3.45), 150px);
  display: flex; flex-wrap: nowrap; gap: 8px; overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; padding-bottom: 4px;
}
.hc-row::-webkit-scrollbar { height: 4px; }
.hc-row::-webkit-scrollbar-thumb { background: var(--line); border-radius: 2px; }
.hc-row::-webkit-scrollbar-track { background: transparent; }
.pty-head { margin-top: 12px; }
.hc {
  flex: 0 0 auto; width: var(--hc-w); height: 112px; min-width: 0; box-sizing: border-box;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 3px;
  padding: 9px 8px 8px; border-radius: 14px; text-align: center; background: var(--surface); border: 1px solid var(--line); color: var(--text);
}
button.hc-go { cursor: pointer; font: inherit; }
button.hc-go:active { background: var(--surface-2); }
.hc-l { max-width: 100%; font-size: 11px; font-weight: 600; color: var(--text-sub); display: inline-flex; align-items: center; justify-content: center; }
.hc-lt { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hc-vis { height: 38px; width: 100%; display: flex; align-items: center; justify-content: center; }
.hc-v { max-width: 100%; font-size: 16px; font-weight: 800; line-height: 1.15; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hc-s { min-height: 14px; max-width: 100%; display: flex; justify-content: center; overflow: hidden; }
.hc-d { display: block; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 11.5px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text-mut); }
.hc-ic { display: inline-grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: color-mix(in srgb, var(--brand-500) 14%, transparent); color: var(--brand-500); }
.hc-ic svg { width: 19px; height: 19px; }
.hc-ring { position: relative; width: 38px; height: 38px; display: inline-block; }
.hc-ring svg { width: 38px; height: 38px; display: block; }
.hc-ring-bg { fill: none; stroke: var(--surface-2); stroke-width: 4.5; }
.hc-ring-fg { fill: none; stroke: var(--brand-500); stroke-width: 4.5; stroke-linecap: round; }
.hc-ring > i { position: absolute; inset: 0; display: grid; place-items: center; font-style: normal; }
.hc-ring .coin-ic { width: 20px; height: 20px; font-size: 10px; }
/* 칩 줄 — 본문 안 칩(`.csecbar` · 켜진 칩 밑줄은 `common/chips.js` 가 그린다). 🟡 2026-10-26 부터 정당 «상세»의 구역 칩만 쓴다(목록 칩은 앱바 공통 칩 바) */
.csecbar { position: sticky; top: 0; z-index: 3; display: flex; gap: 0; overflow-x: auto; scrollbar-width: none; margin: 8px -14px 0; padding: 8px 8px 0; background: var(--bg); border-bottom: 1px solid var(--line); box-shadow: 0 var(--chip-gap) 0 0 var(--bg); position: sticky; }
.csecbar::-webkit-scrollbar { display: none; }
.csecbar .chip { font-size: 16px; }
/* 상세 — 코인 상세의 머리 · 큰 숫자 · 변화 칩 · 칩 막대 · 카드 · 줄 */
.cinfo { padding: 0 2px 24px; }
.chead { display: flex; align-items: center; gap: 12px; margin: 4px 0 4px; }
.chead h2 { font-size: 19px; margin: 0; letter-spacing: -.3px; min-width: 0; overflow-wrap: anywhere; }
.chead h2 .crank { margin-left: 6px; padding: 1px 7px; border-radius: 999px; background: var(--surface-2); color: var(--text-sub); font-size: 11.5px; font-style: normal; font-weight: 800; vertical-align: 2px; }
.chead .cboard, .pmem-top .cboard { margin-left: auto; flex: none; padding: 6px 12px; border: 1px solid var(--line-2); border-radius: 999px; background: none; color: var(--text); font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer; }
.chead .cboard:active, .pmem-top .cboard:active { background: var(--surface-2); }
.cprice { display: flex; align-items: baseline; gap: 10px; margin: 2px 0 6px; }
.cprice b { font-size: 28px; letter-spacing: -.5px; }
.cprice small { color: var(--text-sub); font-size: 14px; }
.cchg { display: flex; gap: 10px; flex-wrap: wrap; margin: 0 0 6px; }
.cchg .chg { font-size: 13px; font-weight: 700; color: var(--text); }
.cchg .chg i { font-style: normal; color: var(--text-mut); font-weight: 600; margin-left: 3px; font-size: 11px; }
.cchg .chg.grp { color: var(--brand-500); }
.cdesc { margin: 8px 4px 4px; font-size: 13px; line-height: 1.55; color: var(--text-sub); }
.cspanel { padding-top: var(--chip-gap); }
.cspanel[hidden] { display: none; }
.cinfo .sect { margin: 18px 2px 8px; font-size: 14px; font-weight: 800; }
.cinfo .sect:first-child { margin-top: 2px; }
.mcards { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 2px 0 10px; }
.mcard { min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; padding: 12px 8px; border: 1px solid var(--line-2); border-radius: 12px; background: none; text-align: center; }
.mcard.wide { grid-column: 1 / -1; }
.mcard-l { font-size: 11.5px; color: var(--text-sub); }
.mcard-v { font-size: 17px; font-weight: 800; letter-spacing: -.3px; font-variant-numeric: tabular-nums; }
.mcard-s { font-size: 11.5px; color: var(--text-mut); }
.mcard-bar { width: 100%; height: 6px; margin-top: 4px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
.mcard-bar i { display: block; height: 100%; border-radius: 3px; background: var(--brand-500); }
.cstat { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 9px 4px; border-bottom: 1px solid var(--line-2); font-size: 14px; }
.cstat span { color: var(--text-sub); flex: 0 0 auto; }
.cstat b { font-weight: 700; text-align: right; overflow-wrap: anywhere; }
.cstat b small { color: var(--text-mut); font-weight: 400; margin-left: 4px; font-size: 11.5px; }
.tagchip { font-size: 12px; padding: 3px 9px; border-radius: 999px; background: var(--surface-2); color: var(--text-sub); border: 1px solid var(--line-2); }
.cstat b.chips-wrap { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
/* 관련 카드 줄 — 코인스쿱 `.bcoin-nrow`·`.bcoin-ncard`·`.bcoin-nthumb`·`.bcoin-noimg`·`.bcoin-news-t`·`.d-rel` 를 복사해 이 저장소 소유로 (2026-10-26 · 사용자 «코인스쿱 관련기사·연관기사 느낌 · 기본 썸네일은 아이콘 흐리게»)
   쓰는 곳: 포인트 상세 «관련 포인트»(duel.js) · 정당 상세 «관련 뉴스»(parties.js) — 카드 조각은 board/list.js `relCardHTML` 하나.
   🟡 코인스쿱과 다른 점: 썸네일 상자를 16:9 로 고정했다 — 거기는 시세 슬라이드가 줄 높이를 정하지만 여기는 그 칸이 없어, 그림 없는 워터마크 칸이 제목 높이로 눌린다. */
.d-rel { margin: 18px 0 0; border-top: 1px solid var(--line); padding-top: 12px; }
.d-rel > .sect, .rel-head { padding: 0 2px 8px; }
.rel-srcline { display: flex; align-items: center; gap: 5px; min-width: 0; }
.rel-src { min-width: 0; font-size: 11px; font-weight: 700; color: var(--brand-600, var(--brand-500)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rel-srcline .lean, .rel-srcline .ptag { flex: none; margin: 0; }
.rel-src.l { color: var(--l); }
.rel-src.r { color: var(--r); }
.bcoin-nrow { display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; padding-bottom: 2px; align-items: stretch; }
.bcoin-nrow::-webkit-scrollbar { display: none; }
.bcoin-ncard {
  flex: 0 0 64%; max-width: 260px; scroll-snap-align: start; cursor: pointer; text-align: left; font: inherit; color: var(--text);
  display: flex; flex-direction: column; gap: 5px; height: auto; min-height: 96px; overflow: hidden;
  padding: 9px 10px; border-radius: 12px; background: var(--surface); border: 1px solid var(--line);
}
.bcoin-ncard:active { background: var(--surface-2); }
.bcoin-nthumb { display: block; width: 100%; flex: none; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 8px; background: var(--surface-2); }
.bcoin-noimg { display: flex; align-items: center; justify-content: center; box-sizing: border-box; background: var(--surface-2); }
.bcoin-noimg img { width: 44%; height: 44%; object-fit: contain; opacity: .32; border-radius: 18%; }
.bcoin-news-t { min-width: 0; font-size: 12.5px; font-weight: 700; color: var(--text); line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: 2.8em; }
/* 정당 상세 — 뉴스 바 · 선거 결과 차트 · 관련 뉴스 · 의원 (2026-10-26 · 코인스쿱 코인 상세의 뉴스 바·차트·관련 뉴스 자리 · 색은 브랜드색 하나) */
.pd-news:not(:empty) { margin: 8px 0 2px; }
.pch-wrap { margin: 10px 0 4px; padding: 12px 12px 8px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); }
.pch-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.pch-head b { font-size: 14px; font-weight: 800; }
.pch-head span { font-size: 12px; color: var(--text-mut); font-weight: 600; }
.pch-seg { margin-top: 8px; }
.pch { display: block; width: 100%; height: auto; margin-top: 6px; overflow: visible; }
.pch rect { fill: var(--brand-500); opacity: .45; }
.pch .last rect { opacity: 1; }
.pch-base { stroke: var(--line); stroke-width: 1; }
.pch-v { fill: var(--text); font-size: 12px; font-weight: 800; text-anchor: middle; font-variant-numeric: tabular-nums; }
.pch-x { fill: var(--text-mut); font-size: 11px; font-weight: 600; text-anchor: middle; }
.pch-note { margin: 2px 2px 0; font-size: 11px; color: var(--text-mut); }
.pbars-wrap { display: grid; gap: 8px; }
.pbars { padding: 10px 12px; border: 1px solid var(--line-2); border-radius: 12px; }
.pbars-h { display: block; margin-bottom: 6px; font-size: 12px; color: var(--text-sub); font-weight: 700; }
.pbar { display: grid; grid-template-columns: 64px 1fr 34px; align-items: center; gap: 8px; padding: 3px 0; font-size: 12.5px; }
.pbar-l { color: var(--text-sub); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pbar-t { height: 8px; border-radius: 4px; background: var(--surface-2); overflow: hidden; }
.pbar-t i { display: block; height: 100%; border-radius: 4px; background: var(--brand-500); min-width: 2px; }
.pbar-v { text-align: right; font-variant-numeric: tabular-nums; }
.pmlist { border-top: 1px solid var(--line-2); }
.pmrow[data-pmem] { cursor: pointer; }
.pmrow[data-pmem]:active { background: var(--surface-2); }
.pmem-top { display: flex; align-items: center; gap: 14px; margin: 6px 0 10px; }
.pm-ph.pm-big { width: 72px; height: 72px; font-size: 26px; }
.pmem-bio { list-style: none; margin: 0 0 6px; padding: 0; font-size: 13.5px; line-height: 1.55; color: var(--text-sub); }
.pmem-bio li { padding: 4px 0; border-bottom: 1px solid var(--line-2); overflow-wrap: anywhere; }
.pmem-id { flex: 1; min-width: 0; }
.pmem-id h2 { margin: 0; font-size: 20px; letter-spacing: -.3px; }
.pmem-id small { color: var(--text-sub); font-size: 13px; }
/* 🟡 정정 2026-10-28: 이 규칙이 `.pmem` 없이 전역 `.linkbtn` 이라 더보기 로그아웃(코인스쿱과 같은 12.5px 밑줄 회색 · 위 `.signout-row`)과 PLUS 약관·개인정보 링크가 굵고 큰 브랜드색으로 덮여 있었다(사용자 «로그아웃 버튼이 코인스쿱과 크기가 다르다»). 의원 상세 안에서만 쓰는 모양이다. */
.pmem .linkbtn { background: none; border: 0; padding: 0; font: inherit; font-weight: 700; color: var(--brand-500); cursor: pointer; text-decoration: none; }
.pmrow { display: flex; align-items: center; gap: 10px; padding: 9px 2px; border-bottom: 1px solid var(--line-2); }
.pm-ph { position: relative; flex: none; width: 40px; height: 40px; border-radius: 50%; overflow: hidden; display: grid; place-items: center; background: var(--surface-2); color: var(--text-sub); font-style: normal; font-weight: 800; font-size: 15px; }
.pm-ph img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top; }
.pm-name { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pm-name b { font-size: 14.5px; }
.pm-name small { font-size: 12px; color: var(--text-mut); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pm-term { flex: none; padding: 2px 8px; border-radius: 999px; background: var(--surface-2); color: var(--text-sub); font-size: 11.5px; font-weight: 700; }
/* 역대 선거 — 코인 상세의 차트 자리: 선거마다 득표율 막대 하나(막대는 브랜드색 하나 · 높낮이로 «잘했다/못했다»를 말하지 않는다 — 숫자와 선거 이름만) */
.ec-wrap { border: 1px solid var(--line); border-radius: 12px; padding: 4px 14px; background: var(--surface); }
.ec-row { padding: 10px 0; }
.ec-row + .ec-row { border-top: 1px solid var(--line-2); }
.ec-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.ec-top b { font-size: 13.5px; }
.ec-top span { font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.ec-bar { height: 8px; margin: 7px 0 5px; border-radius: 4px; background: var(--surface-2); overflow: hidden; }
.ec-bar i { display: block; height: 100%; border-radius: 4px; background: var(--brand-500); min-width: 2px; }
.ec-sub { font-size: 12px; color: var(--text-sub); line-height: 1.5; }
.ec-win { color: var(--brand-500); font-weight: 800; }
/* 공약 접이식 */
.ptyd-pl { border: 1px solid var(--line); border-radius: 12px; margin-top: 8px; overflow: hidden; background: var(--surface); }
.ptyd-pl > summary { display: flex; justify-content: space-between; gap: 8px; padding: 12px 14px; cursor: pointer; list-style: none; font-size: 13.5px; }
.ptyd-pl > summary::-webkit-details-marker { display: none; }
.ptyd-pl > summary span { font-size: 12px; color: var(--text-mut); }
.ptyd-pl ol { margin: 0; padding: 0 14px 6px 32px; }
.ptyd-pl li { padding: 5px 0; font-size: 13px; line-height: 1.55; }
.ptyd-pl li summary { cursor: pointer; font-weight: 700; }
.ptyd-pl li p { margin: 6px 0 8px; padding: 8px 10px; border-radius: 8px; background: var(--surface-2); font-size: 12.5px; font-weight: 400; color: var(--text-sub); white-space: pre-line; }
.ptyd-rl { margin-right: 6px; padding: 0 6px; border-radius: 999px; background: var(--surface-2); color: var(--text-mut); font-size: 11px; font-style: normal; font-weight: 700; }
.ptyd-none { margin: 0; padding: 4px 4px 8px; font-size: 13px; line-height: 1.6; color: var(--text-mut); }
/* 링크 카드 */
.linkcard { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 4px 14px; margin-bottom: 8px; }
.linkrow { display: flex; justify-content: space-between; align-items: center; gap: 10px; width: 100%; padding: 12px 0; border: 0; background: none; color: var(--text); font: inherit; font-size: 14px; cursor: pointer; text-align: left; }
.linkrow + .linkrow { border-top: 1px solid var(--line-2); }
.linkrow small { color: var(--text-mut); font-size: 11.5px; }
.ptyd-board { width: 100%; margin-top: 18px; }

/* ── 로그인 시트(1단계 마크업 `.sheet-b`) — 코인스쿱 `.btn` 위에 얹는다 ── */
.sheet-b { padding: 0 var(--pad) 4px; }
.sheet-b h3 { margin: 4px 0 6px; font-size: 17px; font-weight: 800; }
.sheet-b .sub { font-size: 13.5px; line-height: 1.55; color: var(--text-sub); margin: 0 0 12px; }
.sheet-b .btn.dark { background: var(--text); color: var(--bg); border-color: transparent; }

/* ── 동적 스플래시 (2026-10-23 «수렴» → 🟡 2026-10-28 «파편 → 합체» 로 바뀌었다 — 아래 해당 절 · 이 머리 문단과 규칙 ①②③ 중 «점이 모인다»·«런치스크린이 카드를 굽는다» 는 옛 판 서술이다) · 코인스쿱 app.css «동적 스플래시» 를 이 앱 색으로 옮겼다 · 마크업 index.html `#splash`) ──
   영상 점들이 가장자리에서 로고 하나로 모인다 — 파랑(좌) · 빨강(우) · 회색(그 밖)이 한 사건으로. **이중 스플래시를 막는 규칙 셋**이 있다 — 고치기 전에 읽을 것.
   ① 🔴 **첫 프레임이 네이티브 런치스크린과 같아야 한다.** `#splash` 의 base 는 단색이고(`#0d1117` — 🟡 2026-10-26 전엔 라이트 `#F3ECE2` · 다크 `#1E1B18` 둘이었다: 기본 테마가 코인스쿱 다크가 되며 한 색으로 · iOS `Splash.imageset` 의 any/any-dark ·
      Android `drawable`/`drawable-night` · `capacitor.config.json backgroundColor` 와 같은 값), 오로라·블롭·점은 전부 `opacity: 0` 에서 **뒤늦게 페이드인**한다.
      🟢 **기본 테마에서만 지키는 규칙이 되었다** (2026-10-27 · 사용자 «스플래시도 테마 색으로 — 하루 프로젝트와 똑같이»): 바탕·글자는 테마 변수(`--bg`·`--text`·`--l`…)를 따른다.
         기본 테마의 `--bg` 가 `#0d1117` 이라 기본은 그대로 이어지고, 다른 테마를 고른 사람은 웹뷰가 뜨는 순간 네이티브 단색 → 테마 색으로 한 번 바뀐다 — 하루 `index.html` 머리가 받아들인 것과 같다
         (네이티브 런치스크린은 앱 테마를 모른다). 🟡 옛 줄 「바탕을 테마(`--bg`)로 칠하지 않는다」는 이날 사용자 결정으로 뒤집었다.
         테마 이름은 첫 페인트 전에 `theme-boot.js` 가 심으므로(밝기 `data-tone` 도) 하루처럼 색을 따로 저장할 필요가 없다.
   ② 🔴 **아이콘 카드는 화면 정중앙이다.** (🟡 2026-10-28 정정: 네이티브 런치스크린은 이제 «단색뿐»이다 — 카드는 `#splash` 만 그린다.) 글자 묶음(`.sp-text`)은 `position: absolute` 로 띄워 아이콘 위치에 영향을 안 준다.
   ③ 🔴 **카드 크기는 118px 고정이다**(코인스쿱 `.sp-icon` 과 같은 값 · 2026-10-28 사용자 «스플래시 아이콘이 좀 큰 것 같다 — 코인스쿱과 동일하게»).
      🟡 정정: 옛 값은 `max(100vw, 100vh) × .26`(≈220px)이었다 — 네이티브 런치스크린에 구운 아이콘 카드(aspectFill 이라 긴 변에 비례)와 겹치게 맞춘 값인데, 런치스크린이 단색이 된 뒤(`LAUNCH_CARD = False`)로는 맞출 상대가 없어 코인스쿱의 두 배 가까이 크기만 했다.
      🔴 `filter: blur()` 를 쓰지 않는다(저사양 안드로이드에서 스플래시가 늦게 뜬다) — radial-gradient 로 만든다.
   🔴 **`.sp-bg` 의 지연+페이드(0.15+0.45초)와 조각 합체(1.0초)는 `app.js` 의 `initShell({ splash: { hold } })` 와 세트다** — `hold` 가 더 커야 장식이 다 뜨고 맞춰진 모습이 서 있다 걷힌다. 한쪽만 고치지 않는다.
   🟢 **파편 → 합체** (2026-10-28 · 사용자 「코인스쿱 스플래시 효과를 그대로」 · 코인스쿱 `app.css` 「파편 → 합체」 를 이 앱 값으로 옮겼다): 옛 «수렴»(점 14개)을 걷고, 아이콘 그림을 **불규칙한 조각**으로 오려 «화면 밖»에서 1.0초에 모아 맞춘다.
      🔴 **첫 프레임은 단색이다**(조각은 화면 밖 · `<img>`·카드 바탕·그림자는 숨음) → **네이티브 런치스크린에도 아이콘 카드가 없어야 이어진다**(iOS `Splash.imageset`·안드로이드 `drawable` 계열의 `splash.png` 단색 — `scripts/make-icons.py LAUNCH_CARD = False`).
      🔴 조각은 «실행할 때마다» `js/splash-boot.js` 가 새로 만든다(모양·시작 자리·돌림·시간·곡선·지연 랜덤 · `--d`+`--t` ≤ .99s). 이 저장소는 index.html 에 인라인 스크립트를 두지 않는다. */
#splash {
  --sp-card: 118px;
  --sp-bg: #0d1117; --sp-text: #f0ece6; --sp-sub: #a09a92;
  --sp-l: #5b8ff0; --sp-r: #ef6a5f;   /* 이름의 «좌»·«우» 글자 — 성향 색(`--l`·`--r`)의 다크 값 · 기본 테마의 스플래시는 이 값 그대로다 */
  position: fixed; inset: 0; z-index: 99999;
  display: grid; place-items: center; overflow: hidden;
  background: var(--sp-bg); color: var(--sp-text);
  transition: opacity .5s ease, transform .5s ease;
}
/* 🔴 `.hide` 가 걷히는 시간(.5s)은 `common/shell.js fadeSplash()` 의 500ms 와 **같아야 한다** — 더 길면 DOM 이 먼저 지워져 뚝 끊긴다. */
#splash.hide { opacity: 0; transform: scale(1.03); pointer-events: none; }
.sp-bg { position: absolute; inset: 0; pointer-events: none; overflow: hidden; opacity: 0; animation: spVeil .45s ease .15s forwards; z-index: 0; }
@keyframes spVeil { to { opacity: 1; } }
/* 🟢 스플래시 뒤 배경에도 테마 장식 (2026-10-28 · 사용자 «테마 적용시 스플래시의 뒤 배경에도 테마에 넣은 효과들 넣어서 꾸며줘»):
   ① `.sp-deco` = 앱 바탕의 머리 그림(`--deco` · 아래 「테마 장식」 절 — `#app` 바탕과 같은 선언이라 같은 자리·크기) ② `.sp-fx` = 흩날리는 입자(`js/themefx.js` 가 `#theme-fx` 와 같이 심는다 · 규칙은 `.tfx`).
   🔴 첫 프레임은 단색 그대로다 — 둘 다 `.sp-bg` 와 같은 지연+페이드(0.15초 뒤)로 켠다(머리 ①). 기본 테마는 `--deco: none` 에 입자도 없어 아무것도 안 그린다. */
#splash .sp-deco { position: absolute; inset: 0; z-index: 0; pointer-events: none; background: var(--deco); opacity: 0; animation: spVeil .45s ease .15s forwards; }
#splash .sp-fx { position: absolute; z-index: 1; }
/* 기본 말고 다른 테마 — 오로라·블롭은 기본(다크) 바탕에 맞춘 색이라 반쯤만 띄운다(하루 `index.html` 머리 `.sp-bg{opacity:.45}` 와 같은 값 · 페이드인은 그대로)
   🔴 "dark" 는 기본 테마 이름이다 — `services/config.js THEME_DEFAULT`·`theme-boot.js` 와 세트 */
:root[data-theme]:not([data-theme="dark"]) .sp-bg { animation-name: spVeilSoft; }
@keyframes spVeilSoft { to { opacity: .45; } }
/* 기본 말고 다른 테마는 바탕·글자가 테마 변수를 따른다(머리 ① · 2026-10-27) — 기본은 위 고정값(네이티브 런치스크린과 같은 색)으로 남긴다 */
:root[data-theme]:not([data-theme="dark"]) #splash { --sp-bg: var(--bg); --sp-text: var(--text); --sp-sub: var(--text-sub); --sp-l: var(--l); --sp-r: var(--r); }
.sp-aurora {
  position: absolute; left: 50%; top: 46%; width: 150vmax; height: 150vmax;
  transform: translate(-50%, -50%); border-radius: 46% 54% 52% 48%; opacity: .14; will-change: transform;
  background: conic-gradient(from 0deg, #5b8ff0, #16978A, #ef6a5f, #16978A, #5b8ff0);
  animation: spSpin 30s linear infinite;
}
@keyframes spSpin { to { transform: translate(-50%, -50%) rotate(360deg); } }
.sp-blob { position: absolute; border-radius: 50%; opacity: .55; will-change: transform; }
.sp-blob1 { width: 340px; height: 340px; left: -90px; top: -70px; background: radial-gradient(circle, rgba(91,143,240,.30), transparent 68%); animation: spDrift1 10s ease-in-out infinite alternate; }
.sp-blob2 { width: 380px; height: 380px; right: -110px; bottom: -90px; background: radial-gradient(circle, rgba(239,106,95,.26), transparent 68%); animation: spDrift2 13s ease-in-out infinite alternate; }
.sp-blob3 { width: 280px; height: 280px; left: 58%; top: -80px; background: radial-gradient(circle, rgba(22,151,138,.22), transparent 68%); animation: spDrift1 15s ease-in-out infinite alternate-reverse; }
@keyframes spDrift1 { from { transform: translate(0, 0); } to { transform: translate(34px, 40px); } }
@keyframes spDrift2 { from { transform: translate(0, 0); } to { transform: translate(-40px, -30px); } }
/* 파편 → 합체 — 화면 밖에 흩어진 아이콘 조각이 모여 하나로 맞춰진다 (위 머리 문단 · 코인스쿱 `app.css` 같은 구획).
   🔴 조각은 같은 PNG 에 `clip-path` 를 씌운 «잘라 보기»라 다 맞춰진 순간(1.0s) `<img>` 로 맞바뀌어도 이음새가 없다(이웃과 0.7px 겹쳐 안티앨리어싱 실선도 없다). `<img>` 를 지우고 조각만 두지 않는다(움직임 줄이기가 `<img>` 에 기댄다).
   🔴 조각 묶음의 크기는 카드와 같다(`--sp-card`) — 조각 좌표는 전부 카드 대비 %(`clip-path`·`--ox`·`--oy`)라 카드 크기가 바뀌어도 따라간다. 시작 자리 `--sx`·`--sy` 는 vw·vh(화면 반폭·반높이보다 먼 타원 위 — 어떤 화면에서도 «화면 밖»).
   🔴 `blur()`·`box-shadow` 트레일을 안 쓴다(위 ③) · 조각은 `transform` 뿐 · 총 길이(1.0s)는 `splash.hold`(1300ms)와 짝. */
.sp-frag { position: absolute; width: var(--sp-card); height: var(--sp-card); pointer-events: none; animation: spFragOut 1s both; }
.sp-frag i {
  position: absolute; inset: 0;
  background: url(../img/splash-icon.png) no-repeat 0 0 / 100% 100%;
  clip-path: var(--p);
  transform-origin: var(--ox) var(--oy);
  will-change: transform;
  transform: translate(var(--sx), var(--sy)) rotate(var(--sr)) scale(.7);
  animation: spFrag var(--t, .5s) var(--ez, ease-out) both;
  animation-delay: var(--d, 0s);
}
/* 모서리 조각은 처음부터 둥글다 (2026-10-28 · 코인스쿱 같은 구획) — `splash-icon.png` 는 모서리가 안 깎인 정사각형이라 조각만 모으면 «각진 네모»가 됐다가 `.sp-icon` 의 `border-radius`(22%)로 깎인 `<img>` 로 맞바뀌는 순간 모서리가 사라졌다.
   네 모서리의 둥근 자리에 걸치는 조각(splash-boot.js 가 `.rc` 를 붙인다 · 나머지는 마스크를 안 쓴다 — 레이어 비용)만 카드와 같은 둥근 사각형(rx 22/100 = `.sp-icon` 22%)으로 마스크한다. 🔴 `.sp-icon` 모서리를 바꾸면 rx·`RZ` 도 같이. */
.sp-frag i.rc {
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='22'/%3E%3C/svg%3E") 0 0 / 100% 100% no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='22'/%3E%3C/svg%3E") 0 0 / 100% 100% no-repeat;
}
@keyframes spFrag {
  from { transform: translate(var(--sx), var(--sy)) rotate(var(--sr)) scale(.7); }
  to { transform: none; }
}
/* 다 맞춰진 순간(100%)에 조각 묶음이 꺼지고 진짜 그림·카드 바탕이 켜진다 */
@keyframes spFragOut { 0%, 99.9% { opacity: 1; } 100% { opacity: 0; } }
/* 도착 파문 — 카드와 **같은 모서리**여야 «카드가 삼켰다»로 읽힌다(원으로 바꾸면 `.sp-ring2` 와 겹쳐 이중 링이 된다). 조각이 맞춰지는 순간 한 번 */
.sp-catch {
  position: absolute; z-index: 1; width: var(--sp-card); height: var(--sp-card); border-radius: 22%;
  border: 1px solid rgba(22, 151, 138, .5); opacity: 0; pointer-events: none; animation: spCatch .6s ease-out .98s forwards;
}
@keyframes spCatch { 0% { transform: scale(1); opacity: .6; } 100% { transform: scale(1.5); opacity: 0; } }
.sp-inner { position: relative; z-index: 2; display: grid; place-items: center; }
.sp-ring { position: absolute; border-radius: 50%; pointer-events: none; visibility: hidden; animation: spShow 0s linear .65s forwards; }   /* 🔴 조각이 모이기 시작한 뒤에 켠다 — 빈 가운데에 후광만 서 있지 않게 */
@keyframes spShow { to { visibility: visible; } }
.sp-ring1 { width: calc(var(--sp-card) * 1.58); height: calc(var(--sp-card) * 1.58); background: radial-gradient(circle, rgba(22, 151, 138, .26), transparent 66%); animation: spPulse 2.8s ease-in-out infinite, spShow 0s linear .65s forwards; }
.sp-ring2 { width: calc(var(--sp-card) * 1.71); height: calc(var(--sp-card) * 1.71); border: 1px solid rgba(22, 151, 138, .2); }
@keyframes spPulse { 0%, 100% { transform: scale(.88); opacity: .5; } 50% { transform: scale(1.14); opacity: .9; } }
.sp-icon {
  position: relative; width: var(--sp-card); height: var(--sp-card); border-radius: 22%; overflow: hidden; background: #0C6A60;
  box-shadow: 0 14px 40px rgba(22, 151, 138, .28), 0 3px 10px rgba(0, 0, 0, .25);
  animation: spIconBox 1s both;   /* 카드 바탕·그림자는 조각이 모이는 동안 없다가 맞춰질 때 켜진다(조각이 바탕까지 들고 온다) */
}
.sp-icon img { display: block; width: 100%; height: 100%; animation: spIconImg 1s both; }
@keyframes spIconImg { 0%, 99.9% { opacity: 0; } 100% { opacity: 1; } }
@keyframes spIconBox {
  0%, 99.9% { background-color: transparent; box-shadow: 0 0 0 rgba(22, 151, 138, 0), 0 0 0 rgba(0, 0, 0, 0); }
  100% { background-color: #0C6A60; box-shadow: 0 14px 40px rgba(22, 151, 138, .28), 0 3px 10px rgba(0, 0, 0, .25); }
}
.sp-shine {
  position: absolute; top: 0; left: -70%; width: 55%; height: 100%; transform: skewX(-18deg); pointer-events: none;
  background: linear-gradient(110deg, transparent, rgba(255, 255, 255, .30), transparent); animation: spShine .6s ease-in-out 1.02s 1 both;   /* 조각이 맞춰진 직후 한 번 */
}
@keyframes spShine { from { left: -70%; } to { left: 150%; } }
/* 🔴 글자 묶음은 떠 있다(위 ②) — `top` 은 카드 아래 가장자리 + 여백 */
.sp-text { position: absolute; z-index: 2; left: 0; right: 0; top: calc(50% + var(--sp-card) / 2 + 25px); text-align: center; animation: spUp .6s cubic-bezier(.22, 1, .36, 1) .15s both; }
@keyframes spUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
/* 이름은 «좌»·«우» 글자만 성향 색이다(같은 굵기·같은 크기 — 어느 쪽도 앞서 보이지 않게) · 부제는 코인스쿱 «… 종합 뉴스채널» 짜임(index.html) */
.sp-name { font-size: 28px; font-weight: 900; letter-spacing: 1px; line-height: 1.2; color: var(--sp-text); }
#splash .sp-l { color: var(--sp-l); }
#splash .sp-r { color: var(--sp-r); }
/* 부제 한 줄(index.html `#splash` 머리 · 🗄 2026-10-26 오후 큰 부제 `.sp-hook` «좌 vs 우 맞대결» 걷음) */
.sp-tag { margin-top: 6px; font-size: 12.5px; font-weight: 500; letter-spacing: -.1px; color: var(--sp-sub); }
.sp-dots { display: flex; justify-content: center; gap: 7px; margin-top: 16px; }
.sp-dots span { width: 7px; height: 7px; border-radius: 50%; background: #5b8ff0; opacity: .85; animation: spBounce 1.1s ease-in-out infinite; }
.sp-dots span:nth-child(2) { animation-delay: .16s; background: #16978A; }
.sp-dots span:nth-child(3) { animation-delay: .32s; background: #ef6a5f; }
@keyframes spBounce { 0%, 80%, 100% { transform: translateY(0); opacity: .45; } 40% { transform: translateY(-7px); opacity: 1; } }
/* 🔴 움직임을 줄이는 설정에서는 **장식을 끄되 보이게는 둔다** — 무한 반복만 멈춘다. */
@media (prefers-reduced-motion: reduce) {
  .sp-aurora, .sp-blob, .sp-ring1, .sp-shine, .sp-dots span, .sp-catch, .sp-icon, .sp-icon img { animation: none; }
  .sp-frag { display: none; }   /* 🔴 움직임을 줄이면 조각은 안 쓴다 — 진짜 그림이 그대로 서 있다 */
  .sp-ring { visibility: visible; }
  .sp-catch { opacity: 0; }
}

/* ── 온보딩 오버레이 (튜토리얼 · 알림 권한 안내 — js/onboard.js) · 2026-10-23 코인스쿱 app.css 의 같은 구획 ──
   🔴 `#overlay` 는 셸 바깥이라 광고 배너 오프셋(--ad-h)을 안 받는다 — safe-area 를 직접 먹는다.
   🔴 z-index 는 스플래시(99999)보다 **아래**다 — 스플래시가 걷히는 동안 오버레이가 먼저 깔려 있어야 페이드가 끝나는 순간 튜토리얼이 이미 자리에 있다. */
.ov {
  position: fixed; inset: 0; z-index: 970; display: flex; flex-direction: column;
  background: var(--bg); color: var(--text);
  padding: calc(var(--safe-top) + 8px) 0 calc(var(--safe-bot) + 16px);
  animation: ovIn .28s ease;
}
.ov.out { animation: ovOut .3s ease forwards; }
@keyframes ovIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes ovOut { to { opacity: 0; } }
.ov-cta { width: 100%; padding: 16px; border: 0; border-radius: var(--r-sm); background: var(--brand-500); color: var(--on-brand); font-size: 16px; font-weight: 700; cursor: pointer; }
.ov-cta:active { background: var(--brand-600, var(--brand-500)); filter: brightness(.92); }
.ov-cta:disabled { opacity: .6; }
.ov-ghost { width: 100%; padding: 14px; margin-top: 8px; border: 0; background: none; color: var(--text-sub); font-size: 14px; cursor: pointer; }
.tut-skip { align-self: flex-end; background: none; border: 0; cursor: pointer; padding: 10px 18px; font-size: 14px; color: var(--text-mut); }
/* 🔴 `overflow: hidden` 이 슬라이드를 가둔다 — 빼면 장이 가로로 늘어서서 화면이 옆으로 스크롤된다 */
.tut-view { flex: 1; overflow: hidden; }
.tut-track { display: flex; height: 100%; transition: transform .32s cubic-bezier(.22, .8, .3, 1); }
.tut-slide { flex: 0 0 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 0 32px; text-align: center; }
.tut-art { width: min(60vw, 200px); height: min(60vw, 200px); margin-bottom: 12px; }
.tut-t { font-size: 21px; font-weight: 700; margin: 0; letter-spacing: -.4px; line-height: 1.35; }
.tut-b { font-size: 14.5px; line-height: 1.65; color: var(--text-sub); margin: 0; }
.tut-foot { padding: 0 24px; }
.tut-dots { display: flex; justify-content: center; gap: 6px; padding: 18px 0; }
.tut-dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--line-2); transition: background .2s, width .2s; }
.tut-dots i.on { width: 18px; border-radius: 3px; background: var(--brand-500); }
.perm-body { flex: 1; display: flex; flex-direction: column; justify-content: center; padding: 0 32px; }
.perm-ic { width: 64px; height: 64px; border-radius: var(--r-md); margin-bottom: 20px; display: grid; place-items: center; background: var(--brand-weak); color: var(--brand-500); }
.perm-ic svg { width: 32px; height: 32px; }
.perm-t { font-size: 22px; font-weight: 700; margin: 0 0 10px; letter-spacing: -.4px; }
.perm-b { font-size: 14.5px; line-height: 1.65; color: var(--text-sub); margin: 0 0 22px; }
.perm-li { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 12px; font-size: 14px; line-height: 1.55; }
.perm-li svg { flex: 0 0 auto; width: 17px; height: 17px; margin-top: 2px; color: var(--brand-500); }
.perm-foot { padding: 0 24px; }

/* ══════════════════════════════════════════════════════════════════════════
   🟢 좌지우지 PLUS · PC 웹 잠금 (2026-10-22 · 코인스쿱 app.css 의 «웹 입장 게이트»·«잠긴 탭»·«구독 팝업» 블록을 옮김 · 색은 테마 변수만)
   ══════════════════════════════════════════════════════════════════════════ */
/* ── 웹 잠금 화면 (app.js `syncWebGate` · index.html `#webgate`) ──
 * 🔴 z-index 는 팝업(.pop)보다 위, 스플래시(99999)보다 아래 — 게이트가 떠 있는 동안 다른 것이 위에 서면 막힌 화면에서 나갈 길이 없다.
 * 🔴 셸 바깥이라 세이프에어리어를 직접 먹는다 · 폭 상한을 준다(넓은 화면에서 글이 한 줄로 늘어지지 않게). */
#webgate .wg {
  position: fixed; inset: 0; z-index: 990;
  display: grid; place-items: center;
  padding: calc(var(--safe-top) + 24px) 20px calc(var(--safe-bot) + 24px);
  background: var(--bg); color: var(--text);
}
#webgate .wg-box { width: 100%; max-width: 340px; text-align: center; }
#webgate .wg-ic {
  display: inline-flex; align-items: center; justify-content: center; width: 64px; height: 64px; margin: 0 auto 18px;
  border-radius: 20px; background: var(--brand-weak); color: var(--brand-500);
}
#webgate .wg-ic svg { width: 30px; height: 30px; }
#webgate .wg-box h1 { margin: 0 0 10px; font-size: 20px; font-weight: 800; letter-spacing: -.3px; }
#webgate .wg-box p { margin: 0 0 22px; font-size: 14px; line-height: 1.7; color: var(--text-sub); }
#webgate .wg-box .btn { width: 100%; margin-top: 8px; }
/* 게이트가 떠 있는 동안 시트·팝업은 게이트 위다(코인스쿱 2026-09-14 — 게이트 밑에 깔려 안 보이던 사고) */
html.webgate-on #sheet-scrim { z-index: 992; }
html.webgate-on .pop { z-index: 994; }
/* 웹 무료 구간의 잠긴 탭 (app.js `renderTabbar` 의 `tablock`) — 아이콘을 흐리게, 오른쪽 위에 작은 자물쇠 */
.tabbtn.tablock > svg { opacity: .42; }
.tabbtn.tablock { position: relative; }
.tabbtn .tablock-ic {
  position: absolute; top: 2px; right: 1px; width: 16px; height: 16px; border-radius: 50%;
  display: grid; place-items: center; background: var(--surface); color: var(--text-sub);
  box-shadow: 0 0 0 1px var(--line); pointer-events: none;
}
.tabbtn .tablock-ic svg { width: 10px; height: 10px; opacity: 1; }
/* «PC 웹버전 사용하기» 시트의 주소 줄 (app.js `webUseSheet`) — 무엇을 복사하는지 보이게 */
.hint.web-url { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; word-break: break-all; color: var(--text); }
/* 글자 수 옆의 «PLUS 는 N자» (board/compose.js `paintCount` · 누르면 PLUS 팝업) — 숫자 칸은 누르기를 안 받으니(pointer-events: none) 이 버튼만 받는다 */
.b-count-plus { background: none; border: 0; padding: 0; color: var(--brand-500); font: inherit; font-weight: 700; cursor: pointer; }
.b-inwrap > .b-count .b-count-plus, .dm-inwrap > .b-count .b-count-plus { pointer-events: auto; }

/* ── PLUS 구독 팝업 (plus.js) — 🔴 스크림·z-index 는 `.pop` 것을 그대로 쓴다(새 층을 만들지 않는다) · 카드는 `.pop-card` 보다 넓다(360 — 요금제 두 장이 접히지 않게) ── */
.plus-pop { padding: calc(var(--safe-top, 0px) + var(--ad-room, 0px)) 18px calc(18px + var(--kb, 0px)); }   /* 🟢 2026-10-28 — 위를 광고 배너 자리만큼 비운다(팝업이 떠 있는 동안 상단 배너가 뜬다 · `common.css --ad-room` · 코인스쿱 30f6d64) */
.plus-card { max-width: 360px; padding: 20px 18px 16px; position: relative; max-height: calc(100dvh - var(--safe-top, 0px) - var(--ad-room, 0px) - 36px); overflow-y: auto; }   /* 🟢 배너 자리 + 아래 여백을 뺀 높이 — 넘치면 카드 안에서 스크롤 */
/* 결제 시트를 여는 동안 — 🔴 팝업을 닫지 않고 카드 위에 딤 + 고리(반투명하게 만들면 결제 화면이 깨진 것처럼 읽힌다) · ✕ 는 그 위라 늘 닫을 수 있다 */
.plus-card.busy { pointer-events: none; }
.plus-card.busy::before { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: inherit; background: var(--scrim, rgba(0, 0, 0, .45)); }
.plus-card.busy .plus-x { pointer-events: auto; opacity: 1; z-index: 3; }
.plus-card.busy::after {
  content: ""; position: absolute; left: 50%; top: 50%; margin: -13px 0 0 -13px; z-index: 2;
  width: 26px; height: 26px; border-radius: 50%;
  border: 2.5px solid var(--line-2); border-top-color: var(--brand-500);
  animation: plus-spin .8s linear infinite;
}
@keyframes plus-spin { to { transform: rotate(360deg); } }
.plus-x { position: absolute; top: 10px; right: 10px; width: 32px; height: 32px; padding: 6px; border: 0; border-radius: 10px; cursor: pointer; background: transparent; color: var(--text-sub); }
.plus-x svg { width: 100%; height: 100%; display: block; }
.plus-x:active { background: var(--surface-2); }
.plus-hero { text-align: center; margin: 2px 0 12px; }
.plus-crest { display: inline-flex; align-items: center; justify-content: center; width: 46px; height: 46px; margin-bottom: 8px; border-radius: 14px; background: var(--brand-weak); color: var(--brand-500); }
.plus-crest svg { width: 24px; height: 24px; }
.plus-card h3 { margin: 0 0 4px; font-size: 19px; font-weight: 800; letter-spacing: -.4px; }
.plus-lede { margin: 0; font-size: 13px; line-height: 1.55; color: var(--text-sub); }
/* 혜택 격자 — 🔴 아이콘 칩 + 제목 + **무료와의 차이**. 부제를 빼지 말 것: «경험치 2배»만으로는 지금이 얼마인지 모르는 사람에게 아무 정보가 아니다.
   🟢 2026-10-28 · 한 줄에 하나씩 쌓던 것을 2열 격자로(코인스쿱 30f6d64 사본 — 「팝업이 커서 상단이 배너에 가려진다」) — 줄이 늘어도 높이는 절반 속도로 자라고, 홀수면 마지막 칸이 한 줄 전체를 쓴다. 칸 안은 아이콘 옆에 제목, 그 아래 한 줄 전체에 부제. */
.plus-bens { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 0 0 14px; }
.plus-ben { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "ic title" "sub sub"; align-items: center; gap: 6px 9px; padding: 10px 11px; background: var(--surface-2); border-radius: var(--r-sm); min-width: 0; }
.plus-ben:last-child:nth-child(odd) { grid-column: 1 / -1; }   /* 항목이 홀수면 마지막 칸이 한 줄 전체 */
.plus-ben > div { display: contents; }   /* 제목·부제가 격자 칸(title·sub)에 바로 앉는다 */
.plus-ben-ic { grid-area: ic; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 9px; background: var(--brand-weak); color: var(--brand-500); }
.plus-ben b { grid-area: title; }
.plus-ben small { grid-area: sub; }
.plus-ben-ic svg { width: 16px; height: 16px; }
.plus-ben-ic.badge svg { width: 19px; height: 19px; }
.plus-ben-ic.badge .pb-bg { fill: var(--brand-500); }
.plus-ben-ic.badge .pb-ck { fill: none; stroke: #111; }
.plus-ben b { display: block; font-size: 13px; font-weight: 700; line-height: 1.3; }
.plus-ben small { display: block; font-size: 11px; line-height: 1.4; color: var(--text-sub); }
/* 요금제 — 🔴 연 결제가 먼저이고 강조다 · 버튼 면은 `--surface-2`(팝업 바탕과 같은 색이면 살 버튼이 묻힌다) */
.plus-plans { display: flex; flex-direction: column; gap: 8px; }
.plus-plan {
  position: relative; display: flex; align-items: center; gap: 10px; width: 100%; padding: 14px 15px; cursor: pointer;
  border: 1.5px solid var(--line-2); border-radius: 13px; background: var(--surface-2); color: var(--text); font-family: inherit; text-align: left;
}
.plus-plan.best { border-color: var(--brand-500); border-width: 2px; background: linear-gradient(0deg, var(--brand-weak), var(--brand-weak)), var(--surface-2); }
.plus-plan .plus-plan-name { color: var(--text); }
.plus-plan:active { transform: scale(.985); }
.plus-plan:disabled { opacity: .45; cursor: default; }
.plus-plan-name { flex: 1; min-width: 0; font-size: 14.5px; font-weight: 700; }
.plus-plan-price { font-size: 15.5px; font-weight: 800; letter-spacing: -.3px; }
.plus-plan.best .plus-plan-price { color: var(--brand-500); }
/* 🔴 배지는 비율이 아니라 문장이다(「N개월분 저렴」 · config `PLUS_SAVE_MONTHS`) · 체험은 스토어가 제안을 줬을 때만 그려진다(`iap.js trialOf`) */
.plus-tag { position: absolute; top: -8px; left: 13px; padding: 2px 8px; border-radius: var(--r-pill); background: var(--brand-500); color: var(--on-brand, #fff); font-size: 10.5px; font-weight: 800; letter-spacing: -.2px; }
.plus-plan-pricebox { display: flex; flex-direction: column; align-items: flex-end; flex: none; text-align: right; }
.plus-plan-line { display: flex; align-items: baseline; gap: 4px; white-space: nowrap; }
.plus-plan-per, .plus-plan-after { font-style: normal; font-size: 12px; font-weight: 600; color: var(--text-sub); }
.plus-plan-was { font-size: 11.5px; font-weight: 600; color: var(--text-sub); text-decoration-thickness: 1.5px; }
.plus-trial-note { font-weight: 600; }
/* 🔴 「구매 복원」은 늘 보인다(기기를 바꾼 사람이 찾을 곳 · 스토어 심사가 요구) — 링크 모양인 것은 의도다(돈을 쓰는 쪽과 흐려지지 않게) */
.plus-restore { display: block; width: 100%; margin: 14px 0 0; padding: 6px; cursor: pointer; border: 0; background: transparent; color: var(--text-sub); font-family: inherit; font-size: 13px; text-decoration: underline; text-underline-offset: 3px; }
.plus-note { margin: 10px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--text-sub); text-align: center; }
.plus-links { display: flex; align-items: center; justify-content: center; gap: 7px; margin-top: 8px; font-size: 11.5px; color: var(--text-sub); }
.plus-links .linkbtn { font-size: 11.5px; }

/* ══════════════════════════════════════════════════════════════════════════
   🟢 **맞대결 피드 머리와 포인트 카드** (2026-10-23 · 사용자 요청 «상단 칩 · 포인트 발언을 뉴스처럼 · 주요바/뉴스바 · 매체명에 채널 아이콘») — 마크업은 `duel.js`
   코인스쿱의 피드 머리(토픽 바 `.ftopic` · HOT `.hot-row`)와 뉴스 카드를 이 앱의 «포인트»로 옮겼다.
   🔴 종류 태그(`.ptag`)는 성향 색(`--l`·`--r`)을 쓰지 않는다 — 브랜드색/중립만(맨 위 `:root` 의 성향 색 규칙). 성향은 카드 왼쪽 줄과 «좌파 성향»·«우파 성향» 글자로만 말한다.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 토픽 바 — 한 줄 알약 · 넘칠 때만 흐른다(골격 `common/autoflow.js`) ──
   🔴 두 벌의 간격이 같아야 되감기가 안 보인다 — 여백은 줄(`.ftopic-seg`)의 오른쪽에만 두고 왼쪽 여백은 창(`.ftopic-view`)이 갖는다. */
#feed-topic:empty, #feed-hot:empty { display: none; }
.ftopic { margin: 10px 0 2px; }
.ftopic-view {
  overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
  border-radius: 999px; background: var(--surface-2); border: 1px solid var(--line); padding-left: 14px;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
}
.ftopic-view::-webkit-scrollbar { display: none; }
.ftopic-track { display: flex; width: max-content; }
.ftopic-seg { display: inline-flex; align-items: center; gap: 22px; padding-right: 22px; height: 36px; flex: none; }
.ftopic-item {
  display: inline-flex; align-items: center; gap: 6px; flex: none; background: none; border: 0; padding: 0;
  color: var(--text); font: inherit; font-size: 13px; font-weight: 600; white-space: nowrap; cursor: pointer;
}
.ftopic-ic { display: inline-flex; width: 16px; height: 16px; flex: none; color: var(--text-sub); }
.ftopic-ic svg { width: 16px; height: 16px; }
.ftopic-ic.cmt, .ftopic-ic.voice { color: var(--brand-500); }
.ftopic-ic.hot { color: var(--hot); }
.ftopic-ic.news { color: var(--brand-500); }   /* 정당 탭 뉴스 바(parties.js `paintNews`) */
.ftopic-k { font-size: 11.5px; font-weight: 800; color: var(--text-sub); }

/* 🟡 피드 칩 줄의 좌우 여백 줄이기(`.chips.fchips .chip { padding: 0 6px }`)는 2026-10-23 사용자 요청(「상단칩 간격이 좁아 · 코인스쿱과 동일한 구조로」)에 걷었다 —
   칩 사이 간격은 코인스쿱과 같은 `.chip` 의 좌우 padding 하나다(위 `.chips` 문단). 칩이 폭보다 많으면 줄이 가로로 스크롤되고 켜진 칩이 보이게 따라간다(`common/chips.js`). */

/* ── 채널 아이콘 — 매체명 자리 · 못 받으면 글자 한 자(`<i>`)가 남는다 ── */
.chic {
  position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 20px; height: 20px; border-radius: 50%; overflow: hidden;
  background: var(--surface-2); color: var(--text-sub); font-size: 11px; font-weight: 800;
}
.chic.l { background: var(--l-soft); color: var(--l); }
.chic.r { background: var(--r-soft); color: var(--r); }
.chic i { font-style: normal; line-height: 1; }
.chic img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; background: var(--surface); }
.duel .side .meta .chic { width: 16px; height: 16px; font-size: 9px; }
.chlist li .chic { width: 22px; height: 22px; margin-right: 2px; vertical-align: middle; }

/* ── 종류 태그 ── */
.ptag {
  /* 🔴 인라인 그대로다 — `inline-block` 이면 iOS WebKit 의 두 줄 말줄임(`.phot h3`·`.pc-h`)이 제목이 다 들어가는데도 끝에 «…» 를 붙인다(2026-10-28 · 사용자 실기기 «말줄임이 아직도 중간에 끝나»).
     코인스쿱 `.tag` 는 평범한 인라인이다. 인라인으로 바꾸자 iOS 시뮬레이터에서 «…» 가 사라졌다. */
  flex: none; margin-right: 6px; white-space: nowrap; padding: 1px 6px; border-radius: var(--r-xs);
  background: var(--brand-weak); color: var(--brand-500); font-size: 10.5px; font-weight: 800; letter-spacing: .2px; line-height: 1.5; vertical-align: 1px;
}
/* 🟢 종류마다 색 (2026-10-26 · 사용자 «카테고리 태그칩 코인스쿱처럼 컬러별로») — 코인스쿱 `.tag-*` 의 «옅은 바탕 + 제 색 글자» 짜임과 색 토큰(`--cau`·`--del`)을 옮겼다.
   발언 = 브랜드색(그대로) · 이슈 = 주황 · 법안 = 보라. 🔴 파랑·빨강은 쓰지 않는다 — 좌·우 성향 색과 겹치면 태그가 성향으로 읽힌다. */
:root { --cau: #ff8a3d; --cau-weak: rgba(255, 138, 61, .17); --del: #a78bfa; --del-weak: rgba(167, 139, 250, .17); }
:root[data-tone="light"] { --cau: #b45309; --cau-weak: rgba(180, 83, 9, .13); --del: #6d4fd6; --del-weak: rgba(109, 79, 214, .12); }
.ptag.k-issue { background: var(--cau-weak); color: var(--cau); }
.ptag.k-bill { background: var(--del-weak); color: var(--del); }

/* ── HOT 카드 — 가로로 흐르는 3~5장 (코인스쿱 `.hotcard`) ── */
.phot {
  flex: 0 0 65%; max-width: 300px; scroll-snap-align: start; cursor: pointer; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px; overflow: hidden;
}
.phot:active { background: var(--surface-2); }
/* 🗄 카드 왼쪽의 성향 줄(파랑·빨강 `inset 3px`)은 걷었다 (2026-10-25 · 사용자 «코인스쿱처럼») — 성향은 카드 안 알약(`.lean`)이 말한다. 클래스 `l`·`r` 은 그대로 붙어 있다(그릴 것만 없다). */
.ph-th { position: relative; flex: none; aspect-ratio: 16 / 9; background: var(--surface-2); }
/* 🔴 그림은 상자에 «절대 배치»다 — 자연 비율(4:3 · 위아래 검은 띠 포함)이 상자 높이를 밀어내면 16:9 가 안 지켜지고 카드가 커진다 */
.ph-th img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.ph-body { display: flex; flex-direction: column; flex: 1; padding: 9px 10px 10px; min-width: 0; }
.phot h3 {
  margin: 0; font-size: 13.5px; font-weight: 700; line-height: 1.45; letter-spacing: -.2px; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ph-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: auto; padding-top: 8px; font-size: 11.5px; color: var(--text-mut); }
.ph-lft { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.ph-foot .lean { flex: none; margin: 0; }
/* 🟢 HOT 카드의 공감·비공감 — 눌리지 않는 표시만(코인스쿱 `.hotcard .reactbtn` · 2026-10-26). 🔴 선택자를 `.phot` 로 좁혀 `.reactbtn.bull.on` 보다 가중치를 올린다(`.reactbtn.ro` 만으로는 진다 — 위 `.bhotcard` 와 같은 함정) */
.phot .reactbar { margin: 0; gap: 5px; }
.phot .reactbtn { height: 25px; padding: 0 7px; }
.phot .reactbtn b svg { width: 13px; height: 13px; }
.phot .reactbtn.ro, .phot .reactbtn.ro.on { cursor: default; background: none; border-color: transparent; color: var(--text-mut); padding: 0 2px; }
/* 대결 칩의 HOT 카드 — 썸네일 둘을 반반 맞대고 가운데에 VS (duel.js `hotDuelHTML`) */
.ph-vs { display: flex; }
.ph-vs img { position: static; width: 50%; height: 100%; object-fit: cover; display: block; }
.ph-vsb {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); padding: 2px 7px; border-radius: 999px;
  background: rgba(0, 0, 0, .72); color: #fff; font-size: 11px; font-weight: 800; letter-spacing: .4px; line-height: 1.5;
}
/* 좌·우 댓글 수 — 성향색 글자(`--l`·`--r`) 둘이 나란히. 공감 줄(`.reactbar`)과 같은 25px 줄 높이라 카드 높이가 칩마다 달라지지 않는다 */
.ph-cmts { display: inline-flex; align-items: center; gap: 10px; min-height: 25px; font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.ph-cmts .l { color: var(--l); }
.ph-cmts .r { color: var(--r); }
.ph-time { flex: 0 0 auto; margin-left: auto; font-size: 11px; font-variant-numeric: tabular-nums; }   /* 코인스쿱 `.hot-time` — 줄어들지 않는다(제목이 길어도 시각이 찌그러지지 않게) */

/* ── 포인트 카드 — 뉴스 카드 (가운데: 시각 태그·종류 태그·제목·요지 + 썸네일 · 아래: 영상 제목·댓글·성향 칩) — 코인스쿱 `.card` ── */
.pcard {
  display: block; width: 100%; text-align: left; cursor: pointer; color: var(--text);
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 12px 14px 11px;
}
.pcard:active { background: var(--surface-2); }
/* 🗄 성향 줄 걷음 — `.phot` 의 그 주석 */
.pc-t { margin-left: auto; flex: none; font-size: 11.5px; color: var(--text-mut); white-space: nowrap; }
.pc-main { display: flex; align-items: flex-start; gap: 10px; }
.pc-text { flex: 1; min-width: 0; }
.pc-h {
  margin: 0; font-size: 15.5px; font-weight: 800; line-height: 1.4; letter-spacing: -.2px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;   /* 🟢 두 줄이 다 차면 말줄임(코인스쿱 제목과 같다 · 2026-10-28) — 서버가 글자 수로 자르지 않는다(`config.js PT_HEAD_MAX`) */
}
.pc-q {
  margin: 5px 0 0; font-size: 13px; line-height: 1.5; color: var(--text-sub);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
/* 🟢 2026-10-26 사용자 «피드목록 썸네일 크기가 코인스쿱과 다른 것 같다 · 동일하게» — 코인스쿱 `.thumb` 그대로 72px 정사각(옛 92px 16:9). 영상 그림(4:3 hqdefault)은 가운데를 잘라 채운다(`cover`). */
.pc-th { flex: none; width: 72px; height: 72px; border-radius: var(--r-xs); overflow: hidden; background: var(--surface-2); }
.pc-th { position: relative; }
.pc-th img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.pc-foot { display: flex; align-items: center; gap: 8px; margin-top: 9px; font-size: 11.5px; color: var(--text-mut); min-width: 0; }
/* 카드 아래 줄의 공감·비공감(mediareact.js `reactCardHTML`) — 코인스쿱 피드 카드의 `reactbar mini` 그대로(버튼 크기는 공통 `.reactbtn` · 28px). 줄 위 여백은 `.pc-foot` 가 쥔다 */
.card-react { margin: 0; }
.pcard .lean { flex: none; margin: 0 0 0 auto; }   /* 성향 칩은 아래 줄 오른쪽 끝 (2026-10-27 걷었다가 같은 날 사용자 요청으로 복원) */
/* 제목 앞 시각 태그 — 코인스쿱 `.ttag` 그대로(제목과 같은 줄에서 시작해 글이 그 뒤로 흐른다 · `inline-flex` + `vertical-align` 세트) */
.ttag {
  margin-right: 6px; vertical-align: 1px;   /* 🔴 `inline-flex` 도 `.ptag` 와 같은 이유로 걷었다(안의 `.clock` 하나뿐이라 gap 도 필요 없다) */
  font-size: 10.5px; font-weight: 700; letter-spacing: 0; line-height: 1.5;
  padding: 1px 6px; border-radius: var(--r-xs); background: var(--surface-2); color: var(--text-mut); white-space: nowrap;
}
.ttag .clock { color: var(--text-sub); font-variant-numeric: tabular-nums; }

/* ── 포인트 상세 — 코인스쿱 뉴스 상세 그대로 (2026-10-25 · 사용자 «코인스쿱에 없는 건 다 빼고 같은 패턴·UI») ──
   머리줄(출처 아이콘·이름 · 시각 · 상대시각) → 제목(종류 태그 앞) → 요약 → 칩 줄(성향) → 반응 줄 → 영상(큰 16:9 카드 `.b-ytcard` · 누르면 그 자리 재생) → 댓글 → 관련(같은 영상의 다른 포인트).
   🗄 «AI 요약» 안내 박스·요지 인용 상자·원본 영상 줄 카드·같은 사건 맞대결 버튼·하단 고지는 걷었다(코인스쿱 `.detail` 에 없다). 뉴스 기사 상세(`news.js`)는 아래 `.pd-*` 를 그대로 쓴다. */
.pt-rel { padding: 0 2px 28px; }   /* 이슈 상세의 «관련 포인트» — 댓글 아래에 선다(코인스쿱 뉴스 상세의 «관련 보도») · 좌우는 `.pdetail` 과 같다 */
.pnews { padding: 12px 2px 28px; }   /* 좌우는 코인스쿱 `.detail` 과 같다(`#scroll` 의 12px 만 — 2026-10-26 · 사용자 «좌우 여백이 더 많다») */
.pn-meta { display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; min-width: 0; margin-bottom: 8px; font-size: 12px; color: var(--text-sub); }
.pn-meta .chic { width: 16px; height: 16px; font-size: 9px; margin-right: 0; }
.pn-src { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pn-dot { color: var(--text-mut); }
.pn-clock, .pn-ago { flex: 0 0 auto; }
.pn-clock { font-variant-numeric: tabular-nums; }
.pn-title { margin: 8px 0 14px; font-size: 20px; line-height: 1.42; font-weight: 700; letter-spacing: -.4px; }
.pn-title .ptag { margin-right: 7px; padding: 3px 7px; font-size: 12px; vertical-align: 3px; letter-spacing: 0; white-space: nowrap; }
.pn-sum { margin: 0; font-size: 15px; line-height: 1.75; color: var(--text); white-space: pre-line; }
.pn-body .pn-sum + .pn-sum { margin-top: 12px; }
.pn-wait { margin: 12px 0 0; font-size: 13px; color: var(--text-mut); }
.pn-srctag { flex: 0 0 auto; margin-left: auto; padding: 5px 9px; border: 1px solid transparent; border-radius: var(--r-xs); background: var(--brand-weak); color: var(--brand-600, var(--brand-500)); font-size: 11px; font-weight: 700; line-height: 1; }   /* 코인스쿱 `.srctag` — 머리줄 오른쪽 끝 */
.pn-srctag:active { border-color: var(--brand-500); }
.pn-srctag + .pn-srctag { margin-left: 4px; }   /* [원문][신고] — 코인스쿱 `.srctag + .srctag` */

.pn-meta .nsrc { width: 16px; height: 16px; font-size: 9px; }
.pn-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }

/* ── (기사 상세 `news.js` 가 쓰는) 옛 포인트 상세 조각 ── */
.pdetail { padding: 12px 2px 28px; }
.pd-h { margin: 12px 0 0; font-size: 20px; font-weight: 800; line-height: 1.4; letter-spacing: -.3px; }
.pd-h .ptag { margin-right: 6px; vertical-align: 2px; }
.pd-ai { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 11.5px; color: var(--text-mut); }
.pd-ai b { padding: 0 6px; border: 1px solid var(--line-2); border-radius: var(--r-xs); font-size: 10.5px; line-height: 1.7; color: var(--text-sub); }
.pd-gist { margin: 10px 0 0; padding: 10px 12px; border-left: 3px solid var(--brand-500); background: var(--surface-2); border-radius: 0 10px 10px 0; font-size: 15px; font-weight: 700; line-height: 1.55; }
.pd-fine { margin: 20px 0 0; font-size: 11.5px; line-height: 1.6; color: var(--text-mut); }

/* ── 뉴스 칩 (2026-10-23 · js/news.js) — 사안 카드 · 비교 화면 · 뉴스 상세 · 매체별 비중
   🔴 성향 색은 기사 «판정»에만 쓴다(`.lean.l/.r` · 중립 `.lean.n` 은 회색) — 매체 자체에는 색을 안 입힌다. 막대·범례·카드 줄 모두 같은 규칙 ── */
.lean.n { color: var(--text-sub); background: var(--surface-2); }
.ncard { display: block; margin-bottom: 10px; padding: 12px 14px 11px; cursor: pointer; background: var(--surface); border: 1px solid var(--line); border-radius: 14px; }
.ncard:active { background: var(--surface-2); }
.nc-top { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.nc-n { font-weight: 700; color: var(--text-sub); }
.nc-n .nc-ic { width: 14px; height: 14px; vertical-align: -2.5px; margin-right: 4px; }   /* 신문 SVG(news.js `PAPER_IC`) */
.nc-main { display: flex; align-items: flex-start; gap: 10px; }   /* 코인스쿱 `.card-main` — 본문 + 오른쪽 썸네일(`.thumb`) */
.nc-body { flex: 1 1 auto; min-width: 0; }
.nc-main .thumb { margin-top: 8px; }
.nc-h { margin: 6px 0 0; font-size: 16px; font-weight: 800; line-height: 1.4; letter-spacing: -.2px; }
.nc-cs { margin: 5px 0 0; font-size: 13px; line-height: 1.55; color: var(--text-sub); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.nc-outs { margin-top: 8px; font-size: 11.5px; color: var(--text-mut); }
.nbar { display: flex; height: 8px; margin-top: 10px; border-radius: 999px; overflow: hidden; background: var(--surface-2); }
.nbar i { display: block; height: 100%; }
.nbar i.l { background: var(--l); } .nbar i.r { background: var(--r); } .nbar i.n { background: var(--line-2); } .nbar i.z { background: var(--surface-2); }
.nleg { display: flex; gap: 10px; margin-top: 5px; font-size: 11.5px; font-weight: 700; color: var(--text-mut); }
.nleg .l { color: var(--l); } .nleg .r { color: var(--r); }
.ngroup { padding: 12px 2px 28px; }   /* 좌우는 다른 상세(`.pdetail`)·피드와 같다 — `#scroll` 의 12px 만 (2026-10-28 · 사용자 «뉴스비교 화면 좌우 여백이 피드보다 많다» · 옛 판은 안쪽에 16px 을 더 넣었다) */
.ncols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; margin-top: 14px; }
.ncol h4, .nmid h4 { display: flex; align-items: center; gap: 6px; margin: 0 0 6px; font-size: 12px; }
.ncol h4 em, .nmid h4 em { font-style: normal; color: var(--text-mut); font-size: 11.5px; }
.nmid { margin-top: 14px; }
.nwait { font-size: 11px; font-weight: 700; color: var(--text-mut); }
.nrow { display: flex; flex-direction: column; gap: 3px; width: 100%; margin-bottom: 6px; padding: 9px 10px; text-align: left; color: var(--text); background: var(--surface); border: 1px solid var(--line); border-radius: 12px; }
.nrow:active { background: var(--surface-2); }
/* 🗄 성향 줄 걷음 — `.phot` 의 그 주석 (비교 화면은 칸 머리 알약이 좌·우를 말한다) */
.nr-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 11.5px; }
.nr-top b { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nr-t { font-size: 13px; font-weight: 700; line-height: 1.4; }
.nr-f { font-size: 11.5px; line-height: 1.45; color: var(--text-sub); }
.nc-vn { margin-left: 6px; padding: 1px 7px; border-radius: 999px; background: var(--surface-2); color: var(--text-sub); font-size: 11px; font-weight: 700; }
/* 방송 리포트 (news.js `vidsHTML`) — 줄 머리는 기사 줄(`.nr-top`·`.nr-t`)과 같은 글자, 영상 카드는 공용 `.b-ytcard` */
.nvids { margin-top: 18px; padding-top: 12px; border-top: 1px solid var(--line); }
.nvids h4 { display: flex; align-items: center; gap: 6px; margin: 0 0 8px; font-size: 13px; font-weight: 800; }
.nvids h4 em { font-style: normal; color: var(--text-mut); font-size: 11.5px; font-weight: 700; }
.nvid { margin-bottom: 8px; }
.nvid .nr-top b { display: inline-flex; align-items: center; gap: 6px; }
.nvid .nr-t { margin-top: 3px; }
.nv-s { margin: 5px 0 0; font-size: 13px; line-height: 1.55; color: var(--text-sub); }
.nsrc { display: inline-grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: var(--surface-2); color: var(--text-sub); font-size: 12px; font-weight: 800; flex: none; }
.nouts { padding-bottom: 24px; }
.nout { margin-bottom: 14px; padding: 11px 14px; background: var(--surface); border: 1px solid var(--line); border-radius: 14px; }
.nout[data-dact] { cursor: pointer; }
.nout[data-dact]:active { background: var(--surface-2); }
.nout-top b { display: inline-flex; align-items: center; gap: 8px; }
.nout-d { margin-bottom: 10px; }
.nout-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.nout-top b { font-size: 14.5px; }
.nout-v { font-size: 11.5px; font-weight: 700; color: var(--text-mut); }
.nout-v.L { color: var(--l); } .nout-v.R { color: var(--r); }
.nout .nbar { margin-top: 8px; }

/* ── 🟢 순위 왕관 배지 (2026-10-26 · 코인스쿱 이식 — 미니게임 카드의 1~3등 · `services/rankbadge.js crownBadgeHTML`) ──
   1·2·3위 = 금·은·동, 그 밑은 구리. 밝은 바탕 테마(`data-tone="light"`)는 한 단 어둡게 되돌린다(금·은이 밝은 바탕 위에서 안 묻히게). */
:root {
  --rank-gold: #f2c14e;     --rank-gold-bg: rgba(242, 193, 78, .16);
  --rank-silver: #c5ccd6;   --rank-silver-bg: rgba(197, 204, 214, .14);
  --rank-bronze: #e38b45;   --rank-bronze-bg: rgba(227, 139, 69, .16);
  --rank-copper: #b97a5e;   --rank-copper-bg: rgba(185, 122, 94, .16);
}
:root[data-tone="light"] {   /* 🟢 2026-10-27: 테마 이름 목록 → data-tone 축(팔레트 머리 「테마 열두 가지」) */
  --rank-gold: #a97c00;     --rank-gold-bg: rgba(201, 150, 0, .16);
  --rank-silver: #6b7686;   --rank-silver-bg: rgba(107, 118, 134, .14);
  --rank-bronze: #b35f22;   --rank-bronze-bg: rgba(179, 95, 34, .14);
  --rank-copper: #8c5a45;   --rank-copper-bg: rgba(140, 90, 69, .13);
}
.tr-crown { --rk: var(--rank-copper); --rk-bg: var(--rank-copper-bg); }
.tr-crown.rk-gold { --rk: var(--rank-gold); --rk-bg: var(--rank-gold-bg); }
.tr-crown.rk-silver { --rk: var(--rank-silver); --rk-bg: var(--rank-silver-bg); }
.tr-crown.rk-bronze { --rk: var(--rank-bronze); --rk-bg: var(--rank-bronze-bg); }
.tr-no.tr-crown {
  display: inline-flex; align-items: center; justify-content: center; gap: 2px;
  color: var(--text); background: var(--rk-bg); padding-left: 5px; padding-right: 6px;
}
.tr-no.tr-crown svg { width: 12px; height: 9px; flex: none; fill: var(--rk); color: var(--rk); }

/* ── 🟢 **테마 장식** (2026-10-27 · 팔레트 머리 「테마 열두 가지」 문단) ───────────────────────────────────
   🔴 머리 그림은 `#app` 의 **바탕 이미지**다 — 따로 요소를 세우면 쌓임 순서(`#app` 은 쌓임 맥락이 아니다)를 건드려 시트·고정 바가 어긋난다.
      `#app` 은 스크롤하지 않는 판이라 그림은 제자리에 있고, 카드가 그 위로 지나간다.
   🔴 그래서 앱바는 장식 테마에서 **투명**이다 — 앱바 밑으로 지나가는 내용은 없다(`#scroll` 은 앱바 «아래»의 형제다). 기본(dark)은 그대로.
   🟡 넓은 폭(태블릿·웹)에서는 그림이 오른쪽 위에 앉고 띠(물결·언덕·스카이라인)는 가로로 이어진다. */
:root { --deco: none; }
#app { background: var(--deco), var(--bg); }
:root[data-theme]:not([data-theme="dark"]) .appbar { background: transparent; -webkit-backdrop-filter: none; backdrop-filter: none; }
/* 🔴 좁은 폭 뿌리 화면은 앱바가 본문 «위에» 떠 있고 그 밑으로 카드가 지나간다(「숨는 바」 문단 · 바탕이 불투명해야 한다) — 그래서 그림을
   `#app` 바탕이 아니라 **바가 달고 다닌다**: 바의 `::after`(바 + 칩 아래 여백 띠 · z -1 이라 바 글자 밑)와 노치 띠(`#app::before`)가 같은 그림을
   `#app` 맨 위 기준으로 깐다 — 가만히 있을 때는 아래 `#app` 바탕과 이음매 없이 이어지고, 바가 숨으면 그림도 같이 올라간다.
   🔴 칩 줄 밑 구분선은 바의 `box-shadow` 가 아니라 이 판의 맨 위 그림층(`--bar-rule`)이다 — 바의 그림자는 z -1 판 «밑»에 깔려 가린다. */
html[data-theme]:not([data-theme="dark"]):not(.wide):not(.no-tabs) #appbar { background: transparent; }
html[data-theme]:not([data-theme="dark"]):not(.wide):not(.no-tabs) #appbar::after {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 100%; z-index: -1; pointer-events: none;
  background: var(--bar-rule, none), var(--deco), var(--bg);
}
html[data-theme]:not([data-theme="dark"]):not(.wide):not(.no-tabs) #appbar.has-chips::after {
  height: calc(100% + var(--chip-gap));
  --bar-rule: linear-gradient(var(--line), var(--line)) left 0 bottom var(--chip-gap) / 100% 1px no-repeat;
}
html[data-theme="news"]:not(.wide):not(.no-tabs) #appbar.has-chips::after {
  --bar-rule: linear-gradient(var(--text), var(--text)) left 0 bottom var(--chip-gap) / 100% 1px no-repeat,
    linear-gradient(var(--text), var(--text)) left 0 bottom calc(var(--chip-gap) + 4px) / 100% 1px no-repeat;
}
html[data-theme]:not([data-theme="dark"]):not(.wide):not(.no-tabs) #app::before { background: var(--deco), var(--bg); }
/* 🗄 날짜 줄 아이콘(`.date-sect::before` · `--date-ic`)은 2026-10-28 에 걷었다 — 사용자 «'오늘' 앞의 테마 아이콘은 오늘 날씨·현재 날씨로 오해할 수 있다» (우산·눈송이가 날씨 표시로 읽혔다) */
/* 탭바 알약 위의 장식 — 겨울은 쌓인 눈 · 봄은 꽃 한 송이 · 비는 유리에 맺힌 물방울 */
:root[data-theme="winter"] .tabpill, :root[data-theme="spring"] .tabpill, :root[data-theme="rain"] .tabpill { position: relative; }
:root[data-theme="winter"] .tabpill::after, :root[data-theme="spring"] .tabpill::after, :root[data-theme="rain"] .tabpill::after {
  content: ""; position: absolute; pointer-events: none;
}
:root[data-theme="winter"] .tabpill::after { left: 18px; right: 18px; top: -6px; height: 11px; background: var(--ic-snowcap) center / 100% 100% no-repeat; }
:root[data-theme="spring"] .tabpill::after { right: 6px; top: -9px; width: 18px; height: 18px; background: var(--ic-spring) center / contain no-repeat; }
:root[data-theme="rain"] .tabpill::after { inset: 0; border-radius: inherit; background: var(--ic-drops) 0 0 / 120px 100% repeat-x; opacity: .9; }

/* 카드 모양 — 테마마다 모서리·그림자·테두리가 다르다. 대상은 목록 카드 셋(대결 `.feed .card` · 포인트 `.pcard` · 뉴스 `.ncard`)과 HOT 카드(`.phot`)
   — 코인스쿱의 `.card`·`.hotcard` 자리다(2026-10-27 이식 · 이 앱의 기본 모서리가 이미 14px 라 코인스쿱의 봄 줄(14px)은 없다). */
:root[data-theme="pastel"] .feed .card, :root[data-theme="pastel"] .pcard, :root[data-theme="pastel"] .ncard, :root[data-theme="pastel"] .phot { border-color: transparent; border-radius: 18px; box-shadow: 0 4px 16px rgba(140, 110, 200, .13); }
:root[data-theme="summer"] .feed .card, :root[data-theme="summer"] .pcard, :root[data-theme="summer"] .ncard, :root[data-theme="summer"] .phot { box-shadow: 0 3px 12px rgba(20, 120, 160, .08); }
:root[data-theme="autumn"] .feed .card, :root[data-theme="autumn"] .pcard, :root[data-theme="autumn"] .ncard, :root[data-theme="autumn"] .phot { border-radius: 10px; }
:root[data-theme="winter"] .feed .card, :root[data-theme="winter"] .pcard, :root[data-theme="winter"] .ncard, :root[data-theme="winter"] .phot { border-color: rgba(170, 205, 255, .12); }
:root[data-theme="space"] .feed .card, :root[data-theme="space"] .pcard, :root[data-theme="space"] .ncard, :root[data-theme="space"] .phot { border-color: rgba(197, 139, 255, .14); border-radius: 16px; }
:root[data-theme="ink"] .feed .card, :root[data-theme="ink"] .pcard, :root[data-theme="ink"] .ncard, :root[data-theme="ink"] .phot { border-radius: 6px; box-shadow: none; }
/* 네온 — 테두리와 켜진 칩이 빛난다 */
:root[data-theme="neon"] .feed .card, :root[data-theme="neon"] .pcard, :root[data-theme="neon"] .ncard, :root[data-theme="neon"] .phot { border-color: rgba(46, 242, 208, .24); box-shadow: 0 0 16px rgba(46, 242, 208, .07); }
:root[data-theme="neon"] .chips-ind { box-shadow: 0 0 10px var(--brand-500); }
:root[data-theme="neon"] .tabpill { box-shadow: var(--sh-3), 0 0 22px rgba(46, 242, 208, .16), inset 0 1px 0 var(--glass-top); }
/* 신문 — 목록 카드 대신 가로줄로 나누고, 앱바 아래는 신문 제호 밑의 두 줄 */
:root[data-theme="news"] .feed .card, :root[data-theme="news"] .pcard, :root[data-theme="news"] .ncard { background: transparent; border: 0; border-bottom: 1px solid var(--line-2); border-radius: 0; box-shadow: none; }
:root[data-theme="news"] .phot { border-radius: 2px; box-shadow: none; }
:root[data-theme="news"] .appbar.has-chips { box-shadow: inset 0 -1px 0 var(--text), inset 0 -4px 0 var(--bg), inset 0 -5px 0 var(--text); }
/* 신문·수묵 — 제목이 명조다(목록 카드 셋 · HOT · 뉴스·포인트 상세 머리). 🔴 한글은 앱에 넣은 서체(`fonts/serif-ko.woff2` · Noto Serif KR Bold · KS X 1001 2,350자 + ASCII · OFL)를 쓴다 —
   iOS·안드로이드 둘 다 기본 한글 명조가 없다. 일본어·중국어는 기기의 명조로 떨어진다(없으면 고딕). 브라우저는 이 두 테마에서만 그 파일을 받는다. */
@font-face { font-family: 'JwSerif'; src: url(../fonts/serif-ko.woff2) format('woff2'); font-weight: 700; font-display: swap; unicode-range: U+0020-007E, U+00A0-00FF, U+2013-2026, U+3001-300F, U+AC00-D7A3; }
:root { --f-serif: 'JwSerif', 'Hiragino Mincho ProN', 'Songti TC', 'Noto Serif CJK KR', serif; }
:root[data-theme="news"] .card-h b, :root[data-theme="news"] .pc-h, :root[data-theme="news"] .nc-h, :root[data-theme="news"] .phot h3, :root[data-theme="news"] .pd-h, :root[data-theme="news"] .pn-title,
:root[data-theme="ink"] .card-h b, :root[data-theme="ink"] .pc-h, :root[data-theme="ink"] .nc-h, :root[data-theme="ink"] .phot h3, :root[data-theme="ink"] .pd-h, :root[data-theme="ink"] .pn-title { font-family: var(--f-serif); font-weight: 700; }

/* ── 흩날리는 장식 (`js/themefx.js` 가 `#theme-fx.tfx`(앱) · `#splash .sp-fx.tfx`(스플래시 뒤 배경)에 입자를 심는다 — 규칙은 클래스 `.tfx` 에 건다) ──
   🔴 머리 영역(위 250px)에서만 움직이고 아래로 갈수록 사라진다 — 기사 위로는 내려오지 않는다.
   🔴 `transform`·`opacity` 만 움직인다(합성 단계 · 레이아웃을 다시 돌지 않는다). 기기의 「동작 줄이기」는 JS 가 «멈춘 판»으로 심는다.
   🔴 z-index 는 앱 안의 고정 바(탭바 6)보다 위 · 시트(940)·인앱(930)·토스트보다 아래다. */
.tfx { position: fixed; left: 0; right: 0; top: 0; height: 250px; z-index: 8; pointer-events: none; overflow: hidden;
  -webkit-mask-image: linear-gradient(#000 55%, transparent); mask-image: linear-gradient(#000 55%, transparent); }
.tfx:empty { display: none; }
#splash ~ #theme-fx { display: none; }   /* 스플래시가 떠 있는 동안은 앱 머리 입자를 안 돌린다(밑에 가려져 안 보이는데 합성 레이어만 한 벌 더 든다) — 스플래시가 걷히면 처음부터 내린다 */
.tfx.paused .p, .tfx.paused .p svg, .tfx.paused .m::before { animation-play-state: paused; }   /* 앱이 뒤로 갔을 때 (themefx.js) */
.tfx .p { position: absolute; top: -20px; left: var(--x); width: var(--s); height: var(--s); opacity: 0;
  animation: tfx-fall var(--t) linear var(--d) infinite; }
.tfx .p svg { display: block; width: 100%; height: 100%; animation: tfx-sway calc(var(--t) / 3) ease-in-out var(--d) infinite alternate; }
.tfx .p.up { top: auto; bottom: -10px; animation-name: tfx-rise; }
.tfx .p.steam { top: 100px; width: 9px; height: 26px; animation-name: tfx-steam; }
.tfx .p.rain { width: 1.3px; height: 16px; border-radius: 1px; background: linear-gradient(rgba(61, 111, 158, 0), rgba(61, 111, 158, .6)); animation-name: tfx-drop; }
.tfx .m { position: absolute; left: var(--x); top: var(--y); width: 70px; height: 1.6px; transform: rotate(-32deg); transform-origin: left center; }
.tfx .m::before { content: ""; position: absolute; inset: 0; border-radius: 2px; background: linear-gradient(90deg, #fff, rgba(255, 255, 255, 0));
  opacity: 0; animation: tfx-meteor var(--t) ease-in var(--d) infinite; }
@keyframes tfx-fall { 0% { transform: translateY(0) rotate(0); opacity: 0 } 10% { opacity: .9 } 100% { transform: translateY(270px) rotate(260deg); opacity: .9 } }
@keyframes tfx-rise { 0% { transform: translateY(0) scale(.6); opacity: 0 } 15% { opacity: .8 } 100% { transform: translateY(-230px) scale(1); opacity: 0 } }
@keyframes tfx-sway { from { transform: translateX(-8px) } to { transform: translateX(10px) } }
@keyframes tfx-steam { 0% { transform: translateY(0) scaleX(.7); opacity: 0 } 25% { opacity: .75 } 100% { transform: translateY(-80px) scaleX(1.3); opacity: 0 } }
@keyframes tfx-drop { 0% { transform: translate(0, 0) rotate(14deg); opacity: 0 } 8% { opacity: 1 } 100% { transform: translate(-64px, 270px) rotate(14deg); opacity: .9 } }
@keyframes tfx-meteor { 0% { transform: translateX(0); opacity: 0 } 3% { opacity: 1 } 14% { transform: translateX(-200px); opacity: 0 } 100% { transform: translateX(-200px); opacity: 0 } }
/* 멈춘 판 — 「동작 줄이기」 · 입자는 제자리(`--rest`)에 그려 둔다 */
.tfx.still .p, .tfx.still .p svg, .tfx.still .m::before { animation: none; }
.tfx.still .p { opacity: .85; transform: translateY(var(--rest)) rotate(30deg); }
.tfx.still .p.up { transform: translateY(calc(var(--rest) * -1)); }
.tfx.still .p.steam { transform: translateY(-26px); opacity: .5; }
.tfx.still .p.rain { transform: translate(0, var(--rest)) rotate(14deg); }

