/* today-content/sheet-layout.css — 학습지/정답지 카드·격자·인쇄 레이아웃 CSS.
   원래 styles.css(오늘수학.html <style>에서 분리)의 "0. 하루수학.html에서 그대로
   복사한 카드/격자/인쇄 CSS" 섹션을 그대로 옮긴 것(2026-08-11, sheet-layout/app-ui
   2파일로 재분리). 절대 손대지 말 것 — 손대면 하루수학과의(또는 이제 오늘수학 자체
   JS 폭 계산 공식과의) 픽셀 단위 일치가 깨질 수 있음. */
/* =========================================================
   0. 하루수학.html에서 그대로 복사한 카드/격자/인쇄 CSS(하루수학.html 11~548행) — 절대
   손대지 말 것, 손대면 하루수학과의 픽셀 단위 일치가 깨질 수 있음
========================================================= */
  :root{
    --paper: #FBF7EE;
    --ink: #2C2A26;
    --ink-soft: #6B6559;
    --line: #DCD3BF;
    --grid-dash: #B9B9B9; /* 세로셈 자리 칸 점선 전용: 중립 회색(2026-08-14: 너무 연해 보인다는
      피드백으로 #E2E2E2→#B9B9B9로 진하게. border-style도 dashed→dotted로 바꿈, 아래
      .digit-box 참고 — 둘 다 순수 색상/스타일 값이라 칸 크기(JS 폭 추정)엔 영향 없음. */
    --teal: #2E6E62;
    --teal-deep: #1F4F46;
    --clay: #C9722F;
    --clay-soft: #F1E0CC;
    --paper-white: #FFFFFF;
    --box: 36pt; /* 자리 숫자 칸 크기: 2학년=36pt, 3~4학년=30pt (JS에서 학년 전환 시 갱신) */

    /* ---- 웹사이트 UI 색상(사이드바/버튼/카드). 인쇄되는 학습지 내부 색상은 위 변수를 그대로 씀. ---- */
    --primary: #2563EB;
    --primary-hover: #1D4ED8;
    --primary-light: #DBEAFE;
    --bg: #F9FAFB;
    --surface: #FFFFFF;
    --title: #1E293B;
    --body-text: #334155;
    --text-secondary: #64748B;
    --ui-border: #E5E7EB;
    --ui-divider: #F1F5F9;
    --success: #22C55E;
    --warning: #F59E0B;
    --error: #EF4444;
    --radius: 4px; /* 버튼/카드 모서리: 아주 살짝만 둥글게 */
  }
  *{box-sizing:border-box;}
  html,body{margin:0;padding:0;}
  /* iOS WebKit(특히 카카오톡 인앱 브라우저 WKWebView)의 자동 텍스트 확대(text autosizing)
     차단(2026-08-22, 사용자 리포트: "카카오톡 브라우저에서 폰트가 box보다 크게 나와서
     겹침"). 이 기능은 뷰포트가 좁을 때 가독성을 위해 브라우저가 임의로 폰트 크기를 키우는
     접근성 휴리스틱인데, 이 앱은 이미 CSS zoom으로 정밀하게 축소 렌더링하는 학습지라 이
     확대가 겹치는 버그로 이어진다 — 전역으로 꺼서 지정한 폰트 크기 그대로 나오게 한다. */
  html{ -webkit-text-size-adjust:100%; text-size-adjust:100%; }
  /* Noto Sans KR Regular 로컬 호스팅(2026-08-22, 배포 준비 — Google Fonts CDN 의존 제거).
     학습지(.sheet)는 전부 font-weight:400이라 이 굵기 하나면 충분(SIL OFL 1.1, Google
     Fonts 배포본과 동일 라이선스). 오늘수학.html의 Google Fonts <link>에서도 Noto Sans KR은
     뺐음 — 남은 CDN 링크는 Gowun Batang/Gothic A1(UI·idx-circle 전용, 로컬 미제공)만 담당. */
  @font-face{ font-family:'Noto Sans KR'; src:url('../fonts/noto-sans-kr-v39-korean-regular.woff2') format('woff2'); font-weight:400; font-style:normal; font-display:swap; }
  /* 학습지 기본 서체 = Noto Sans KR Regular(2026-08-21, 사용자 확정: 학습지 전체
     통일, 나눔스퀘어라운드·Gowun Batang·SUIT Variable 등 요소별 서체 지정 폐기).
     .sheet에만 지정하고 상속에 맡긴다(과거엔 `.sheet *`+`!important`로 모든 자손을
     강제 덮어썼으나, 이제 요소별 규칙이 전부 이 값과 같은 값을 직접 선언하고 있어
     불필요해짐 — 예외는 .idx-circle 하나뿐, 그 규칙 자체에서 처리). */
  .sheet{ font-family:'Noto Sans KR',sans-serif; font-weight:400; }
  body{ background: var(--bg); color: var(--body-text); min-height:100vh; }
  .app{ display:grid; grid-template-columns: 296px 1fr; min-height:calc(100vh - 76px); }
  .app.sidebar-hidden{ grid-template-columns: 1fr; }

  /* ---------- Topbar ---------- */
  .topbar{ display:flex; align-items:flex-end; gap:22px; padding:16px 28px; background:var(--title); border-bottom:1px solid #0F172A; position:sticky; top:0; z-index:10; }
  .topbar-brand{ font-family:'Gowun Batang',serif; font-weight:700; font-size:16px; color:#fff; margin-bottom:9px; margin-right:4px; white-space:nowrap; }
  .topbar-repeat-btn{ border:1px solid #475569; background:transparent; color:#E2E8F0; padding:9px 14px; font-size:12.5px; font-weight:600; cursor:pointer; font-family:inherit; margin-bottom:2px; }
  .topbar-repeat-btn:hover{ background:#334155; }
  .topbar-field{ display:flex; flex-direction:column; gap:4px; }
  .topbar-field label{ font-size:10.5px; color:#94A3B8; font-weight:600; letter-spacing:.02em; }
  .topbar-field select{ border:1px solid #CBD5E1; background:#fff; color:var(--title); padding:8px 10px; font-size:13px; font-weight:600; font-family:inherit; width:190px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .topbar-field select:focus{ outline:1.5px solid var(--primary); outline-offset:-1px; }
  .topbar-generate-btn{ margin-left:6px; border:2px solid #93C5FD; background:var(--primary); color:#fff; padding:12px 26px; font-size:15px; font-weight:800; cursor:pointer; font-family:inherit; margin-bottom:1px; box-shadow:0 0 0 3px rgba(37,99,235,.25); }
  .topbar-generate-btn:hover{ background:var(--primary-hover); }
  .topbar-generate-btn:hover{ background:var(--primary-hover); }

  /* 간편 모드: 학습지를 한 번 만들기 전까지는 사이드바(연습량/더보기)를 숨겨둔다. */
  .sidebar.simple-hide-until-generated.not-generated-yet{ display:none; }

  /* ---------- Sidebar ---------- */
  .sidebar{ background: var(--surface); color:var(--body-text); padding:16px 18px 32px; position:sticky; top:0; height:100vh; overflow-y:auto; border-right:1px solid var(--ui-border); }
  .brand{ font-family:'Gowun Batang',serif; font-size:18px; font-weight:700; line-height:1.35; color:var(--title); display:flex; align-items:flex-start; gap:8px; margin-bottom:6px; }
  .brand .dot{ width:8px;height:8px;background:var(--primary);display:inline-block; margin-top:6px; flex:none; }
  .brand-sub{ font-size:12px; color:var(--text-secondary); margin-bottom:20px; letter-spacing:.02em; }
  .field{ margin-bottom:0; padding:14px 0; border-bottom:1px solid var(--ui-divider); }
  .field-row{ display:flex; align-items:center; justify-content:space-between; gap:12px; }
  .field-row-label{ font-size:13px; font-weight:700; color:var(--title); }
  .field-select-inline{ border:1px solid var(--ui-border); background:var(--surface); color:var(--body-text); padding:8px 10px; font-size:14px; font-family:inherit; max-width:165px; }
  .field:first-of-type{ padding-top:0; }
  .field-label{ font-size:13px; font-weight:700; color:var(--title); margin-bottom:10px; display:flex; align-items:center; gap:6px; }
  .field-label .num{ font-family:'Gowun Batang',serif; color:var(--primary); font-size:12.5px; }
  .chip-group{ display:flex; flex-wrap:wrap; gap:0; border:1px solid var(--ui-border); }
  .chip{ border:none; border-right:1px solid var(--ui-border); background:transparent; color:var(--body-text); padding:6px 12px; font-size:12.5px; cursor:pointer; user-select:none; }
  .chip:last-child{ border-right:none; }
  .chip:hover{ background:var(--ui-divider); }
  .chip.active{ background:var(--title); color:#fff; }
  .chip.block{ width:100%; text-align:left; border-right:none; border-bottom:1px solid var(--ui-border); }
  .chip.block:last-child{ border-bottom:none; }

  /* 풀이 형태: 2×2 카드형 선택지. 각 칸에 그 형태로 실제 렌더링되는 미니 예시 문제를 넣어서
     글자 설명만으로는 잘 안 와닿는 "세로셈/가로셈, 자리선 있음/없음" 차이를 한눈에 보여준다. */
  .solveform-heading{ display:block; margin-bottom:9px; }
  .solveform-grid{ display:grid; grid-template-columns:1fr 1fr; gap:7px; }
  .solveform-tile{ border:1.5px solid var(--ui-border); border-radius:var(--radius); background:var(--surface); padding:8px 6px 7px; display:flex; flex-direction:column; align-items:center; gap:5px; cursor:pointer; user-select:none; }
  .solveform-tile:hover{ border-color:#93C5FD; background:var(--ui-divider); }
  .solveform-tile.active{ border-color:var(--primary); background:var(--primary-light); }
  .solveform-tile.disabled{ cursor:not-allowed; opacity:.4; }
  .solveform-tile.disabled:hover{ border-color:var(--ui-border); background:var(--surface); }
  .solveform-preview{ width:100%; height:56px; display:flex; align-items:center; justify-content:center; overflow:hidden; background:#fff; border:1px solid var(--ui-divider); border-radius:2px; }
  .solveform-preview .problem-card{ pointer-events:none; }
  /* 문제 번호(원문자)는 폰트 크기가 13pt 고정이라 --box를 줄여도 같이 안 줄어들어서, 작은
     미리보기 안에서는 유독 크게 튀어 보인다 — 미리보기에서는 "형태 비교"가 목적이라 번호
     없이 문제 모양만 보여준다. */
  .solveform-preview .problem-idx{ display:none; }
  /* .line-cell/.line-cell-minor의 border-top 두께(2.2pt/1.2pt)는 실제 학습지 박스 크기
     (28~36pt)에 맞춰 고정된 값이라, 미리보기의 훨씬 작은 --box(11px)에서는 그대로 쓰면
     글자 크기 대비 구분선이 두꺼워 보인다(실측·스크린샷으로 확인) — 미리보기 전용으로만
     비례해서 얇게 오버라이드한다(실제 학습지 CSS는 안 건드림). */
  .solveform-preview .line-cell{ border-top-width:0.8px; }
  .solveform-preview .line-cell-minor{ border-top-width:0.45px; }
  .solveform-tile-title{ font-size:11px; font-weight:700; color:var(--title); line-height:1.3; }
  .solveform-tile-sub{ font-size:9px; color:var(--text-secondary); line-height:1.25; }
  .solveform-note{ font-size:11px; color:var(--text-secondary); margin-top:8px; line-height:1.5; }
  .chip.half{ flex:0 0 50%; text-align:center; }
  .type-list{ display:flex; flex-direction:column; }
  .type-chip{ border:none; border-bottom:1px solid var(--ui-divider); background:transparent; padding:8px 2px; font-size:12.5px; line-height:1.4; cursor:pointer; display:flex; align-items:center; gap:8px; }
  .type-chip:last-child{ border-bottom:none; }
  .type-chip:hover{ background:var(--ui-divider); }
  .type-chip-main{ display:flex; align-items:flex-start; gap:8px; flex:1; min-width:0; }
  .type-chip .box{ width:12px;height:12px;border-radius:50%;border:1.5px solid var(--ui-border); flex:none; margin-top:2px; }
  .type-chip .type-label{ flex:1; color:var(--body-text); }
  .type-chip-limit{ display:flex; flex-direction:column; align-items:center; gap:2px; flex:none; }
  .type-chip-limit .limit-label{ font-size:9px; color:var(--text-secondary); white-space:nowrap; }
  .type-chip .type-chip-limit input.type-limit-input{ width:36px; height:20px; padding:0 4px; text-align:center; background:var(--surface); border:1px solid var(--ui-border); color:var(--body-text); font-size:11px; font-family:inherit; }
  .type-chip.active .box{ background:var(--primary); border-color:var(--primary); box-shadow:inset 0 0 0 2px #fff; }
  .type-chip.active .type-label{ color:var(--title); font-weight:600; }
  .group-label{ font-size:10.5px; text-transform:uppercase; letter-spacing:.08em; color:var(--text-secondary); margin:10px 0 4px; font-weight:700; }
  .group-label:first-child{ margin-top:0; }

  input[type=number]{ width:100%; background:var(--surface); border:1px solid var(--ui-border); color:var(--body-text); padding:8px 10px; font-size:14px; font-family:inherit; }
  .sub-label{ font-size:11.5px; color:var(--text-secondary); margin:10px 0 5px; }
  .day-type-row{ display:flex; align-items:center; gap:6px; padding:5px 0; border-bottom:1px solid var(--ui-divider); }
  .day-type-row .day-tag{ font-family:'Gowun Batang',serif; font-size:12px; color:var(--title); width:38px; flex:none; }
  .day-type-row select{ flex:1; min-width:0; background:var(--surface); border:1px solid var(--ui-border); color:var(--body-text); padding:6px 8px; font-size:12px; font-family:inherit; }
  .day-type-row select option{ color:var(--body-text); }
  .day-type-row input.day-count-input{ width:40px; flex:none; height:26px; padding:0 4px; text-align:center; background:var(--surface); border:1px solid var(--ui-border); color:var(--body-text); font-size:11.5px; font-family:inherit; }
  .hint{ font-size:11.5px; color:var(--text-secondary); margin-top:6px; line-height:1.5; }
  .hint b{ color:var(--primary); font-weight:700; }
  .toggle-row{ display:flex; align-items:center; background:transparent; border:none; padding:0; cursor:pointer; }
  .toggle-row-desc{ font-size:12.5px; color:var(--body-text); }
  .field-select{ width:100%; border:1px solid var(--ui-border); background:var(--surface); color:var(--body-text); padding:8px 10px; font-size:13px; font-family:inherit; }
  .switch{ width:34px; height:18px; background:var(--ui-border); position:relative; transition:.15s; flex:none; }
  .switch::after{ content:''; position:absolute; width:14px;height:14px;background:#fff; top:2px; left:2px; transition:.15s; }
  .toggle-row.on .switch{ background:var(--primary); }
  .toggle-row.on .switch::after{ left:18px; }

  .generate-btn{ width:100%; margin-top:16px; background:var(--primary); color:#fff; border:none; padding:12px; font-size:14px; font-weight:600; cursor:pointer; font-family:inherit; }
  .generate-btn:hover{ background:var(--primary-hover); }
  .generate-btn:disabled{ background:var(--ui-border); color:var(--text-secondary); cursor:not-allowed; }
  /* 정답지 확인하기 — 학습지 생성(파랑, primary)과 구분되는 보조 색(틸) — 정답지 관련
     요소(day-badge, answerkey-item b)에 이미 쓰던 색과 통일. */
  .generate-btn.secondary{ background:var(--teal); margin-top:8px; }
  .generate-btn.secondary:hover{ background:var(--teal-deep); }
  .generate-btn.secondary:disabled{ background:var(--ui-border); color:var(--text-secondary); cursor:not-allowed; }
  /* 화면(학습지/정답지)에 따라 "다음에 누를 만한" 버튼을 은은한 테두리로 강조하고,
     반대로 지금 누르면 위험한(=방금 만든 결과가 사라지는) 버튼은 눈에 덜 띄게 낮춘다
     (item #14 이어서, 2026-08-03 사용자 요청 — "실수로 학습지 생성하기를 누를 수 있음").
     needs-regen(주황, 깜빡임)과는 다른 색·비애니메이션으로 "위험 경고"와 "추천"을 구분함. */
  .generate-btn.suggested{ box-shadow:0 0 0 3px rgba(13,148,136,.28); }
  .generate-btn.muted{ background:var(--ui-border); color:var(--text-secondary); }
  .generate-btn.muted:hover{ background:var(--ui-border); }
  /* 학년/단원/용지/풀이형태 등 레이아웃에 크게 영향을 주는 설정을 바꾼 뒤,
     "다시 생성"을 눌러야 반영된다는 걸 눈에 띄게 알려준다. */
  .generate-btn.needs-regen, .topbar-generate-btn.needs-regen{
    background: var(--warning);
    box-shadow: 0 0 0 3px rgba(245,158,11,.3);
    animation: needsRegenPulse 1.6s ease-in-out infinite;
  }
  .generate-btn.needs-regen:hover, .topbar-generate-btn.needs-regen:hover{ background:#D97706; }
  @keyframes needsRegenPulse{ 0%,100%{ opacity:1; } 50%{ opacity:.78; } }
  .chip.chip-soon{ opacity:.55; font-style:italic; }
  .disabled-section{ opacity:.35; pointer-events:none; filter:grayscale(.4); }

  /* 간편 모드: 연습 유형 / 출력 형태 목록 (카드 대신 얇은 구분선 리스트) */
  .practice-type-grid{ display:flex; flex-direction:column; }
  .practice-card{ border:none; border-bottom:1px solid var(--ui-divider); background:transparent; padding:9px 2px; cursor:pointer; display:flex; align-items:flex-start; gap:10px; }
  .practice-card:last-child{ border-bottom:none; }
  .practice-card:hover{ background:var(--ui-divider); }
  .practice-card .icon{ display:none; }
  .practice-card .txt{ flex:1; }
  .practice-card::before{ content:''; width:12px; height:12px; border-radius:50%; border:1.5px solid var(--ui-border); flex:none; margin-top:2px; }
  .practice-card.active::before{ background:var(--primary); border-color:var(--primary); box-shadow:inset 0 0 0 2px #fff; }
  .practice-card .title{ font-weight:600; font-size:13px; color:var(--title); }
  .practice-card .desc{ font-size:11px; color:var(--text-secondary); margin-top:2px; line-height:1.4; }
  .practice-card.active .title{ color:var(--primary-hover); }
  .practice-card.soon{ opacity:.5; cursor:not-allowed; }
  .practice-card.soon .title::after{ content:' (준비중)'; font-size:10px; font-weight:400; color:var(--text-secondary); }

  .hint-inline{ font-size:10.5px; color:var(--text-secondary); font-weight:400; }

  /* 간편 모드: 문제 수 프리셋 */
  .preset-group{ display:flex; flex-direction:column; }
  .preset-btn{ border:none; border-bottom:1px solid var(--ui-divider); background:transparent; padding:9px 2px; cursor:pointer; display:flex; justify-content:space-between; align-items:center; }
  .preset-btn:last-child{ border-bottom:none; }
  .preset-btn:hover{ background:var(--ui-divider); }
  .preset-btn .title{ font-weight:600; font-size:13px; color:var(--title); display:flex; align-items:center; gap:8px; }
  .preset-btn .title::before{ content:''; width:12px; height:12px; border-radius:50%; border:1.5px solid var(--ui-border); flex:none; }
  .preset-btn.active .title::before{ background:var(--primary); border-color:var(--primary); box-shadow:inset 0 0 0 2px #fff; }
  .preset-btn .sub{ font-size:11px; color:var(--text-secondary); }
  .preset-btn.active .title{ color:var(--primary-hover); }

  .count-stepper{ display:flex; align-items:center; gap:8px; }
  .count-stepper button{ width:24px; height:24px; border:1px solid var(--ui-border); background:var(--surface); color:var(--title); font-size:14px; line-height:1; cursor:pointer; font-family:inherit; }
  .count-stepper button:hover{ background:var(--ui-divider); }
  .count-stepper button:disabled{ opacity:.35; cursor:not-allowed; background:var(--surface); }
  .count-stepper span[id$="ValueDisplay"]{ font-size:14px; font-weight:700; color:var(--title); min-width:20px; text-align:center; }

  /* 지난번처럼 만들기 / 더 자세히 설정하기 */
  .repeat-btn{ width:100%; border:1px solid var(--ui-border); background:transparent; color:var(--body-text); padding:8px 12px; font-size:12.5px; font-weight:600; cursor:pointer; font-family:inherit; text-align:left; }
  .repeat-btn:hover{ background:var(--ui-divider); }
  .more-settings-btn{ width:100%; border:none; background:none; color:var(--text-secondary); font-size:12px; font-weight:600; cursor:pointer; font-family:inherit; text-align:left; padding:2px 0; }
  .more-settings-btn:hover{ color:var(--title); }
  .sub-field{ margin-top:14px; }
  .sub-field .field-label{ font-size:12px; }

  /* 예상 출력 정보 */
  .print-info{ margin:14px 0 0; padding:10px 0 0; border-top:1px solid var(--ui-divider); font-size:11.5px; line-height:1.6; color:var(--body-text); }
  .print-info b{ color:var(--title); }

  /* 추천 배지 (다음 차시) */
  .next-badge{ display:inline-block; margin-left:6px; padding:1px 6px; border:1px solid var(--warning); color:var(--warning); font-size:10px; font-weight:700; vertical-align:1px; }

  /* 시작 화면: 카드 갤러리 대신 얇은 구분선 목록 */
  .example-section{ max-width:1180px; margin:24px auto 0; padding:0 24px; }
  .example-section .tagline{ font-family:'Gowun Batang',serif; font-weight:700; font-size:20px; color:var(--title); margin-bottom:4px; }
  .sample-gallery{ max-width:820px; margin:0 auto 32px; padding:0 24px; }
  .sample-gallery .sub{ color:var(--title); font-size:16px; font-weight:700; margin:16px 0 4px; }
  .example-label{ font-size:12px; color:var(--text-secondary); font-weight:600; margin:12px 0 8px; }
  .example-preview{ user-select:none; flex-wrap:nowrap !important; justify-content:flex-start !important; align-items:flex-start !important; overflow-x:auto; overflow-y:hidden; padding:4px 4px 12px; margin-bottom:4px !important; height:430px; box-sizing:content-box; }
  .preview-box{ display:flex; flex-wrap:nowrap; align-items:flex-start; gap:14px; }
  .example-preview .sheet{ border-color:var(--ui-border); }
  .sample-cards{ display:flex; border-top:2px solid var(--title); }
  .sample-card{ flex:1; background:transparent; border:none; border-right:1px solid var(--ui-border); padding:20px 16px; cursor:pointer; display:flex; flex-direction:column; align-items:flex-start; gap:8px; }
  .sample-card:last-child{ border-right:none; }
  .sample-card:hover{ background:var(--primary-light); }
  .sample-card.active{ background:var(--primary-light); }
  .sample-card.active .name{ color:var(--primary-hover); }
  .sample-card:hover .name{ color:var(--primary-hover); }
  .sample-card .icon{ display:none; }
  .sample-card .name{ font-weight:800; font-size:17px; color:var(--title); }
  .sample-card .quote{ font-size:13.5px; color:var(--body-text); line-height:1.5; }
  .sample-card .cta{ display:none; }
  .print-btn{ width:100%; margin-top:8px; background:transparent; border:1px solid var(--ui-border); color:var(--body-text); padding:10px; font-size:13px; cursor:pointer; font-family:inherit; font-weight:600; }
  .print-btn:hover{ background:var(--ui-divider); }

  /* ---------- Main / Preview ---------- */
  .main{ padding:16px 20px 40px; display:flex; flex-direction:column; align-items:center; }
  .main-header{ width:100%; max-width:1000px; margin-bottom:10px; display:flex; align-items:baseline; justify-content:space-between; gap:12px; }
  .main-header h1{ font-family:'Gowun Batang',serif; font-size:26px; margin:0 0 6px; color:var(--title); }
  .main-header p{ margin:0; color:var(--text-secondary); font-size:13.5px; }
  .back-to-worksheet-btn{ background:none; border:none; color:var(--primary); font-size:13.5px; font-weight:600; cursor:pointer; font-family:inherit; padding:4px 0; flex:none; }
  .back-to-worksheet-btn:hover{ text-decoration:underline; }

  .sheet-wrap{ background:transparent; padding:0; display:flex; flex-direction:column; align-items:center; gap:26px; width:100%; max-width:1000px; }
  .sheet-wrap.multi-page{ flex-direction:row; flex-wrap:wrap; align-items:flex-start; justify-content:center; max-width:100%; gap:20px 16px; }
  /* .sheet 내부도 위의 전역 프리텐다드 규칙을 그대로 따른다. */
  .sheet{ background:var(--paper-white); position:relative; border:1px solid var(--ui-border); transform-origin:top center; }
  /* ✅ 근본 해결(2026-07-28): 화면 미리보기 축소를 인라인 style.zoom 직접 세팅 대신 CSS
     커스텀 프로퍼티(--preview-zoom)로 값만 전달하고, 그 값을 실제 zoom으로 소비하는 규칙은
     @media screen 안에만 둔다. 예전엔 scaleSheets()가 .sheet에 인라인 zoom을 직접 박아넣고,
     인쇄 시점에 그 값을 JS로 "지우거나"(beforeprint 타이밍에 의존, 이벤트가 안 불리면 무용지물)
     인쇄 CSS에서 `!important`로 "덮어쓰는"(스펙상 인라인보다 우선하지만, 실제 사용자 환경의
     인쇄 파이프라인에서 재현이 안 되는 사례가 있었음 — 원인은 특정 못 함) 방식에 의존해야
     했다. 이 방식은 애초에 "인쇄 시엔 이 규칙 자체가 존재하지 않는다"를 media query 자체가
     구조적으로 보장하므로, 지우거나 덮어쓸 대상 자체가 없다 — 커스텀 프로퍼티(--preview-zoom)는
     여전히 인라인으로 남아있어도 아무 효과가 없다(그 값을 읽어서 실제 zoom을 적용하는 규칙이
     print 컨텍스트엔 없으므로). */
  @media screen{ .sheet{ zoom:var(--preview-zoom, 1); } }
  /* 소책자(제책) 인쇄 임포지션 전용 래퍼(fixes.md #46, 2026-08-16) — 인쇄 직전
     (오늘수학.html preparePrintLayout)에만 잠깐 #sheetWrap에 만들어졌다 인쇄가 끝나면
     사라지므로(화면 미리보기엔 절대 안 남음) media 제한 없이 일반 규칙으로 둬도 안전하다.
     A5(a4half, 148×210mm) 두 쪽을 가로로 나란히 붙인 물리적 한 장 크기 — 오늘수학.html의
     BOOKLET_SPREAD_W_MM(=PAPER_SIZES.a4half.w*2)과 반드시 같은 값이어야 한다. */
  .booklet-spread{ display:flex; width:296mm; height:210mm; }
  .sheet-wrap.multi-page .sheet, .example-preview .sheet{ transform-origin:top left; flex-shrink:0; }
  .page-tag{ position:absolute; top:-11px; left:14px; background:var(--teal-deep); color:#fff; font-size:10.5px; padding:3px 9px; border-radius:6px; font-weight:600; letter-spacing:.02em; }
  /* 페이지가 여러 장일 때만 붙는 하단 페이지 번호. .sheet-inner(overflow:hidden, flex column)
     안에 넣으면 기존 콘텐츠의 세로 flex 흐름에 끼어들어 밀리거나 잘릴 수 있어서, .page-tag와
     같은 방식으로 .sheet의 직계 자식(position:relative 기준 absolute)으로 둬서 완전히
     독립시킨다. .sheet-inner의 padding(--pad-y)이 이미 인쇄 여백으로 비워두는 영역이라
     그 안쪽에 자연스럽게 들어간다. .page-tag/.spiral와 달리 인쇄에도 보여야 하는 요소라
     `.spiral, .page-tag{display:none}`(@media print) 목록엔 포함하지 않는다. */
  .page-footer{ position:absolute; left:0; right:0; bottom:8mm; text-align:center; font-size:8.5pt; color:var(--ink-soft); font-variant-numeric:tabular-nums; letter-spacing:.02em; } /* 2026-08-14: 너무 아래(용지 끝에 붙어 보임)라는 피드백으로 4mm→8mm(용지 하단에서 더 띄움) */
  .spiral{ position:absolute; left:-13px; top:0; bottom:0; width:26px; display:flex; flex-direction:column; justify-content:space-evenly; align-items:center; }
  .spiral span{ width:13px;height:13px;border-radius:50%; background:var(--paper); border:2px solid #B9AE94; }
  /* 헤더 증가분을 위 여백에서 빼지 않는다. 상·하단 여백과 헤더 예약 공간을 모든
     페이지에서 동일하게 유지해, 문제 수에 따라 헤더가 줄어 보이지 않게 한다. */
  .sheet-inner{ width:100%; height:100%; padding:var(--pad-y) var(--pad-x); display:flex; flex-direction:column; overflow:hidden; }

  .sheet-title-row{ display:flex; justify-content:space-between; align-items:flex-end; border-bottom:1.5pt solid var(--ink); padding-bottom:8pt; margin-bottom:12pt; gap:12pt; } /* 2026-08-14: 두꺼워 보인다는 피드백으로 2.5pt→1.5pt(.sheet-header-main과 통일) */
  .sheet-title{ font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:15pt; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; }
  /* 요청: "정답지에도 차시명 똑같이 있어야해" — 정답지 헤더의 .sheet-title-row는 원래
     .sheet-title 하나뿐이라 justify-content:space-between이 아무 효과가 없었는데, 그 자연스러운
     빈 오른쪽 자리에 차시명을 얹는다(하루수학의 .sheet-crumb-unit과 같은 보조 텍스트 스타일). */
  .sheet-title-lesson{ font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:10.5pt; color:var(--ink-soft); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:55%; flex-shrink:0; }

  /* 학습지 헤더 2단 구성(2026-08-15 재설계 → 2026-08-16 QR/이름 자리 교체, 둘 다 사용자
     지정 — 이전 구조/이력은 history.md 참고). 위 줄(.sheet-header-top): 브랜드("오늘수학")는
     왼쪽 끝, QR은 오른쪽 끝(경계선 없는 얇은 줄). 아래 줄(.sheet-header-main): 왼쪽=단원명
     (+선수학습이면 그 아래 작은 표기), 가운데=차시명(이 헤더에서 가장 강조하는 정보로
     승격), 오른쪽=이름 칸(2026-08-16: QR과 자리를 맞바꿔 아래 줄로 내려옴).
     문제 영역과 맞닿는 경계선(border-bottom)은 .sheet-header-main에만 있다. 헤더 전체
     높이는 페이지마다 같은 공간으로 예약되며, 문제 수가 많아도 헤더는 축소되지 않는다. */
  /* 위쪽 줄(브랜드/QR) 아래 회색 구분선은 뺌(사용자 지정, 2026-08-18) — 아래
     .sheet-header-main이 사방 테두리 박스라 그 자체로 이미 시각적 경계가 뚜렷해서, 위
     줄까지 선이 있으면 선이 두 개로 겹쳐 보였다. */
  .sheet-header-top{ display:flex; flex:0 0 auto; justify-content:space-between; align-items:center; padding-bottom:3pt; margin-bottom:4pt; }
  .sheet-brand{ align-self:flex-end; font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:9pt; letter-spacing:-0.01em; color:var(--ink); }

  /* 헤더 박스(2026-08-18 재설계, 사용자 지정: 사방 테두리 + 칼럼 사이 점선 구분선 —
     이미지 목업 기준). 이전엔 border-bottom 한 줄만 있었지만 이제 .sheet-header-main
     자체가 사방 테두리 박스가 되고, 칼럼 사이 점선(border-right dotted)이 안쪽 구분선
     역할을 한다. column-gap을 없애고 각 칼럼에 동일한 좌우 padding을 줘서 점선이 항상
     간격 중앙이 아니라 칼럼 경계에 딱 붙게 한다. */
  /* 칼럼 비율 1:3:1(사용자 지정, 2026-08-18 — 이미지 목업처럼 가운데 차시 칼럼을 훨씬
     넓게). 이전 1:1.3:1보다 가운데가 확 넓어져서 배지+제목이 길어져도 옆 칼럼을 안 밀어낸다. */
  /* 헤더 박스는 차시명 괄호 설명 유무와 무관하게 25mm를 유지한다. 세로 padding을 없애면
     칼럼의 점선이 바깥 테두리 안쪽 끝까지 온전히 이어지고, 각 칼럼의 flex 중앙정렬이
     내용을 고정 높이의 정확한 세로 가운데에 둔다. */
  .sheet-header-main{ display:grid; flex:0 0 25mm; grid-template-columns:minmax(0,1fr) minmax(0,3fr) minmax(0,1fr); align-items:stretch; column-gap:0; height:25mm; border:1.3pt solid var(--ink); padding:0; margin-bottom:12pt; }
  /* 좌/우 칼럼이 항상 같은 폭(1fr)을 갖도록 `minmax(0,Nfr)`을 쓴다 — 2026-08-14 grid
     재작업에서 확인된 두 함정(순수 `1fr auto 1fr`은 min-content 차이만큼 어긋나고, 가운데를
     `auto`로 두면 긴 차시명이 옆 칼럼을 침범함, 자세한 실측은 history.md 참고)이 이
     재설계에도 그대로 적용되므로 `minmax(0,Nfr)` 3트랙 + 가운데 비율 2배 구조를 유지한다. */
  /* align-items:stretch(그리드 컨테이너) + 각 칼럼 내부 justify-content:center: 점선
     구분선(border-right)이 박스 전체 높이만큼 이어지게 하면서, 그 안의 내용(텍스트
     줄들)은 칼럼 높이 가운데에 오게 한다(사용자 지정, 2026-08-18 — 이미지 목업 기준). */
  .sheet-header-left{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2pt; min-width:0; padding:0 12pt; border-right:0.75pt solid var(--ink-soft); }
  /* 단원명이 길면 잘리는(ellipsis) 대신 글씨 크기를 줄인다(사용자 지정, 2026-08-15) —
     실제 font-size는 pageHeaderHTML()이 텍스트 길이 기준으로 인라인 style에 넣는다
     (unitHeadlineFontSizePt() 참고). 여기 12pt는 짧은 단원명(기본 케이스) 기준값 —
     2026-08-18 헤더 재설계에서 단원명이 "번호+주제"/"괄호 설명" 두 줄로 나뉘며 한 줄당
     글자 수가 줄어든 만큼, 또 사용자가 "이미지보다 살짝 작아도 된다"고 확인해서 기존
     13.5pt 기준에서 한 단계씩 낮춤(unitHeadlineFontSizePt() 참고). */
  .sheet-header-unit{ font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:12pt; color:var(--ink); white-space:nowrap; max-width:100%; }
  .sheet-header-grade-semester{ font-size:9.5pt; font-weight:400; color:var(--ink-soft); white-space:nowrap; }
  /* 단원명 괄호 설명(예: "(1)")을 별도 줄로 분리(2026-08-18, 사용자 지정: "두줄분리는
     맞고") — 단, 단원 번호 자체("N.")는 지워지지 않고 첫 줄(.sheet-header-unit)에 그대로
     남는다("단원번호는 필요해"). splitTrailingParen()으로 나눔, .sheet-crumb-paren과
     같은 보조 텍스트 위계(연하고 작게)이되 왼쪽 칼럼 전용 클래스로 분리해 독립 조정 가능. */
  .sheet-header-unit-paren{ font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:9pt; color:var(--ink-soft); white-space:nowrap; }
  .sheet-header-prereq-tag{ font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:9pt; color:var(--ink-soft); white-space:nowrap; }
  .sheet-header-center{ display:flex; flex-direction:column; align-items:center; justify-content:center; min-width:0; width:100%; text-align:center; padding:0 12pt; border-right:0.75pt solid var(--ink-soft); }
  /* justify-self:center(=shrink-to-fit 폭)였을 때, 차시명이 길면 그 텍스트가 자기 grid
     트랙 폭보다 더 넓게 계산되면서 overflow:hidden이 안 걸리고(부모 자체가 아직 좁게
     shrink되기 전 상태로 잘못 sizing됨) 오른쪽 칼럼(QR 캡션)을 침범하는 문제가 실측으로
     확인됨(6px 겹침). width:100%로 grid 트랙 폭을 그대로 확정 폭으로 채우면(justify-self
     기본값 stretch와 동일한 효과), 자식(.sheet-crumb)의 줄바꿈이 항상 그 확정 폭 기준으로
     정상 작동한다. */
  /* 차시명(가운데): 예전엔 단원명 아래 보조 정보(11.5pt, 연한 색)였지만, 단원명 줄이
     "N. 주제" 한 줄로 짧아지고 가운데 칼럼이 헤더의 주 정보가 되면서 볼드·진한 색·큰
     크기로 승격(사용자 지정, 2026-08-15: "오늘수학 글씨 작게, 차시 제목 글씨는 크게").
     ⚠️ 말줄임(ellipsis)로 잘리면 안 된다는 사용자 지정(2026-08-15) — 캔버스로 실측해
     폰트를 줄여 한 줄에 욱여넣는 방식도 시도했었지만(2026-08-16) 사용자가 "그냥
     줄바꿈해도 될 것 같다"고 되돌려서, white-space:nowrap+overflow:hidden을 다시 빼고
     자연스럽게 줄바꿈되게 둔다. 길어지면 이 칸(row2) 높이가 늘어날 수 있는데, 문제 영역
     높이는 growBlocksToFit이 항상 실제 렌더링을 재서 정하므로(CLAUDE.md "실측 방식"
     원칙) 별도 대응 없이도 안전하다. */
  /* 요청(2026-08-17): "차시명 줄바꿈은 줄게, 가로 길이는 원래대로" — max-width(폭)는 그대로
     두고 word-break만 keep-all로: 기본 CJK 줄바꿈 규칙은 숫자+한글 사이도 아무 데서나
     끊어서(예: "올림 1|회)") 어색했다. keep-all은 공백에서만 끊는다. */
  /* 차시 배지(2026-08-18 헤더 재설계, 사용자 지정 반영) — "N-M 제목" 형식에서 단원 번호
     (N)는 이제 왼쪽 칼럼에 나오므로 여기선 뺀다. 차시 순번(M)만 2자리("01")로 잘라
     제목 왼쪽에 연한 회색 배지로 붙인다(splitLessonBadge() 참고). 혼합/단원정리/선수학습
     등 이 형식이 아닌 라벨은 배지 없이 제목만 보인다. */
  .sheet-crumb-row{ display:flex; align-items:baseline; justify-content:center; gap:6pt; max-width:100%; min-width:0; }
  .sheet-crumb-badge{ font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:15pt; color:#8A8479; flex:none; font-variant-numeric:tabular-nums; }
  .sheet-crumb-title{ font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:18pt; line-height:1.25; color:var(--ink); max-width:100%; word-break:keep-all; }
  /* 차시명 괄호 설명(예: "(올림)")은 제목과 같은 줄에서 함께 굵게 읽히면 정작 핵심 연산
     이름보다 부연설명이 시각적으로 더 무거워 보였다(사용자 지정) — 별도 줄로 내리고
     작게·연하게·비볼드로 낮춰서 보조 정보라는 위계를 분명히 한다. display:block이라
     부모(.sheet-header-center)의 text-align:center를 그대로 물려받아 가운데 정렬된다. */
  .sheet-crumb-paren{ display:block; font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:11pt; color:var(--ink-soft); margin-top:2pt; }
  /* 오른쪽 칼럼(2026-08-18 재설계, 사용자 지정: "날짜 한 줄 이름 한 줄") — 이전엔 이름
     칸 한 줄뿐이었지만 이제 .sheet-field-box를 두 번 세로로 쌓는다. 그대로 재사용 가능한
     이유: #sheetWrap.name-off .sheet-field-box{display:none;} 토글이 이미 클래스 셀렉터
     기준이라 두 줄 다 같이 켜지고 꺼짐(별도 상태 추가 없이 기존 토글 재사용, CLAUDE.md
     "state 스코프 추적 원칙" 상 새 플래그가 필요 없는 경우 — 두 줄이 항상 함께 나타나고
     사라지는 게 의도이므로). */
  .sheet-header-right{ display:flex; flex-direction:column; justify-content:center; align-items:stretch; gap:0; padding:0; }
  /* QR(2026-08-16, 위 줄로 이동): 글씨(캡션)가 QR 왼쪽, QR 사각형이 오른쪽 — HTML에서도
     .qr-caption을 .qr-canvas보다 먼저 써서 자연스러운 flex 순서로 왼쪽에 오게 한다. 캡션은
     "N쪽"/"정답" 두 줄(<br>, applyQrCodes() 참고) — 한 줄 표기였을 때보다 세로 공간이
     필요해서 QR 사각형 높이(12mm)에 맞춰 가운데 정렬. */
  .qr-block{ --qr-size: 12mm; display:flex; align-items:center; gap:5pt; flex:none; }
  .qr-canvas{ width:var(--qr-size); height:var(--qr-size); flex:none; }
  .qr-canvas svg{ display:block; width:100%; height:100%; }
  .qr-caption{ font-size:7.5pt; color:var(--ink-soft); letter-spacing:.02em; font-variant-numeric:tabular-nums; line-height:1.25; text-align:right; white-space:nowrap; }
  /* 이름/날짜 칸: 2026-08-15에 회색 테두리 박스·밑줄을 없앤 민무늬 형태로 바꿨고,
     2026-08-16에 QR과 자리를 맞바꿔 아래 줄(.sheet-header-main) 오른쪽으로 옮김 — 둘 다
     사용자 지정. 2026-08-18: 오른쪽 칼럼이 세로 2줄(날짜/이름)이 되며 행 높이를
     14pt→12pt로 살짝 줄여 두 줄이 이전 한 줄과 비슷한 세로 공간 안에 들어오게 함. */
  .sheet-field-box{ display:flex; align-items:center; justify-content:flex-start; width:100%; height:50%; min-width:0; padding:0 12pt; }
  .sheet-field-box + .sheet-field-box{ border-top:0.75pt solid var(--ink-soft); }
  .sheet-field-label{ font-family:'Noto Sans KR',sans-serif; font-size:11pt; font-weight:400; color:var(--ink); white-space:nowrap; }
  .sheet-field-blank{ display:none; }
  /* 단원 진단평가: 기록값을 쓸 수 있도록 분·초 바로 앞에 짧은 여백을 둔다. */
  .sheet-time-field{ gap:4pt; padding-left:6pt; }
  .sheet-time-blank{ display:inline-block; width:13pt; height:1em; flex:0 0 13pt; }
  /* 일반 학습지 날짜: 날짜·월·일 사이에 학생이 적을 짧은 여백을 둔다. */
  .sheet-date-field{ gap:4pt; padding-left:6pt; }
  .sheet-date-blank{ display:inline-block; width:16pt; height:1em; flex:0 0 16pt; }
  .sheet-name-field{ padding-left:6pt; }

  /* fixes.md #45(2026-08-15): QR 코드·이름 칸 전역 토글 — 3단계 패널의 스위치가
     `#sheetWrap`에 `qr-off`/`name-off` 클래스를 붙였다 뗐다 하는 것만으로 순수 CSS로
     숨긴다(JS에서 pageHeaderHTML()이 아예 안 그리게 만드는 대신 이 방식을 쓴 이유는 아래
     참고). `.qr-block`(.sheet-header-top 오른쪽)/`.sheet-field-box`(.sheet-header-main
     오른쪽 칼럼)가 사라져도 그 자리가 비어 보일 뿐 grid 트랙 폭(1fr) 자체나 헤더 높이는 안 바뀐다
     — 위 "header mm 예산" 주석에 이미 적혀 있듯 그 예산은 실제 렌더링 높이를 재서 정하는
     게 아니라 `marginsFor`의 고정값(A4=24mm 등)이라 애초에 헤더 안 콘텐츠 유무와 무관하다.
     ⚠️ 왜 JS에서 조건부로 렌더링하지 않고 CSS 토글을 택했는지: `pageHeaderHTML()`이 만든
     HTML은 `itemCache`(카트 항목별 생성 결과 캐시)에 그대로 저장된다 — 토글을 JS 렌더링
     조건으로 두면 이미 캐시된 항목은 "생성하기"를 다시 눌러 캐시를 비우기 전까지 토글이
     반영 안 되고(=한 세션 앞서 발견한 "캐시가 자기 나이를 모르는" 문제가 그대로 재발),
     심지어 "생성하기"를 누르면 문제 자체가 새로 무작위 추첨되어 버려 순수 화면 옵션 하나
     때문에 이미 골라둔 문제 숫자가 바뀌는 부작용까지 생긴다. CSS 클래스 토글은 이미 그려진
     DOM(`#sheetWrap`)에 즉시 적용되고 캐시된 HTML 문자열 자체는 안 건드리므로 이 문제가
     구조적으로 없다. */
  #sheetWrap.qr-off .qr-block{ display:none; }
  #sheetWrap.name-off .sheet-field-box{ display:none; }
  /* 소책자(A5) 모드 전용(fixes.md #46 후속, 2026-08-16, 사용자 지정) — 이름 칸·QR을 표지
     하나에 모으므로 낱장(내용 페이지)마다 반복해서 그릴 필요가 없다. `.booklet-cover`
     자체(표지)는 `.sheet-field-box`/`.qr-block`을 애초에 안 갖고 있어(각자 `.cover-name`/
     `.cover-qr-row`를 따로 씀) 이 규칙과 안 겹친다 — `:not(.booklet-cover)`는 안전을 위해
     명시. `qr-off` 전역 토글을 켜면(사용자가 QR 자체를 끄면) 표지의 `.qr-block`도 같은
     `.qr-off` 규칙(위)에 걸려 같이 사라진다. */
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-field-box{ display:none; }
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .qr-block{ display:none; }
  /* 요청(2026-08-17): "소책자 각 페이지에 단원명 없애고 차시명은 한줄 안에 들어가게" —
     단원명은 이미 앞표지에 한 번 나오니 낱장마다 반복할 필요가 없다(이름 칸·QR을 표지로
     모은 것과 같은 이유). 단원명이 빠지면 왼쪽 칼럼이 거의 비므로(선수학습 배지만 남을 수
     있음) 가운데(차시명) 칼럼 비율을 확 늘려서 A5의 좁은 폭에서도 괄호 앞 본문이 한 줄에
     들어갈 여유를 준다(괄호 설명은 애초에 별도 줄로 빠지므로 이 폭 계산엔 안 낀다,
     .sheet-crumb-paren 참고). */
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-header-unit{ display:none; }
  /* 2026-08-18 헤더 재설계로 단원명 괄호 설명이 별도 줄(.sheet-header-unit-paren)로
     분리되면서, 위 줄을 숨길 때 이 괄호 줄만 혼자 남아 떠 보이는 걸 막기 위해 같이 숨김. */
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-header-unit-paren{ display:none; }
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-header-main{ grid-template-columns:minmax(0,0.4fr) minmax(0,2.2fr) minmax(0,0.4fr); }
  /* 소책자는 차시명만 빠르게 읽히도록 한 칸 헤더로 단순화한다. 일반 A4 학습지에는
     영향을 주지 않는다. */
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-header-main{
    flex-basis:22mm;
    grid-template-columns:minmax(0,1fr);
    height:22mm;
    border-width:1pt;
    margin-bottom:10pt;
  }
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-header-left,
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-header-right{ display:none; }
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-header-center{
    grid-column:1;
    padding:0 8pt;
    border-right:0;
  }
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-crumb-prefix,
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-crumb-badge{ display:none; }
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-crumb-row{ gap:0; }
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-crumb-title{ font-size:16pt; }
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-crumb-paren{ font-size:10pt; margin-top:1pt; }
  /* 요청(2026-08-17 후속 2건): "윗쪽 선 없애기" → "오늘수학도 없어도 될 것 같은데" —
     QR(이미 표지로 이동)·이름 칸이 빠지고 나면 이 줄엔 브랜드 텍스트 "오늘수학"만 남는데,
     그것도 앞표지에 이미 있어 반복이라 같이 뺀다. 두 자식이 다 사라지므로 줄 자체
     (.sheet-header-top, 구분선+padding+margin 전부)를 통째로 접어서 빈 여백이 남지 않게
     한다. 차시명 아래 굵은 선(.sheet-header-main)은 문제 영역과의 실질적 경계라 그대로 둔다. */
  #sheetWrap.booklet-active .sheet:not(.booklet-cover) .sheet-header-top{ display:none; }
  /* 표지에 모인 QR — 헤더용 12mm보다 크게, 캡션은 아래로(세로 배치) — applyQrCodes()가
     소책자 모드를 감지하면 여기(.cover-qr-row)에 청크별로 `.qr-block`을 채워 넣는다. */
  .cover-qr-row{ display:flex; flex-wrap:wrap; justify-content:center; gap:12pt; margin-top:38pt; }
  .cover-qr-row .qr-block{ --qr-size:18mm; flex-direction:column; align-items:center; gap:3pt; }
  .cover-qr-row .qr-caption{ font-size:7pt; color:var(--ink-soft); text-align:center; }

  /* 특정 차시(예: g1_three_add_sum10)만 헤더 바로 아래에 붙는 볼드체 풀이 안내 문구
     (instructionNoteHTML() 참고, 이 유형을 단독으로 볼 때만 붙음). .sheet-header-main의
     margin-bottom(12pt)이 이미 위쪽 간격을 만들어주므로 이 요소는 아래쪽 간격만 준다. */
  .sheet-instruction{ font-weight:400; font-size:11.5pt; margin-bottom:10pt; }

  .day-header{ display:flex; align-items:center; gap:10pt; border-bottom:2.5pt solid var(--ink); padding-bottom:10pt; margin-bottom:16pt; }
  .day-badge{ font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:18pt; background:var(--teal); color:#fff; padding:6pt 16pt; border-radius:8pt; flex:none; white-space:nowrap; }
  .day-sub{ font-size:10pt; color:var(--ink-soft); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; flex:1; }
  .day-name{ margin-left:auto; font-size:10pt; color:var(--ink-soft); }
  .day-name .line{ display:inline-block; width:70pt; border-bottom:1px solid var(--ink-soft); margin-left:4pt; }

  .cover-page{ flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; gap:6pt; }
  /* 요청(2026-08-17): "표지에 '오늘수학'은 학습지랑 같은 폰트로" — 학습지 낱장 헤더의
     브랜드 텍스트(.sheet-brand)와 같은 폰트로 맞춤. 이전엔 font-family 지정이 없어 상속된
     기본 폰트로 나왔었다. */
  .cover-eyebrow{ font-family:'Noto Sans KR',sans-serif; font-size:11pt; letter-spacing:.1em; color:var(--teal); font-weight:400; }
  .cover-grade-semester{ font-family:'Noto Sans KR',sans-serif; font-size:22pt; font-weight:400; color:var(--ink); margin-top:2pt; }
  .cover-unit-title{ font-family:'Noto Sans KR',sans-serif; font-size:30pt; font-weight:400; line-height:1.25; color:var(--teal-deep); }
  .cover-name{ margin-top:34pt; font-size:12.5pt; }
  .cover-name .line{ display:inline-block; width:150pt; border-bottom:1.4pt solid var(--ink); margin-left:6pt; }
  .cover-info{ margin-top:34pt; font-size:11pt; color:var(--ink-soft); border-top:1px dashed var(--line); padding-top:14pt; }

  .closing-page{ flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; gap:8pt; }
  .closing-brand{ font-family:'Noto Sans KR',sans-serif; font-size:20pt; color:var(--teal-deep); }
  .closing-msg{ font-size:23pt; font-weight:400; }
  .closing-sub{ font-size:11pt; color:var(--ink-soft); }

  .problem-grid{ flex:1; display:flex; flex-wrap:wrap; align-content:space-evenly; justify-content:flex-start; min-height:0; }
  .problem-groups{ flex:1; display:flex; flex-direction:column; justify-content:space-evenly; min-height:0; }
  .problem-group{ flex:0 0 auto; display:flex; flex-wrap:wrap; align-content:space-evenly; justify-content:flex-start; min-height:0; }
  /* 가로셈(스스로) 전용 2단 배치: 워드 문서의 "2단 나누기"처럼 전체 폭을 반으로 나눠 왼쪽 단을
     채우고 오른쪽 단을 채운다. 각 단 안에서는 세로로 space-between(균등 분배), 가로로는
     항상 단의 왼쪽 가장자리에 맞춘다(가운데 정렬 아님). */
  .problem-cols2{ flex:1; display:flex; justify-content:space-between; gap:28pt; min-height:0; position:relative; }
  /* space-between이 아니라 space-evenly로 둔다 — space-between은 "카드 사이"에만 남는 공간을
     나눠주고 맨 위 카드 앞/맨 아래 카드 뒤에는 아무것도 안 남기지만(실측: 카드 사이 61.6px vs
     맨 아래 카드 뒤 0px), space-evenly는 앞/사이/뒤를 전부 똑같이 취급해서 자동으로 균등하게
     나눠준다. 페이지가 목표 문제 수(state.count)로 인해 실제 최대 용량보다 적게 찼을 때도
     남는 공간을 전부 그 균등 배분에 맡기므로, 페이지가 꽉 차 보이는 기존 느낌도 그대로 유지된다
     (margin-bottom을 카드에 직접 고정으로 넣는 방식도 시도했었는데, 그러면 목표 문제 수가 실제
     용량보다 적을 때 남는 공간이 균등 배분되지 않고 맨 아래에 큰 빈 칸으로 그대로 남아서 되돌림). */
  .problem-cols2 .col2{ flex:1 1 0; min-width:0; display:flex; flex-direction:column; justify-content:space-evenly; align-items:flex-start; }
  .problem-card{ display:flex; flex-direction:column; }
  /* 2단 가로셈 레이아웃에서는 각 문제 카드 자체에 풀이 여백(padding-bottom, 테두리 없는 빈 영역)을
     포함시킨다 — space-evenly와 합쳐지면 몇 번째 카드든(맨 아래 카드 포함) "카드+풀이 여백+균등
     배분된 간격"이 항상 똑같은 크기의 구역이 된다. */
  .col2 .problem-card{ padding-bottom:var(--box); }
  /* 2026-08-15(사용자 지정): 가로셈에서 원문자가 식보다 살짝 처져 보인다는 피드백 — 실측
     결과 `.problem-idx`(wrapper) 자체는 `.horiz-eq`와 정확히 같은 y중심에 있었는데(2pt
     diffs.circleVsEq는 0), 그 *안의* `.idx-circle`(inline-flex span)이 wrapper 안에서
     2.25px 아래로 치우쳐 있었다 — `.problem-idx`가 `display:flex`가 아니라 일반 block이라,
     자식인 `.idx-circle`(inline-level box)의 세로 위치가 flex 중앙정렬이 아니라 CSS 인라인
     baseline 정렬 규칙을 따랐기 때문(부모의 font-size:13pt/line-height:1 스트럿 기준
     baseline에 맞춰지는데, 이게 wrapper 박스의 기하학적 중앙과 정확히 일치하지 않음).
     `.problem-idx` 자신을 flex 컨테이너로 바꿔서(align-items:center) 자식을 baseline이
     아니라 항상 박스 중앙에 정확히 놓이게 한다 — justify-content는 안 줌(기본값
     flex-start 유지: combine/split처럼 이 안에 idx 하나만 왼쪽 정렬로 있던 기존 배치를
     그대로 보존하기 위함, 이 규칙엔 자식이 항상 하나뿐이라 justify-content 자체가 결과에
     영향 없음 — 안전하게 생략). 이 변경으로 `.problem-idx` 자신의 렌더링 높이가 바뀌므로
     idxRowPx(오늘수학.html cardPixelSize)도 반드시 재측정해서 같이 갱신할 것. */
  .problem-idx{ display:flex; align-items:center; font-family:'Noto Sans KR','Gothic A1',sans-serif; font-weight:400; font-size:13pt; line-height:1; color:var(--teal); margin-bottom:4pt; }
  /* circledNumber()가 유니코드 원문자 글자 대신 이 클래스로 원을 직접 그린다(2026-08-11,
     사용자 리포트: 폰트마다 원문자 글리프 커버리지가 달라 숫자 모양이 칸마다 들쭉날쭉했고,
     50 넘으면 원문자 자체가 없었음). 숫자는 항상 이 규칙이 지정하는 폰트 하나로만 그려짐
     — 글자별 폰트 폴백 자체가 안 생김.
     - width/height는 반드시 같은 값(진짜 원, border-radius:50%)이어야 하고, **pt 고정값
       (em 아님)** — 처음엔 width/height/font-size를 전부 em(자기 자신의 font-size 기준)
       으로 엮었다가, font-size를 줄이면 width/height의 em 기준값 자체도 같이 줄어들어
       버려(중첩 compounding) "숫자도 작게, 원도 살짝만 작게"를 의도했는데 원이 의도보다
       훨씬 작아지는 실수를 한 번 했다 — 그래서 원 크기(width/height)와 글자 크기
       (font-size)를 서로 무관한 pt 값으로 완전히 분리해서 각각 독립적으로 조정 가능하게
       바꿈.
     - 숫자가 원을 "너무 꽉 채운다"는 지적으로 fill ratio(숫자 높이/원 지름)를 약 0.6까지
       낮춤, 중심이 살짝 위로 치우쳐 보인다는 지적은 실측(Range로 순수 텍스트 바운딩
       박스 측정)해서 원인(줄 상자 자체가 아니라 미세한 폰트 메트릭 차이)을 확인 후
       font-size를 낮추는 과정에서 함께 해소됨(재실측 dx/dy 둘 다 0.01px 이내).
     - 폰트는 Gothic A1(부모 .problem-idx의 SUIT Variable 우선 스택과 다름 — 이 원
       안에서만 오버라이드, 다른 곳엔 영향 없음). SUIT/Gothic A1/Noto Sans KR 세 폰트로
       "99"를 직접 렌더링해서 비교한 결과, Gothic A1이 원 안 여백이 가장 넉넉해서
       (SUIT는 중간, Noto Sans KR은 오히려 더 빽빽함) 선택함(사용자 지정, "끼여있는
       느낌"이라 폰트를 바꿔달라는 요청).
     이 폭(width)은 cardPixelSize()의 idxWidthPx(가로셈 카드 폭 추정)가 실측해서 참조하는
     값이므로, 이 규칙(특히 width)을 바꾸면 idxWidthPx도 반드시 다시 실측해서 맞출 것. */
  /* ✅ 2026-08-15(사용자 지정): "원이 아직도 크다, 더 줄여줘" — 14.25pt(19px)→12pt(16px)로
     추가로 줄임(-3px). font-size는 채움비(0.56 정도)를 유지하도록 같은 비율로 8pt→6.75pt.
     width가 바뀌었으므로 cardPixelSize()의 idxWidthPx(가로셈 카드 폭 추정)·idxRowPx(세로
     배치 카드 높이 추정, combine/split과 공유)를 반드시 같이 재실측해서 맞출 것(둘 다
     오늘수학.html에서 재조정 완료, 실제 브라우저 렌더링으로 idxRowPx 재측정함). */
  /* 2026-08-15: 숫자가 원 기하학적 중앙보다 살짝 위에 있다는 피드백(실측으로도 -0.5px
     확인됨, Gothic A1 숫자 글리프의 ascent/descent가 완전히 대칭이 아니라서 생기는 흔한
     현상) — padding-top만 살짝 줘서(align-items:center가 border-box에서 padding을 뺀
     content box를 기준으로 중앙정렬하므로, 위쪽 padding을 늘리면 그만큼 content box
     자체가 아래로 내려가 숫자도 같이 내려감) 광학적으로 중앙에 오도록 보정. */
  /* font-family만 학습지 기본값(Noto Sans KR)의 예외 — 실측 비교로 확정된 값이라 유지
     (SUIT/Gothic A1/Noto Sans KR 세 폰트로 "99"를 직접 렌더링 비교, Noto Sans KR이 원 안
     여백이 가장 빽빽해서 제외하고 Gothic A1 선택, 아래 상세 주석 참고). */
  .idx-circle{ display:inline-flex; align-items:center; justify-content:center; width:12pt; height:12pt; border-radius:50%; border:0.9pt solid currentColor; box-sizing:border-box; padding-top:2pt; font-size:6.75pt; font-weight:400; font-family:'Gothic A1',sans-serif; font-variant-numeric:lining-nums tabular-nums; }
  /* 2026-08-14: "원이 너무 크고 숫자가 가운데가 아니다"는 피드백 — DOM 레벨 중앙정렬 자체는
     정밀 측정으로 이미 확인됨(Range bounding box 기준 dx/dy 0.01~0.03px, 이전 세션과 동일
     결과 재확인). 원(width/height)은 그대로 두고(바꾸면 idxWidthPx/idxRowPx 재실측 필요)
     font-size만 6.55pt→8pt(채움비 0.46→0.56)로 키워서 숫자가 원 안에서 "떠 보이는" 느낌을
     줄임 — 채움비가 낮을수록 주변 여백이 비대칭으로 보이는 착시가 커지므로, 채움비를 높이는
     쪽이 "크다"와 "안 가운데" 두 불만을 동시에 완화한다. font-weight도 700으로(다른 문제
     숫자들과 통일, 위 .digit-box 등 참고). */
  /* 세 수의 덧셈 문제(g1_three_add_sum10) 1번 문제에서 "합이 10이 되는 두 수" 예시로
     보여주는 동그라미. 처음엔 숫자를 별도 inline-flex span(너비 1.6em 고정)으로 감쌌는데,
     그러면 그 span 자체가 일반 숫자보다 훨씬 넓어서 .eq-row(gap:0, 각 박스 폭이 콘텐츠
     기준 auto)의 전체 폭이 늘어나 1번 식만 다른 식보다 눈에 띄게 길어 보였다(사용자 리포트:
     "동그라미만큼 식이 길어지네"). `position:absolute` + `::after`로 바꿔서, 원은 순전히
     장식(문서 흐름에서 빠짐)이고 `.eq-box`의 실제 레이아웃 폭은 동그라미 없는 숫자와
     완전히 동일하게 유지되도록 함 — 그래서 1번 식과 다른 식의 전체 길이가 정확히 같아진다.
     em 단위(부모 .eq-box의 font-size, calc(var(--box)*0.55) 기준)라 학년별 --box 크기가
     달라도 숫자 글자에 비례해 항상 딱 맞게 그려진다. cardPixelSize()의 폭 추정(boxPx*6.3,
     고정값)과는 무관(어차피 절대위치라 레이아웃에 전혀 관여하지 않음). */
  .eq-box.term-circled{ position:relative; }
  /* 지름 1.6em으로 처음 만들었더니, 두 자리(합이 10이 되는 두 수)가 바로 옆칸(사이에
     연산기호 하나만 있는 경우)일 때 원 두 개가 서로 겹치면서 그 사이 "+" 기호까지
     가려버렸다(실측: 숫자 칸 너비≈10.4px, 옆 칸까지 거리≈20.3px, boxPx=48 기준). 지름을
     1.05em(≈27.7px)로 줄이면 인접한 두 원이 겹치는 폭(≈17.3px)이 그 거리 안에 항상
     들어와서 기호가 안 가려진다(실측으로 검증, 여유 ≈3px). */
  .eq-box.term-circled::after{ content:''; position:absolute; left:50%; top:50%; width:1.05em; height:1.05em; transform:translate(-50%,-50%); border-radius:50%; border:0.09em solid var(--ink); pointer-events:none; }
  /* 정답지(showAnswer=true)에서는 학습지(검정)와 구분되도록 동그라미를 사이트 포인트
     컬러(파란색)로 그린다(사용자 요청, 2026-08-12). */
  .eq-box.term-circled.term-circled-answer::after{ border-color:var(--primary); }
  /* 가로셈 전용: 문제 번호를 식과 같은 줄, 왼쪽에 배치한다. */
  .problem-card-inline{ display:flex; flex-direction:row; align-items:center; gap:6pt; }
  .problem-card-inline .problem-idx{ margin-bottom:0; flex:none; }
  /* 이 margin-bottom(8pt)은 원래 .horiz-eq 자신에게 있었는데, .problem-card-inline이 flex
     row라서 align-items:center가 margin까지 포함한 박스 기준으로 정렬한다 — .horiz-eq만
     아래쪽 margin이 있고 .problem-idx는 없으니, 실제 텍스트(내용물) 기준으로는 idx가 식보다
     더 아래로 밀려 보이는 버그가 있었다(2~4학년 가로셈에서 원문자가 식보다 처져 보임).
     이 margin을 .horiz-eq 밖, .problem-card(세로 flex column)의 자식인 .problem-card-inline
     쪽으로 옮기면 idx/식 정렬은 순수 콘텐츠 높이(24px/17px)만으로 맞춰지고, 아래(vgrid 작업공간)
     와의 간격은 그대로 유지된다(총 카드 높이는 안 바뀜 — cardPixelSize의 eqLinePx 공식과도
     여전히 맞음). blank_eq/three_num처럼 .problem-card 자체에 .problem-card-inline이 합쳐진
     경우(자식이 아니라 자기 자신)는 이 선택자에 안 걸려서 영향 없음. */
  .problem-card > .problem-card-inline{ margin-bottom:8pt; }
  .horiz-eq{ font-family:'Noto Sans KR',sans-serif; font-variant-numeric:lining-nums tabular-nums; font-size:calc(var(--box) * 0.5); font-weight:400; line-height:1; color:var(--ink); display:flex; align-items:center; gap:4pt; white-space:nowrap; }
  /* 가로셈 전용 차시는 세로셈 숫자(0.64배)보다 살짝 작되, 기본 가로셈(0.5배)보다 크게 읽힌다. */
  .horiz-eq.horiz-eq--larger{ font-size:calc(var(--box) * 0.60); }
  /* 가로셈(스스로) 전용 — 아래에 계산용 작업공간이 없어서 문장 끝에 답 쓸 자리를 남겨둔다.
     가로셈(세로셈 틀 있음)은 답을 아래 세로셈 칸에 쓰므로 "=" 기호와 이 빈칸을 아예 안 그린다
     (renderHorizontalWorkspaceHTML 참고 — freeform일 때만 붙인다). */
  .horiz-blank{ display:inline-block; width:calc(var(--box) * 1.5); height:1pt; }
  /* 학습지형 정답지(showAnswer)에서만 붙는 .filled — 밑줄(height:1pt)이 아니라 실제 글자를
     보여줘야 하므로 높이 제한을 풀고 폰트는 .horiz-eq(부모)에서 그대로 물려받는다. */
  .horiz-blank.filled{ height:auto; min-width:calc(var(--box) * 1.5); }
  .freeform-space{ border:none; background:transparent; }
  .vgrid{ display:grid; position:relative; }
  .digit-box{ width:var(--box); height:var(--box); border-right:0.7pt dashed var(--grid-dash); border-bottom:0.7pt dashed var(--grid-dash); display:flex; align-items:center; justify-content:center; font-size:calc(var(--box) * 0.64); line-height:1; font-weight:400; font-variant-numeric:lining-nums tabular-nums; font-family:'Noto Sans KR',sans-serif; background:#fff; }
  .digit-box.b-top{ border-top:0.7pt dashed var(--grid-dash); }
  .digit-box.b-left{ border-left:0.7pt dashed var(--grid-dash); }
  .digit-box.b-nobottom{ border-bottom:none; }
  .digit-box.b-noright{ border-right:none; }
  /* 학습지형 정답지 전용 — 가로셈(세로셈 틀 있음)의 나눗셈 부분곱/나머지 줄, 곱셈
     부분곱 줄 위에 자리값 보조선 있음 모드(.line-cell)와 같은 굵은 구분선을 긋는다.
     border-box라 칸 높이 안에 흡수되어 실제 차지하는 세로 공간에는 영향 없음. */
  .digit-box.answer-step-divider{ border-top:2.2pt solid var(--ink); }
  .digit-plain{ width:var(--box); height:var(--box); display:flex; align-items:center; justify-content:center; font-size:calc(var(--box) * 0.64); font-weight:400; font-variant-numeric:lining-nums tabular-nums; font-family:'Noto Sans KR',sans-serif; }

  /* 학습지형 정답지(showAnswer): 빈칸 자리에 채워 넣는 정답 글자는 전부 이 색으로 —
     문제집 배경(--paper/--ink)과 뚜렷이 구분되는 파란색. 위치·크기는 각 렌더 함수가
     기존 빈칸 자리(.digit-box/.digit-plain/.eq-box 등, 전부 이미 flex 가운데 정렬)에
     그대로 넣으므로 이 클래스는 색만 담당한다. */
  .ans-fill{ color:var(--primary); }

  /* 1학년 특수 유형: 모으기/가르기 (긴 직사각형을 위/아래 2줄로, 필요한 줄만 두 칸으로) */
  .combine-table{ display:inline-flex; flex-direction:column; border:1.3pt solid var(--ink); }
  .combine-row{ display:flex; }
  .combine-cell{ width:var(--box); height:var(--box); display:flex; align-items:center; justify-content:center; font-weight:400; font-variant-numeric:lining-nums tabular-nums; font-family:'Noto Sans KR',sans-serif; font-size:calc(var(--box) * 0.58); border-right:1.3pt solid var(--ink); border-bottom:1.3pt solid var(--ink); background:#fff; box-sizing:border-box; }
  .combine-cell.merged{ width:calc(var(--box) * 2); }
  .combine-row:last-child .combine-cell{ border-bottom:none; }
  .combine-row .combine-cell:last-child{ border-right:none; }
  .combine-cell.blank{ background:#fff; }

  /* 1학년 특수 유형: □ 안의 수 찾기 / 세 수의 덧셈·뺄셈(가로형) */
  .eq-row{ display:flex; align-items:center; gap:0; }
  .eq-box{ min-width:var(--box); height:var(--box); padding:0 2.8pt; border:none; display:flex; align-items:center; justify-content:center; font-weight:400; font-variant-numeric:lining-nums tabular-nums; font-family:'Noto Sans KR',sans-serif; font-size:calc(var(--box) * 0.55); box-sizing:border-box; }
  /* 빈칸이 아닌(=값이 채워진) 자리는 정사각형 칸으로 안 그려서, 숫자가 칸 한가운데
     정렬되며 생기는 "칸 절반만큼의 안 보이는 여백"을 없앤다 — 이 여백 때문에 숫자-기호
     간격이 2·3·4학년 가로셈(글자만 바로 붙어 있는 구조)보다 훨씬 넓어 보였다(실측:
     boxPx=48 기준 1학년 23.04px vs 2학년 7.2px, 3배 이상 차이). 빈칸(.eq-box.blank)은
     "답을 쓸 칸"이라는 시각적 의미가 있어 계속 정사각형을 유지해야 하므로 이 규칙에서 제외.
     .eq-op가 이미 height:var(--box)를 갖고 있어 이 칸의 높이를 없애도 줄 높이는 그대로다. */
  .eq-box.plain{ min-width:0; padding:0; }
  .eq-box.blank{ background:#fff; position:relative; }
  .eq-box.blank::before{ content:''; position:absolute; left:calc(var(--box) * 0.1); right:calc(var(--box) * 0.1); top:calc(var(--box) * 0.1); bottom:calc(var(--box) * 0.1); border:1.3pt solid var(--ink); }
  .eq-op{ height:var(--box); display:flex; align-items:center; justify-content:center; font-weight:400; font-family:'Noto Sans KR',sans-serif; font-size:calc(var(--box) * 0.55); color:var(--ink); line-height:1; }
  .eq-answer-blank{ display:inline-block; width:calc(var(--box) * 1.3); height:1pt; align-self:center; }
  /* 학습지형 정답지 전용 — 밑줄(height:1pt) 대신 숫자 한 칸 높이로 키우고 가운데 정렬,
     폰트는 .eq-box와 동일하게 맞춘다(원래 이 자리에 있었을 값이 채워진 .eq-box와 시각적으로
     통일되도록). */
  .eq-answer-blank.filled{ height:var(--box); display:inline-flex; align-items:center; justify-content:center; font-weight:400; font-variant-numeric:lining-nums tabular-nums; font-family:'Noto Sans KR',sans-serif; font-size:calc(var(--box) * 0.55); }
  .op-char{ display:inline-block; font-family:'Noto Sans KR',sans-serif; font-weight:400; padding:0 calc(var(--box) * 0.15); }
  .line-cell{ border-top:1.5pt solid var(--ink); height:0; align-self:start; } /* 2026-08-14: 두꺼워 보인다는 피드백으로 2.2pt→1.5pt(카드 높이 추정 오차는 기존 허용 범위 0~1px 이내로 유지) */
  .line-cell-minor{ border-top:0.8pt solid #A9AFAE; height:0; align-self:start; } /* 덜 중요한 구분선: 얇은 회색(2026-08-14: 1.2pt→0.8pt 동일 이유) */
  .answer-box-empty{ background:#fff; }
  .answer-box-full{ } /* 중간 빈 줄: 테두리 없이 그냥 여백 (예: 곱셈 부분곱 두 줄) */
  /* 학습지형 정답지 전용 — 빈 여백에 정답 숫자를 오른쪽 정렬로 채운다(위 자리값 행들의
     오른쪽 정렬과 맞춤). 그리드 트랙이 이미 크기를 고정하므로 display를 바꿔도 칸 크기엔
     영향 없음. */
  .answer-box-full.filled{ display:flex; align-items:center; justify-content:flex-end; padding-right:calc(var(--box) * 0.15); font-weight:400; font-variant-numeric:lining-nums tabular-nums; font-family:'Noto Sans KR',sans-serif; font-size:calc(var(--box) * 0.56); }
  .answer-box-final{ border-bottom:1.5pt solid var(--ink); } /* 맨 마지막 답 쓰는 줄만 마무리 선 */

  /* 3학년 나눗셈 검산(빈칸 채우기, 2026-08-16 OX→빈칸 채우기 개편): 나눗셈식(이미 푼 나눗셈,
     빈칸 없음) 한 줄과 검산식(몫×나누는 수 [+나머지] = 나뉘는 수, 빈칸 있음) 한~두 줄을
     세로로 쌓는다. 두 식이 "구분"돼야 한다는 요청 반영: 각 줄 앞에 .divcheck-label로
     "나눗셈식"/"검산식" 글자 라벨을 붙인다(색만으로 구분하지 않고 텍스트로도 구분). 풀이
     형태 선택과 무관하게 항상 이 형태로만 그린다(renderDivCheckHTML, 다른 1학년 특수
     유형과 같은 방식). 값 칸/기호는 .eq-box·.eq-op·opIconHTML을 그대로 재사용해 다른
     가로셈 유형과 같은(더 큰) 글자 크기를 쓴다(요청: "폰트 크기 늘리고").
     빈칸 위치 규칙은 renderBlankEqHTML과 동일: "=" 바로 뒤(줄의 맨 끝)는 밑줄
     (.eq-answer-blank), 그 외 자리는 테두리 네모(.eq-box.blank). */
  .problem-card-divcheck{ display:flex; flex-direction:row; align-items:flex-start; gap:10pt; }
  .problem-card-divcheck .problem-idx{ margin-bottom:0; flex:none; }
  /* flex:1 — 2026-08-17: 카드 폭이 고정값(renderDivCheckHTML의 inline width, 좌우 열 정렬
     목적)이라 이 안쪽 body도 idx/gap을 뺀 나머지 폭을 그대로 채워야 divcheck-row들의
     align-items:stretch가 그 고정 폭 기준으로 작동한다(안 그러면 body가 내용물 크기로만
     줄어들어 카드 오른쪽에 빈 여백이 생기고 정작 줄들은 늘어나지 않음). */
  .divcheck-body{ display:flex; flex-direction:column; gap:7pt; flex:1; min-width:0; }
  .divcheck-row{ display:flex; align-items:center; gap:7pt; }
  .divcheck-row-check{ align-items:flex-start; }
  /* 2026-08-17: "나눗셈 식 밑에 배경을 주든지 해서 확실히 구분되게" — 나눗셈식 줄(이미 다
     푼, "주어진 값") 하나에만 옅은 배경을 얹어 아래 검산식(빈칸 채워야 하는 "할 일")과
     확실히 구분한다. 라벨 텍스트만으론 부족하다는 피드백이라 라벨+배경 이중으로 구분.
     인쇄 잉크 부담 적은 옅은 회색(--ui-divider, 이미 화면 UI에도 쓰는 톤). 패딩이 생긴
     만큼 divCheckCardWidthPx(오늘수학.html)도 같이 반영해야 함. */
  .divcheck-row:not(.divcheck-row-check){ background:var(--ui-divider); border-radius:5pt; padding:4pt 8pt; }
  .divcheck-check-col{ display:flex; flex-direction:column; gap:4pt; }
  .divcheck-label{ flex:none; font-family:'Noto Sans KR',sans-serif; font-weight:400; font-size:calc(var(--box) * 0.22); color:var(--ink-soft); border:1pt solid var(--ink-soft); border-radius:3pt; padding:1.5pt 5pt; white-space:nowrap; }
  /* 2026-08-17: "칸이 좀 더 길어야 — 두 자리/세 자리 써야 하는 경우 있음. 그런데 칸 크기
     구분은 하지마" — 어느 자리(몫/나누는 수/나머지)가 비든 항상 최대 3자리를 손글씨로 쓸
     여유가 있도록 전부 같은 폭으로 넓힌다(자리별로 다르게 하면 그 자체로 정답 자릿수
     힌트가 됨). div_check 카드 밖(blank_eq 등)의 .eq-box.blank는 원래 폭(1×box) 그대로
     둬야 하므로 .problem-card-divcheck 안에서만 override. */
  .problem-card-divcheck .eq-box.blank{ min-width:calc(var(--box) * 1.7); }

  /* 곱셈표(g2_mul_table_grid, customRender:'mul_table', fixes.md #24): 지금까지의 모든
     유형은 "문제 카드 여러 개"인데, 이건 9×9 격자 표 하나가 페이지 전체를 차지하는 완전히
     다른 형태다 — buildMulTableLessonPage(오늘수학.html)가 카드 파이프라인
     (cardPixelSize/measureShapeGroup 등, var(--box) 기반)을 전혀 안 타고 그 시점의 실제
     가용 폭·높이를 10등분해서 칸 크기(--mul-cell)를 직접 계산해 <table>의 인라인
     style로 넣어준다 — 그래서 폰트 크기 등도 var(--box)가 아니라 var(--mul-cell) 기준으로
     잡는다(용지 크기가 달라져도 이 표 스타일 안에서 항상 일관되게 맞음). */
  .mul-table-wrap{ display:flex; flex:1; align-items:center; justify-content:center; min-height:0; }
  .mul-table{ border-collapse:collapse; }
  .mul-table th, .mul-table td{ width:var(--mul-cell); height:var(--mul-cell); box-sizing:border-box; text-align:center; padding:0; font-variant-numeric:lining-nums tabular-nums; font-family:'Noto Sans KR',sans-serif; }
  .mul-th{ font-weight:400; border:1.3pt solid var(--ink); font-size:calc(var(--mul-cell) * 0.34); }
  .mul-corner{ font-size:calc(var(--mul-cell) * 0.3); color:var(--ink-soft); }
  .mul-td{ font-weight:400; border:0.7pt solid var(--ink); font-size:calc(var(--mul-cell) * 0.34); }

  /* 나눗셈: 다른 연산과 통일된 점선 자리 칸 스타일 */
  /* 나누는 수 | 나뉘는 수 경계의 ")" 곡선 + 그 위 가로선은 하나의 SVG path로 그린다
     (.div-bracket-curve, JS의 bracketCurveWithLine이 삽입 — grid/blank 공용, 자세한 이유는
     그 함수 주석 참고). left/top/width/height는 그 함수가 매번 인라인 style로 직접 넣으므로
     여기서는 position:absolute와 클릭 통과(pointer-events:none)만 공통으로 잡아둔다. */
  .bracket-left{ position:relative; }
  .div-bracket-curve{ position:absolute; overflow:visible; pointer-events:none; }

  .answerkey-list{ display:grid; font-size:11.5pt; gap:0; }
  .answerkey-item{ padding:5pt 6pt; border-bottom:1px dashed var(--line); }
  .answerkey-item b{ color:var(--teal-deep); }
  /* 빠른 정답(한 장에 여러 차시/일차를 모을 때)의 구간 제목 — 항목 번호가 차시마다 1부터
     다시 시작되므로, 어느 차시 것인지 구분할 수 있도록 구간마다 넣는다. */
  .answerkey-group-label{ font-family:'Noto Sans KR',sans-serif; font-weight:400; color:var(--teal-deep); margin:10pt 0 3pt; padding-bottom:2pt; border-bottom:1pt solid var(--teal); }
  .answerkey-group-label:first-child{ margin-top:0; }

  .empty-state{ padding:60px 30px; text-align:center; color:var(--ink-soft); }
  .empty-state .big{ font-family:'Gowun Batang',serif; font-size:19px; color:var(--ink); margin-bottom:8px; }

  .hidden{ display:none !important; }

  @media print{
    body *{ visibility:hidden; }
    .print-area, .print-area *{ visibility:visible; }
    .print-area{ position:absolute; left:0; top:0; }
    /* ✅ 진짜 근본 원인 발견(2026-07-28, 3차): 3일간 쫓던 게 전부 zoom이 아니었다. 위
       `visibility:hidden`(body *)은 요소를 안 보이게만 할 뿐 레이아웃 공간은 그대로
       차지한다(`display:none`과 다름) — 그런데 `#topbar`(간편 모드에서만 화면에 보이는
       상단바, `.simple-only`)는 고급 모드에선 `.hidden`(display:none)이 걸려 애초에
       레이아웃에 없지만, 간편 모드에선 화면엔 보이다가 인쇄 시 visibility:hidden만 걸려서
       "안 보이지만 91px(실측)만큼 여전히 공간을 차지"하는 상태로 남는다. Playwright로 직접
       측정: 단일 페이지 학습지 기준 `document.documentElement.scrollHeight`가 간편 모드
       1291px vs 고급 모드 1200px — 정확히 91px(topbar 높이) 차이. `.sheet-inner`(실제
       콘텐츠) 자체의 크기는 두 모드에서 완전히 동일함(1122.5px, 오차 0)을 재확인했는데도
       이 차이가 났다 — 즉 인쇄 콘텐츠 자체가 아니라 "인쇄 시점에 브라우저가 보는 전체 문서
       높이"가 모드에 따라 달랐던 것. 실제 인쇄 파이프라인이 이 부풀려진 전체 문서 높이
       기준으로 "맞춰 찍기"류 축소를 적용하는 것으로 추정 — `.sheet` 자체의 CSS
       zoom/computed size는 완전히 정상인데도(DevTools "Emulate CSS media type: print"로
       사용자가 직접 재확인, 간편/고급 모두 zoom:1) 실제 인쇄 결과만 다르게 나온 이유가
       이걸로 설명됨(한 장 학습지처럼 짧은 콘텐츠일수록 91px의 비중이 커서 체감 차이가
       크고, 단원 학습지처럼 여러 페이지로 이미 긴 콘텐츠는 91px이 묻혀서 덜 두드러짐 —
       사용자가 "한 장이 단원보다 더 작게 나온다"고 한 관찰과 정확히 일치).
       **해결**: 인쇄 시 실제 학습지(`.print-area`) 이외의 모든 최상위 UI 요소를
       `visibility:hidden`뿐 아니라 `display:none`으로 완전히 레이아웃에서 제거해서, 전체
       문서 높이가 항상 실제 인쇄될 콘텐츠 높이와 정확히 같아지도록 강제한다 — 이제
       간편/고급 모드가 무엇을 보여주고 있었든(토글 상태와 무관하게) 인쇄 시엔 항상
       `#sheetWrap` 하나의 높이만 문서 전체 높이가 된다. */
    .inspect-toggle, #topbar, #sidebarPanel, .main-header, .lab-panel{ display:none !important; }
    /* .app{min-height:calc(100vh - 76px)}가 인쇄 시에도 그대로 적용되면, 위에서 sidebar/
       topbar를 없애 실제 콘텐츠 높이가 줄어들어도 뷰포트 높이 기준 최소 높이가 남아있어서
       문서 전체 높이가 실제 인쇄 콘텐츠보다 커질 수 있다(모드와 무관하게 균일하게 남는
       잔여 여백이지만, 어차피 인쇄 시 뷰포트 개념 자체가 무의미하므로 없애는 게 맞다). */
    .app{ min-height:0 !important; display:block !important; }
    /* body{min-height:100vh}도 같은 이유로 인쇄 시 뷰포트 높이만큼 문서를 억지로 늘릴 수
       있으므로 같이 무효화한다 — 이건 모드와 무관하게 항상 있던 잔여 여백이라 이번에 보고된
       "모드에 따라 다름" 버그의 원인은 아니었지만, "인쇄 시 문서 높이 = 실제 콘텐츠 높이"
       원칙을 완전하게 만들기 위해 같이 정리한다. */
    body{ min-height:0 !important; }
    /* ⏪(2026-07-27→28) !important로 인라인 zoom을 "덮어쓰는" 방식(아래 이전 버전 설명)은
       Playwright emulateMedia로는 완벽히 재현/검증됐지만, 실제 사용자 브라우저의 진짜 인쇄
       파이프라인에서는 효과가 없었다(사용자 재확인: 회색 테두리는 없어졌는데 — 그건
       border:none !important가 먹힌 것 — 콘텐츠 크기는 그대로 작았음, 왼쪽 위에 뭉쳐있는
       모습). !important가 스펙상 인라인 스타일보다 우선하는 건 맞지만, 실제 인쇄 파이프라인이
      (headless CDP printToPDF와 달리) 그 CSS 재계산을 완전히 반영하기 전 시점의 값을
       기준으로 페이지를 만드는 것으로 보여서, "값을 덮어쓰기"에 계속 의존하는 한 이런 종류의
       타이밍 문제를 근본적으로 못 없앤다고 판단함. **아래로 완전히 대체함.**
       ✅ 근본 해결(2026-07-28): "지우기"/"덮어쓰기" 둘 다 결국 인라인 zoom이라는 실체가 있고,
       그걸 언제 없애느냐는 타이밍(이벤트가 불리는 시점, 인쇄 파이프라인이 CSS를 반영하는
       시점)에 의존하는 한 재현 안 되는 경우가 계속 나올 수 있다는 게 위 시도로 확인됐다.
       그래서 애초에 화면 미리보기 축소를 "인라인 zoom 값을 세팅했다가 인쇄 시 없애는" 방식
       자체를 버리고, "zoom을 실제로 적용하는 CSS 규칙 자체가 @media screen 안에만 존재"하도록
       바꿨다(`.sheet{zoom:var(--preview-zoom,1);}` 규칙, 위쪽 `.sheet{background:...}` 정의
       바로 옆 참고). scaleSheets()는 이제 `.sheet.style.zoom`이 아니라 커스텀 프로퍼티
       `--preview-zoom`만 세팅한다 — 커스텀 프로퍼티 자체는 인쇄 컨텍스트에도 인라인으로 남아
       있을 수 있지만, 그 값을 실제 zoom으로 "소비"하는 규칙이 print 컨텍스트엔 아예 없으므로
       아무 효과가 없다. 이러면 "지울 값도, 덮어쓸 값도 없다"는 게 media query 자체가 보장하는
       구조적 사실이 되어, beforeprint 타이밍이나 인쇄 파이프라인의 CSS 반영 시점과 완전히
       무관해진다. `.sheet`/`.sheet-inner`엔 이제 zoom 관련 선언이 필요 없다(어차피 인쇄 시엔
       애초에 걸릴 규칙이 없으므로) — border/배경/여백 제거만 남김. */
    .sheet{ box-shadow:none !important; border:none !important; background:none !important; margin:0 !important; padding:0 !important; }
    .sheet-inner{ margin:0 !important; }
    /* ✅ 인쇄 시 마지막에 빈 페이지 한 장이 더 붙던 버그(2026-08-11): 예전엔 모든 .sheet-inner에
       예외 없이 page-break-after:always를 걸었는데, 이게 맨 마지막 페이지에도 그대로 걸려서
       "이 페이지 뒤에도 또 페이지가 있다"고 브라우저가 해석해 진짜 마지막 콘텐츠 뒤에 아무
       내용 없는 페이지가 하나 더 생겼다. .sheet-inner는 각자 자기 부모(.sheet) 안에서 유일한
       자식이라 .sheet-inner 자신에 :last-of-type을 걸어도 항상 참이 되어 의미가 없으므로(전부
       "유일한 자식"), 대신 형제 관계가 실제로 있는 .sheet(#sheetWrap의 직계 자식들) 쪽에서
       "마지막 페이지가 아닌 것"만 걸러 그 안의 .sheet-inner에만 break를 건다. */
    /* #sheetWrap의 직계 자식(.sheet)만 대상으로 한정한다 — 소책자 임포지션 인쇄 중엔
       `.booklet-spread`(물리적 한 장, #sheetWrap의 직계 자식) 안에 원래 낱장 `.sheet` 두 개가
       나란히 중첩되는데, 이 선택자가 깊이 무관하게 매칭되면 그 중첩된 낱장에도 페이지 나누기가
       걸려 물리적 한 장 중간에서 엉뚱하게 쪽이 갈린다(fixes.md #46, 2026-08-16). 소책자가
       아닌 기존 모든 경우엔 `.sheet`가 항상 #sheetWrap의 직계 자식이라 동작이 그대로다. */
    #sheetWrap > .sheet:not(:last-child) .sheet-inner{ page-break-after:always; }
    /* 소책자 임포지션 인쇄 중 물리적 한 장(가로 296mm=A5 두 쪽) 단위로 나눈다 — 각 물리적
       한 장 자체가 이미 정확한 물리 크기를 갖고 있어(위 .sheet-inner에 break를 준 것과 같은
       원칙: "정확한 명시적 크기를 가진 요소에 break를 건다"), 여기서 바로 걸어도 안전하다. */
    #sheetWrap > .booklet-spread:not(:last-child){ page-break-after:always; }
    .spiral, .page-tag{ display:none; }
    /* #sheetWrap(=.sheet-wrap.print-area)은 화면용 width:100%;max-width:1000px;align-items:center가
       인쇄에도 그대로 남아있으면, 실제 용지 폭(예: A4=793.7px)보다 넓은 1000px짜리 박스 안에
       .sheet가 가운데 정렬되어 좌우로 큰 여백(각 ~103px)이 생긴다 — 사용자 리포트("왼쪽,
       오른쪽, 아래쪽 여백")의 원인이었다(위쪽 @page margin:0 수정만으론 안 잡히던 부분).
       width/max-width를 없애서(auto) .sheet 자기 폭 그대로 줄어들게(shrink-to-fit) 하면
       가운데 정렬할 여지 자체가 없어져서 좌우 여백이 사라지고, 그 결과 아래쪽으로 밀려나던
       "맞춰 찍기" 축소도 같이 없어진다. */
    .sheet-wrap{ background:none; padding:0; gap:0; width:auto; max-width:none; }
    /* 실제 원인은 여기 있었다: 단원 학습지/소책자처럼 페이지가 2장 이상이면 #sheetWrap에
       .multi-page 클래스가 붙는데(화면에서 "여러 쪽 한눈에 보기" 3열 격자 미리보기용,
       scaleSheets() 참고), 이 클래스의 선택자(.sheet-wrap.multi-page, 클래스 2개)가 바로
       위 .sheet-wrap(클래스 1개)보다 CSS 우선순위가 더 높아서 — 인쇄 시에도 화면용
       flex-direction:row/justify-content:center/flex-wrap:wrap이 그대로 이겨버리고 있었다.
       그 결과 여러 쪽이 인쇄용 "한 쪽씩 세로로 쌓기"가 아니라 화면용 "3열 격자"로 배치되면서
       좌우로 가운데 정렬되고(왼쪽/오른쪽 여백), 격자 줄바꿈이 인쇄 페이지 나누기와 안 맞아
       아래쪽에도 여백처럼 보이는 빈 공간이 생겼다(첫 페이지 상단만 그대로라 위쪽만
       여백이 없어 보였음 — 사용자가 "위쪽만 여백 없고 왼쪽/오른쪽/아래쪽만 있으며 가운데
       정렬이 맞다"고 정확히 관찰한 그대로). 인쇄 시엔 이 클래스가 붙어있어도 항상 화면용
       격자를 무시하고 세로로 한 쪽씩 쌓이도록 명시적으로 되돌린다. */
    .sheet-wrap.multi-page{ flex-direction:column; flex-wrap:nowrap; align-items:flex-start; justify-content:flex-start; width:auto; max-width:none; gap:0; }
  }

  /* 요소 이름 보기(인스펙터) — 개발자도구처럼 커서를 올리면 클래스명을 보여준다 */
  .inspect-toggle{ position:fixed; bottom:18px; right:18px; z-index:99999; background:#1E293B; color:#fff; border:none; padding:10px 16px; border-radius:20px; font-size:12.5px; font-weight:700; font-family:'Noto Sans KR',sans-serif; cursor:pointer; box-shadow:0 3px 10px rgba(0,0,0,.25); opacity:0.88; }
  .inspect-toggle:hover{ opacity:1; }
  .inspect-toggle.active{ background:var(--primary,#2563EB); opacity:1; }
  .inspect-tooltip{ position:fixed; z-index:100000; background:#1E293B; color:#fff; font-size:11.5px; font-family:'SFMono-Regular',Consolas,monospace; padding:5px 9px; border-radius:5px; pointer-events:none; white-space:nowrap; display:none; line-height:1.5; box-shadow:0 2px 8px rgba(0,0,0,.3); }
  .inspect-tooltip b{ color:#93C5FD; }
  .inspect-highlight{ outline:2px solid #EF4444 !important; outline-offset:-1px; }

  /* 레이아웃 실험실(?lab=1) — 자간/폰트 크기·종류를 실시간으로 만져보는 개발용 패널.
     항상 존재하지 않고 URL에 ?lab=1이 있을 때만 JS가 붙인다(맨 아래 스크립트 참고). */
  .lab-panel{ position:fixed; top:16px; right:16px; z-index:99998; width:220px; background:#fff; border:1px solid var(--ui-border,#E5E7EB); border-radius:12px; box-shadow:0 8px 24px rgba(0,0,0,.18); padding:14px 16px 16px; font-family:'Noto Sans KR',sans-serif; }
  .lab-panel h4{ margin:0 0 10px; font-size:13px; font-weight:800; color:var(--title,#1E293B); }
  .lab-panel label{ display:flex; justify-content:space-between; align-items:baseline; font-size:11.5px; font-weight:700; color:var(--text-secondary,#64748B); margin:12px 0 4px; }
  .lab-panel label span.lab-val{ font-weight:600; color:var(--text-secondary,#64748B); }
  .lab-panel input[type=range]{ width:100%; }
  .lab-panel select, .lab-panel input[type=number]{ width:100%; padding:6px 8px; border:1px solid var(--ui-border,#E5E7EB); border-radius:6px; font-size:12.5px; font-family:inherit; box-sizing:border-box; }
  .lab-panel .lab-note{ font-size:10px; color:var(--text-secondary,#64748B); margin-top:5px; line-height:1.45; }
  .lab-panel .lab-reset{ margin-top:14px; width:100%; padding:7px; border:1px solid var(--ui-border,#E5E7EB); background:var(--bg,#F9FAFB); border-radius:6px; font-size:12px; font-weight:700; cursor:pointer; font-family:inherit; color:var(--body-text,#334155); }
  .lab-panel .lab-reset:hover{ background:var(--ui-divider,#F1F5F9); }


