/* app.css — 기본 컴포넌트 5종 + 랜딩(§6.1) 레이아웃.
 * 색·크기·시간은 전부 tokens.css 참조. 리터럴 금지 (CLAUDE.md §8.1). */

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  /* 여백은 책상, .page 는 그 위에 놓인 종이 한 장이다. 둘이 같은 색이면
     1200px 기둥이 허공에 뜬 것처럼 보인다 — 여백에 이유를 준다.
     책상에는 종이와 다른 결을 준다 (§4 의도적 위반, tokens.css 참조). */
  background-color: var(--paper-lo);
  background-image: var(--grain-desk);
  /* 종이가 화면 바닥까지 닿게 한다. .page 에 100vh 를 주면 wordband 높이만큼
     넘쳐 유령 스크롤이 생기므로, 남은 높이를 flex 로 준다. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  /* 한국어는 어절 중간에서 끊으면 안 된다. 이게 없으면 좁은 화면에서
     "이야기가" 가 "이야 / 기가" 로 쪼개진다. break-word 는 끊을 수 없는
     긴 문자열(URL·핸들)만을 위한 탈출구다. */
  word-break: keep-all;
  overflow-wrap: break-word;
}

/* §4 종이 질감은 노이즈 오버레이 1장으로만. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
}

h1,
h2,
h3,
p {
  margin: 0;
}

/* 컴포넌트의 display:flex 가 UA의 [hidden] 을 이겨버린다.
 * hidden 을 붙였는데 보이는 사고를 막으려면 여기서 못박아야 한다. */
[hidden] {
  display: none !important;
}

/* §2.3 링크는 색만으로 정보 전달하지 않는다 — 밑줄 필수 */
a {
  /* --tide 가 아니라 --tide-text. 링크는 12~16px 로 쓰이는데 --tide 는 그 크기에서
   * 4.27:1 로 AA 에 못 미친다 — Design.md §2.3 이 이미 금지한 조합이었다. */
  color: var(--tide-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
a:hover {
  color: var(--wine);
}

/* §11 포커스 링. 절대 제거 금지. */
:where(a, button, [tabindex]):focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
/* 입력칸도 같은 링을 쓴다. 브라우저 기본 테두리는 얇고 브라우저마다 달라, 링크에서
   입력칸으로 넘어가는 순간 포커스가 어디 있는지 놓치기 쉽다. :where 라 특이도가 0 —
   검색칸처럼 감싼 상자에 링을 거는 곳(.search)은 그대로 이긴다. */
:where(input, textarea, select, summary):focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* 맨 앞의 "본문으로 건너뛰기". 평소에는 화면 밖에 있다가 Tab 으로 처음 들어온
   사람에게만 왼쪽 위에 나타난다. display:none 이나 visually-hidden 으로 숨기면
   포커스를 받아도 보이지 않는다. */
.skip-link {
  position: absolute;
  top: var(--s-2);
  left: var(--s-2);
  z-index: 1000;
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-sm);
  background: var(--ink);
  color: var(--paper);
  font-size: var(--t-ui);
  transform: translateY(-200%);
}
.skip-link:focus-visible {
  transform: none;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.meta {
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  line-height: var(--lh-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-45);
}
.meta--upper {
  text-transform: uppercase;
}

/* ═══ 컴포넌트 1 · Button (primary / ghost / quiet) ═══════════ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  /* 버튼은 <a> 로도 쓴다. 전역 a 규칙의 밑줄을 끈다 — §2.3 의 "밑줄 필수" 는
     본문 속 링크 이야기고, 버튼은 채움과 테두리로 이미 구분된다. */
  text-decoration: none;
  padding: var(--s-4) var(--s-8);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-micro) var(--ease), color var(--dur-micro) var(--ease);
}
/* 꺼진 버튼은 꺼져 보여야 한다.
 *
 * .btn 에는 이 규칙이 없었다. 화면 코드가 50곳에서 disabled 를 세우는데
 * (쿨다운, 이미 제출함, 로그인 안 함, 오늘의 단어 없음 …) 어느 것도 눈에
 * 보이지 않아, 멀쩡해 보이는 버튼을 눌렀는데 아무 일도 일어나지 않았다.
 * 아래 개별 규칙 셋(.reader-pages__nav .btn, .report-btn, .shelftag)은
 * 각자의 사정이 있어 그대로 이긴다.
 *
 * 대비는 WCAG 1.4.3 이 비활성 컴포넌트를 면제하지만, 읽을 수는 있어야 하므로
 * 0.45 까지만 내린다. */
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.btn--primary {
  background: var(--flame);
  color: var(--on-flame);
  box-shadow: var(--shadow-print);
}
/* 색을 다시 못박는다. 전역 `a:hover { color: var(--wine) }` 는 명시도가
   .btn--primary 보다 높아서(타입 선택자 a 가 하나 더 붙는다) .btn 을 <a> 로
   쓴 순간 호버 글자색이 --wine 으로 뒤집힌다 — 하필 배경도 --wine 이라
   글자가 통째로 사라졌다. .btn 은 §2.3 의 "링크"가 아니라 버튼이라 여기서
   색을 전부 스스로 소유해야 한다. */
.btn--primary:hover {
  background: var(--wine);
  color: var(--on-flame);
}
.btn--ghost {
  background: transparent;
  border-color: var(--flame);
  color: var(--wine);
}
.btn--ghost:hover {
  background: var(--wash-flame);
  color: var(--wine);
}
.btn--quiet {
  background: var(--paper-hi);
  border-color: var(--rule);
  color: var(--ink);
}
.btn--quiet:hover {
  background: var(--paper-lo);
  color: var(--ink);
}
.btn--block {
  width: 100%;
}

/* ═══ 컴포넌트 2 · StickerLabel / RuleSticker ═════════════════ */

.sticker {
  display: inline-flex;
  align-items: center;
  padding: var(--s-1) var(--s-3);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-70);
  transform: rotate(-2deg);
  transition: transform var(--dur-micro) var(--ease);
}
.sticker:hover {
  transform: rotate(1deg);
}

/* 개설 시 확정된 규칙. 청록 점선 = 시스템이 관리하는 정보 (§6.2) */
.sticker--rule {
  border: var(--border-dashed);
  border-color: var(--tide-dash);
  background: var(--wash-tide);
  font-weight: 500;
  transform: none;
}
.sticker--rule:hover {
  transform: none;
}

/* 개설자 혼자 남은 채 방치 중 — 진짜 사라질 위기만 강조한다 ("구조 요청").
 * 글자색은 검증된 --ink-70 그대로 두고 배경·테두리만 --flame 계열로 바꾼다 —
 * 이 크기의 글자에 --flame 을 직접 칠하면 대비가 모자란다(Design.md §2.3
 * 이 --flame·--tide 를 작은 글자에서 금지한 것과 같은 이유). */
.sticker--rescue {
  border: var(--border-solid);
  border-color: var(--flame);
  background: var(--wash-flame);
  /* 규칙 스티커(숫자·기호)와 달리 문장이라, 고정폭으로 두면 한글 자간이 벌어진다.
   * 옆의 `이어 쓰기` 링크와 같은 UI 글꼴로 맞춘다. */
  font-family: var(--font-ui);
  font-size: var(--t-ui-sm);
  letter-spacing: normal;
  font-weight: 600;
  transform: none;
}
.sticker--rescue:hover {
  transform: none;
}

/* ═══ 컴포넌트 3 · StoryCard (진행 중=점선 / 완성=실선) ═══════ */

.story-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-6);
  border: var(--border-dashed);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.story-card--bound {
  border: var(--border-solid);
}
.story-card__head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--s-3);
}
.story-card__title {
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  line-height: var(--lh-title);
  color: var(--wine);
}
.story-card__line {
  font-family: var(--font-read);
  font-size: var(--t-body);
  line-height: var(--lh-read-lead);
  color: var(--ink-70);
}
/* 아직 아무도 쓰지 않은 이야기 — 초대 문구는 한 톤 더 흐리게 (§10) */
.story-card__line--empty {
  color: var(--ink-45);
}
.story-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding-top: var(--s-1);
  border-top: var(--border-solid);
}
/* 카드 바닥의 이동 손잡이. 14px 글줄만으로는 23.8px 라 24px 하한에 못 미친다. */
.story-card__foot a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

/* ═══ 컴포넌트 4 · BookSpine (색띠 = 그 책을 쓴 사람들) ═══════ */

.spine {
  /* relative가 없으면 안의 .visually-hidden(absolute)이 이 요소의
   * overflow를 벗어나, 가로 스크롤된 선반 안 좌표 그대로 페이지를 넓힌다. */
  position: relative;
  display: flex;
  width: var(--spine-w);
  height: var(--spine-h);
  border: var(--border-solid);
  background: var(--spine-bg, var(--wine));
  overflow: hidden;
  text-decoration: none;
  transition: transform var(--dur-micro) var(--ease);
}
.spine:hover {
  transform: translateY(-6px);
}
.spine--curated {
  border: var(--border-double);
}
/* 참여 순서대로 쌓인 세로 줄무늬. 하드 스톱만 — 그라디언트 금지(§12) */
.spine__band {
  flex: none;
  /* 고정 8px 이면 22px 짜리 좁은 책등에서 폭의 36% 를 먹어 제목이 들어갈 자리가
   * 없었다. 3D 책등 텍스처(js/shelf3d.js)는 이미 폭의 22% 로 그리고 있었으므로,
   * 비율로 맞춰 두 뷰가 같은 그림이 되게 한다 (§7 "같은 시각 규칙"). */
  width: 22%;
  background: var(--band);
}
.spine__title {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  writing-mode: vertical-rl;
  /* 숫자·영문도 한글처럼 똑바로 세운다.
   * 기본값(mixed)은 한글만 세우고 라틴 문자와 숫자는 옆으로 눕혀 한 덩어리로
   * 흘린다 — "2026년8월 시집" 에서 숫자만 누워 글줄이 휘어 보였다. 3D 책등
   * 텍스처(js/shelf3d.js)는 처음부터 한 자씩 똑바로 그리므로 두 뷰가 어긋나기도
   * 했고, spineMetrics 의 글자 수 계산도 모든 글자를 한 칸으로 세고 있다. */
  text-orientation: upright;
  /* 세로쓰기라 위아래가 곧 글줄의 길이다. 여백이 없으면 글자가 테두리에 닿는다.
   * ★ vertical-rl 에서 block 축은 '가로'다. padding-block 을 쓰면 이 여백이
   * 위아래가 아니라 좌우로 붙어서, 글자가 지나갈 통로를 16px 이나 빼앗는다.
   * 실제로 안쪽 폭 16px 에 글줄이 27px 을 요구해 여섯 권 모두 옆이 잘려 있었다.
   * 세로쓰기에서 위아래 여백은 padding-inline 이다. */
  padding-inline: var(--s-2);
  /* 글줄의 두께 = 글자 크기. body 의 1.7 을 물려받으면 13px 글자가 22px 통로를
   * 요구해서, 좁은 책등에서는 무엇을 해도 잘린다. */
  line-height: 1;
  white-space: nowrap;
  /* 말줄임은 js/spine.js 의 fitTitle 이 어절 경계에서 미리 끊는다 — 3D 책등
   * 텍스처와 같은 자리에서 끊기게 하려면 CSS 에 맡길 수 없다. 아래 두 줄은
   * 본문 서체가 아직 안 내려왔을 때처럼 글자 폭이 예상과 다를 때를 위한
   * 안전장치다. 전체 제목은 접근성 이름과 목록 뷰에 그대로 남는다. */
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-display);
  /* 긴 제목은 실제 책등처럼 글자를 줄여 담는다. 값은 library.js 가 계산한다. */
  font-size: var(--spine-fs, var(--t-caption));
  letter-spacing: 0.08em;
  /* 세로쓰기 공백이 한글 음절만큼 벌어져 제목이 끊어진 것처럼 보였다.
   * js/spine.js 의 SPACE_STEP(PER_CHAR - 0.3)과 짝을 이루는 값 — 3D 책등
   * 텍스처도 같은 0.3 에서 공백 폭을 뺀다. */
  word-spacing: -0.3em;
  color: var(--spine-fg, var(--paper));
}
/* 진행 중 — 아직 책등만, 점선 실루엣 (§6.4) */
.spine--writing {
  border: var(--border-dashed);
  background: color-mix(in srgb, var(--paper-hi) 50%, transparent);
}

.shelf__row {
  display: flex;
  align-items: flex-end;
  gap: var(--s-2);
  margin: 0;
  padding: 0 var(--s-1) var(--s-3);
  list-style: none;
  border-bottom: 2px solid var(--rule);
}

/* ═══ 컴포넌트 5 · WaveDivider ════════════════════════════════ */

.wave {
  flex: 1;
  height: 10px;
  color: var(--rule-dash);
}
.section-head {
  display: flex;
  align-items: center;
  gap: var(--s-4);
}
/* 물결선은 .section-head 안에서 flex: 1 로 남는 폭을 먹는다. flex 가 아닌
   자리에 홀로 놓일 때는 스스로 폭을 가져야 한다. */
.wave--full {
  width: 100%;
}
/* 제목 바로 아래에 놓일 때. 붙여 두면 밑줄처럼 보인다. */
.wave--gap {
  margin-top: var(--s-3);
}
.section-head__title {
  flex: none;
  font-family: var(--font-display);
  font-size: var(--t-title);
  line-height: var(--lh-title);
}
/* 구획 안의 작은 머리. 리더의 판권, 데일리의 '나머지 시' 처럼 페이지 구획이
   아니라 그 안의 묶음을 여는 자리다. 예전에는 마크업에 font-size: 24px 로
   박혀 있었는데 24px 는 §3.3 타입 스케일(56·40·28·20·16·13·12)에 없는
   숫자다. 스케일 위로 되돌린다. */
.section-head--sub .section-head__title {
  font-size: var(--t-subtitle);
  line-height: var(--lh-subtitle);
}

/* 본문 옆에서 거들 때의 meta. 기본값(--ink-45)은 종이 위에서 충분히 옅은데,
   작업대의 상태줄처럼 계속 읽어야 하는 자리에서는 한 단계 진해야 한다. */
.meta--strong {
  color: var(--ink-70);
}

/* 가운데로 두는 리드. 데일리의 결과 화면처럼 그 구획이 통째로 가운데 정렬일
   때만 쓴다 — 왼쪽 정렬 구획에 섞으면 축이 갈라진다. */
.page-lead--center {
  text-align: center;
}

/* 페이지 폭을 가로지르는 오류 줄. 폼 안이 아니라 화면 위쪽에 놓인다. */
.form-error--page {
  width: min(620px, 92%);
  margin: var(--s-6) auto 0;
}

/* 데일리 결과의 수상작 자리. 옆에 나머지 시 목록이 서고, 좁아지면 접힌다. */
.daily-winner {
  flex: 1;
  min-width: 300px;
}

/* 작업대의 진행 상황 목록(dl) — 기본 여백을 걷고 세로로 세운다.
   같은 화면의 .rule-list(규칙 ul)와는 다른 것이다. 처음에 이름을 겹쳐 썼더니
   나중에 정의된 .rule-list 가 이겨서 gap 이 조용히 8px 로 바뀌었다. */
.wb-facts {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

/* ═══ 랜딩 (§6.1) ═════════════════════════════════════════════ */

/* 오늘의 단어는 하루 종일 사이트 어디서나 상단 얇은 띠에 (§6.8) */
.wordband {
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-2) var(--s-6);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
}
.wordband a {
  /* 띠는 얇지만 링크는 24px 을 확보한다. 띠 자체 높이는 늘리지 않으려고
     세로 여백 대신 최소 높이와 가운데 정렬로 잡는다. */
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  color: var(--paper);
}

/* 좌우 세로 테두리는 두지 않는다. Design.md §4 의 "테두리 언어"는 상태를 말하는
   것(실선=확정, 점선=진행 중)이고, 판면을 감싸던 이 선은 상태를 말하지 않는
   장식이었다. 빼도 §4 의 상태 언어는 그대로 남는다. */
.page {
  position: relative;
  z-index: 2;
  max-width: var(--w-content);
  margin: 0 auto;
  /* 배경 이미지는 배경색 위·자식 아래에 깔린다. .page 가 불투명해지며
     가려진 종이 결을 여기서 되살린다. */
  background-color: var(--paper);
  background-image: var(--grain-paper);
  /* §4 실선 = 확정된 것. 종이의 재단면이다 */
  border-inline: var(--border-solid);
  /* 종이가 책상에서 떠오른다 (§4 의도적 위반, tokens.css 참조) */
  box-shadow: var(--shadow-page);
  flex: 1;
  width: 100%;
}

