/* ════════════════════════════════════════════════════════════════════════════
   shell.css — 이 사이트의 껍데기. **한 벌만 존재한다.** (v2.60)

   왜 파일로 뺐나. 2026-08-02 발주자 지적: "정적페이지의 디자인을 비슷하게 맞추는
   쪽으로 구현하면 어색함을 해결하기 어려울거 같은데 비슷하게 될 뿐 같은 페이지처럼
   보이지 않아." 정확한 진단이었다. 그때까지 껍데기 규칙이 **두 벌**이었다 —
   public.html 안의 <style> 한 벌, app/services/posts.py 의 _CSS 한 벌. 두 벌을
   손으로 맞추면 값이 같아도 규칙이 다르고, 한쪽만 고치면 그날부터 갈린다. 실제로
   헤더 마크가 42px 대 38px, 내비 클래스가 .site-nav 대 nav.top 이었다.

   그래서 값을 베끼는 대신 **파일 하나를 같이 링크한다.** 메인이든 /posts 든
   /stock/005930 이든 브라우저가 받는 껍데기 바이트가 같다. 이제 '비슷하게'가
   불가능하다 — 같거나, 파일을 고쳤거나 둘 중 하나다.

   읽는 순서: style.css(디자인 시스템·팔레트·.wrap/.card) → shell.css(이 파일).
   이 파일은 style.css 위에 얹는 확장이라 **반드시 뒤에** 온다.

   ?v= 는 Cloudflare 정적 캐시 무효화. 손으로 적지 않는다 —
   app/services/share.py 의 _asset_stamp() 가 파일 mtime 으로 찍는다.
   ════════════════════════════════════════════════════════════════════════════ */

  /* ── 공개판 전용 스타일 (style.css 위에 얹는 확장) ── */

  /* ── v2.20 브랜드 배경 ────────────────────────────────────────────────────
     발주: "웹 컨텐츠 배경이 휑하니까 해당 브랜딩에 맞는 배경으로".
     마크가 '칩 패키지'이므로 배경도 같은 말을 하게 한다 —
       · 44px  격자 = 다이(die) 격자
       · 176px 격자 = 레티클 샷 경계. 44 의 4배라 두 격자가 어긋나지 않는다.
       · 앰버/틸 글로우 = 팔레트의 코어(--core)·확인(--confirm) 색
     원칙 셋:
       1) fixed — 데이터 화면은 배경이 같이 움직이면 숫자를 읽기 힘들다. 안 움직인다.
       2) 아주 낮은 알파 — 격자가 숫자보다 눈에 띄면 실패다. 화면을 꽉 채우되 안 시끄럽게.
       3) 마지막 레이어에 var(--bg) — 이 레이어 하나로 불투명해서, body 배경 전파 순서에
          상관없이 항상 같은 그림이 나온다.
     inset 대신 top/right/bottom/left 를 쓴 건 구형 사파리(<14.5) 때문. CSS 는 조용히
     죽으므로 파싱 실패 대신 폴백이 필요하다. 그라디언트 색 정지점도 다중위치 축약
     ('0 1px') 대신 긴 문법을 쓴 이유가 같다. */
  body{ --die:44px; --shot:176px; }   /* 격자 피치 — 아래 미디어쿼리에서 이 둘만 바꾼다 */
  body::before{
    content:""; position:fixed; top:0; right:0; bottom:0; left:0;
    z-index:-1; pointer-events:none;
    background:
      radial-gradient(900px 620px at 12% -8%,  rgba(255,179,71,.10), transparent 62%),
      radial-gradient(820px 560px at 92% 108%, rgba(78,201,176,.09), transparent 62%),
      repeating-linear-gradient(0deg,
        rgba(120,150,190,.085) 0px, rgba(120,150,190,.085) 1px,
        transparent 1px, transparent var(--shot)),
      repeating-linear-gradient(90deg,
        rgba(120,150,190,.085) 0px, rgba(120,150,190,.085) 1px,
        transparent 1px, transparent var(--shot)),
      repeating-linear-gradient(0deg,
        rgba(120,150,190,.05) 0px, rgba(120,150,190,.05) 1px,
        transparent 1px, transparent var(--die)),
      repeating-linear-gradient(90deg,
        rgba(120,150,190,.05) 0px, rgba(120,150,190,.05) 1px,
        transparent 1px, transparent var(--die)),
      var(--bg);
  }
  @media print{ body::before{display:none} }

  /* ── v2.62 최상단 정렬 ─────────────────────────────────────────────────────
     발주자 지적: "오늘의 지수랑 그 외 탭을 이동할 때 최상단 마진 값이 달라서 탭
     이동 시마다 화면이 위아래로 들썩거려."

     실측(브라우저): 메인 header.top = 84px, 정적 페이지 = 12px. 차이 72px 이
     전부 **비어 있는 #app-cta 한 칸**이었다. 앱 설치 배너 자리인데, 스크립트가
     채우지 않는 동안에도 .wrap 의 위아래 여백(12 + 60)만큼 자리를 차지한다.

     그래서 정적 페이지에 72px 을 더 얹는 대신 **빈 칸이 자리를 안 잡게** 한다.
     그쪽이 맞는 이유: (1) 그 72px 은 디자인이 아니라 빈 컨테이너의 부작용이고,
     (2) 다섯 페이지에 죽은 여백을 복사하는 대신 한 곳을 고치는 것이고,
     (3) 앱 배너가 실제로 뜨는 날에는 내용이 있으니 자리를 잡는 게 맞다.
     renderAppCta() 가 host.innerHTML = "" 로 비우므로 :empty 가 정확히 맞물린다. */
  #app-cta:empty{padding-top:0; padding-bottom:0}

  /* ── v2.20 브랜드 락업 ────────────────────────────────────────────────────
     발주: "MINDEX / M-INDEX 로 브랜딩", "Mindex 가 메모리 반도체 지수를 뜻한다는 걸
     알릴 수 있어야". 그래서 이름을 세 겹으로 설명한다:
       마크(칩+M) → 이름(M-INDEX, M 만 앰버) → 부제(메모리 반도체 투자지수)
       → 어원 한 줄(M = Memory 메모리 반도체 · INDEX = 지수).
     마크를 파일 대신 인라인 SVG 로 둔 이유: 요청 한 번을 줄이고, 첫 페인트에서 로고가
     늦게 나타나 헤더가 덜컥이는 걸 없앤다. 대신 패스는 gen_brand.py 의 mark.svg 와
     같아야 한다 — 마크를 바꾸면 gen_brand.py 를 먼저 고치고 여기 패스를 맞춘다.
     h1 은 유지한다(SEO). 검색엔진이 읽는 제목 텍스트는 부제(data-i18n="title")다. */
  header.top{align-items:center}
  .brand{margin:0; font-size:inherit; letter-spacing:normal}
  .brand-lockup{display:flex; align-items:center; gap:11px; text-decoration:none; color:inherit}
  .brand-mark{width:42px; height:42px; flex:none; display:block; transition:filter .18s}
  .brand-lockup:hover .brand-mark{filter:drop-shadow(0 0 10px rgba(255,179,71,.5))}
  .brand-txt{display:flex; flex-direction:column; min-width:0}
  .brand-name{font-family:'Space Grotesk','IBM Plex Sans KR',sans-serif; font-size:27px;
    font-weight:700; line-height:1.02; letter-spacing:.004em; color:var(--ink); white-space:nowrap}
  .brand-name i{font-style:normal; color:var(--core)}
  .brand-sub{font-family:'IBM Plex Sans KR',sans-serif; font-size:12px; font-weight:500;
    line-height:1.3; letter-spacing:-.01em; color:var(--ink-dim); margin-top:4px}
  /* 어원 한 줄 — 헤더 밑에 붙는 각주. 이름 뜻을 대놓고 풀어 주는 자리다. */
  .brand-etym{margin:10px 0 0; font-family:'IBM Plex Mono',monospace; font-size:11px;
    line-height:1.7; letter-spacing:.02em; color:var(--ink-faint)}
  .brand-etym b{color:var(--core); font-weight:600}
  .brand-etym u{text-decoration:none; color:var(--confirm); font-weight:600}
  .brand-etym i{font-style:normal; color:var(--line); padding:0 2px}

  /* 4단계 경보 바 — 어드민 stage4-bar와 동일 문법 */
  #banner4{display:none; position:sticky; top:0; z-index:60; background:var(--bad); color:#fff;
    padding:12px 16px; text-align:center; font-weight:800; border-radius:0 0 12px 12px;
    box-shadow:0 6px 24px rgba(255,92,108,.45)}
  #banner4.show{display:block; animation:pulse-bar 1.6s ease-in-out infinite}
  /* 매크로 시세 스트립 */
  .macro-strip{display:grid; grid-template-columns:repeat(5,1fr); gap:10px; margin-top:16px}
  .mkt{background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:10px 12px}
  .mkt .k{font-family:'IBM Plex Mono'; font-size:9.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-faint)}
  .mkt .v{font-family:'Space Grotesk'; font-size:17px; font-weight:600; font-variant-numeric:tabular-nums; margin-top:1px}
  .mkt .d{font-family:'IBM Plex Mono'; font-size:9.5px; color:var(--ink-faint)}
  html{scroll-behavior:smooth}
  /* 동작 최소화 선호(v2.24.1) — 이 한 줄을 같이 끄지 않으면 JS 가 scrollTo({behavior:"auto"})
     로 불러도 계산된 scroll-behavior 가 smooth 라 그대로 미끄러진다. 맨 위로 버튼뿐 아니라
     퀵 내비 앵커 이동에도 함께 걸린다. */
  @media (prefers-reduced-motion:reduce){ html{scroll-behavior:auto} }
  /* 퀵 내비 (v2.23) — 예전엔 흐린 글씨의 얇은 테두리라 '버튼'으로 안 읽혔다.
     눌리는 것처럼 보이게 한다: 또렷한 글자, 떠 있는 그림자, 이동을 뜻하는 꺾쇠,
     hover 에 살짝 떠오르고 :active 에 눌리는 반응. 탭 영역도 손가락 크기로 키운다. */
  /* overflow-x:auto 는 세로도 같이 잘라낸다 — 위아래 여백이 없으면 알약 그림자가 잘린다 */
  /* v2.45 분석 리포트 진입 — 칩이 아니라 한 줄을 통째로 쓴다.
     코어(앰버) 계열로 칠한다: 이 페이지에서 앰버는 '이 지수가 실제로 보는 것'의
     색이고, 리포트가 하는 말이 정확히 그것이다. 퀵 내비(중립 회색)와 색이 갈려야
     같은 줄의 또 다른 칩으로 안 읽힌다. */
  /* v2.45 리포트 CTA 스타일은 v2.57 에서 제거했다 — portal-row 로 합쳤다.
     쓰지 않는 규칙을 남겨 두면 다음 사람이 어느 쪽이 사는 코드인지 모른다. */
  .quick-nav{display:flex; gap:8px; margin-top:8px; padding:4px 0 8px; overflow-x:auto;
    -webkit-overflow-scrolling:touch; scrollbar-width:none}
  .quick-nav::-webkit-scrollbar{display:none}
  /* 오른쪽에 더 있을 때만 흐린다 (JS 가 .more 를 붙였다 뗀다) — 끝까지 밀면 선명해진다 */
  .quick-nav.more{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 36px),transparent);
    mask-image:linear-gradient(90deg,#000 calc(100% - 36px),transparent)}
  .quick-nav a{flex:none; display:inline-flex; align-items:center; gap:6px; cursor:pointer;
    font-family:'IBM Plex Mono'; font-size:11.5px; font-weight:600; letter-spacing:.05em;
    color:var(--ink); border:1px solid var(--line); border-radius:999px; padding:9px 14px;
    text-decoration:none; white-space:nowrap;
    background:linear-gradient(180deg, var(--panel), rgba(0,0,0,.16));
    box-shadow:inset 0 1px 0 rgba(255,255,255,.06), 0 2px 6px rgba(0,0,0,.30);
    transition:transform .12s ease, color .12s ease, border-color .12s ease, box-shadow .12s ease}
  .quick-nav a::after{content:"›"; font-size:14px; line-height:1; color:var(--ink-faint);
    transition:transform .12s ease, color .12s ease}
  .quick-nav a:hover{color:var(--accent); border-color:var(--accent); transform:translateY(-1px);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.08), 0 5px 14px rgba(0,0,0,.38)}
  .quick-nav a:hover::after{color:var(--accent); transform:translateX(2px)}
  .quick-nav a:active{transform:translateY(0) scale(.97);
    box-shadow:inset 0 2px 5px rgba(0,0,0,.40)}
  .quick-nav a:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
  @media (prefers-reduced-motion:reduce){
    .quick-nav a, .quick-nav a::after{transition:none}
    .quick-nav a:hover, .quick-nav a:active{transform:none}
  }
  /* 코어 경고 (어드민 #core-warning 동일) */
  .core-warning{display:none; margin:10px 0 12px; padding:14px 18px; border-radius:12px;
    background:rgba(255,92,108,.12); border:1px solid rgba(255,92,108,.4);
    color:var(--bad); font-weight:600; font-size:13px}
  #gauge-red{display:none; position:absolute; inset:-6px; border-radius:12px;
    background:rgba(255,92,108,.26); pointer-events:none}
  #gauge-red.show{display:block; animation:pulse-bar 1.6s ease-in-out infinite}
  /* 층위 지표 상세 */
  details.inds{margin:8px 0 2px}
  details.inds summary{color:var(--ink-faint); cursor:pointer; font-size:11px; padding:2px 0}
  details.inds table.data{margin-top:6px; font-size:12px}
  /* 종목 섹션 타이틀 */
  .sec-title{font-family:'IBM Plex Mono'; font-size:12px; letter-spacing:.14em; text-transform:uppercase;
    color:var(--ink-dim); font-weight:600; margin:22px 0 0; display:flex; align-items:center; gap:10px}
  .sec-title::before{content:''; width:22px; height:2px; background:currentColor}
  .kv .v .src-tag, .src-tag{font-size:10px; color:var(--ink-faint)}
  /* 캘린더 칩 */
  #calendar{display:flex; flex-wrap:wrap; gap:8px; margin-top:8px}
  /* 시뮬레이션 */
  .sim .s-note{color:var(--ink-faint); font-size:12px; line-height:1.6; margin:2px 0 12px}
  .sim .s-actions{display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px}
  .sim .s-result{display:flex; align-items:baseline; gap:12px; flex-wrap:wrap}
  .sim .s-result .num{font-family:'Space Grotesk'; font-size:44px; font-weight:700; line-height:1;
    font-variant-numeric:tabular-nums}
  .sim .s-result .pill{font-size:12px; font-weight:600; border:1px solid var(--line); border-radius:999px; padding:5px 14px}
  .sim .s-delta{font-family:'IBM Plex Mono'; font-size:11.5px; color:var(--ink-dim); font-variant-numeric:tabular-nums}
  .sim .s-lys{display:flex; gap:14px; flex-wrap:wrap; font-size:12px; color:var(--ink-dim);
    margin:10px 0 2px; font-variant-numeric:tabular-nums}
  .sim .s-corewarn{display:none; color:var(--bad); font-size:12px; margin-top:6px; font-weight:600}
  .sim h3{font-family:'IBM Plex Mono'; font-size:11px; letter-spacing:.12em; text-transform:uppercase;
    color:var(--ink-faint); font-weight:600; margin:18px 0 4px}
  .sim h3 span{font-variant-numeric:tabular-nums}
  .sim .s-row{padding:7px 0; border-bottom:1px solid var(--grid)}
  .sim .s-row:last-child{border-bottom:0}
  .sim .s-top{display:flex; gap:8px; align-items:baseline; font-size:12.5px; margin-bottom:4px}
  .sim .s-top b{margin-left:auto; font-family:'Space Grotesk'; font-variant-numeric:tabular-nums}
  .sim .s-sub{margin-top:3px}
  .sim .s-ctl{display:flex; gap:10px; align-items:center}
  .sim .s-ctl input[type=number]{width:96px; font-family:'IBM Plex Mono'; font-size:12.5px; padding:5px 8px}
  /* 차트 */
  #chart{width:100%; display:block; margin-top:6px}
  .chart-cap{font-family:'IBM Plex Mono'; color:var(--ink-faint); font-size:11px; margin-top:8px}
  details.htable{margin-top:10px; font-size:11px; color:var(--ink-dim)}
  details.htable summary{cursor:pointer; color:var(--ink-faint)}
  details.htable table.data{margin-top:8px; font-size:12px}
  /* 오늘의 브리핑 (v2.18) */
  .brief .b-top{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:10px}
  .brief .b-play{flex:none}
  .brief .b-when{font-family:'IBM Plex Mono'; color:var(--ink-faint); font-size:11px; margin-left:auto}
  .brief .b-text{font-size:14px; line-height:1.85; color:var(--ink); word-break:keep-all}
  /* 문장 단위 하이라이트(v2.24). 조각을 못 믿을 때만 통째로 강조한다 — :not(.seg) */
  .brief .b-text.speaking:not(.seg){color:var(--accent)}
  /* 굵기·크기는 절대 건드리지 않는다. 낭독 중에 글자 폭이 바뀌면 줄바꿈이 흔들려
     본문 전체가 덜컹거린다. 색만 바꾸면 리플로가 없다. */
  .brief .b-text .b-seg{transition:color .22s linear}
  .brief .b-text .b-seg.said{color:var(--ink-dim)}
  .brief .b-text .b-seg.now{color:var(--accent)}
  @media (prefers-reduced-motion:reduce){ .brief .b-text .b-seg{transition:none} }
  .brief .b-prog{height:3px; border-radius:2px; background:var(--line); overflow:hidden; margin:0 0 12px}
  .brief .b-prog i{display:block; height:100%; width:0; background:var(--accent); transition:width .25s linear}
  .brief .t-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(132px,1fr)); gap:8px; margin-top:14px}
  .brief .t-card{display:block; text-decoration:none; background:var(--panel-2); border:1px solid var(--line);
    border-radius:10px; padding:9px 10px; transition:border-color .15s ease, transform .15s ease}
  .brief .t-card:hover{border-color:var(--accent); transform:translateY(-1px)}
  .brief .t-nm{font-size:11.5px; color:var(--ink-dim); display:flex; align-items:baseline; gap:5px}
  .brief .t-nm .tk{font-family:'IBM Plex Mono'; font-size:9.5px; color:var(--ink-faint)}
  .brief .t-v{font-family:'IBM Plex Mono'; font-size:15px; margin-top:4px; font-variant-numeric:tabular-nums}
  .brief .t-d{font-family:'IBM Plex Mono'; font-size:11px; margin-top:2px; font-variant-numeric:tabular-nums}
  .brief .t-up{color:var(--up,#ef5f5f)} .brief .t-dn{color:var(--down,#4d9dff)} .brief .t-fl{color:var(--ink-faint)}
  .brief .b-src{color:var(--ink-faint); font-size:10.5px; margin-top:9px; line-height:1.6}
  .brief .b-news{margin-top:12px; padding-top:10px; border-top:1px solid var(--grid); font-size:12.5px}
  .brief .b-news .bn-h{color:var(--ink-faint); font-size:11px; margin-bottom:5px}
  .brief .b-news a{color:var(--ink-dim); text-decoration:none; display:block; padding:3px 0; line-height:1.5}
  .brief .b-news a:hover{color:var(--accent)}
  .brief.pulse{animation:briefPulse 1.6s ease 2}
  @keyframes briefPulse{0%,100%{border-color:var(--line)} 50%{border-color:var(--accent)}}
  .notify .b-opt{display:flex; gap:8px; align-items:center; margin-top:12px; font-size:12.5px; color:var(--ink-dim)}
  .notify .b-opt input{width:16px; height:16px; accent-color:var(--accent)}
  /* 뉴스 */
  .news .n-item{display:flex; gap:10px; padding:8px 0; border-bottom:1px solid var(--grid); font-size:13px}
  .news .n-item:last-child{border-bottom:0}
  .news a{color:var(--ink); text-decoration:none; line-height:1.5; flex:1}
  .news a:hover{color:var(--accent)}
  .news .n-meta{font-family:'IBM Plex Mono'; color:var(--ink-faint); font-size:10.5px; flex:none;
    text-align:right; min-width:78px}
  .news .n-empty{color:var(--ink-faint); font-size:12.5px; padding:10px 2px}
  .news .n-more{display:block; width:100%; margin-top:10px}
  /* 알림 */
  .notify .msg{color:var(--ink-dim); font-size:12.5px; margin-top:10px; line-height:1.6}
  /* 게시판 */
  .board .b-row{display:flex; gap:8px; align-items:center}
  .board #b-nick{width:160px}
  .board #b-web{position:absolute; left:-9999px; width:1px; height:1px; opacity:0}
  .board textarea{font:inherit; color:var(--ink); background:var(--panel-2); border:1px solid var(--line);
    border-radius:8px; padding:8px 10px; width:100%; margin:8px 0; resize:vertical}
  .board .b-hint{color:var(--ink-faint); font-size:11px; flex:1}
  .b-post{border-top:1px solid var(--line); padding:11px 2px}
  .b-post .b-head{display:flex; gap:7px; font-size:11.5px; color:var(--ink-dim); margin-bottom:4px}
  .b-post .b-head .tag{font-family:'IBM Plex Mono'; color:var(--ink-faint)}
  .b-post .b-head .tm{margin-left:auto; font-family:'IBM Plex Mono'; color:var(--ink-faint)}
  .b-post .b-body{font-size:13.5px; line-height:1.6; white-space:pre-wrap; word-break:break-word}
  .b-empty{color:var(--ink-faint); font-size:12.5px; padding:12px 2px}

  /* ── v2.41 마스코트 '민이' ────────────────────────────────────────────────
     자산은 scripts/mascot_assets.py 가 만든다. 여기서는 배치만 한다.
     규약(가이드 03·04):
       · 표정은 색·글리프·글자를 **대체하지 않고 덧붙인다.** 색각 이상·저시력에서
         표정 차이는 안 읽히므로, 민이를 지워도 화면이 상태를 다 말해야 한다.
         그래서 전부 aria-hidden 이고 대체텍스트를 주지 않는다.
       · 한 화면에 큰 민이는 하나. 소형(<=44px)은 무손 컷만 쓴다.
       · 뉴스처럼 사실을 전달하는 자리에는 넣지 않는다 — 캐릭터가 정보의 중립성을
         흔든다. (빈 뉴스 목록은 '사실이 없는 상태'라 예외로 허용) */
  .minyi{display:block; flex:none; user-select:none; -webkit-user-drag:none}
  .minyi-empty{display:flex; flex-direction:column; align-items:center; gap:8px;
               padding:18px 2px 12px; text-align:center}
  .minyi-empty .minyi{width:96px; height:auto; opacity:.92}
  .minyi-empty .e-t{font-size:13px; color:var(--ink-dim); font-weight:600}
  .minyi-empty .e-s{font-size:12px; color:var(--ink-faint)}
  .push-banner .pb-t{display:flex; align-items:center; gap:9px}
  .push-banner .minyi{width:38px; height:38px}
  #alert-banner .minyi{width:26px; height:26px; margin-left:2px}
  .brief .b-top .minyi{width:30px; height:30px; margin-left:auto}
  /* 지난 발표 상세 (v2.43) — 캘린더 칩을 누르면 아래로 펼쳐진다 */
  .cal-chip.openable{cursor:pointer; transition:border-color .15s, transform .08s}
  .cal-chip.openable:hover{border-color:var(--ink-faint); transform:translateY(-1px)}
  .cal-chip.openable:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
  .cal-chip.has-rec{border-color:rgba(78,201,176,.45)}
  .cal-chip .cal-more{color:var(--ink-faint); font-weight:700; margin-left:1px}
  .cal-recap{flex-basis:100%; background:var(--panel-2); border:1px solid var(--line);
    border-radius:11px; padding:11px 13px; margin-top:4px}
  .cal-recap .cr-h{font-size:13.5px; font-weight:700; margin-bottom:6px}
  .cal-recap .cr-kv{display:flex; gap:10px; font-size:12.5px; padding:3px 0}
  .cal-recap .cr-kv .k{color:var(--ink-faint); min-width:96px}
  .cal-recap .cr-kv .v{font-variant-numeric:tabular-nums}
  .cal-recap .cr-kv .v.good{color:var(--good)} .cal-recap .cr-kv .v.bad{color:var(--bad)}
  .cal-recap .cr-note{margin-top:7px; font-size:12.5px; line-height:1.55; color:var(--ink-dim);
    background:rgba(107,159,255,.06); border:1px solid rgba(107,159,255,.22);
    border-radius:9px; padding:8px 10px}
  .cal-recap .cr-none{font-size:12.5px; line-height:1.55; color:var(--ink-dim)}
  .cal-recap .cr-stale{margin-top:8px; font-size:12px; line-height:1.5; color:var(--warn);
    background:rgba(255,207,92,.08); border:1px solid rgba(255,207,92,.32);
    border-radius:9px; padding:7px 10px}
  .cal-recap .cr-foot{margin-top:8px; font-size:11px; color:var(--ink-faint)}
  /* 램프 단계별 알림 옵트인 (v2.55). 손가락 칸 44px 을 지킨다 — 체크박스만
     작게 두면 모바일에서 옆 항목이 눌린다. 모바일 비중이 273/402 다. */
  .stage-opts{display:flex; flex-direction:column; gap:2px; margin-top:8px}
  .stage-opts label{display:flex; align-items:center; gap:9px; min-height:44px;
    font-size:13px; color:var(--ink-dim); cursor:pointer}
  .stage-opts input{width:17px; height:17px; flex:none; accent-color:var(--core)}
  .stage-opts b{color:var(--ink); font-weight:600}
  /* 사이트 내비 (v2.56). 정적 페이지(nav.top)와 **같아 보여야** 한 서비스로 읽힌다.
     퀵 내비(.quick-nav)는 같은 화면 앵커라 성격이 다르므로 생김새를 일부러 다르게 둔다 —
     둘이 똑같이 생기면 어느 쪽이 페이지를 떠나는 링크인지 알 수 없다. */
  .site-nav{display:flex; gap:16px; align-items:center; flex-wrap:wrap;
    margin:10px 0 2px; padding-bottom:9px; border-bottom:1px solid var(--line)}
  .site-nav a{font-size:13.5px; color:var(--ink-dim); text-decoration:none;
    min-height:34px; display:inline-flex; align-items:center}
  .site-nav a:hover{color:var(--ink)}
  .site-nav a.on{color:var(--core); font-weight:600}
  /* 별도 페이지 진입 카드 (v2.57). 내비 한 줄만으로는 안 눌린다 —
     이름만 있으면 무엇이 있는지 모르고, 모르면 안 누른다(v2.45 에서 배운 것). */
  .portal-row{display:grid; grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
    gap:10px; margin:12px 0 4px}
  .portal-row a{display:block; background:var(--panel); border:1px solid var(--line);
    border-radius:12px; padding:13px 15px; text-decoration:none; color:var(--ink);
    transition:border-color .15s, transform .15s}
  .portal-row a:hover{border-color:var(--core); transform:translateY(-1px)}
  .portal-row b{display:block; font-size:14.5px; font-weight:600; margin-bottom:2px}
  .portal-row b::after{content:" ›"; color:var(--ink-faint); font-weight:400}
  .portal-row span{display:block; color:var(--ink-dim); font-size:12.5px; line-height:1.5}
  .senti-q{display:flex; align-items:center; gap:11px}
  .senti-q .minyi{width:44px; height:44px}
  /* 오늘의 질문 (v2.42) — 커뮤니티의 첫 카드. 질문이 없는 날은 통째로 감춘다. */
  #q-panel .q-row{display:flex; gap:11px; align-items:flex-start; margin-top:8px}
  #q-panel .minyi{width:44px; height:44px}
  #q-panel .q-bubble{flex:1; background:var(--panel-2); border:1px solid rgba(255,179,71,.3);
    border-radius:12px; border-top-left-radius:4px; padding:11px 13px; font-size:14.5px; line-height:1.55}
  #q-panel .q-meta{display:flex; gap:8px; flex-wrap:wrap; margin-top:9px}
  #q-panel .q-reply{border-left:2px solid var(--line); margin:8px 0 0 14px; padding:5px 0 5px 10px;
    font-size:13px; color:var(--ink-dim)}
  #q-panel .q-past{margin-top:12px; border-top:1px dashed var(--line); padding-top:10px}
  #q-panel .q-past .q-i{display:flex; gap:8px; padding:5px 0; font-size:12.5px; color:var(--ink-dim)}
  #q-panel .q-past .q-i .d{font-family:'IBM Plex Mono'; color:var(--ink-faint); flex:none}
  /* 시선 배지 (v2.25) — 운영자 글임을 닉이 아니라 배지로 말한다.
     익명 게시판이라 닉네임은 누구나 흉내 낼 수 있고, 배지는 서버 컬럼에서만 온다. */
  .b-post.official{background:linear-gradient(90deg, rgba(255,179,71,.055), transparent 62%);
    border-left:2px solid var(--core); padding-left:10px}
  .b-post.official .b-head b{color:var(--core)}
  .b-post .b-badge{font-size:10px; font-weight:600; letter-spacing:.02em; padding:1px 5px;
    border:1px solid var(--core); border-radius:4px; color:var(--core); line-height:1.5}
  /* v2.50 회원 표시. 운영자 배지(--core)와 색을 나눈다 — 둘이 같은 색이면
     '로그인한 사람'과 '운영자'가 한 덩어리로 읽힌다. 테두리 없이 글자만 흐리게. */
  .b-post .b-me{font-size:10px; font-weight:600; color:var(--ink-faint); line-height:1.5}
  /* ── 스레드 (v2.33) ────────────────────────────────────────────────────────
     평면 목록을 한 겹짜리 스레드로 바꿨다. 답글은 세로선 하나로만 들여쓴다 —
     배경색을 또 깔면 '시선' 글의 배지 배경과 겹쳐 어느 쪽이 운영자 글인지가
     흐려진다. 들여쓰기는 14px 한 단계뿐이고 그 이상 깊어질 수 없다(서버가
     한 겹으로 못 박는다). */
  .b-thread{border-top:1px solid var(--line)}
  .b-thread .b-post{border-top:0}
  .b-replies{margin:0 0 4px 14px; padding-left:12px; border-left:2px solid var(--line)}
  .b-replies .b-post{padding:9px 2px}
  .b-replies .b-body{font-size:13px}
  .b-foot{display:flex; align-items:center; gap:10px; padding:0 2px 10px; margin-left:14px}
  .b-reply-btn{font:inherit; font-size:11.5px; color:var(--ink-dim); background:none; cursor:pointer;
    border:1px solid var(--line); border-radius:6px; padding:3px 9px; line-height:1.5}
  .b-reply-btn:hover{color:var(--accent); border-color:var(--accent)}
  .b-reply-btn.on{color:var(--accent); border-color:var(--accent)}
  .b-rcount{font-size:11.5px; color:var(--ink-faint); font-family:'IBM Plex Mono'}
  /* 신고 버튼은 오른쪽 끝에 흐리게 둔다. 답글과 같은 무게로 놓으면 게시판의
     첫인상이 '신고할 수 있는 글들'이 된다. */
  .b-report-btn{font:inherit; font-size:11.5px; color:var(--ink-faint); background:none;
    cursor:pointer; border:1px solid transparent; border-radius:6px; padding:3px 8px; margin-left:auto}
  .b-report-btn:hover{color:var(--bad); border-color:var(--bad)}
  .b-report-btn.armed{color:var(--bad); border-color:var(--bad); font-weight:600}
  .b-blocked{display:flex; align-items:center; gap:10px; margin:6px 2px 0;
    font-size:11.5px; color:var(--ink-faint)}
  .b-blocked button{font:inherit; font-size:11.5px; color:var(--accent); background:none;
    cursor:pointer; border:1px solid var(--line); border-radius:6px; padding:3px 8px}
  /* 앱 설치 안내 (v2.47).
     데스크톱은 상단 배너, 모바일은 무언가를 **한 뒤에** 나오는 한 줄이다.
     모바일에서도 배너로 두면 첫 화면의 목이 좋은 자리를 광고가 먹는다 —
     이 사이트에서 그 자리는 지수 숫자의 것이다. */
  .app-cta{display:flex; align-items:center; gap:12px; flex-wrap:wrap;
    margin:0 0 14px; padding:12px 14px; border:1px solid var(--line); border-radius:12px;
    background:linear-gradient(90deg, rgba(255,179,71,.10), rgba(78,201,176,.06))}
  .app-cta .ac-t{font-weight:600; color:var(--ink)}
  .app-cta .ac-s{font-size:12px; color:var(--ink-dim); flex:1 1 220px}
  .app-cta a{font:inherit; font-size:12.5px; font-weight:600; text-decoration:none;
    color:var(--bg); background:var(--core); border-radius:8px; padding:8px 13px}
  .app-cta a.alt{color:var(--ink); background:var(--panel-2); border:1px solid var(--line)}
  .app-cta button{font:inherit; font-size:11.5px; color:var(--ink-faint); background:none;
    border:0; cursor:pointer; padding:6px}
  .app-hint{margin:8px 0 0; font-size:12px; color:var(--ink-dim);
    display:flex; align-items:center; gap:8px; flex-wrap:wrap}
  .app-hint a{color:var(--core); font-weight:600; text-decoration:none}
  .app-hint a:hover{text-decoration:underline}
  .b-rform{margin:0 0 12px 26px; padding-left:12px; border-left:2px solid var(--line)}
  .b-rform textarea{font:inherit; color:var(--ink); background:var(--panel-2);
    border:1px solid var(--line); border-radius:8px; padding:7px 9px; width:100%;
    margin:0 0 6px; resize:vertical; font-size:13px}
  .b-rform .b-row{display:flex; gap:8px; align-items:center}
  .b-rform input{font:inherit; font-size:12.5px; color:var(--ink); background:var(--panel-2);
    border:1px solid var(--line); border-radius:8px; padding:5px 8px;
    flex:1 1 auto; min-width:0; max-width:170px}
  .b-rform .mini{flex:none; margin-left:auto}
  /* 힌트는 줄을 따로 쓴다 — 같은 줄에 두면 390px 화면에서 버튼 글자가 두 줄로 접힌다. */
  .b-rform .b-hint{display:block; color:var(--ink-faint); font-size:11px; margin-top:6px}

  /* ── 방문자 심리 투표 (v2.25 · 자리 v2.33) ────────────────────────────────
     ★ 처음엔 차트 바로 아래에 뒀다. 근거는 GA 섹션 조회였다 — 사람들이 머무는
     자리가 차트 부근이고(chart/alerts/layers/stocks 26회 vs board 14회) 게시판
     옆에 두면 '글 쓰는 사람만 보는 것'이 된다고 봤다. **그 판단이 틀렸다.**
     7일 동안 들어온 표가 2표다(공개 임계는 20표). GA 가 말한 건 '차트를 본다'
     였지 '차트까지 스크롤한다'가 아니었고, 실제로 그 자리는 열다섯 장 중
     열한 번째 카드였다. 1인당 1.0페이지인 사이트에서 열한 번째는 없는 것이다.
     그래서 종합지수 카드 **바로 다음**으로 올린다. 자리만 옮긴 게 아니라
     문장이 이어지게 만든 것이다 — 위가 기계의 계산, 여기가 "당신은?".
     퀵내비에도 링크를 넣었다(#senti-panel). 다음 판단 근거는 GA 가 아니라
     /api/public/sentiment 의 n 이다. 그게 안 늘면 자리가 문제가 아니다.
     색은 3분할이 아니라 발산(diverging) 이다 — 오른다(따뜻)·비슷하다(중립
     회색)·내린다(차가움). 한국 시장 관습대로 상승이 적색, 하락이 청색이고,
     브리핑 카드(.t-up/.t-dn)와 같은 변수를 쓴다. */
  .senti .senti-q{font-size:14.5px; font-weight:600; margin:2px 0 12px; line-height:1.5}
  .senti-btns{display:flex; gap:8px; flex-wrap:wrap}
  .senti-btns button{flex:1 1 92px; padding:11px 8px; font:inherit; font-size:13.5px; font-weight:600;
    color:var(--ink-dim); background:var(--panel-2); border:1px solid var(--line);
    border-radius:9px; cursor:pointer; transition:border-color .15s, color .15s, background .15s}
  .senti-btns button:hover:not(:disabled){border-color:var(--ink-faint); color:var(--ink)}
  .senti-btns button:disabled{cursor:default; opacity:.75}
  .senti-btns button[data-v="up"].on{color:var(--up,#ef5f5f); border-color:var(--up,#ef5f5f);
    background:rgba(239,95,95,.10)}
  .senti-btns button[data-v="flat"].on{color:var(--ink); border-color:var(--ink-faint);
    background:var(--panel)}
  .senti-btns button[data-v="down"].on{color:var(--down,#4d9dff); border-color:var(--down,#4d9dff);
    background:rgba(77,157,255,.10)}
  .senti-out{margin-top:14px}
  .senti-head{display:flex; align-items:baseline; gap:9px; flex-wrap:wrap}
  .senti-idx{font-family:'IBM Plex Mono'; font-size:29px; font-weight:600; line-height:1}
  .senti-cap{color:var(--ink-faint); font-size:11.5px; font-family:'IBM Plex Mono'; margin-left:auto}
  .senti-lbl{font-size:12.5px; color:var(--ink-dim)}
  /* 누적 막대 — 채움 사이 2px 은 색이 아니라 배경색 테두리로 낸다.
     인접 색이 맞닿으면 경계가 한 색으로 뭉개진다. */
  .senti-bar{display:flex; height:11px; border-radius:6px; overflow:hidden; margin-top:11px;
    background:var(--panel-2)}
  .senti-bar i{display:block; height:100%; box-shadow:0 0 0 1px var(--panel) inset}
  .senti-bar i+i{border-left:2px solid var(--panel)}
  .senti-legend{display:flex; gap:14px; flex-wrap:wrap; margin-top:9px;
    font-size:11.5px; color:var(--ink-dim)}
  .senti-legend span{display:inline-flex; align-items:center; gap:5px}
  .senti-legend em{width:8px; height:8px; border-radius:2px; font-style:normal; flex:none}
  .senti-legend b{font-family:'IBM Plex Mono'; font-weight:500; color:var(--ink)}
  .senti-wait{color:var(--ink-dim); font-size:12.5px; line-height:1.6}
  .senti-wait b{font-family:'IBM Plex Mono'; color:var(--ink)}
  .senti .senti-note{color:var(--ink-faint); font-size:11px; line-height:1.6; margin:12px 0 0}
  /* 면책 */
  .disclaimer-card{color:var(--ink-faint); font-size:11.5px; line-height:1.7}
  .disclaimer-card b{color:var(--ink-dim)}
  /* 검증표 링크 (v2.26) — 면책 카드와 푸터 사이. 카드보다 조금 밝게 둔다: 면책은
     읽고 넘기는 문장이지만 이건 눌러 보라고 있는 줄이라 같은 회색이면 안 보인다. */
  .verify-link{color:var(--ink-faint); font-size:11.5px; line-height:1.7; margin:10px 2px 0}
  .verify-link b{color:var(--ink-dim)}
  .verify-link a{color:var(--accent); text-decoration:none; border-bottom:1px solid currentColor}
  /* 스레드 줄. 검증표 줄과 같은 결로 두되 한 단계 더 물러나 있게 한다 —
     저 줄은 '한계를 적어 두었다'는 약속이고 이건 '다른 자리에도 있다'는 안내다. */
  .social-link{color:var(--ink-faint); font-size:11.5px; line-height:1.7; margin:6px 2px 0}
  .social-link a{color:var(--ink-dim); text-decoration:none; border-bottom:1px solid currentColor}
  /* 툴팁 */
  .tooltip{position:fixed; pointer-events:none; background:var(--panel-2); border:1px solid var(--line);
    border-radius:9px; padding:8px 11px; font-size:12px; display:none; z-index:9;
    font-variant-numeric:tabular-nums; line-height:1.55; box-shadow:0 8px 22px rgba(0,0,0,.5)}
  /* 푸시 소프트 프롬프트 */
  .push-banner{position:fixed; left:50%; transform:translate(-50%,130%); bottom:calc(14px + env(safe-area-inset-bottom));
    width:min(92%,560px); background:var(--panel-2); border:1px solid var(--line); border-radius:14px;
    padding:14px 16px; z-index:70; box-shadow:0 10px 34px rgba(0,0,0,.5); transition:transform .35s ease}
  .push-banner.show{transform:translate(-50%,0)}
  .push-banner .pb-t{font-size:13px; line-height:1.55; margin-bottom:10px}
  .push-banner .pb-btns{display:flex; gap:8px; justify-content:flex-end}
  /* 맨 위로 (v2.24.1) — 우하단 고정. 문제는 버튼을 만드는 게 아니라 자리다:
     푸시 배너는 left:50% 중앙정렬에 width:min(92%,560px) 라, 뷰포트 폭이 약 680px 보다
     좁으면 배너 오른쪽 끝이 16px 안쪽까지 들어온다 — 즉 휴대폰에서는 반드시 겹친다.
     배너를 무시하고 계속 읽는 사람에게 버튼이 세션 내내 가려지므로 '배너 뜨면 숨기기'가
     아니라 '배너 위로 비켜서기'를 택했다. 비켜설 높이는 문구·언어(한/영)·iOS a2hs 변형에
     따라 달라지므로 상수로 박지 않고 JS 가 실측해 --lift 에 넣는다.
     z-index 는 배너(70)보다 낮은 60 — 겹치는 순간이 있다면 결정을 요구하는 쪽이 위여야 한다. */
  .to-top{position:fixed; z-index:60; display:flex; align-items:center; justify-content:center;
    right:calc(16px + env(safe-area-inset-right));
    bottom:calc(16px + env(safe-area-inset-bottom) + var(--lift,0px));
    width:44px; height:44px; padding:0; border-radius:50%; cursor:pointer; color:var(--ink);
    background:linear-gradient(180deg, var(--panel-2), rgba(0,0,0,.22));
    border:1px solid var(--line); box-shadow:0 6px 18px rgba(0,0,0,.45);
    opacity:0; visibility:hidden; transform:translateY(10px);
    transition:opacity .18s ease, transform .18s ease, box-shadow .12s ease, color .12s ease,
      border-color .12s ease, bottom .3s ease, visibility 0s linear .18s}
  /* visibility 로 숨겨야 안 보이는 버튼이 탭 순서에 안 낀다. 사라질 때만 지연을 주고
     (페이드가 끝난 뒤 사라지게) 나타날 때는 지연 0 이라 즉시 포커스를 받는다. */
  .to-top.show{opacity:1; visibility:visible; transform:translateY(0);
    transition:opacity .18s ease, transform .18s ease, box-shadow .12s ease, color .12s ease,
      border-color .12s ease, bottom .3s ease, visibility 0s}
  .to-top:hover{color:var(--accent); border-color:var(--accent); box-shadow:0 8px 22px rgba(0,0,0,.5)}
  .to-top:active{transform:translateY(0) scale(.94)}
  .to-top:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
  .to-top svg{width:20px; height:20px; display:block}
  /* 화면만 올리고 포커스를 본문 아래 남겨두면 키보드 사용자는 제자리다 — 머리말로 옮긴다.
     그러려면 header 에 tabindex="-1" 이 필요하고, 그 부작용으로 마우스 클릭에도 테두리가
     생기므로 :focus 는 지우고 :focus-visible 만 남긴다. */
  header.top:focus{outline:none}
  header.top:focus-visible{outline:2px solid var(--accent); outline-offset:4px; border-radius:8px}
  @media (prefers-reduced-motion:reduce){
    .to-top{transform:none; transition:opacity .18s ease, visibility 0s linear .18s}
    .to-top.show{transform:none; transition:opacity .18s ease, visibility 0s}
    .to-top:active{transform:none}
  }
  @media (max-width:860px){
    .macro-strip{grid-template-columns:repeat(3,1fr)}
    .sim .s-ctl input[type=number]{width:86px}
    /* 배경 격자도 같이 줄인다 — 좁은 화면에서 176px 레티클 격자는 선이 두어 개만
       보여서 '격자'가 아니라 '줄 그은 자국'으로 읽힌다. 4배수 관계는 유지. */
    body{ --die:33px; --shot:132px }
  }
  @media (max-width:560px){
    /* 어원 줄의 종목 꼬리는 접는다. 좁은 화면에서 세 줄이 되면 각주가 아니라 문단이 된다. */
    .etym-more{display:none}
  }
  @media (max-width:480px){
    header.top h1{font-size:17px}
    .macro-strip{grid-template-columns:repeat(2,1fr)}
    .brand-lockup{gap:9px}
    .brand-mark{width:34px; height:34px}
    .brand-name{font-size:21px}
    .brand-sub{font-size:11px; margin-top:3px}
    .brand-etym{font-size:10px; margin-top:8px}
  }