/* ── 제본소: 도련 여백의 재단 표시와 눈금 ────────────────────
 * 이 여백은 재단 전 전지의 도련 자리다. 실제 인쇄에서 바로 이 자리에 재단
 * 표시와 눈금이 찍힌다 — 여백에 무언가를 얹는 게 아니라, 원래 거기 있던 것을
 * 되돌리는 쪽으로 채운다.
 *
 * 지난번 색띠와 갈리는 지점: 색을 쓰지 않는다. 잉크 한 색의 1px 선뿐이고,
 * 면이 아니라 눈금이다. 팔레트(§2.1)도 최대폭(§4)도 건드리지 않는다.
 *
 * 여백이 좁으면 표시가 화면 끝에 닿아 잘린 것처럼 보인다. 표시가 놓일 폭
 * (눈금 12px + 간격 8px = 20px)의 세 배가 남는 넓은 화면에서만 그린다. */
@media (min-width: 1320px) {
  .page::before,
  .page::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: var(--s-3);
    pointer-events: none;
    background-image:
      /* 재단 표시 — 종이의 위·아래 모서리 */
      linear-gradient(var(--rule-dash), var(--rule-dash)),
      linear-gradient(var(--rule-dash), var(--rule-dash)),
      /* 눈금 — 48px 마다 긴 눈, 12px 마다 짧은 눈 */
      repeating-linear-gradient(to bottom, var(--rule-dash) 0 1px, transparent 1px var(--s-12)),
      repeating-linear-gradient(to bottom, var(--rule) 0 1px, transparent 1px var(--s-3));
    background-size: 100% 1px, 100% 1px, 100% 100%, var(--s-1) 100%;
    background-repeat: no-repeat;
  }
  /* 짧은 눈은 재단선에 붙는다 — 자를 종이에 대고 읽는 방향 */
  .page::before {
    right: calc(100% + var(--s-2));
    background-position: left top, left bottom, left top, right top;
  }
  .page::after {
    left: calc(100% + var(--s-2));
    background-position: left top, left bottom, left top, left top;
  }
}

.nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-6);
  /* 세로 여백을 --s-6(24px)에서 줄인다. 예전에는 내비만 109px 로, 화면
   * 높이의 10% 넘게 먹으면서 정작 내용은 그만큼 밀려 있었다. */
  /* 좌우 여백은 변수 하나로 둔다 — 책장 페이지의 공식(.page--full .nav)이 이 값을
     따라가야 모바일에서 함께 줄어든다. */
  --nav-pad: var(--s-8);
  padding: var(--s-3) var(--nav-pad);
  /* 로그인/비로그인, 페이지 이동에 상관없이 같은 높이. 아래 내용이 그만큼
   * 위아래로 흔들리지 않는다. */
  min-height: var(--nav-h);
  border-bottom: var(--border-solid);
}
/* 책장 페이지(.page--full)에서는 내비도 화면 끝까지 늘어나, 워드마크와 링크가
 * 1919px 양 끝에 떨어져 서고 가운데가 통째로 비었다. 줄은 끝까지 긋되 내용은
 * 다른 페이지와 같은 1200px 기둥에 맞춘다. */
.page--full .nav {
  /* 다른 페이지에서 워드마크가 서는 자리 = 1200px 기둥의 왼쪽 + 내비 안쪽 여백.
   * 그 둘을 그대로 더해야 페이지를 옮겨도 워드마크가 제자리에 있다.
   * 안쪽 여백은 --nav-pad 를 쓴다. 예전에는 --s-8 을 박아 두어 모바일에서 다른 페이지가
   * 16px 로 줄 때 이 페이지만 32px 을 지고 있었고, 선택자가 두 단계라 모바일 규칙도
   * 이겼다 — 360px 에서 책장 페이지 헤더만 두 줄로 접혔다(72 → 109px). */
  padding-inline: max(var(--nav-pad), calc((100% - var(--w-content)) / 2 + var(--nav-pad)));
}
/* §3.1 워드마크는 항상 --wine 한 색 */
/* 헤더의 로고는 글자가 아니라 그림이다 — /brand/logo-horizontal.svg (brand/build.py 가
   굽는다). 예전에는 고운바탕 20px 글자였다. 로고 파일이 생긴 뒤로 같은 이름을 CSS 로
   한 번 더 그리지 않는다. 포커스 링은 링크에 걸리므로 inline-flex 로 그림을 감싼다.
   640px 아래에서는 <picture> 가 워드마크(잇다만, 45px)로 바꾼다. 가로 조합(110px)으로는
   375px 에서 헤더가 두 줄로 접혔다(72 → 109px, 로그아웃 기준 436px 부터 한 줄). 640 은 헤더가
   이미 접힘 규칙을 바꾸는 기준점이라 새 숫자를 만들지 않았다. */
.wordmark {
  display: inline-flex;
  flex: none;
  text-decoration: none;
}
/* 28px — 그림 높이의 75% 가 '잇다' 글자라, 예전 20px 글자와 같은 크기로 읽힌다 */
.wordmark img {
  display: block;
  height: 28px;
  width: auto;
}
.nav__links {
  display: flex;
  align-items: center;
  gap: var(--s-8);
  font-size: var(--t-ui);
}
.nav__links a {
  /* 글자는 작아도 누르는 영역은 손가락만큼 — 44px (WCAG 2.5.5). 위아래로만
     넓혀서 링크 사이의 간격(gap)은 그대로다. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--ink-70);
  text-decoration: none;
}
.nav__links a:hover {
  color: var(--wine);
  text-decoration: underline;
}

/* 내비의 버튼은 본문 CTA 와 크기가 달라야 한다. .btn 의 기본 패딩(16/32)이
 * 그대로 들어와 60px 짜리 버튼이 되면서, 내비 혼자 109px 를 먹고 있었다. */
.nav__links .btn {
  /* --nav-h 안에 들어가야 한다. 세로 12px 이면 버튼이 52px 가 되어 정해 둔
   * 높이를 밀어낸다. */
  padding: var(--s-2) var(--s-4);
  font-size: var(--t-ui);
}

/* 구역 이동과 계정을 가른다. 장식이 아니라 두 무리를 나누는 선이다. */
.nav__account {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding-left: var(--s-6);
  border-left: var(--border-solid);
}
/* 사진이 있으면 '내 서재' 앞에 붙는다. 없으면 회색 원판을 만들지 않는다 —
 * 빈 동그라미는 정보가 아니라 얼룩이다. */
.nav__me {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
.nav__avatar {
  position: relative;
  overflow: hidden;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--paper-lo);
}
/* 사진을 못 읽으면 paintAvatar 가 img 를 빼는데, 그러면 빈 회색 원판만 남는다.
 * 비어 있는 자리는 정보가 아니라 얼룩이므로 통째로 접는다. */
.nav__avatar:empty {
  display: none;
}
/* 로그아웃은 텍스트 무게로. 상자는 로그인(할 일이 있는 쪽)이 갖는다. */
.nav__signout {
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-45);
  font-family: var(--font-ui);
  font-size: var(--t-ui);
  cursor: pointer;
}
.nav__signout:hover {
  color: var(--wine);
  text-decoration: underline;
}

@media (max-width: 640px) {
  .nav {
    flex-wrap: wrap;
    gap: var(--s-3);
  }
}

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-6);
  padding: var(--s-16) var(--s-8) var(--s-12);
  text-align: center;
}
.hero__title {
  font-family: var(--font-display);
  font-size: var(--t-display-xl);
  line-height: var(--lh-display-xl);
  letter-spacing: var(--ls-display-xl);
  text-wrap: pretty;
}
.hero__lead {
  max-width: 46ch;
  font-family: var(--font-read);
  font-size: var(--t-read);
  line-height: var(--lh-read);
  color: var(--ink-70);
}

.shelf {
  /* 판은 책이 놓인 만큼만이다.
   *
   * 예전에는 세로로 쌓아서 선반(.shelf__row 의 아래 괘선)이 상자 폭 전체로
   * 늘어났다. 실측으로 판 1134px 에 책 125px — 11% 만 찬 선반이 되고, 나머지
   * 89% 는 "빈 상자"로 읽혔다. 라이브러리 화면은 같은 문제를 채를 책에 맞춰
   * 세우고 남은 벽에 책 무더기를 쌓는 것으로 풀었다(.room__cases--stocked).
   * 여기서는 미리보기라 그 가구를 다 들이지 않고, 판을 책에 맞추고 남는 폭에
   * 설명을 세운다.
   *
   * 첫 칸이 auto 라 책이 늘면 판도 늘어난다. 랜딩은 12권까지만 보여주므로
   * (js/home.js) 폭이 무한정 벌어지지 않는다. */
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: end;
  gap: var(--s-6) var(--s-8);
  padding: var(--s-8);
  border: var(--border-solid);
  background: var(--paper-hi);
}
/* 설명은 책 옆, 선반 높이에 맞춰 앉는다 */
.shelf > .meta {
  padding-bottom: var(--s-3);
  max-width: 38ch;
}
@media (max-width: 720px) {
  /* 좁은 화면에서는 책 옆에 설명이 설 자리가 없다. 아래로 내린다. */
  .shelf {
    grid-template-columns: minmax(0, 1fr);
  }
  .shelf > .meta {
    padding-bottom: 0;
  }
}
/* 책장 머리 — 제목·물결선·권수. .section-head 와 같은 구획 머리이므로 같은
   물결선을 쓴다. 옆에 권수가 붙는 것만 다르다. 예전에는 이 머리만 물결선이
   없어서, 바로 아래 '지금 쓰는 중' 과 형제로 보이지 않았다 (Design.md §4
   "섹션 구분은 직선 대신 SVG 물결선"). */
.shelf__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-4);
  margin-bottom: var(--s-3);
}
/* 랜딩에서 '제목 정하는 중' 묶음이 위 구획과 붙지 않게 띄운다. 예전에는
   마크업에 margin-top 이 인라인으로 박혀 있었다. */
.section-head--spaced {
  margin-top: var(--s-8);
}
.story-grid--spaced {
  margin-top: var(--s-6);
}

.shelf__head .wave {
  /* 제목과 권수 사이를 채운다. baseline 정렬이라 밑줄 높이로 내려앉는다. */
  align-self: center;
}
.shelf-region {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: 0 var(--s-8) var(--s-4);
}
.shelf__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-4);
}

/* [3D]/[목록] 토글 — 목록 뷰는 동등한 기능 (§6.4) */
.toggle {
  display: flex;
  gap: var(--s-2);
}
.toggle__btn {
  padding: var(--s-2) var(--s-3);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: transparent;
  font: inherit;
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-70);
  cursor: pointer;
}
.toggle__btn[aria-pressed='true'] {
  border-color: var(--tide);
  background: var(--wash-tide);
  color: var(--ink);
}

.now-writing {
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
  padding: var(--s-12) var(--s-8) var(--s-16);
}
.story-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--s-6);
}
.cta {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s-4);
  padding-top: var(--s-4);
}
.cta__aside {
  font-size: var(--t-ui);
  color: var(--ink-70);
}

/* §2.3 --tide 는 크림 위 4.3:1 이다. 링크 텍스트로 쓰려면
 * 18.5px 이상이거나 굵은 14px 이상이어야 한다. 여기 링크는 후자로 맞춘다. */
.story-card__foot a,
.cta__aside a {
  font-size: var(--t-ui-sm);
  font-weight: 600;
}

.footer {
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-6) var(--s-8);
  border-top: var(--border-solid);
}

@media (max-width: 860px) {
  .nav {
    --nav-pad: var(--s-4);
  }
  .hero,
  .shelf-region,
  .now-writing,
  .footer {
    padding-inline: var(--s-4);
  }
  .nav__links {
    gap: var(--s-4);
  }
  .hero {
    align-items: flex-start;
    text-align: start;
  }
  .shelf__row {
    overflow-x: auto;
  }
}

/* 로그인하면 메뉴에 사진·차례 숫자·로그아웃이 붙어 360px 에서 헤더가 두 줄로 접혔다
 * (일반 사용자 91px, 운영자 116px) — 로고를 바꾸기 전부터 그랬다. 메뉴 폭 283px 중
 * 빈칸만 64px 이었다. 간격과 구분선 여백을 절반으로, 글자를 한 단계 줄이면 일반
 * 사용자는 360px 에서도 한 줄이다. 링크가 하나 더 붙는 운영자까지 320px 에서 한
 * 줄이다(홈에서 25px 남음).
 * 위 860px 블록의 .nav__links gap 을 이겨야 하므로 반드시 그 뒤에 둔다. */
@media (max-width: 640px) {
  .nav__links {
    gap: var(--s-2);
  }
  .nav__links,
  .nav__links a,
  .nav__links .btn,
  .nav__signout {
    font-size: var(--t-ui-sm);
  }
  .nav__account {
    gap: var(--s-2);
    padding-left: var(--s-2);
  }
}

/* ═══ 폼 (§6.2 작품 개설) ═════════════════════════════════════ */

.form-page {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: var(--s-12) var(--s-8) var(--s-16);
  display: flex;
  flex-direction: column;
  gap: var(--s-12);
}
.page-head {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.page-title {
  font-family: var(--font-display);
  font-size: var(--t-display-l);
  line-height: var(--lh-display-l);
  letter-spacing: var(--ls-display-l);
}
.page-lead {
  color: var(--ink-70);
}
.field-group {
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}
.field {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.field__label {
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-70);
}
/* 확정된 값은 실선, 입력 대기는 점선 (§4 테두리 언어) */
.field__input {
  width: 100%;
  padding: var(--s-4) var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
  font-family: var(--font-read);
  font-size: var(--t-read);
  line-height: var(--lh-read);
  color: var(--ink);
}
/* 꺼진 입력칸도 마찬가지다. 둘 다 배경을 직접 칠하고 있어서 브라우저 기본
   회색이 나오지 않는다 — 눌러도 커서가 들어오지 않는 칸이 멀쩡해 보였다.
   글 쓰는 칸은 셋이다 — 폼 입력(.field__input), 작업대 입력(.composer__input),
   데일리 입력(.compose-box__input). 셋 다 배경을 자기가 칠하므로 한 규칙에
   같이 적는다. 하나 빠뜨리면 그 화면에서만 조용히 멀쩡해 보인다. */
.field__input:disabled,
.composer__input:disabled,
.compose-box__input:disabled {
  background: var(--paper-lo);
  color: var(--ink-45);
  cursor: not-allowed;
}

.field__input--open {
  border: var(--border-dashed);
  min-height: 84px;
  resize: vertical;
}
.field__input::placeholder {
  color: var(--ink-45);
}
.counter {
  align-self: flex-end;
}

.preset-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}
.chip {
  padding: var(--s-3) var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
  font: inherit;
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
}
.chip[aria-pressed='true'] {
  border-color: var(--flame);
  background: var(--wash-flame);
  color: var(--wine);
}

.fieldset {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  border: 0;
  margin: 0;
  padding: 0;
}
.fieldset__legend {
  font-family: var(--font-display);
  font-size: var(--t-title);
  line-height: var(--lh-title);
  padding: 0;
}
.option-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding: var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
  cursor: pointer;
}
.option-row:has(input:checked) {
  border-color: var(--flame);
  background: var(--wash-flame);
}
/* 라디오만 겨냥한다. `.option-row input` 으로 두면 옆의 숫자 입력까지
 * 16px로 눌러버려 값이 보이지 않는다(.option-row__num 보다 특정성이 높다). */
.option-row input[type='radio'] {
  accent-color: var(--flame);
  width: 16px;
  height: 16px;
}
.option-row__name {
  font-weight: 600;
}
.option-row__num {
  /* border-box라 padding·스피너까지 이 폭 안에 들어간다. ch로 잡으면 값이 잘린다. */
  width: calc(var(--s-12) + var(--s-8));
  padding: var(--s-2) var(--s-3);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--t-body);
  color: var(--ink);
}
.option-row__tail {
  color: var(--ink-70);
}

/* 드롭다운이 아니라 두 장의 카드. 각 카드에 결과를 함께 적는다 (§6.2) */
.choice-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--s-4);
}
.choice-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-6);
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper-hi);
  text-align: start;
  font: inherit;
  cursor: pointer;
}
.choice-card[aria-pressed='true'] {
  border-color: var(--flame);
  background: var(--wash-flame);
}
.choice-card__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
}
.choice-card__name {
  font-family: var(--font-display);
  /* 24px 은 스케일 밖이었고, 바로 위 fieldset__legend(28px)와 4px 밖에 차이가
     나지 않아 카드 이름이 그 질문만큼 커 보였다. 한 단 내려 28 > 20 > 15 로
     간격을 벌린다. */
  font-size: var(--t-subtitle);
  color: var(--ink);
}
.choice-card[aria-pressed='true'] .choice-card__name {
  color: var(--wine);
}
/* 라벨은 양쪽 카드에 두고 선택된 쪽만 드러낸다. visibility 라 자리를
 * 차지한 채로 있어서 고를 때 레이아웃이 흔들리지 않는다. */
.choice-card__mark {
  visibility: hidden;
  color: var(--wine);
  font-weight: 500;
}
.choice-card[aria-pressed='true'] .choice-card__mark {
  visibility: visible;
}
.choice-card__desc {
  font-size: var(--t-ui);
  line-height: var(--lh-body);
  color: var(--ink-70);
}

/* 시스템이 관리하는 정보 = 청록 점선 (§2.4) */
.callout {
  display: flex;
  gap: var(--s-4);
  padding: var(--s-6);
  border: var(--border-dashed);
  border-color: var(--tide-dash);
  border-radius: var(--r-md);
  background: var(--wash-tide);
}
.callout__mark {
  flex: none;
  font-family: var(--font-mono);
  font-weight: 500;
  color: var(--tide-text);
}
.callout p {
  font-size: var(--t-ui);
  line-height: var(--lh-body);
  color: var(--ink-70);
}

.form-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-6);
  padding-top: var(--s-6);
  border-top: var(--border-solid);
}
.form-foot__note {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-ui-sm);
  color: var(--ink-70);
}

/* ═══ 작업대 (§6.3) ═══════════════════════════════════════════ */

.wb-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--s-8);
  padding: var(--s-8);
  border-bottom: var(--border-solid);
}
.wb-head__topic {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.wb-head__title {
  font-family: var(--font-display);
  font-size: var(--t-display-l);
  line-height: var(--lh-display-l);
  letter-spacing: var(--ls-display-l);
  color: var(--wine);
}
.wb-head__stickers {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}

.workbench {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
}
.wb-main {
  display: flex;
  flex-direction: column;
  gap: var(--s-8);
  padding: var(--s-8);
  border-right: var(--border-solid);
}
.wb-body {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  max-width: var(--measure-read);
}

/* 처음부터 펼쳤을 때. 본문이 길어져도 입력창이 화면 밖으로 밀려나면 안 되므로
 * 본문만 스크롤한다. 아래로 갈수록 진해지는 인쇄물처럼 위쪽에 페이드를 둔다. */
.wb-body--full {
  max-height: 46vh;
  overflow-y: auto;
  padding: var(--s-4) var(--s-4) 0 0;
  border-top: var(--border-solid);
  mask-image: linear-gradient(to bottom, transparent 0, var(--ink) var(--s-8));
}

.wb-scroll {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.wb-scroll__btn {
  padding: var(--s-2) var(--s-3);
  border: var(--border-dashed);
  border-radius: var(--r-sm);
  background: transparent;
  font: inherit;
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-70);
  cursor: pointer;
}
.wb-scroll__btn:hover {
  border-color: var(--tide-dash);
  background: var(--wash-tide);
  color: var(--ink);
}
.wb-scroll__btn[aria-expanded='true'] {
  border-style: solid;
  border-color: var(--rule);
  background: var(--paper-hi);
}

/* 직전 문장만 --ink 100%, 그 앞은 흐리게 — "바로 앞을 받아쓴다" (§6.3) */
.sentence {
  font-family: var(--font-read);
  font-size: var(--t-read);
  line-height: var(--lh-read);
  letter-spacing: var(--ls-read);
  color: var(--ink-70);
}
.sentence--current {
  display: flex;
  gap: var(--s-4);
  color: var(--ink);
}
.sentence__bar {
  flex: none;
  width: 3px;
  background: var(--author-color, var(--wine));
}
.sentence--current .sentence__text {
  color: var(--ink);
}
.sentence__by {
  display: block;
  margin-top: var(--s-1);
}

.composer {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.composer__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-4);
}
/* 입력창은 항상 점선(열려 있음). 제출 성공 시 실선으로 굳는다 (§6.3) */
.composer__input {
  width: 100%;
  min-height: 96px;
  padding: var(--s-6);
  border: var(--border-dashed);
  border-radius: var(--r-md);
  background: var(--paper-hi);
  font-family: var(--font-read);
  font-size: var(--t-read);
  line-height: var(--lh-read);
  color: var(--ink);
  resize: vertical;
}
.composer__input::placeholder {
  color: var(--ink-45);
}
.composer__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
}
.composer__hint {
  font-size: var(--t-caption);
  line-height: var(--lh-caption);
  color: var(--ink-45);
}
/* 쿨다운 중 — 목업에 없는 상태 (CLAUDE.md §8.4) */
.composer--cooldown .composer__input {
  background: var(--paper-lo);
  color: var(--ink-45);
}

.round-mark {
  display: flex;
  align-items: center;
  gap: var(--s-4);
}

/* CandidateCard — 후보 문장 + 득표 막대 */
.candidates {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.candidate {
  display: flex;
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper-hi);
  overflow: hidden;
}
.candidate--voted {
  border-color: var(--flame);
  background: var(--wash-flame);
}
.candidate__bar {
  flex: none;
  width: 4px;
  background: var(--author-color, var(--wine));
}
.candidate__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-6);
}
.candidate__text {
  font-family: var(--font-read);
  font-size: var(--t-read);
  line-height: var(--lh-read);
  color: var(--ink);
}
.candidate__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
  flex-wrap: wrap;
}
.byline {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-caption);
  color: var(--ink-70);
}
.dot {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: var(--border-solid);
  background: var(--author-color, var(--wine));
}
.candidate__tally {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}
/* VoteBar — 길이로만 표현 (§9) */
.votebar {
  width: 180px;
  max-width: 100%;
  height: 8px;
  border: var(--border-solid);
  background: var(--paper-lo);
}
.votebar__fill {
  display: block;
  height: 100%;
  width: var(--pct);
  background: var(--flame);
}

.side {
  display: flex;
  flex-direction: column;
  gap: var(--s-8);
  padding: var(--s-8) var(--s-6);
  background: var(--paper-hi);
}
.side__block {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.side__block + .side__block {
  padding-top: var(--s-6);
  border-top: var(--border-solid);
}
.side__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-3);
}
.side__row dt {
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-70);
}
/* 여기가 유일하게 초 단위로 변하는 영역 — 청록 (§6.3) */
.side__row dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-subtitle);
  font-weight: 500;
  color: var(--tide);
}
.countdown-box {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-4);
  border: var(--border-dashed);
  border-color: var(--tide-dash);
  border-radius: var(--r-md);
  background: var(--wash-tide);
}
.countdown {
  font-family: var(--font-mono);
  /* 30px 은 스케일(§3.3) 밖이었다. 숫자 표시라 한 단 내려도 위계가 그대로다. */
  font-size: var(--t-title);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--tide-text);
}
.countdown-box__note,
.side__note {
  font-size: var(--t-caption);
  line-height: var(--lh-caption);
  color: var(--ink-70);
}
/* ProgressBar — 줄무늬 (§9, 레퍼런스 계승) */
.progress {
  height: 10px;
  border: var(--border-solid);
  background: var(--paper-lo);
}
.progress__fill {
  display: block;
  height: 100%;
  width: var(--pct);
  background: repeating-linear-gradient(
    135deg,
    var(--tide) 0 6px,
    var(--tide-dash) 6px 12px
  );
}
.rule-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--t-ui-sm);
  line-height: 1.6;
  color: var(--ink-70);
}
.avatars {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.avatar {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid var(--paper-hi);
  box-shadow: 0 0 0 1px var(--rule);
  background: var(--author-color, var(--wine));
}
.avatar--more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  height: 28px;
  padding: 0 var(--s-2);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  box-shadow: none;
  background: transparent;
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  color: var(--ink-45);
}

/* 모바일 작업대는 세로 스택, 상태 바는 하단 고정 (§11) */
.wb-statusbar {
  display: none;
}
@media (max-width: 860px) {
  .workbench {
    grid-template-columns: minmax(0, 1fr);
  }
  .wb-main {
    border-right: 0;
    padding: var(--s-4);
  }
  .wb-head {
    padding: var(--s-4);
  }
  .side {
    padding: var(--s-6) var(--s-4);
  }
  .form-page {
    padding: var(--s-8) var(--s-4) var(--s-12);
  }
  /* 360px까지 무너지지 않아야 한다 (§11) — 좌우 여백을 16px로 줄인다 */
  .profile-head,
  .stamps-block,
  .tabs,
  .panels,
  .lib-head,
  .filters,
  .shelves,
  .book-list,
  .vote-head,
  .cover-grid,
  .vote-foot,
  .word-hero,
  .daily-head,
  .poem-area,
  .daily-write,
  .preview-switch,
  .reader-bar {
    padding-inline: var(--s-4);
  }
  .wb-statusbar {
    position: sticky;
    bottom: 0;
    z-index: 3;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--s-4);
    padding: var(--s-3) var(--s-4);
    border-top: var(--border-solid);
    background: var(--wash-tide);
    backdrop-filter: none;
  }
  .wb-statusbar__time {
    font-family: var(--font-mono);
    font-size: var(--t-subtitle);
    font-weight: 500;
    color: var(--tide);
  }
  /* 좁은 화면에서는 펼친 본문을 더 낮게. 입력창까지 내려가는 거리를 줄인다. */
  .wb-body--full {
    max-height: 34vh;
  }
}

/* ═══ EmptyState (§9) — 빈 상태는 초대다 (§10) ════════════════ */

.empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-12) var(--s-8);
  border: var(--border-dashed);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.empty__title {
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  color: var(--ink);
}
.empty__body {
  color: var(--ink-70);
}

/* ═══ 로그인 · 가입 (M1) ══════════════════════════════════════ */

.auth-page {
  width: min(420px, 100%);
  margin: 0 auto;
  padding: var(--s-16) var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-8);
}
.auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.auth-switch {
  display: flex;
  gap: var(--s-2);
}
.auth-switch__btn {
  flex: 1;
  padding: var(--s-3);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
  font: inherit;
  font-weight: 600;
  color: var(--ink-70);
  cursor: pointer;
}
.auth-switch__btn[aria-pressed='true'] {
  border-color: var(--flame);
  background: var(--wash-flame);
  color: var(--wine);
}
/* 에러는 사과하지 않고 방법을 알려준다 (§10) */
.form-error {
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--flame);
  border-radius: var(--r-sm);
  background: var(--wash-flame);
  font-size: var(--t-ui);
  color: var(--wine);
}
.form-error:empty {
  display: none;
}
.field__hint {
  font-size: var(--t-caption);
  color: var(--ink-45);
}
.auth-form__forgot {
  align-self: center;
  font-size: var(--t-caption);
}
/* 다른 문으로 들어오기 (구글). 폼과 같은 폭으로, 한 칸 떨어뜨려 둔다. */
.auth-alt {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.auth-alt[hidden] {
  display: none;
}
.auth-alt__or {
  align-self: center;
}
/* 구글 로그인 버튼. 크기·색·글꼴·여백이 전부 구글 브랜딩 가이드라인의 값이다
   (웹: G 왼쪽 12px, G 와 글자 사이 10px, 글자 오른쪽 12px / Roboto Medium 14/20).
   사이트의 버튼 모양으로 바꾸지 않는다. 테두리는 "안쪽 1px" 이라 inset 그림자로 긋는다 —
   border 로 그으면 바깥으로 1px 커진다. */
.gsi-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 40px;
  padding: 0 12px;
  border-radius: var(--r-sm);
  background: var(--google-fill);
  box-shadow: inset 0 0 0 1px var(--google-stroke);
  color: var(--google-text);
  font-family: var(--font-google);
  font-weight: 500;
  font-size: 14px;
  line-height: 20px;
  text-decoration: none;
}
.gsi-btn[hidden] {
  display: none;
}
.gsi-btn:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
.gsi-btn__logo {
  flex: none;
  width: 20px;
  height: 20px;
}

/* ═══ 데일리 라이팅 (§6.8) ════════════════════════════════════ */

/* 단계는 실제로는 KST 시각이 정한다(00:00 / 21:00 / 24:00).
 * 정적 프로토타입이라 세 단계를 눈으로 비교할 수 있게 스위처를 둔다. */
.preview-switch {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
  padding: var(--s-3) var(--s-8);
  border-bottom: var(--border-dashed);
  background: var(--paper-lo);
}
.preview-switch__note {
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-45);
}
.stage {
  display: none;
  flex-direction: column;
}
.daily:has(#phase-write:checked) #stage-write,
.daily:has(#phase-vote:checked) #stage-vote,
.daily:has(#phase-result:checked) #stage-result {
  display: flex;
}

.word-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-16) var(--s-8) var(--s-8);
}
.word-hero__word {
  font-family: var(--font-display);
  font-size: var(--t-display-xl);
  line-height: var(--lh-display-xl);
  letter-spacing: 0.04em;
  color: var(--wine);
}

/* 3단계 진행 — 청록 (§6.8) */
.ptrack {
  display: flex;
  align-items: flex-start;
  width: min(520px, 100%);
  margin: 0 auto;
  padding: 0;
  list-style: none;
}
.ptrack__step {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
}
.ptrack__rail {
  display: flex;
  align-items: center;
  width: 100%;
}
.ptrack__rail::before,
.ptrack__rail::after {
  content: '';
  flex: 1;
  height: 2px;
  background: var(--rule);
}
.ptrack__step:first-child .ptrack__rail::before,
.ptrack__step:last-child .ptrack__rail::after {
  background: transparent;
}
.ptrack__dot {
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid var(--rule-dash);
  background: var(--paper);
}
.ptrack__step--now .ptrack__dot {
  border-color: var(--tide);
  background: var(--tide);
}
.ptrack__step--now .ptrack__rail::before {
  background: var(--tide);
}
.ptrack__name {
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-45);
}
.ptrack__step--now .ptrack__name {
  color: var(--ink);
}
.ptrack__when {
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-45);
}
.ptrack__step--now .ptrack__when,
.daily-head__clock {
  font-family: var(--font-mono);
  font-size: var(--t-read);
  font-weight: 500;
  color: var(--tide-text);
}

/* 결과 단계의 머리 — 가운데 날짜·단어·편수와 제목 (mockups 8c) */
.result-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-12) var(--s-8) 0;
  text-align: center;
}

/* 쓰는 단계 안내 상자의 숫자 ("지금 148명이…") */
.callout__num {
  font-family: var(--font-mono);
  font-size: var(--t-body);
  font-weight: 500;
  color: var(--tide-text);
}

/* 이번 주 띠 — 월~일과 결선 (Design.md §6.8 "주기") */
.week-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-6);
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.week {
  display: flex;
  align-items: flex-end;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.week__day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-1);
}
.week__dot {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid var(--wine);
  background: var(--paper);
}
.week__day--today .week__dot {
  background: var(--wine);
}
.week__day--future .week__dot {
  border: 1.5px dashed var(--rule-dash);
  background: none;
}
.week__day--final {
  margin-inline-start: var(--s-2);
  padding-inline-start: var(--s-3);
  border-left: var(--border-solid);
}
/* 왕관은 주홍 (Design.md §6.7 — 금색이 아니다) */
.week__crown {
  font-size: var(--t-subtitle);
  line-height: 1;
  color: var(--flame);
}
.week__name {
  font-size: var(--t-meta);
  color: var(--ink-45);
}

/* 글자처럼 보이는 버튼 ("이 시 공유"). 링크와 같은 색·밑줄 — 누르면 어딘가로
   가는 대신 공유 창이 뜨므로 <a> 가 아니라 <button> 이다. */
.text-link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--tide-text);
  text-decoration: underline;
  cursor: pointer;
}
.text-link:hover {
  color: var(--wine);
}

.daily-write {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-8);
  padding: 0 var(--s-8) var(--s-16);
}
.compose-box {
  width: min(620px, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.compose-box__input {
  width: 100%;
  min-height: 180px;
  padding: var(--s-6);
  border: var(--border-dashed);
  border-radius: var(--r-md);
  background: var(--paper-hi);
  font-family: var(--font-read);
  font-size: var(--t-read);
  line-height: var(--lh-read);
  color: var(--ink);
  resize: vertical;
}
.compose-box__input::placeholder {
  color: var(--ink-45);
}
.compose-box__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-4);
}
.live-count {
  font-family: var(--font-mono);
  font-size: var(--t-body);
  font-weight: 500;
  color: var(--tide);
}

.daily-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--s-6);
  padding: var(--s-12) var(--s-8) var(--s-6);
  border-bottom: var(--border-solid);
}
.daily-head__votes {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s-2);
}
.poem-area {
  padding: var(--s-8);
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}
/* 내가 쓴 시에는 투표할 수 없다 (§2.2 데일리 규칙) */
.poem-card--mine {
  background: var(--paper-lo);
}
.poem-card__self {
  padding: var(--s-3);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  text-align: center;
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-45);
}

.result-top {
  display: flex;
  gap: var(--s-6);
  align-items: stretch;
  flex-wrap: wrap;
}
.winner-card {
  flex: 1;
  min-width: 300px;
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
  padding: var(--s-8);
  border: 2px double var(--wine);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.winner-card__poem {
  font-family: var(--font-read);
  font-size: var(--t-read-lead);
  line-height: var(--lh-read);
}
.winner-card__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding-top: var(--s-4);
  border-top: var(--border-solid);
}
.result-side {
  width: min(300px, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.mine-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-6);
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper-lo);
}
.mine-card__foot {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding-top: var(--s-4);
  border-top: var(--border-solid);
}
.week-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-6);
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.week-strip {
  display: flex;
  align-items: flex-end;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.week-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-1);
}
.week-day__dot {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid var(--wine);
  background: var(--paper);
}
.week-day--won .week-day__dot {
  background: var(--wine);
}
.week-day--future .week-day__dot {
  border: var(--border-dashed);
}
.week-day__label {
  font-size: var(--t-meta);
  color: var(--ink-45);
}
.week-strip__sep {
  width: 1px;
  height: 34px;
  margin: 0 var(--s-1);
  background: var(--rule);
}
/* 왕관도 주홍. 금색 금지 (§12) */
.week-day__crown {
  font-size: var(--t-subtitle);
  line-height: 1;
  color: var(--flame);
}

/* ═══ 프로필 (§6.7) ═══════════════════════════════════════════ */

.profile-head {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--s-8);
  padding: var(--s-12) var(--s-8) var(--s-8);
  border-bottom: var(--border-solid);
}
.avatar-lg {
  /* span 이라 기본이 inline 이고 그때는 width/height 가 먹지 않는다. 예전에는
   * 부모가 flex 라 자동으로 블록이 됐을 뿐이라, 그 우연에 기대지 않는다. */
  display: block;
  flex: none;
  width: 88px;
  height: 88px;
  border-radius: 50%;
  border: 4px solid var(--author-color, var(--wine));
  background: var(--paper-hi);
}
.profile-head__body {
  flex: 1;
  min-width: 240px;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.profile-head__name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-4);
}
/* 왕관은 주홍. 금색 트로피 금지 (§12). 현재 보유자에게만 (§6.7) */
.crown {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--flame);
  border-radius: var(--r-sm);
  background: var(--wash-flame);
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--wine);
  transform: rotate(-2deg);
}
.crown__mark {
  font-size: var(--t-body);
  color: var(--flame);
}
/* 통계는 숫자 나열 대신 띠 그래프 1개 (§6.7).
 * §9에 따라 헤더에 "내 색" 점은 두지 않는다 — 색은 작품마다 다르다. */
.ribbon-block {
  width: min(300px, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.stamps-block {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-6) var(--s-8);
}
.stamps {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--s-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
/* 배지가 아니라 도장(印). 원형, 미세 회전, 라벨 병기 (§6.7) */
.stamp {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  width: 96px;
}
.stamp__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  border: 2px solid var(--wine);
  border-radius: 50%;
  font-family: var(--font-display);
  /* 17px 은 스케일 밖. 54px 원 안에 두 줄이 들어가는지 재 보고 --t-body 로
     내렸다. */
  font-size: var(--t-body);
  line-height: 1.1;
  text-align: center;
  color: var(--wine);
  transform: rotate(var(--tilt, -3deg));
}
.stamp__label {
  font-size: var(--t-meta);
  text-align: center;
  color: var(--ink-70);
}
.stamp--more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  border: var(--border-dashed);
  border-radius: 50%;
  font-family: var(--font-mono);
  font-size: var(--t-caption);
  color: var(--ink-45);
}

/* 탭도 라디오 + :has() 로. 스크립트 없이 세 패널을 전환한다. */
/* ── 프로필의 두 기둥 ─────────────────────────────────────────
 * 여태 프로필은 폭 1200px 짜리 띠를 여섯 개 쌓은 것이었다. 띠마다 든 것은
 * 짧아서(도장 하나, 노드 다섯, 설정 두 줄) 어느 띠를 봐도 오른쪽 절반이
 * 비었고, 한 띠씩 고쳐서는 끝나지 않았다 — 빈 곳은 띠의 문제가 아니라 띠를
 * 쌓았다는 사실의 문제였다.
 *
 * 문장에 넓은 쪽을 주고 곁의 것들을 오른쪽 기둥에 모은다. 1.7:1 로 어긋나게
 * 두는 이유는 둘이 같은 무게로 보이면 안 되기 때문이다 — 프로필의 주인공은
 * 이 사람이 쓴 문장이고, 도장과 관계도는 곁이다. */
.profile-grid {
  display: grid;
  /* 곁기둥에 420px 하한을 둔다. 비율로만 두면 1400px 아래에서 기둥이 좁아지고
     관계도가 따라 줄어드는데(실측: 950px 화면에서 그림 313px, 이름표 10.6px),
     이 서체에서 10px 은 읽히지 않는다. 좁아질 때는 문장 쪽이 먼저 줄어든다. */
  grid-template-columns: minmax(0, 1.7fr) minmax(420px, 1fr);
  /* 늘어나게 둔다(stretch). 곁기둥을 내용 높이에 맞추면 가르는 선이 중간에서
     끊겨, 단을 나눈 것이 아니라 상자를 하나 얹은 것처럼 보인다. */
}
.profile-rail {
  /* 두 기둥을 가르는 선 하나. 곁기둥에 배경색이나 상자를 주면 페이지에
     붙은 위젯처럼 보인다 — 같은 종이 위의 다른 단이어야 한다. */
  border-left: var(--border-solid);
  /* 세로 흐름으로 두는 것은 설정 손잡이를 기둥 맨 아래에 붙이기 위해서다
     (.settings 의 margin-block-start: auto) */
  display: flex;
  flex-direction: column;
}
/* 구획을 가르는 선은 아래가 아니라 위에 긋는다. 아래에 그으면 뒤따르는 구획이
   숨었을 때 — 남의 프로필에는 설정이, 아직 함께 쓴 사람이 없으면 관계도가
   없다 — 아무것도 가르지 않는 선이 기둥 중간에 남는다. :last-child 는 숨은
   형제도 세므로 그것으로는 지울 수 없었다. */
.profile-rail > * + * {
  border-top: var(--border-solid);
}
/* 곁기둥은 400px 남짓이라 관계도(385px)가 꽉 찬다. 좌우 여백을 띠와 같은
   --s-8 로 두면 그림이 눌리므로 여기서만 줄인다. */
.profile-rail .circle-block {
  padding-inline: var(--s-4);
}

@media (max-width: 900px) {
  .profile-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .profile-rail {
    border-left: 0;
    border-top: var(--border-solid);
  }
}

.tabs {
  display: flex;
  gap: var(--s-2);
  padding: var(--s-6) var(--s-8) 0;
  flex-wrap: wrap;
}
.tabs__radio {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.tab {
  padding: var(--s-3) var(--s-4);
  border: var(--border-solid);
  border-bottom: none;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  background: var(--paper-hi);
  font-size: var(--t-ui);
  font-weight: 600;
  color: var(--ink-70);
  cursor: pointer;
}
.tabs__radio:checked + .tab {
  background: var(--paper);
  color: var(--ink);
  box-shadow: inset 0 3px 0 var(--flame);
}
.tabs__radio:focus-visible + .tab {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
.panels {
  padding: 0 var(--s-8) var(--s-12);
}
.panel {
  display: none;
  flex-direction: column;
  gap: var(--s-6);
  padding-top: var(--s-6);
  border-top: var(--border-solid);
}
.profile:has(#tab-sentences:checked) #panel-sentences,
.profile:has(#tab-books:checked) #panel-books,
.profile:has(#tab-daily:checked) #panel-daily {
  display: flex;
}

/* ── 이어지지 않은 문장 ───────────────────────────────────────
 * 중단된 이야기에 남은 문장들. 이어진 문장 아래에 괘선 하나로 갈라 둔다.
 * 상자를 씌우거나 흐리게 하지 않는다 — 실패 목록처럼 보이면 안 된다.
 * 남은 문장이 하나도 없으면 이 묶음 자체가 나오지 않는다 (js/profile.js). */
.unfinished {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  margin-top: var(--s-8);
  padding-top: var(--s-8);
  border-top: var(--border-dashed);
}
.unfinished__note {
  max-width: 62ch;
}
/* 묶음을 파일로 가져가는 손잡이. 묶음이 flex 세로라 그냥 두면 한 줄을 다 차지해
 * 문장들보다 무거워진다. 글자만큼만 벌게 둔다. */
.unfinished__get {
  align-self: flex-start;
  font-size: var(--t-ui-sm);
}
/* 묶음 안의 첫 인용은 제목 바로 아래라 윗줄이 필요 없다 */
.unfinished .quote-row:first-of-type {
  padding-top: 0;
  border-top: none;
}

.quote-row {
  display: flex;
  gap: var(--s-4);
  padding-top: var(--s-6);
  border-top: var(--border-solid);
}
.quote-row:first-child {
  padding-top: 0;
  border-top: none;
}
.quote-row__bar {
  flex: none;
  width: 4px;
  background: var(--author-color, var(--wine));
}
.quote-row__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  min-width: 0;
}
.quote-row__text {
  font-family: var(--font-read);
  font-size: var(--t-read);
  line-height: var(--lh-read);
}
.quote-row__share {
  align-self: center;
  flex: none;
  font-size: var(--t-ui-sm);
  font-weight: 600;
}

.book-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--s-4);
}
.book-tile {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.book-tile__title {
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  color: var(--wine);
}

/* ═══ PoemCard (§6.8 · §9) — 익명 / 수상 2종 ══════════════════
 * 카드 크기를 글자 수에 따라 바꾸지 않는다. 짧은 시가 시각적으로
 * 불리해지면 안 된다 (§6.8 시각 처리). */

.poem-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  /* 모든 줄을 가장 높은 카드에 맞춘다. 짧은 시가 작은 카드에 담기면 시각적으로
     불리해진다 — "카드 크기를 글자 수에 따라 바꾸지 않는다" (Design.md §6.8).
     높이를 px 로 못박지 않는 이유: 신고 버튼과 검토 표시가 붙으면 넘친다. */
  grid-auto-rows: 1fr;
  gap: var(--s-6);
}
.poem-grid > .empty {
  grid-column: 1 / -1;
}
.poem-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  min-height: 260px;
  padding: var(--s-6);
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
/* 수상작만 이중 테두리로 승격. 익명 카드와의 차이가 가장 명확한
 * 시각적 위계여야 한다 (§6.8) */
.poem-card--winner {
  border: 2px double var(--wine);
}
/* 결과 단계의 "나머지" 카드. 이미 끝난 시라 버튼이 없어 한 단계 작다 */
.poem-card--small {
  min-height: 200px;
}
.poem-card__head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--s-3);
}
.poem-card__text {
  flex: 1;
  font-family: var(--font-read);
  font-size: var(--t-read);
  line-height: var(--lh-read);
  color: var(--ink);
}
.poem-card__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding-top: var(--s-3);
  border-top: var(--border-solid);
}
/* 익명 카드에는 번호 도장만. 색·이니셜 등 신원 단서 금지 (§6.8 · §12) */
.anon-tag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: var(--border-solid);
  border-radius: 50%;
  font-family: var(--font-mono);
  font-size: var(--t-caption);
  color: var(--ink-45);
}
.private-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-2);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-70);
}

/* ═══ ContributionRibbon (§7 · §9) ═══════════════════════════
 * 같은 데이터가 표지 색띠 · 프로필 띠 그래프 · 책등에서 재사용된다. */

.ribbon {
  display: flex;
  height: 8px;
  overflow: hidden;
}
.ribbon--tall {
  height: 14px;
  border: var(--border-solid);
}
.ribbon__seg {
  flex: var(--w, 1);
  background: var(--author-color);
}
/* 내 몫만 강조하고 나머지는 잠재운다 (§6.7 "내 색이 차지하는 비율") */
.ribbon__seg--mine {
  background: var(--author-color);
}
.ribbon__seg--other {
  background: repeating-linear-gradient(
    135deg,
    var(--paper-lo) 0 5px,
    var(--paper-hi) 5px 10px
  );
}

/* ═══ PhaseTracker (§6.6 · §6.8) ══════════════════════════════ */

.phases {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
  justify-content: center;
  margin: 0;
  padding: 0;
  list-style: none;
}
.phases__link {
  width: 24px;
  height: 1px;
  background: var(--rule);
}
.phase {
  padding: var(--s-2) var(--s-3);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-45);
}
.phase--done {
  transform: rotate(-2deg);
}
.phase--done:nth-of-type(even) {
  transform: rotate(1deg);
}
/* 지금 살아 움직이는 단계 = 청록 (§2.4) */
.phase--now {
  border: var(--border-dashed);
  border-color: var(--tide-dash);
  background: var(--wash-tide);
  font-weight: 500;
  color: var(--ink-70);
  transform: rotate(-1deg);
}
.phase--future {
  border: var(--border-dashed);
  background: transparent;
  transform: rotate(2deg);
}

/* ═══ 제목 투표 (§6.6) ════════════════════════════════════════ */

.vote-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-8);
  padding: var(--s-12) var(--s-8) var(--s-6);
  text-align: center;
}
.vote-head__intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
}
.vote-head__lead {
  max-width: 48ch;
  color: var(--ink-70);
}

.cover-grid {
  /* 예전에는 grid + repeat(auto-fit, minmax(230px, 1fr)) 였다. 범인은 위쪽의
     1fr 이다 — auto-fit 이 빈 트랙을 접고 나면 남은 한 칸이 판 전체를 먹고,
     2:3 비율이 그 폭을 그대로 높이로 늘렸다. 후보가 하나일 때 표지가
     1100×1650px, 문서 높이 2417px. 책 표지가 아니라 벽이었다.
     격자를 버리고 줄로 세운다. 표지는 다 같은 크기라 칸을 맞출 것이 없고,
     무엇보다 접힌 트랙이 남기는 빈 간격 때문에 후보 하나가 가운데에서
     60px 어긋났다(실측). 줄은 그런 것이 없다. */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-6);
  padding: var(--s-6) var(--s-8) var(--s-12);
}
/* 표지는 넓어질수록 좋아지는 물건이 아니다. 늘리지 않고, 좁을 때만 줄인다 —
   폭 320px 짜리 화면에서도 shrink 가 알아서 맞춘다(실측: 넘침 없음). */
.title-card,
.cover-add {
  flex: 0 1 260px;
}
.cover-grid > .empty {
  flex: 1 1 100%;
}
/* 제안하기는 버튼이 아니라 아직 아무것도 안 적힌 표지 한 장이다. 표지들과
   같은 2:3 으로 서서 자기가 무엇이 될 자리인지 스스로 말한다. 후보 카드는
   표지 아래에 제안자·득표·투표 버튼을 더 달고 있으므로 이 칸이 더 짧다 —
   위를 맞춰 표지끼리 한 줄에 서게 한다. */
.cover-add {
  align-self: start;
  width: 100%;
  aspect-ratio: 2 / 3;
  padding: var(--s-6);
  border-style: dashed;
  border-radius: 0;
  text-align: center;
}
.title-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.title-card--voted {
  border-color: var(--flame);
  background: var(--wash-flame);
}
/* 후보를 실제 표지로 렌더한다 — 투표 = 표지를 고르는 행위 (§6.6) */
.cover {
  aspect-ratio: 2 / 3;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-8) var(--s-6);
  border: var(--border-solid);
  background: var(--cover-bg, var(--wine));
}
.cover__meta {
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--cover-meta, var(--paper-60));
}
.cover__title {
  font-family: var(--font-display);
  font-size: var(--t-title);
  line-height: var(--lh-title);
  color: var(--cover-fg, var(--paper));
}
.title-card__foot {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.vote-foot {
  display: flex;
  /* 머리글·표지와 같은 축. 제안 버튼이 표지 칸으로 옮겨 가면서 이 줄에는
     안내문 하나만 남았고, space-between 은 그것을 왼쪽 끝으로 밀었다. */
  justify-content: center;
  text-align: center;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-6);
  padding: 0 var(--s-8) var(--s-12);
}
.vote-foot__note {
  font-size: var(--t-ui);
  color: var(--ink-70);
}
/* 제목 제안 — 브라우저 prompt() 대신 지면 위의 폼으로 (§9) */
.propose {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  width: min(560px, 100%);
  margin: 0 auto var(--s-12);
  padding: var(--s-6);
  border: var(--border-dashed);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.propose__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-4);
}

/* ═══ 책장 (§6.4) ═════════════════════════════════════════════ */

.lib-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--s-8);
  padding: var(--s-12) var(--s-8) var(--s-6);
}
.lib-head__count {
  color: var(--ink-70);
}
.lib-head__num {
  font-family: var(--font-numeral);
  font-weight: 600;
  color: var(--wine);
}
.lib-tools {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.search {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
}
.search input {
  width: 200px;
  max-width: 100%;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--t-ui-sm);
  color: var(--ink);
}
.search input:focus-visible {
  outline: none;
}
.search:has(input:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
.search__mark {
  font-family: var(--font-mono);
  font-size: var(--t-ui-sm);
  color: var(--ink-45);
}

/* [책장]/[목록] — 목록 뷰는 동등한 기능이어야 한다 (§6.4).
 * 라디오 + :has() 로 전환한다. 3D 없이도 전부 접근 가능(§12). */
.viewswitch {
  display: flex;
  border: var(--border-solid);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.viewswitch label {
  padding: var(--s-3) var(--s-4);
  background: var(--paper-hi);
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-70);
  cursor: pointer;
}
.viewswitch label + label {
  border-left: var(--border-solid);
}
.viewswitch label:has(input:checked) {
  background: var(--wash-tide);
  color: var(--ink);
}
.viewswitch label:has(input:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: calc(var(--focus-offset) * -1);
}
.viewswitch input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.filters {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
  padding: 0 var(--s-8) var(--s-4);
}

.list-view {
  display: none;
}
.library:has(#view-list:checked) .shelf-view {
  display: none;
}
.library:has(#view-list:checked) .list-view {
  display: block;
}

.shelves {
  display: flex;
  flex-direction: column;
  gap: var(--s-8);
  padding: var(--s-4) var(--s-8) var(--s-12);
}
.shelf-section {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.shelf-section__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-4);
}
.shelf-section__title {
  font-family: var(--font-display);
  font-size: var(--t-title);
  line-height: var(--lh-title);
}
.shelf-case {
  padding: var(--s-8) var(--s-8) 0;
  border: var(--border-solid);
  background: var(--paper-hi);
  overflow-x: auto;
}
.shelf-case .shelf__row {
  border-bottom-width: 3px;
  padding-bottom: var(--s-3);
}
/* 책 hover → 살짝 튀어나오며 제목 (§6.4). 여기서는 정적 카드로 병기 */
.book-note {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--s-2);
  width: 260px;
  margin-left: var(--s-6);
  padding: var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper);
  transform: rotate(-1deg);
}
.book-note__title {
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  color: var(--wine);
}
.shelf-aside {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--s-2);
  margin-left: var(--s-6);
  padding-bottom: var(--s-3);
}
.legend {
  display: inline-flex;
  align-items: center;
  width: max-content;
  padding: var(--s-2) var(--s-3);
  border: var(--border-double);
  border-radius: var(--r-sm);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-70);
}

/* 무한 스크롤 금지 — 선반 단위 페이징 (§12) */
.pager {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--s-4);
  padding-top: var(--s-2);
}

.book-list {
  margin: 0;
  padding: 0 var(--s-8) var(--s-12);
  list-style: none;
}
.book-row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-6) 0;
  border-bottom: var(--border-solid);
}
.book-row__band {
  flex: none;
  width: 10px;
  height: 56px;
  border: var(--border-solid);
  background: var(--band);
}
.book-row--curated .book-row__band {
  border: var(--border-double);
}
.book-row--writing .book-row__band {
  border: var(--border-dashed);
}
.book-row__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  min-width: 0;
}
.book-row__title {
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  color: var(--wine);
}
.book-row__mine {
  flex: none;
  padding: var(--s-1) var(--s-2);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-70);
}

/* ═══ 리더 (§6.5) — 성역. 애니메이션·배지·팝업 없음 (§1.5) ════ */

.reader-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-8);
  border-bottom: var(--border-solid);
}
.reader-bar__back {
  font-size: var(--t-ui);
  font-weight: 600;
  color: var(--ink-70);
  text-decoration: none;
}
.reader-bar__back:hover {
  color: var(--wine);
  text-decoration: underline;
}
.reader-bar__tools {
  display: flex;
  align-items: center;
  gap: var(--s-4);
}

/* 글자 크기 3단계 · 기여자 색 토글 — 라디오/체크박스 + :has() 로 처리한다.
 * 리더에 스크립트를 얹지 않기 위한 선택. */
.sizer {
  display: flex;
  border: var(--border-solid);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.sizer label {
  padding: var(--s-2) var(--s-3);
  background: var(--paper-hi);
  font-family: var(--font-mono);
  color: var(--ink-70);
  cursor: pointer;
}
.sizer label + label {
  border-left: var(--border-solid);
}
.sizer label:nth-of-type(1) {
  font-size: var(--t-meta);
}
.sizer label:nth-of-type(2) {
  font-size: var(--t-ui-sm);
}
.sizer label:nth-of-type(3) {
  font-size: var(--t-body);
}
.sizer input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.sizer label:has(input:checked) {
  background: var(--wash-tide);
  font-weight: 500;
  color: var(--ink);
}
.sizer label:has(input:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: calc(var(--focus-offset) * -1);
}

.switch {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-70);
  cursor: pointer;
}
.switch:has(input:checked) {
  border-color: var(--tide);
  background: var(--wash-tide);
  color: var(--ink);
}
.switch:has(input:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
.switch input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.switch__track {
  width: 34px;
  height: 18px;
  padding: 2px;
  border: 1px solid var(--rule-dash);
  border-radius: 20px;
  background: var(--paper-lo);
  display: inline-flex;
  align-items: center;
}
.switch:has(input:checked) .switch__track {
  border-color: var(--tide);
  background: var(--tide);
  justify-content: flex-end;
}
.switch__knob {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--rule-dash);
}
.switch:has(input:checked) .switch__knob {
  background: var(--paper);
}

.reader {
  width: min(var(--w-reader), 100%);
  margin: 0 auto;
  padding: var(--s-16) var(--s-4) var(--s-16);
  display: flex;
  flex-direction: column;
  gap: var(--s-12);
}
.reader__head {
  /* .book-get 이 매달릴 기준 */
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  text-align: center;
}
/* 이 책을 파일로 가져가는 손잡이. 표제지 안, 제목 바로 아래에 선다.
 * 예전에는 본문 맨 아래 '이 책을 파일로 간직하기' 고스트 버튼이었다가
 * 도구줄의 케밥이었다. 아래에 두면 마지막 장까지 넘겨야 보이고, 도구줄에
 * 두면 '책장으로'·글자 크기와 나란히 서서 사이트의 손잡이로 읽힌다.
 * 배경도 테두리도 없다 — 표제지에 상자를 얹으면 그것부터 눈에 든다. */
.book-get {
  /* 표제지의 오른쪽 위 모서리. 흐름에 두면 제목·서지와 한 줄로 쌓여 읽는
     차례에 끼어든다 — 손잡이는 모서리에서 기다리는 편이 낫다. */
  position: absolute;
  top: 0;
  right: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 누를 수 있는 것의 하한(WCAG 2.5.8)보다 넉넉하게. 아이콘만 있는 손잡이라
     글자만큼의 폭을 스스로 벌지 못한다. */
  width: 40px;
  height: 40px;
  /* 전역 a 규칙의 밑줄을 끈다. 여기 밑줄이 그어질 글자가 없다. */
  text-decoration: none;
  color: var(--ink-45);
  transition: color var(--dur-micro) var(--ease);
}
.book-get:hover {
  color: var(--wine);
}
/* 좁은 화면에서는 제목이 판을 가득 채워 모서리에 자리가 없다. 흐름으로
   되돌려 서지 아래에 세운다. */
@media (max-width: 720px) {
  .book-get {
    position: static;
  }
}

.reader__title {
  font-family: var(--font-display);
  font-size: var(--t-display-l);
  line-height: var(--lh-display-l);
  letter-spacing: var(--ls-display-l);
  color: var(--wine);
}
.read-lead {
  max-width: var(--measure-read);
  font-family: var(--font-read);
  font-size: var(--t-read-lead);
  line-height: var(--lh-read-lead);
  font-style: italic;
  color: var(--ink-70);
}
.read-body {
  display: flex;
  flex-direction: column;
  gap: var(--s-8);
  max-width: var(--measure-read);
  /* 리더 본문만 부크크명조(--font-print) — 인쇄본 느낌. 사용자 요청으로
     Design.md §3.2 의 Reading 역할과 별개 토큰을 둔 것 (tokens.css 참고). */
  font-family: var(--font-print);
  font-size: var(--t-read);
  line-height: var(--lh-read);
  letter-spacing: var(--ls-read);
}
/* 3단계 크기 조절 — .reader-pages__flow 도 .read-body 클래스를 함께 달고
   있어서(reader.html) 이 규칙을 그대로 물려받는다. 선택자를 늘릴 필요가
   없었다. */
.reader-page:has(#size-s:checked) .read-body {
  font-size: var(--t-read-s);
}
.reader-page:has(#size-l:checked) .read-body {
  font-size: var(--t-read-l);
}

/* .reader-pages 의 브레이크아웃(폭 100vw)이 가로 스크롤을 만들지 않도록.
 * .page 는 다른 페이지에서도 쓰므로 여기 건드리지 않고 .reader-page 에만 건다. */
.reader-page {
  overflow-x: hidden;
}

/* ── 리더 — 좌우 페이지 스프레드 (js/paginate.js) ─────────────
 *
 * 폭 자체(컬럼 폭 × 컬럼 수)는 js/paginate.js 가 인라인 스타일로 정한다 —
 * 여기서는 그 계산의 기준이 되는 상한(.reader-pages 의 max-width)과, JS 가
 * 건드리지 않는 나머지(높이·정렬·중철 표시)만 정한다. */
.reader-pages {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-6);
}
/* 리더 페이지에서만 — .reader 는 --w-reader(680px) 로 좁게 잡혀 있다. 본문
   단폭(Design.md §3.3 62ch)에는 맞지만 좌우 펼침면은 그보다 넓어야 두 지면이
   나란히 들어간다. 표준 "브레이크아웃"으로 화면 폭까지 벗어난 뒤 안에서 다시
   가운데 정렬한다. 실제 상한은 js/paginate.js 의 MAX_WIDTH 가 인라인으로 건다.
   3D 책장의 펼친 책은 자기 상자가 이미 지면 크기라 이 규칙을 받지 않는다. */
.reader-page .reader-pages {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
}
.reader-pages__viewport {
  --folio-space: 2.4rem;
  position: relative; /* 쪽번호(.folio)의 기준 */
  overflow: hidden;
  height: min(66vh, 640px);
  /* 지면의 바깥 여백은 padding 이 아니라 투명 테두리로 준다 — overflow 는
     패딩 상자에서 자르므로 padding 으로 주면 넘긴 페이지의 글자가 여백 안까지
     비쳐 들어온다. 테두리 바깥은 침범당하지 않는다. 종이색과 중철 그늘은
     background-origin/clip 이 border-box 라 이 여백 아래까지 그대로 깔린다.
     (안쪽 여백 — 마주 본 두 지면 사이 — 은 column-gap 이고 paginate.js 담당) */
  border: var(--s-8) solid transparent;
  background-color: var(--print-paper);
  background-origin: border-box;
  background-repeat: no-repeat;
  outline: 1px solid var(--rule); /* 종이의 가장자리. 테두리 자리는 여백이 이미 쓰고 있다 */
  box-shadow: var(--shadow-page);
  /* 실제 폭은 js/paginate.js 가 인라인으로 설정한다 */
}
@media (min-width: 900px) {
  .reader-pages__viewport {
    border-width: var(--s-12) var(--s-16);
  }
}
/* 마주 본 두 지면 사이의 접힌 골. Design.md §4 의 블러 그림자 금지를 의도적으로
   어긴다 — tokens.css 의 --print-gutter 주석 참고. */
.reader-pages__viewport[data-spread='true'] {
  background-image: var(--print-gutter);
  background-size: 240px 100%;
  background-position: center;
}
/* 한 지면에 다 들어가는 이야기는 책을 펼칠 이유가 없다. paginate.js 가 인라인
   폭·컬럼 설정을 지우고, 여기서 지면 액자를 걷어 평소 본문으로 되돌린다. */
.reader-pages__viewport[data-single='true'] {
  /* 인라인 폭이 사라지면 flex 항목이라 글자 폭까지 쪼그라든다 — 짧은 이야기가
     한 줄에 두어 글자씩 끊겼다. 부모 폭을 그대로 받고, 글의 단폭은 아래 flow 가 잡는다. */
  align-self: stretch;
  height: auto;
}
/* 지면 액자를 걷는 것은 리더 페이지에서만이다. 3D 펼친 책은 상자 자체가
   종이라, 액자를 걷으면 글이 종이 가장자리에 붙어 버린다. */
.reader-page .reader-pages__viewport[data-single='true'] {
  border: 0;
  outline: 0;
  background: none;
  box-shadow: none;
}
.reader-pages__viewport[data-single='true'] .reader-pages__flow {
  max-width: var(--measure-read);
  height: auto;
  margin-inline: auto;
}
.reader-pages__flow {
  /* .read-body 의 flex 레이아웃을 컬럼 흐름으로 바꾼다. gap 은 flex 전용이라
     여기서는 아무 효과가 없지만, 페이지 안 문단 사이는 margin 으로 잡는다. */
  display: block;
  max-width: none;
  /* 쪽번호 자리를 본문에서 미리 떼어 둔다 — 그래야 마지막 줄과 번호가 겹치지
     않고, 한 지면에 들어가는 줄 수도 그만큼 정직하게 줄어든다. */
  height: calc(100% - var(--folio-space));
  column-fill: auto;
  /* column-width 는 js/paginate.js 가 인라인으로 정확히 맞춘다 — 폭이
     페이지 폭의 정수배가 아니면 브라우저가 컬럼을 넓혀 계산이 어긋난다. */
}
.reader-pages__flow p {
  margin-block-end: var(--s-6);
  /* 인쇄된 책의 본문은 양쪽이 맞아 있다. 한글은 음절 사이에서 줄이 나뉘므로
     낱말을 억지로 벌리지 않고 고르게 채운다 — word-break 는 기본값을 그대로 둔다. */
  text-align: justify;
}
.reader-pages__flow .read-lead {
  max-width: none;
  margin-block-end: var(--s-6);
  /* 도입부는 본문이 아니라 제사(題詞)다. 한두 줄짜리를 양쪽으로 늘리면
     기울임체가 어색하게 벌어진다. */
  text-align: start;
}
/* 쪽번호. 펼침면에서는 바깥쪽 두 귀퉁이, 한 지면만 보일 때는 가운데 아래 —
   실제 책이 쪽번호를 놓는 자리다. */
.folio {
  position: absolute;
  bottom: 0;
  font-family: var(--font-print);
  font-size: var(--t-caption);
  line-height: var(--lh-caption);
  color: var(--ink-45);
  font-variant-numeric: tabular-nums;
}
.folio--left {
  left: 0;
}
.folio--right {
  right: 0;
}
.reader-pages__viewport[data-spread='false'] .folio--left {
  left: 50%;
  transform: translateX(-50%);
}

.reader-pages__nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-4);
}
.reader-pages__nav .btn {
  min-width: 88px; /* 누를 수 있는 것의 하한 (WCAG 2.5.8) 여유 있게 */
}
.reader-pages__nav .btn:disabled {
  opacity: 0.4;
  cursor: default;
}

/* 기본은 색 없는 순수 본문. 토글을 켜야 기여자 색이 드러난다 (§6.5)
 *
 * 밑줄 2px 만으로는 누가 썼는지 알 수 없었다. 12색 램프의 가운데 여섯 색은
 * 서로 몇 %씩밖에 차이 나지 않는데(#955148 #765954 #576260 …), 본문 글자
 * 아래 2px 선으로는 그 차이가 보이지 않는다. 색은 선보다 면에서 훨씬 잘
 * 읽히므로 옅은 바탕을 깔고 밑줄을 3px 로 키운다. 바탕이 깔리면 작성자가
 * 바뀌는 지점이 덩어리 경계로 보여서, 색을 구분하지 못해도 "여기서 사람이
 * 바뀌었다"는 것은 읽힌다.
 *
 * 테두리는 토글과 무관하게 늘 3px 다 — 켤 때 두께가 변하면 줄이 밀린다. */
.s {
  border-bottom: 3px solid transparent;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  /* 여백을 음수 마진으로 되돌린다. 바탕을 깔되 글자 자리는 그대로 둔다 */
  padding: 0 3px 1px;
  margin: 0 -3px;
  /* 문장이 줄바꿈되면 각 줄에 바탕이 따로 깔려야 한다 */
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-color var(--dur-micro) var(--ease);
}
/* 토글이 꺼져 있어도 어느 문장 위인지는 보여야 한다 (색은 쓰지 않는다) */
.s:hover {
  background: var(--paper-hi);
}
.reader-page:has(#contrib:checked) .s {
  background: color-mix(in srgb, var(--author-color) 16%, transparent);
  border-bottom-color: var(--author-color);
}
.reader-page:has(#contrib:checked) .s:hover {
  background: color-mix(in srgb, var(--author-color) 30%, transparent);
}
/* 본문을 복사할 때 숨은 작성자 표시가 따라오지 않게 한다 */
.s__by {
  user-select: none;
}

/* ── 문장 위 기여자 카드 (js/hovercard.js) ────────────────── */
.hovercard {
  position: fixed;
  z-index: 70; /* 3D 책장 무대(60)의 펼친 책 위에도 떠야 한다 */
  display: flex;
  align-items: center;
  gap: var(--s-3);
  max-width: 280px;
  padding: var(--s-3) var(--s-4);
  background: var(--paper-hi);
  border: var(--border-solid);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-print); /* §4 오프셋 실선 */
  color: inherit;
  text-decoration: none;
  font-family: var(--font-ui);
  opacity: 0;
}
.hovercard__ring {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--author-color, var(--wine));
}
.hovercard__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.hovercard__name {
  font-size: var(--t-ui);
  font-weight: 600;
}
.hovercard__handle {
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-45);
}
.hovercard__meta {
  margin-top: var(--s-1);
  font-size: var(--t-meta);
  color: var(--ink-70);
}
/* 한 줄 설명만 담은 카드 — 사람이 아니라 말이라 색 원판도 굵기도 없다 */
.hovercard--note .hovercard__ring {
  display: none;
}
.hovercard--note .hovercard__name {
  font-weight: 400;
  color: var(--ink-70);
}
/* 꼬리 — 카드가 화면 끝에서 밀려도 --arrow-x 로 문장을 계속 가리킨다 */
.hovercard::after {
  content: '';
  position: absolute;
  left: var(--arrow-x, 50%);
  bottom: -6px;
  width: 10px;
  height: 10px;
  margin-left: -5px;
  background: var(--paper-hi);
  border-right: var(--border-solid);
  border-bottom: var(--border-solid);
  transform: rotate(45deg);
}
.hovercard.is-below::after {
  bottom: auto;
  top: -6px;
  transform: rotate(225deg);
}

.contributors {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--s-6) var(--s-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.contributor {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}
/* 테두리 3px 로는 어떤 색인지 알기 어렵다. 본문의 바탕색과 맞춰 보려면
 * 면으로 보여야 한다 — 색을 확인하는 기준점이 이 목록이다. */
.contributor__ring {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 3px solid var(--author-color);
  background: var(--author-color);
}
.contributor__name {
  font-size: var(--t-ui-sm);
  font-weight: 600;
}
.contributor__more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: var(--border-solid);
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  color: var(--ink-45);
}



/* ── 입체 책장 (Design.md §8) ──────────────────────────────────
 * 3D 는 흰 박스의 배경·테두리를 그리지 않는다. CSS 가 계속 그리고 그 위에
 * 책만 얹히므로, 정지 상태에서는 2D 책등과 구분되지 않는다. */
.shelf-case--3d {
  position: relative;
}
/* 책등 줄은 화면에서 빠지되 초점은 받는다. 초점이 오면 다시 나타난다 —
 * 포커스 링만 있고 대상이 보이지 않는 상태를 만들지 않는다 (§11) */
.shelf-case--3d .shelf__row {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-micro) var(--ease);
}
.shelf-case--3d .shelf__row:focus-within {
  opacity: 1;
  pointer-events: auto;
}
/* 마우스를 받는 자리. 3D 캔버스는 화면 전체를 덮고 있어서 클릭을 가로채면
 * 페이지 나머지가 죽는다. 그래서 책장 자리만 여기서 받는다. */
.shelf3d-hit {
  position: absolute;
  inset: 0;
}

.shelf3d-stage {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
}
.shelf3d-gl,
.shelf3d-css {
  position: absolute;
  inset: 0;
}
.shelf3d-gl {
  z-index: 0;
}
.shelf3d-css {
  z-index: 1; /* 펼친 지면은 책 메시 앞에 온다 */
}
.shelf3d-stage.is-open {
  pointer-events: auto;
}
.shelf3d-stage.is-open::before {
  content: '';
  position: absolute;
  inset: 0;
  /* 표지가 잉크색이라 너무 어두우면 펼친 책이 배경에 묻는다 */
  background: color-mix(in srgb, var(--ink) 46%, transparent);
  animation: bookdim var(--dur-panel) var(--ease) both;
}
@keyframes bookdim {
  from { opacity: 0; }
  to { opacity: 1; }
}
/* 책을 읽는 동안 배경이 스크롤되면, 닫을 때 책이 옛 자리로 날아간다 */
body.has-open-book {
  overflow: hidden;
}

/* ── 펼쳐진 책의 지면 ─────────────────────────────────────────── */
.bookopen {
  position: relative;
  /* 크기는 펼친 3D 책이 정한다 (js/shelf3d.js). 값이 없으면 그냥 읽기 좋은 폭. */
  width: min(var(--page-w, 760px), 92vw);
  height: min(var(--page-h, 78vh), 86vh);
  overflow: hidden; /* 지면은 넘기는 것이지 굴리는 것이 아니다 */
  /* 상자 자체는 자리만 잡는다 — 보이는 것은 그 안의 두 박스다 */
  opacity: 0;
  transition: opacity var(--dur-panel) var(--ease);
}
.bookopen.is-open {
  opacity: 1;
}
/* 책을 덮는 자리. 예전에는 상자 오른쪽 위에 절대배치돼 있었는데, 그 자리가
   마침 오른쪽 칸의 머리라서 "함께 쓴 4명"을 닫는 버튼처럼 읽혔고, 칸은 버튼을
   피하려고 위쪽 48px 을 비워 두어야 했다. 이제 그냥 칸의 마지막 줄이다. */
.bookopen__close {
  padding: var(--s-3);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: transparent;
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-70);
  cursor: pointer;
}
.bookopen__close:hover {
  background: var(--wash-tide);
  color: var(--ink);
}

/* 3D 책장의 펼친 책 — 왼쪽은 책, 오른쪽은 함께 쓴 사람들. 크기는 화면이
   아니라 펼친 책(--page-w/--page-h)이 정하므로 높이는 남는 자리를 그대로
   받고, 화면 폭 미디어쿼리도 쓰지 않는다. */
.bookpage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 168px;
  column-gap: var(--s-4);
  height: 100%;
}
/* 왼쪽 박스 — 표제지부터 본문까지, 여백까지 전부 흰 종이 한 장이다. */
.bookpage__pages {
  min-height: 0;
  padding: var(--s-6);
  border: var(--border-solid);
  border-radius: var(--r-lg);
  background: var(--print-paper);
  box-shadow: 0 2px 0 var(--rule); /* 오프셋 실선만. 블러 그림자 금지 (§4) */
}
.bookpage__pages .reader-pages {
  height: 100%;
}
/* 넘기기는 책의 가구다 — 종이 위에 놓인 폼 컨트롤이 아니라. 지면 바깥 아래
   띠에 쪽번호와 같은 줄로 앉히고, 테두리와 채움을 걷어 글자만 남긴다. 리더
   페이지는 화면 하나를 통째로 쓰므로 거기서는 원래 버튼 그대로다 (§8.3 은
   '같은 책이 다르게 보이면 안 된다'이지, 액자까지 같으라는 뜻은 아니다). */
.bookopen .reader-pages {
  position: relative;
  gap: 0;
}
.bookopen .reader-pages__nav {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  justify-content: space-between;
  /* 두 화살표 사이는 종이다 — 눌러도 아무 일이 없어야 하고, 그 아래 문장의
     호버 카드도 이 띠에 가로막히면 안 된다 */
  pointer-events: none;
}
.bookopen .reader-pages__nav .btn {
  pointer-events: auto;
  min-width: 0;
  padding: var(--s-2) var(--s-3);
  border-color: transparent;
  background: none;
  /* 조용하되 죽어 있지는 않게. 쪽번호(--ink-45)보다는 진해야 표제지처럼
     넘기는 것 말고 할 일이 없는 지면에서 살아 있는 것으로 읽힌다. */
  color: var(--ink-70);
}
.bookopen .reader-pages__nav .btn:hover:not(:disabled) {
  background: none;
  color: var(--wine);
}

.bookopen .reader-pages__viewport {
  flex: 1;
  min-height: 0;
  height: auto;
  /* 이미 흰 종이 위다 — 지면이 자기 종이·테두리를 또 그리지 않는다 */
  border-width: var(--s-4) var(--s-6);
  background-color: transparent;
  outline: 0;
  box-shadow: none;
}

/* 표제지 — 한 지면을 통째로 쓰고, 본문은 다음 지면에서 시작한다 */
.bookcover {
  display: flex;
  height: 100%;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  text-align: center;
  text-wrap: balance;
  break-after: column;
}
.bookcover__title {
  font-family: var(--font-display);
  font-size: var(--t-display-l);
  line-height: var(--lh-display-l);
  letter-spacing: var(--ls-display-l);
  color: var(--wine);
}

/* 오른쪽 칸 — 이 책의 판권지.
 *
 * 세로로 긴 칸을 짧은 목록 하나로 채우려 하면 아래가 반쯤 빈다 (4인 공저에서
 * 651px 중 271px 이 빈 칸이었다). 자리를 줄이는 대신 사람마다 문장 수에
 * 비례한 높이를 주고 칸을 끝까지 나눠 갖게 한다. 빈 자리가 생길 수 없고,
 * 덤으로 방금 책장에서 뽑아 온 그 책등 — 문장 순서대로 쌓인 색띠 — 이 된다.
 * 그림자는 책만 진다: 판권지는 책 위에 얹힌 종이지 두 번째 책이 아니다. */
.bookpage__who {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: var(--s-4);
  overflow: hidden; /* 닫기는 목록을 따라 굴러가지 않는다 */
  padding: var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-lg);
  background: var(--paper-hi);
}
/* 사이드바에서는 한 줄에 한 사람씩 */
.bookpage__who .contributors {
  display: flex;
  flex-direction: column;
  /* 사이를 벌리면 띠가 네 토막으로 끊겨 그냥 목록이 된다. 붙여 놓아야 하나의
     책등이고, 색이 바뀌는 자리가 곧 사람이 바뀌는 자리다. */
  gap: 0;
  min-height: 0;
  /* 띠는 칸의 왼쪽 끝 — 종이와 맞닿는 자리 — 에 선다. 책등이 서 있는 그 자리다. */
  margin-left: calc(-1 * var(--s-4));
  overflow-y: auto; /* 사람이 많은 책도 왼쪽 박스를 밀어내지 않는다 */
}
.bookpage__who .contributor {
  gap: var(--s-2); /* 띠 8px + 8px = 이름이 칸의 제목과 같은 선에서 시작한다 */
  /* 문장 수가 곧 띠의 두께다. --weight 는 js/book-content.js 가 심는다 —
     shorthand 로 쓰면 값이 하나만 잘못돼도 flex 전체가 초기값으로 돌아간다. */
  flex-grow: var(--weight, 1);
  flex-shrink: 1;
  /* auto 로 두면 글자 높이가 먼저 자리를 차지해 4문장과 2문장이 122:88 로
     붙어 버린다. 0 이어야 남는 자리가 정확히 문장 수 비율로 갈린다. */
  flex-basis: 0;
  /* 사람이 많아 자리가 모자라면 여기까지 눌린다. 두 줄이 딱 들어가는 44px
     로는 앞 사람의 "N문장"과 다음 사람의 이름이 맞붙어 어느 쪽 것인지
     흐려진다 — 색이 바뀌는 자리 말고도 숨 쉴 틈이 있어야 한다. */
  min-height: var(--s-12);
}
/* 점이 아니라 기둥 — 책등의 그 색띠와 같은 것이다 */
.bookpage__who .contributor__ring {
  align-self: stretch;
  /* 3D 책등의 색띠와 같은 두께 (js/spine.js 의 BAND_RATIO × 책등 두께) */
  width: 8px;
  height: auto;
  border: 0;
  /* 모서리를 굴리면 이음매마다 잘록해져 다시 네 토막으로 보인다 */
  border-radius: 0;
}
.bookpage__whotitle {
  /* 아래 여백은 칸의 격자 gap 이 준다 — margin 을 겹치면 32px 이 된다 */
  margin-bottom: 0;
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
}

.depth {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
  font-size: var(--t-ui-sm);
  font-weight: 600;
  color: var(--ink-70);
  cursor: pointer;
}
.depth:has(input:disabled) {
  color: var(--ink-45);
  cursor: not-allowed;
}
.depth:has(input:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* ═══ §5 허용 모션 ② · ⑤ ══════════════════════════════════════
 * §5 는 모션 5종만 허용한다. 아래 둘은 명세에 있는데 구현이 없던 것이고,
 * 나머지 셋(①책 펼침 / ③스티커 회전 / ④실시간 카운트)은 이미 동작한다.
 * 새 모션을 여기 말고 다른 데 만들지 말 것. */

/* ② 새 문장이 위에서 종이처럼 내려앉는다.
 * 투표 모드에서 채택된 후보가 본문 자리로 내려앉는 것도 같은 경로다 —
 * 확정된 문장은 모드와 무관하게 sentence 스트림으로 도착한다. */
@keyframes sentence-drop {
  from {
    opacity: 0;
    /* 살짝 기울어 떨어졌다가 평평하게 눕는다 — 종이가 내려앉는 방식 */
    transform: translateY(-10px) rotate(-0.6deg);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.sentence--new {
  animation: sentence-drop var(--dur-panel) var(--ease) both;
}

/* ⑤ 도장이 찍히는 순간 — 200ms, 미세 오버슈트 후 정착.
 * --tilt 는 정지 상태의 기울기(§6.7)라 모든 키프레임에서 유지해야 한다.
 * 안 그러면 찍히는 동안 도장이 똑바로 섰다가 삐뚤어진다. */
@keyframes stamp-press {
  from {
    opacity: 0;
    transform: rotate(var(--tilt, -3deg)) scale(1.45);
  }
  70% {
    opacity: 1;
    transform: rotate(var(--tilt, -3deg)) scale(0.94);
  }
  to {
    opacity: 1;
    transform: rotate(var(--tilt, -3deg)) scale(1);
  }
}
.stamp__mark--new {
  animation: stamp-press 200ms var(--ease) both;
}

/* ═══ M9 · 신고·중재 ═════════════════════════════════════════
 * §10 "판단 표현 대신 사실만". 경고색(주홍)을 쓰지 않는다 — 아직 판정되지
 * 않은 글에 빨간 딱지를 붙이면 표시가 곧 판결이 된다. */
.review-flag {
  display: inline-flex;
  align-items: center;
  margin-top: var(--s-2);
  padding: var(--s-1) var(--s-3);
  border: var(--border-dashed); /* 점선 = 아직 열려 있는 것 (§4 테두리 언어) */
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-70);
}

/* 신고는 드물게 쓰는 기능이라 조용히 둔다. 호버·포커스에서만 또렷해진다. */
.report-btn {
  margin-left: var(--s-3);
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--t-meta);
  color: var(--ink-45);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.report-btn:hover,
.report-btn:focus-visible {
  color: var(--ink);
}
.report-btn[disabled] {
  cursor: progress;
  color: var(--ink-45);
}
/* 두 번째 누름을 기다리는 중. 아직 아무 일도 일어나지 않았으므로
   경고색이 아니라 또렷해지기만 한다. */
.report-btn--armed {
  color: var(--ink);
  font-weight: 600;
}

/* ═══ 문서 페이지 (이용약관 · 신고/문의) ══════════════════════
 * 읽기 위한 화면이므로 리더와 같은 폭을 쓴다 (Design.md §4 리더 680px). */
/* ⚠ Design.md §4 를 의도적으로 벗어난 자리다.
 *
 * §4 는 "리더는 680px"이라 정하지만, 문서 페이지는 그 폭 옆에 목차를 두어
 * 남는 여백을 채운다. 읽는 줄 길이 자체는 여전히 680px 다 — 글줄을 늘리면
 * 눈이 되돌아오기 힘들어져, 여백은 채워도 읽기가 나빠진다.
 * 채우는 것은 폭이 아니라 옆자리다.
 *
 * 되돌리려면 이 블록과 .doc__toc 를 함께 지우면 된다. */
.doc {
  width: min(1040px, 100%);
  margin: 0 auto;
  /* 상단 패딩이 없으면 제목이 내비에 붙는다. 다른 상단 섹션(.hero, .lib-head,
     .profile-head, .reader)이 전부 --s-12~16 을 쓰므로 같은 리듬을 따른다. */
  padding: var(--s-16) var(--s-4) var(--s-16);

  display: grid;
  grid-template-columns: minmax(0, var(--w-reader)) 1fr;
  gap: 0 var(--s-12);
  align-content: start;
}
/* 본문은 전부 첫 칸에. 목차만 옆자리를 쓴다. */
.doc > * {
  grid-column: 1;
  min-width: 0;
}
.doc__section {
  margin-top: var(--s-12);
}
.doc__h {
  margin-bottom: var(--s-4);
  font-family: var(--font-display);
  font-size: var(--t-title);
  line-height: var(--lh-title);
}
.doc p {
  margin: 0 0 var(--s-4);
  line-height: var(--lh-read);
}
.doc__list {
  margin: 0 0 var(--s-4);
  padding-left: var(--s-6);
  line-height: var(--lh-read);
}
.doc__list li {
  margin-bottom: var(--s-2);
}
/* 점선 = 아직 열려 있는 것. 확정된 규칙이 아니라 알림이다 (§4 테두리 언어) */
.doc__note {
  margin-top: var(--s-6);
  padding: var(--s-4) var(--s-6);
  border: var(--border-dashed);
  border-radius: var(--r-md);
  color: var(--ink-70);
}
/* 반드시 읽어야 하는 대목. 경고색 대신 왼쪽 굵은 선으로만 세운다 (§12 그라디언트·강조색 남용 금지) */
.doc__warn {
  padding-left: var(--s-4);
  border-left: 3px solid var(--rule);
}

/* 목차 — 남는 여백이 빈칸이 아니라 길잡이가 되게 한다.
   본문을 따라 스크롤하다 어디쯤인지 알 수 있도록 붙어 있는다. */
.doc__toc {
  grid-column: 2;
  /* 행을 미리 정의하지 않았으므로 전부 암시적 그리드다. 거기서 -1 은 명시적
     그리드의 끝(=1번 선)을 가리켜 `1 / -1` 이 사실상 1행이 되고, 첫 행 높이가
     목차만큼 밀려 제목 아래에 빈 공간이 생긴다. span 으로 걸친다. */
  grid-row: 1 / span 100;
  align-self: start;
  position: sticky;
  top: var(--s-8);
  padding-left: var(--s-6);
  border-left: var(--border-solid);
}
.doc__toc h2 {
  margin-bottom: var(--s-4);
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-45);
}
.doc__toc ol {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: toc;
}
.doc__toc li {
  counter-increment: toc;
  margin-bottom: var(--s-3);
  line-height: 1.4;
}
.doc__toc a {
  /* 목차는 문장 속 링크가 아니라 이동 손잡이다. 24px 을 확보한다. */
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  color: var(--ink-70);
  text-decoration: none;
  font-size: var(--t-ui);
}
.doc__toc a::before {
  content: counter(toc) '. ';
  color: var(--ink-45);
  font-family: var(--font-mono);
}
.doc__toc a:hover,
.doc__toc a:focus-visible {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* 지금 읽고 있는 절 (§11 색만으로 알리지 않으므로 굵기도 함께 바꾼다) */
.doc__toc a[aria-current='true'] {
  color: var(--wine);
  font-weight: 700;
}

/* 좁은 화면에는 옆자리가 없다. 목차는 본문 제목의 복사본이라 빼도 정보가 사라지지 않는다. */
@media (max-width: 900px) {
  .doc {
    grid-template-columns: minmax(0, 1fr);
  }
  .doc__toc {
    display: none;
  }
}

/* 푸터 링크 — 지금까지 텍스트였다. 링크는 밑줄이 필수다 (§2.3) */
.footer__links {
  display: inline-flex;
  gap: var(--s-4);
}

/* fieldset 은 flex column 이라 자식이 폭 전체로 늘어난다. 라디오 묶음은
   내용만큼만 차지해야 한다. library 의 .lib-tools(가로 배치) 안에서는 늘어나지
   않으므로, fieldset 안에 있을 때만 제한한다. */
.fieldset .viewswitch {
  align-self: flex-start;
}

/* ═══ 운영 화면 ══════════════════════════════════════════════
 * 읽고 판단하는 화면이라 장식을 넣지 않는다. 카드는 실선(확정된 것)이 아니라
 * 아직 처리하지 않은 일이므로 점선으로 둔다 (§4 테두리 언어). */
/* 운영 화면은 카드에 본문과 버튼이 함께 들어가 리더 폭으로는 좁다.
   width 를 덮으므로 max-width 만 주면 min() 이 남아 어긋난다. */
.admin {
  width: min(820px, 100%);
}
.admin__notice {
  margin-bottom: var(--s-8);
}
.admin__card {
  padding: var(--s-4) var(--s-6);
  margin-bottom: var(--s-4);
  border: var(--border-dashed);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.admin__text {
  margin: var(--s-2) 0;
  white-space: pre-wrap; /* 시는 줄바꿈이 내용이다 */
  line-height: var(--lh-read);
}
.admin__reasons {
  color: var(--ink-70);
}
/* 핸들 하나를 받는 한 줄짜리 폼. 좁아지면 버튼이 아래로 내려간다. */
.admin__shadow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-block: var(--s-4);
}
.admin__shadow .field__input {
  flex: 0 1 14rem;
}

.admin__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-4);
}
.admin__note {
  flex: 1 1 220px;
  min-width: 0;
}
/* 두 번째 누름을 기다리는 중. 아직 아무 일도 일어나지 않았으므로
   경고색이 아니라 또렷해지기만 한다 (신고 버튼과 같은 규칙). */
.is-armed {
  font-weight: 700;
}
.admin__log {
  margin: var(--s-4) 0 0;
  padding: 0;
  list-style: none;
}
.admin__log li {
  padding: var(--s-2) 0;
  border-bottom: var(--border-solid);
}
.admin__log li:last-child {
  border-bottom: 0;
}

/* §5 — reduced-motion 에서는 **움직임만** 걷어낸다.
 *
 * 어지럼증(전정기관)을 일으키는 것은 이동·확대·회전이지 페이드가 아니다.
 * 예전에는 `*` 에 transition/animation-duration 을 0.01ms 로 박아 전부 죽였는데,
 * 그러면 §5 가 "①은 크로스페이드로" 하며 남기라고 한 페이드까지 같이 사라져
 * 화면이 뚝뚝 끊긴다. 그래서 실제로 움직이는 곳만 무효로 만든다.
 *
 * 이 사이트에서 움직이는 것은 아래 둘뿐이다. 나머지 transform(.crown,
 * .stamp__mark, .phase--*, .book-note, .sticker 의 기본 기울기)은 정지 상태의
 * 형태라 §4 이지 모션이 아니고, 나머지 트랜지션과 @keyframes 는 전부 페이드다.
 *
 * ⚠ 이동·확대·회전하는 모션을 새로 넣으면 반드시 여기에 상쇄 규칙을 추가할 것. */
@media (prefers-reduced-motion: reduce) {
  /* 스티커의 기본 기울기는 §4의 정적 형태이므로 유지한다.
   * 제거하는 것은 §5-③의 "호버 시 미세 회전"뿐. */
  .sticker:hover {
    transform: rotate(-2deg);
  }
  /* §5 — 책등이 위로 튀어나오는 이동을 없앤다 */
  .sticker--rule:hover,
  .spine:hover {
    transform: none;
  }
  /* §5 — reduced-motion 이면 ②는 제거, ⑤는 페이드 인으로 남긴다 */
  .sentence--new {
    animation: none;
  }
  .stamp__mark--new {
    animation: stamp-fade var(--dur-micro) var(--ease) both;
  }
  @keyframes stamp-fade {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}

/* ═══ 책장 방 — 벽을 따라 선 여러 채의 책장 ═══════════════════
 *
 * 예전에는 1200px 종이 위에 흰 상자 하나였고, 그 안에 "제본된 책" 이라는
 * 제목까지 붙어 있었다. 탭이 없는 화면에서 그 제목은 같은 말을 두 번 하는
 * 것이었다. 지금은 화면 전체가 방이고, 벽을 따라 책장이 여러 채 서 있다.
 *
 * 한 채를 칸으로 나눈 것이 아니라 **채가 여럿**이다. 그래서 채마다 자기
 * 몰딩·기둥·굽을 갖고, 채 사이로는 벽이 보인다.
 *
 * 도구는 따로 떠 있는 툴바가 아니라 방의 일부다 — 검색과 보기 전환은 벽에
 * 걸린 레일에, 정렬은 첫 책장 선반 앞면의 라벨 택에.
 *
 * ⚠ Design.md 를 여러 곳에서 벗어난다(목재 색, 최대폭 1200px, 그림자).
 *   "규정은 배제해도 좋다"는 결정에 따른 것이고 이 페이지에만 적용된다. */

.page--full {
  max-width: none;
  border-inline: 0;
  box-shadow: none;
}
.page--full::before,
.page--full::after {
  content: none;
}
.library {
  display: flex;
  flex-direction: column;
}

/* ── 방 ────────────────────────────────────────────────────── */
.room {
  position: relative; /* .shelf3d-hit 가 여기에 붙는다 */
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* 벽. 나무보다 밝아야 책장이 '놓인' 것으로 읽힌다 */
  background: var(--paper-hi) var(--grain-desk);
}

/* 벽에 걸린 레일 — 제목·검색·보기 전환 */
.room__rail {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  padding: var(--s-3) var(--s-6);
  border-bottom: 1px solid var(--rule);
}
.room__plate {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  color: var(--ink);
}
.room__count {
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink-45);
}
.search--rail {
  flex: 1;
  max-width: 380px;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: var(--border-solid);
  border-radius: var(--r-sm);
  background: var(--paper-hi);
}
.search--rail .search__mark {
  color: var(--ink-45);
}
.search--rail input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
}

/* 벽 위의 컨트롤은 리더의 .viewswitch / .switch 를 그대로 쓴다. 벽이
 * --paper-hi 라 같은 색이면 묻히므로, 여기서는 한 톤 낮춰 '벽에 붙은 물건'
 * 으로 보이게만 한다. */
.room__rail .viewswitch label,
.room__rail .switch {
  padding: var(--s-2) var(--s-3);
  background: var(--paper);
  font-size: var(--t-caption);
}
/* 두 칸의 폭을 맞춰 둔다. 선택 시 굵어지는 글자가 칸 폭을 바꾸면 컨트롤
 * 가장자리가 밀린다 — 서가 택에서 고친 것과 같은 어긋남이다. */
.room__rail .viewswitch label {
  min-width: 4.5em;
  text-align: center;
}
/* --wash-tide(10%)는 이 레일 위에서 미선택 칸과 밝기 차가 1.01:1 이라, 색상
 * 으로만 구분된다(§11 위반). 같은 페이지의 다른 택일 컨트롤(서가 라벨 택)과
 * 같은 언어 — 놋쇠 바탕 — 로 맞춘다. on/off 인 입체 토글은 청록 그대로 두어
 * '어느 것을 골랐나' 와 '켰나' 가 서로 다른 표시를 갖게 한다. */
.room__rail .viewswitch label:has(input:checked) {
  background: var(--brass);
  color: var(--ink);
  font-weight: 700;
}

/* ── 책장이 서는 자리 ──────────────────────────────────────── */
.room__cases {
  position: relative; /* 3D 클릭 영역(.shelf3d-hit)이 여기에만 깔린다 */
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: stretch;
  gap: var(--s-6); /* 채 사이로 벽이 보인다 */
  padding: var(--s-6) var(--s-6) 0;
  /* 가로만 흐른다. overflow-x 만 auto 로 두면 세로도 auto 가 되어, 반올림에서
     생긴 1px 때문에 세로 스크롤바가 붙는다. */
  overflow-x: auto;
  overflow-y: hidden;
}
/* 방 바닥 — 책장이 딛고 선 면 */
.room__floor {
  flex: none;
  height: 26px;
  background: var(--wood-dark) var(--grain-wood);
  border-top: 2px solid var(--wood-line);
}

/* ── 책장 한 채 ────────────────────────────────────────────── */
.case {
  flex: 1 1 0;
  min-width: 240px;
  display: flex;
  flex-direction: column;
  background: var(--wood-dark) var(--grain-wood);
  border: 2px solid var(--wood-line);
  border-bottom: 0;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
}
/* 채마다 몰딩. 위 칸이 조금 더 넓게 튀어나온다 */
.case__crown {
  flex: none;
  height: 16px;
  margin: -2px -6px 0;
  background: var(--wood) var(--grain-wood);
  border: 2px solid var(--wood-line);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  box-shadow: inset 0 -4px 0 rgba(140, 108, 85, 0.3);
}
.case__body {
  position: relative; /* 빈 채의 안내 라벨이 여기에 걸린다 */
  flex: 1;
  min-height: 0;
  display: flex;
}

/* 빈 책장 — 한 톤 물러서고, 무엇을 기다리는 자리인지 적는다 */
.case--empty {
  opacity: 0.55;
}
.case__wait {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  /* §4 스티커형 라벨. 살짝 기울어야 붙여 놓은 것으로 읽힌다 */
  rotate: -2deg;
  max-width: 82%;
  padding: var(--s-2) var(--s-3);
  border: var(--border-dashed);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink-45);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  text-align: center;
  text-wrap: balance;
}
.case__post {
  flex: none;
  width: 14px;
  background: var(--wood) var(--grain-wood);
  border-inline: 1px solid var(--wood-line);
}
.case__shelves {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.case__level {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.bay {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: flex-end;
  /* 책은 선반 왼쪽부터 쌓인다. 가운데 정렬은 책이 공중에 뜬 것처럼 보인다 */
  justify-content: flex-start;
  gap: var(--s-2);
  margin: 0;
  padding: 0 var(--s-2);
  list-style: none;
  overflow: hidden;
}
.case__board {
  flex: none;
  height: 12px;
  position: relative;
  background: var(--wood-face) var(--grain-wood);
  border-top: 1px solid rgba(255, 255, 255, 0.55);
  border-bottom: 2px solid var(--wood-line);
}
.case__plinth {
  flex: none;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--wood) var(--grain-wood);
  border-top: 2px solid var(--wood-line);
}
/* 채마다 붙는 작은 놋쇠 표찰 — 이 책장이 몇 권을 들고 있는가 */
.case__tally {
  font-family: var(--font-mono);
  /* 10px 은 이 서체에서 읽히지 않는다. --wood-line 은 나무 위 2.51:1 로
   * AA 에도 못 미쳤다 — 나무결과 같은 색이니 당연하다. */
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  color: var(--ink);
}

/* ── 함께 이어 쓴 사람들 ───────────────────────────────────────
 * 노드 하나에 사람 하나. 가까울수록 함께 완성한 책이 많다.
 * 색은 §9 대로 "그 작품에서의 색"이라 사람을 뜻하지 않는다 — 정보는 이름과
 * 권수가 지고, 색은 거들기만 한다 (§11 색만으로 정보 전달 금지). */
.circle-block {
  padding: var(--s-6) var(--s-8);
  /* 한때 설명문을 그림 옆에 세워 두었다. 1200px 기둥에 385px 짜리 그림
     하나뿐이라 옆이 비어서였는데, 그림이 곁기둥(.profile-rail)으로 들어간
     지금은 채울 빈 곳이 없다. 다시 위아래로 세운다. */
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s-4);
}
.circle__svg {
  /* 실제 폭은 js/circle.js 가 내용에 맞춰 인라인으로 정한다. 여기서는 좁은
     화면에서 넘치지 않게만 막는다. */
  max-width: 100%;
  height: auto;
}
.circle__link {
  /* 굵고 흐린 선은 흙탕물이 된다. 얇고 또렷하게 — 굵기가 곧 함께 쓴 권수다. */
  opacity: 0.75;
  stroke-linecap: round;
  /* <path> 는 기본이 채우기다. 빼지 않으면 실이 아니라 부채꼴이 된다. */
  fill: none;
}
.circle__me {
  fill: var(--paper-hi);
  stroke: var(--ink);
  stroke-width: 1.5;
}
.circle__node {
  stroke: var(--paper-hi);
  stroke-width: 1.5;
}
.circle__melabel,
.circle__label {
  font-family: var(--font-ui);
  /* 이름표가 선 위에 얹히면 둘 다 안 읽힌다. 종이색 테두리를 글자 뒤에 먼저
     깔아 후광을 만든다 — 흰 상자를 그리는 것보다 가볍고 모양이 덜 상한다. */
  stroke: var(--paper);
  stroke-width: 3;
  paint-order: stroke fill;
}
.circle__melabel {
  font-size: var(--t-caption);
  font-weight: 700;
  fill: var(--ink);
}
.circle__label {
  font-size: var(--t-caption);
  fill: var(--ink-70);
}
.circle__note,
.circle__more {
  max-width: 62ch;
}



/* ── 아직 꽂히지 않은 것들 ─────────────────────────────────────
 * 벽에 남은 자리다. 빈 책장을 늘어세우는 대신 책을 쌓아 두고 먼지를 앉힌다 —
 * 자리가 남았다는 말은 같지만, 빈 가구가 줄지어 선 방은 고장으로 읽힌다.
 * 앞 채가 꽉 차면 바로 이 자리에 새 책장이 선다. */
.room__cases--stocked .case {
  /* 남은 폭을 나눠 갖지 않는다. 그래야 책이 늘어도 책장 폭이 흔들리지 않고,
     새 채가 무더기가 있던 그 자리에 선다. */
  flex: 0 0 var(--case-w);
}
.stockpile {
  flex: 1 1 0;
  min-width: 160px;
  display: flex;
  flex-direction: column;
  /* 무더기는 방 한가운데가 아니라 마지막 책장 옆에 내려놓는다 — 벽이 넓어도
     책장 곁에 붙어 있어야 "여기까지 꽂았다"로 읽힌다. */
  align-items: flex-start;
  justify-content: flex-end;
  gap: var(--s-6);
  padding-inline: var(--s-8);
}
/* 이 자리에서는 쪽지가 무더기 바로 위에 놓인다 (빈 채에서는 한가운데였다) */
.stockpile .case__wait {
  position: static;
  translate: none;
  margin-left: var(--s-6);
}
.stockpile__piles {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: var(--s-8);
  opacity: 0.6; /* 오래 놓여 있어 색이 바랬다 */
}
.stockpile__pile {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.stockpile__book {
  display: block;
  height: 14px;
  border: 1px solid var(--wood-line);
  border-radius: 1px;
}
/* 먼지. 바닥으로 갈수록 짙게 앉는다 — 얇은 막 하나로, 블러는 쓰지 않는다 (§4) */
.stockpile__piles::after {
  content: '';
  position: absolute;
  inset: -12px -20px 0;
  background: linear-gradient(to top, var(--paper-60), transparent 70%), var(--grain-desk);
  pointer-events: none;
}

/* ── 선반 앞면의 라벨 택 = 정렬 ────────────────────────────── */
.shelftags {
  position: absolute;
  top: -1px;
  left: var(--s-3);
  display: flex;
  gap: var(--s-1);
  margin: 0;
  z-index: 1;
}
.shelftag {
  display: inline-flex;
  align-items: center;
  /* 누를 수 있는 것은 24px 이상. 2px 여백이면 높이가 18px 라 손가락이 빗나간다. */
  min-height: 24px;
  padding: var(--s-1) var(--s-2);
  border: 1px solid var(--wood-line);
  border-top: 0;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  background: var(--paper-lo);
  color: var(--ink-70);
  font-family: var(--font-ui);
  /* 11px 은 스케일 밖이라 --t-meta(12px) 로 올렸다. 배지가 커지는 쪽이라
     min-height 24px 안에 그대로 들어가고, 읽기에도 낫다. */
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  white-space: nowrap;
  cursor: pointer;
  /* 택을 세로로 움직이지 않는다. 나란히 걸린 줄에서 하나만 3px 내려오면
   * '빠져나온 택' 이 아니라 정렬이 어긋난 것으로 읽힌다. 선택은 놋쇠 바탕과
   * 굵기로 말한다 — 이 서체에서 굵기는 폭을 바꾸지 않아 옆 택을 밀지 않는다
   * (실측: 400→700 에서 폭 차 0px). */
  transition: background-color var(--dur-micro) var(--ease), color var(--dur-micro) var(--ease);
}
.shelftag:hover {
  background: var(--paper-hi);
  color: var(--ink);
}
.shelftag[aria-pressed='true'] {
  background: var(--brass);
  color: var(--ink);
  font-weight: 700;
}
.shelftag:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* 책등은 나무 위에 서므로 테두리를 한 톤 진하게 */
.case .spine {
  border-color: var(--wood-line);
}

/* 빈 방에 놓이는 안내판 */
.room__note {
  margin: auto;
  max-width: 38ch;
  padding: var(--s-6);
  border: var(--border-dashed);
  border-radius: var(--r-md);
  background: var(--paper);
  text-align: center;
}

/* ── 목록 뷰 ───────────────────────────────────────────────── */
/* 3D 를 켜면 2D 책등은 자리만 남기고 사라진다 (mount3d 가 .room 에 표식을 단다) */
.library:has(#view-list:checked) .room__cases,
.library:has(#view-list:checked) .room__floor {
  display: none;
}
.library:has(#view-list:checked) .list-view {
  display: block;
  flex: 1;
  padding: var(--s-8);
  background: var(--paper);
  overflow-y: auto;
}
.library:has(#view-shelf:checked) .list-view {
  display: none;
}
.footer--case {
  padding-inline: var(--s-6);
  background: var(--paper-lo);
}

@media (max-width: 720px) {
  .room__rail {
    flex-wrap: wrap;
    gap: var(--s-3);
  }
  .search--rail {
    max-width: none;
    margin-left: 0;
    order: 3;
    flex-basis: 100%;
  }
  .room__cases {
    gap: var(--s-3);
    padding-inline: var(--s-3);
  }
}

/* 히어로의 '한 문장 이어 쓰기' 를 누르면 닿은 곳이 잠깐 밝아진다.
 *
 * 앵커 링크는 화면을 순간이동시키므로, 어디에 도착했는지 스스로 말해 주지
 * 않으면 사용자가 방금 무슨 일이 있었는지 모른다. 특히 이 버튼은 페이지
 * 안에서만 움직여서 아무 일도 없는 것처럼 보였다.
 *
 * 배경만 바뀌는 페이드다 — 이동·확대·회전이 없으므로 §5 의 reduced-motion
 * 상쇄 규칙이 필요 없다(그 블록이 남기라고 한 바로 그 종류다). */
.is-flash {
  animation: section-flash 1.4s var(--ease) both;
}
@keyframes section-flash {
  from {
    background-color: var(--wash-tide);
  }
  to {
    background-color: transparent;
  }
}

/* ── 프로필 사진 ────────────────────────────────────────────
 * 사진은 기여자 색 테두리 '안' 에 들어간다. §7 이 색띠를 이 서비스의
 * 시그니처로 못박고 있어서, 사진이 색을 밀어내면 안 된다. */
.avatar-lg,
.avatar,
.contributor__ring,
.hovercard__ring {
  position: relative;
  overflow: hidden;
}
.avatar__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

/* 아바타 자체가 사진을 바꾸는 손잡이다. 겹쳐 두므로 머리 높이가 변하지 않고,
 * 동작이 그 대상 위에 놓인다. */
.avatar-set {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
}
.avatar-slot {
  position: relative;
  width: 88px;
  height: 88px;
}
.avatar-edit {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  cursor: pointer;
}
.avatar-edit input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.avatar-edit__scrim {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--scrim);
  color: var(--paper);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  opacity: 0;
  transition: opacity var(--dur-micro) var(--ease);
}
/* 스크림은 :focus-within 으로 연다. :has(input:focus-visible) 도 동작하지만,
 * 여기서는 "내 안에 초점이 있다" 가 정확히 필요한 것이고 :focus-within 이
 * 그것을 위해 있는 선택자다 — 지원 범위도 넓고 :has() 보다 단순하다.
 * 마우스로 눌러도 열리지만, 누르는 즉시 파일 창이 뜨므로 문제되지 않는다.
 * 포커스 링은 :has(input:focus-visible) 로 따로 건다 — 마우스 클릭에는
 * 링이 뜨지 않아야 하는데 :focus-within 은 그것을 구분하지 못한다. */
.avatar-edit:hover .avatar-edit__scrim,
.avatar-edit:focus-within .avatar-edit__scrim {
  opacity: 1;
}
/* §11 포커스 링은 제거하지 않는다. 입력이 1px 라 링을 겹침 쪽에 그린다. */
.avatar-edit:has(input:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
/* 늘 보이는 작은 표식. 이 저장소가 ⌕ · ♔ 를 쓰듯 글리프로 둔다. */
.avatar-edit__badge {
  position: absolute;
  right: 0;
  bottom: 0;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border: 2px solid var(--paper);
  border-radius: 50%;
  background: var(--ink);
  color: var(--paper);
  font-size: var(--t-ui);
  line-height: 1;
}
/* 지우기는 어쩌다 한 번 쓰는 동작이라 상자를 주지 않는다 (§4 상자보다 여백). */
.avatar-set__clear {
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-45);
  font-family: var(--font-ui);
  font-size: var(--t-meta);
  letter-spacing: var(--ls-meta);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.avatar-set__clear:hover {
  color: var(--wine);
}


/* ── 내 차례 ────────────────────────────────────────────────
 * 이어쓰기 규칙이 "연속 2문장 불가" 라 문장을 쓴 사람은 자리를 비운다.
 * 돌아왔을 때 무엇이 기다리는지 알려 주는 자리다. 재촉하지 않는다 —
 * 기다리는 것이 없으면 구역 자체가 나타나지 않고, 경고색을 쓰지 않는다. */
.myturn {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  /* 아래를 0 으로 두면 뒤따르는 .shelf-region 이 padding-top: 0 이라(앞 구역의
     아래 여백에 기대는 구조) 책장 제목이 카드에 그대로 붙는다. */
  padding: var(--s-12) var(--s-8);
}
.myturn__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--s-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.myturn__item {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-4);
  border: var(--border-solid);
  border-radius: var(--r-md);
  background: var(--paper-hi);
}
.myturn__link {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  color: inherit;
  text-decoration: none;
}
.myturn__link:hover .myturn__topic {
  color: var(--wine);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.myturn__topic {
  font-family: var(--font-display);
  font-size: var(--t-subtitle);
  line-height: var(--lh-title);
}
/* 마지막 문장이 곧 이어 쓸 자리다. 본문 서체로 두어 읽히게 한다. */
.myturn__last {
  font-family: var(--font-read);
  font-size: var(--t-body);
  line-height: var(--lh-read-lead);
  color: var(--ink-70);
}

/* 내비의 개수. 경고가 아니라 셈이므로 붉은색을 쓰지 않는다. */
.nav__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 20px;
  /* 옅은 워시(--wash-tide)로 두면 11px 숫자의 대비가 4.39 라 AA(4.5)에 못 미친다.
     채우면 4.98 이 나오고, 돌아오라는 신호가 흐릿하지 않은 편이 이 배지의 목적에 맞다. */
  background: var(--tide-text);
  border: 1px solid var(--tide-text);
  color: var(--paper);
  font-family: var(--font-mono);
  /* 11px 은 스케일 밖. --t-meta(12px) 로 올린다 — 대비는 글자 크기와 무관하고
     (둘 다 일반 텍스트 기준 4.5:1), 12px 쪽이 읽힌다. */
  font-size: var(--t-meta);
  line-height: 1;
}

@media (max-width: 720px) {
  .myturn {
    padding-inline: var(--s-4);
  }
}

/* 프로필의 설정 서랍. 접혀 있을 때는 손잡이 하나고, 펼치면 그 안에서
   점선 상자들이 나온다.
 *
 * 곁기둥은 왼쪽 단에 맞춰 늘어나므로(.profile-grid 의 stretch) 내용이 짧으면
 * 남는 자리가 이 아래로 떨어진다. 예전에는 '설정' 한 줄이 80px 띠를 차지하고
 * 그 아래로 57px 이 더 비어, 접힌 서랍이 빈 상자처럼 보였다. 손잡이를 기둥
 * 맨 아래에 붙이면 남는 자리는 위로 올라가 숨 돌릴 자리가 되고, 단은 아무것도
 * 아닌 데서 끊기는 대신 손잡이로 끝난다. */
.settings {
  margin-block-start: auto;
  padding: var(--s-4) var(--s-8);
}
.settings__toggle {
  /* inline-flex 가 아니라 flex 인 것은 auto 여백을 받기 위해서다 —
     인라인 상자는 auto 여백을 무시한다 */
  display: flex;
  align-items: center;
  justify-content: center;
  /* 기둥의 오른쪽 끝. 왼쪽에 두면 위 구획들의 제목과 한 줄로 읽혀 이름 없는
     제목처럼 보인다. 오른쪽 구석은 손잡이의 자리다 (.book-get 과 같다). */
  margin-inline-start: auto;
  /* 아이콘만 있는 손잡이는 글자만큼의 폭을 스스로 벌지 못한다 —
     누를 수 있는 것의 하한(WCAG 2.5.8)보다 넉넉하게 */
  width: 56px;
  height: 56px;
  cursor: pointer;
  color: var(--ink-45);
  transition: color var(--dur-micro) var(--ease);
  /* 기본 삼각형은 브라우저마다 모양도 색도 달라 쓰지 않는다. 펼쳤는지 여부는
     <details> 가 읽어 주는 기기에 이미 알리고, 눈으로는 서랍이 열린 것이 보인다. */
  list-style: none;
}
.settings__toggle::-webkit-details-marker {
  display: none;
}
/* 선 굵기는 여기서 준다 — 얹히면 색과 함께 굵기도 따라 진해진다. 26px 짜리
   가는 선에서는 색만 바꿔 봐야 팔레트에서 가장 진한 잉크로 가도 티가 잘
   안 났다(종이 위 대비 8.0:1 → 8.9:1). 진해 보이는 것은 결국 획의 양이다. */
.settings__toggle svg {
  stroke-width: 1.6;
  transition: stroke-width var(--dur-micro) var(--ease);
}
.settings__toggle:hover,
.settings[open] .settings__toggle {
  color: var(--ink);
}
.settings__toggle:hover svg,
.settings[open] .settings__toggle svg {
  stroke-width: 2.1;
}

/* 알림 켜기/끄기 한 줄. 작업대와 프로필이 같은 것을 쓴다 (§8.3) */
.notify-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  margin-block-start: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border: var(--border-dashed);
  border-radius: var(--r-sm);
  color: var(--ink-70);
  font-size: var(--t-caption);
}
.notify-row .btn {
  padding: var(--s-2) var(--s-4);
  font-size: var(--t-caption);
  /* 누를 수 있는 것의 하한 (WCAG 2.5.8) */
  min-height: 32px;
}
/* 이메일 등록 줄만 입력창을 하나 더 얹는다 — 남는 공간을 먹고, 너무
   좁아지면(모바일) 줄바꿈되도록 최소 폭을 둔다 */
.notify-row .field__input {
  flex: 1 1 180px;
  min-height: 32px;
  padding: var(--s-2) var(--s-3);
  font-size: var(--t-caption);
}
/* 계정 삭제 줄. 설명이 길어 한 줄을 통째로 쓰고, 입력과 버튼은 그 아래에 놓인다. */
.notify-row--delete .meta,
.notify-row--delete .form-error {
  flex-basis: 100%;
}
