/* stillsame.me — one stylesheet, no build step.
 *
 * Tokens are the "ink and paper" palette of principles section 6, in the
 * three-state pattern (:root light / prefers-color-scheme dark guarded against
 * an explicit light choice / [data-theme="dark"]).
 * Mobile first; two panes from 640px up (원칙 §5). */

:root {
  --bg: #F5F0DA;
  --surface: #FFFDF6;
  --surface2: #ECE6CF;
  --bubbleMe: #E1E5EF;
  --onBubbleMe: #26282E;
  --text: #26282E;
  --text2: #64676F;
  --border: #E3DDC6;
  --primary: #3D5A99;
  --onPrimary: #FFFFFF;
  --counterpart: #8C5F32;
  --ctint: rgba(140, 95, 50, 0.16);
  /* 원칙 §6 — 강조색을 아주 옅게 깐 바탕. 행동 카드 하나가 이것을 쓴다(증거
     카드의 surface2 와 눈으로 갈려야 하기 때문이다). 라이트는 종이 위라 12%
     로 충분하고, 다크는 바닥이 이미 어두워 같은 양이 surface2 에 묻히므로 더
     깐다. */
  --ptint: rgba(61, 90, 153, 0.16);
  --ok: #35704F;
  --warn: #7E6116;
  --danger: #A04A42;
  --focus: #3D5A99;
  --selection: #D5DDF0;
  --shade: rgba(0, 0, 0, 0.10);
  --font: -apple-system, BlinkMacSystemFont, "Pretendard", "Noto Sans KR",
    "PingFang SC", "Microsoft YaHei", "Hiragino Sans", "Noto Sans JP",
    sans-serif;
  color-scheme: light;
}

/* 원칙 §5 — 타입 스케일과 그에 딸린 치수. 텍스트 크기 설정(작게/보통/크게)이
 * 바꾸는 것은 이 변수들뿐이고, 모든 컴포넌트는 여기서 크기를 읽는다.
 * v0.8 에서 iOS 16px 기준으로 재설정: 본문 16/22 · 보조 14/20 · 캡션 12/16 ·
 * 제목 17/22 semibold (보통). 큰 제목 28 은 세 단계 모두 같다.
 * 원칙 §4 (v0.11) — 채팅 목록은 한 단계씩 작다: 제목 --fs-lt 는 본문보다,
 * 미리보기 --fs-ls 는 보조보다 한 단계 아래(작게 14/12 · 보통 15/13 · 크게 17/15).
 * 시각은 그대로 캡션이다.
 * 딸린 치수는 모두 여기서 나온다:
 *   말풍선 높이 = --lh-body + 2 × --bpad-y   (보통 22 + 20 = 42)
 *   목록 행     = --row-h                     (56 / 60 / 66)
 *   입력 필·플로팅 바 = --pill-h              (40 / 44 / 48) */
:root {
  --fs-body: 16px;  --lh-body: 22px;  --lh-read: 25px;
  --fs-sec: 14px;   --lh-sec: 20px;
  --fs-cap: 12px;   --lh-cap: 16px;
  --fs-title: 17px; --lh-title: 22px;
  --fs-h1: 22px;    --lh-h1: 30px;
  --fs-lg: 28px;    --lh-lg: 34px;
  --fs-lt: 15px;    --lh-lt: 20px;
  --fs-ls: 13px;    --lh-ls: 18px;
  --bpad-y: 10px;   --bpad-x: 14px;
  --row-h: 60px;
  --pill-h: 44px;
}

:root[data-text-size="small"] {
  --fs-body: 15px;  --lh-body: 21px;  --lh-read: 24px;
  --fs-sec: 13px;   --lh-sec: 18px;
  --fs-cap: 12px;   --lh-cap: 16px;
  --fs-title: 16px; --lh-title: 22px;
  --fs-h1: 21px;    --lh-h1: 28px;
  --fs-lt: 14px;    --lh-lt: 19px;
  --fs-ls: 12px;    --lh-ls: 16px;
  --bpad-y: 9px;    --bpad-x: 13px;
  --row-h: 56px;
  --pill-h: 40px;
}

:root[data-text-size="large"] {
  --fs-body: 18px;  --lh-body: 24px;  --lh-read: 27px;
  --fs-sec: 16px;   --lh-sec: 22px;
  --fs-cap: 13px;   --lh-cap: 18px;
  --fs-title: 19px; --lh-title: 24px;
  --fs-h1: 24px;    --lh-h1: 32px;
  --fs-lt: 17px;    --lh-lt: 22px;
  --fs-ls: 15px;    --lh-ls: 20px;
  --bpad-y: 11px;   --bpad-x: 15px;
  --row-h: 66px;
  --pill-h: 48px;
}

/* 필 하나에서 파생되는 값들 — 세 단계 어디서나 같은 식으로 따라온다.
 * --pill-r   접힌 필 높이의 절반. 늘어나도 이 값을 유지한다(스타디움이 되지 않는다).
 * --pill-pad 32 원판이 필 안에서 상하 가운데 서는 안쪽 여백(테두리 1px 포함).
 * --snd-pad  36 보내기 원이 같은 자리에 서는 안쪽 여백. 원판(32)과 보내기 원(36)은
 *            지름이 다르므로 같은 여백을 쓰면 중심선이 2px 어긋난다 — 필의 안쪽
 *            높이(--pill-h - 테두리 2)에서 각자의 지름을 빼고 반으로 나눈다.
 *            그러면 두 원의 중심이 모두 필의 중심선(--pill-r)에 온다.
 * --hbar-h   플로팅 헤더가 차지하는 높이(위 여백 + 원판 높이). 목록·방·설정의
 *            스크롤 영역은 그만큼 위를 비워 둔다 (원칙 §4).
 * --col      지금 판의 단(段) 최대 폭 — 채팅 판은 720, 그 밖의 페이지는 560
 *            (원칙 §5). 헤더·본문·입력 필이 모두 이 하나를 읽으므로 세 조각이
 *            같은 좌우 선에 선다. 판이 단보다 좁으면 단은 판에서 좌우 여백만
 *            뺀 폭이 되므로 좁은 화면과 확장 패널은 예전 그대로다.
 * --sbw      그 판의 스크롤러가 비워 둔 스크롤바 폭(state.js 의 trackGutter 가
 *            판마다 재서 넣는다). 단은 스크롤러 안쪽에서 재므로 떠 있는 헤더와
 *            입력 필도 오른쪽을 그만큼 비워야 글과 가운데가 같다. 겹쳐 뜨는
 *            스크롤바(모바일·기본 macOS)에서는 0 이라 아무것도 움직이지 않는다. */
:root {
  --pill-r: calc(var(--pill-h) / 2);
  --pill-pad: calc((var(--pill-h) - 34px) / 2);
  --snd-h: 36px;
  --snd-pad: calc((var(--pill-h) - 2px - var(--snd-h)) / 2);
  --hbar-h: calc(var(--pill-h) + 12px + env(safe-area-inset-top));
  --col-chat: 720px;
  --col-page: 560px;
  --col: var(--col-page);
  /* 가상 키보드 (원칙 §3.2). state.js 의 visualViewport 처리기가 덮어쓴다:
   * --vvh 보이는 뷰포트 높이, --kb 키보드가 가린 높이, --vvtop 위로 밀린 양.
   * 안드로이드(resizes-content)와 데스크톱에서는 --kb 가 0 으로 남는다. */
  --kb: 0px;
  --vvtop: 0px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #10141F;
    --surface: #181D2A;
    --surface2: #222838;
    --bubbleMe: #2B3B5E;
    --onBubbleMe: #E2E5EC;
    --text: #E2E5EC;
    --text2: #9BA2B1;
    --border: #2B3242;
    --primary: #A9BEF0;
    --onPrimary: #0F1728;
    --counterpart: #D9A46E;
    --ctint: rgba(217, 164, 110, 0.16);
    --ptint: rgba(169, 190, 240, 0.30);
    --ok: #6FCBA0;
    --warn: #E0BE6A;
    --danger: #F08A80;
    --focus: #A9BEF0;
    --selection: #2E3E63;
    --shade: rgba(0, 0, 0, 0.45);
    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --bg: #10141F;
  --surface: #181D2A;
  --surface2: #222838;
  --bubbleMe: #2B3B5E;
  --onBubbleMe: #E2E5EC;
  --text: #E2E5EC;
  --text2: #9BA2B1;
  --border: #2B3242;
  --primary: #A9BEF0;
  --onPrimary: #0F1728;
  --counterpart: #D9A46E;
  --ctint: rgba(217, 164, 110, 0.16);
  --ptint: rgba(169, 190, 240, 0.30);
  --ok: #6FCBA0;
  --warn: #E0BE6A;
  --danger: #F08A80;
  --focus: #A9BEF0;
  --selection: #2E3E63;
  --shade: rgba(0, 0, 0, 0.45);
  color-scheme: dark;
}

/* --- base ------------------------------------------------------------------ */

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: var(--fs-body)/var(--lh-body) var(--font);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  overscroll-behavior: none;
}

h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.3; }
p { margin: 0 0 8px; }
button { font: inherit; color: inherit; }

:focus { outline: none; }
/* 원칙 §5 — 크기가 변하지 않는 1px primary 테두리. 브라우저 기본 링은 없다. */
:focus-visible { outline: none; }

/* 원칙 §5 — 앱의 막대는 하나다: 얇고, 트랙은 비어 있고, 손잡이는 글자색을 옅게
 * 섞은 그 색. 규칙은 이 하나뿐이라 목록도 시트도 판도 같은 막대를 쓴다 —
 * `::-webkit-scrollbar*` 를 곁들이면 표준 속성을 아는 브라우저가 그쪽을 통째로
 * 무시하므로 자리마다 다른 막대가 서게 된다. 막대의 *자리*는 그리는 것이 아니라
 * 스크롤하는 상자를 어디에 두느냐의 문제다(시트의 `.sheet-scroll`). */
* {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--text2) 35%, transparent) transparent;
}

/* Pressed feedback: everything that can be pressed dims and shrinks a little
   (원칙 §5 — 모든 액션에 시각 피드백). */
.btn, .cta-btn, .chip, .ldisc, .ebk, .bbt, .qbt, .shx, .snd, .exp, .ft, .seg button,
.dchip .x,
.orow, .irow, .r, .cmn button, .hd, .hsr, .rx, .lnk, .tbtn, button.av, .rsc, .dbx, .prw, .ma,
.home-lang, .home-guide, .panel-signin, .panel-hint a, .panel-hint button {
  transition: opacity 0.12s ease-out, transform 0.12s ease-out;
  -webkit-tap-highlight-color: transparent;
}
.btn:active, .cta-btn:active, .chip:active, .ldisc:active, .ldisc.is-on,
.ebk:active, .bbt:active, .qbt:active,
.shx:active, .snd:active, .exp:active, .ft:active, .seg button:active,
.dchip .x:active, .hd:active, .hsr:active, button.av:active, .rsc:active, .dbx:active, .ma:active,
.panel-signin:not([aria-disabled]):active {
  opacity: 0.7;
  transform: scale(0.97);
}
.orow:active, .irow:active, .r:active, .cmn button:active,
.rx:active, .lnk:active, .tbtn:active, .prw:active, .home-lang:active, .home-guide:active,
.panel-hint a:active, .panel-hint button:active { opacity: 0.7; }
/* 원칙 §5 — 위험 색은 휴지통의 것이다. 같은 자리의 ⋮(`.mo`)는 메뉴를 열 뿐이다. */
.rx:not(.mo):active { color: var(--danger); }

/* 원칙 §5 — 데스크톱 hover 도 같은 규칙. A row a pointer is over lifts onto
   surface2: a paper tint in light, a step towards the light in dark. */
@media (hover: hover) and (pointer: fine) {
  .r:hover, .orow:hover, .cmn button:hover, button.irow:hover, .rrow:hover
  { background: var(--surface2); }
  /* 원칙 §5 — 헤더의 뒤로 · ⋮ · + 원판은 표면색이 한 단계 올라간다. */
  .tbtn:hover { background: var(--surface2); }
  /* 시트의 조용한 답도 눌리는 것이라 같은 규칙으로 답한다. */
  .qbt:hover { background: var(--surface2); }
  /* 원칙 §5 — 시트의 ✕ 원판은 이미 surface2 위에 있으므로 글리프만 본문 색으로. */
  .shx:hover { color: var(--text); }
  button.hd:hover { background: var(--surface2); }
  /* 검색 필은 이미 surface 위의 조각이므로 hover 는 글자 색으로만 답한다. */
  .hsr:hover { color: var(--text); }
  .r.is-active:hover, .orow.sel:hover, .cmn button.sel:hover { background: var(--selection); }
  /* 카드 행은 이미 surface2 위에 있으므로 한 단계 위인 selection 으로. */
  .orow.card:hover, .prow.card:hover, .olist .orow:hover { background: var(--selection); }
  /* 고른 줄은 이미 selection 위에 있으므로 hover 는 그 색을 한 걸음 더 올린다. */
  .olist .orow.sel:hover { background: color-mix(in srgb, var(--selection) 86%, var(--text)); }
  /* these two already sit on surface2, so their hover is in the ink */
  .seg button:hover, .ft:hover, .exp:hover { color: var(--text); }
  /* 입력 필의 언어 원판은 이미 surface2 위에 있으므로 hover 에서 selection 으로
     한 단계 올라간다. 편집기의 뒤로 원판은 헤더 원판과 같은 규칙이다. */
  .ldisc:hover { background: var(--selection); color: var(--primary); }
  .ebk:hover { background: var(--surface2); }
  .rx:not(.mo):hover { color: var(--danger); }
  .lnk:hover { text-decoration: underline; }
  /* 홈의 시작 링크는 본문 크기라 밑줄만으로는 약하다 — 글자가 본문 색으로 올라온다. */
  .home-start .lnk:hover { color: var(--text); }
  /* 채워진 버튼(시작하기)은 색을 한 단계 올린다 — 누르는 순간의 0.7 은 그대로다. */
  .cta-btn:hover { background: color-mix(in srgb, var(--primary) 88%, var(--text)); }
  /* 원칙 §2 — 메타 줄의 글리프는 hover 에서 본문 색이 된다. */
  .ma:hover { color: var(--text); opacity: 1; }
  /* 시트의 판정 줄을 치우는 ✕ 는 글자만 올라온다. */
  .dbx:hover { color: var(--text); }
  /* 홈페이지의 화면 언어 버튼도 같다 — 바탕 없이 글자만 올라온다. */
  .home-lang:hover, .home-guide:hover { color: var(--text); }
  /* 패널의 로그인 버튼은 구글 판이므로 hover 도 구글의 것이다. 기다리는 동안의
     같은 판은 누를 수 없으므로 답하지 않는다. */
  .panel-signin:not([aria-disabled]):hover { background: #f8f9fa; }
  /* 로그인 카드의 조용한 버튼(「취소」 · 「탭 다시 열기」)은 이미 surface2 위에
     서 있으므로 카드 행과 같이 한 단계 위인 selection 이 깔린다. */
  .login-card .btn-quiet:hover { background: var(--selection); }
}

.muted { color: var(--text2); }
.small { font-size: var(--fs-sec); line-height: var(--lh-sec); }
.spacer { flex: 1 1 auto; }
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* 원칙 §5 — 아이콘은 icons.js 하나에서만 나오고(20 격자 · 1.5 선 · 둥근 끝),
 * 크기는 이 규칙 하나가 정한다: 자기가 놓인 줄의 글자 크기에 매인 1.25em 이므로
 * 메타 줄(12)에서는 15, 메뉴·원판(16)에서는 20 이 된다. 텍스트 크기 설정도
 * 같은 변수를 타고 따라온다. */
.ic { display: block; flex: 0 0 auto; width: 1.25em; height: 1.25em; }
/* 체크 원판의 체크는 종이색이다 — 원판이 currentColor 로 차 있으므로 그 위의
 * 선만 표면 색으로 오려 낸다 (원칙 §3.11). */
.ic .paper {
  fill: none; stroke: var(--surface);
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}

/* Reset for every element the app uses as a tap target. */
.tap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* --- boot / auth / onboarding views ---------------------------------------- */

/* body 는 스크롤하지 않으므로(overflow: hidden) 세로로 남는 것은 이 안에서
 * 스크롤해야 한다. 가운데 정렬을 `justify-content` 로 하면 넘칠 때 위가
 * 잘리므로, 첫 조각의 auto 마진이 남는 공간을 나눠 가진다. */
.view-center {
  height: 100vh; height: 100dvh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 24px 20px calc(24px + env(safe-area-inset-bottom));
}
.view-center > :first-child { margin-block: auto; }

/* 원칙 §4 — 로그인 화면은 홈페이지다: 카드가 아니라 한 단의 글이다. 세 조각
 * (맨 위 한 줄 띠 · 읽는 글 · 맨 아래 캡션)이 같은 왼쪽 모서리에 서고, 가운데
 * 조각의 auto 마진이 남는 공간을 나눠 가진다. 좁은 화면에서 글이 화면보다
 * 길어지므로 여기도 스크롤 컨테이너다. */
.view-home {
  height: 100vh; height: 100dvh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: 0 20px calc(24px + env(safe-area-inset-bottom));
}
.home-col { width: 100%; max-width: var(--col); margin-inline: auto; }
/* 띠가 차지하는 높이는 앱 헤더와 같다(--hbar-h — 위 여백 + 필 높이 + safe-area). */
.home-bar {
  flex: none;
  display: flex; align-items: center; gap: 8px;
  height: var(--hbar-h);
  padding-top: env(safe-area-inset-top);
}
/* 마크와 이름은 한 조각이다 — 로그인 페이지에서는 그 조각이 홈으로 가는
   링크이고, 홈에서는 제자리를 가리킬 뿐이라 링크가 아니다. */
.home-brand {
  display: flex; align-items: center; gap: 8px;
  min-width: 0;
  color: inherit; text-decoration: none;
}
.home-mark { flex: none; display: flex; color: var(--primary); }
.home-mark .ic { width: 20px; height: 20px; }
.home-name {
  font-size: var(--fs-title); line-height: var(--lh-title);
  font-weight: 600; letter-spacing: -0.01em;
}
/* 화면 언어 — 지구본 하나와 지금 언어가 스스로를 부르는 이름(한국어 ·
   English · 中文 · 日本語)뿐이고 캐럿은 없다(입력 필의 언어 원판과 같은 규칙).
   읽을 수 없는 말로 적힌 언어는 고를 수 없으므로, 이 자리와 메뉴에서만은
   번역된 이름을 쓰지 않는다. 누르면 앱의 메뉴가 그대로 열린다. */
/* 사용법으로 가는 링크 — 언어 버튼과 같은 판, 같은 높이, 그 왼쪽에 선다. 오른쪽
   정렬은 이 링크가 맡고 언어 버튼은 그 옆에 붙는다. */
.home-guide {
  flex: none; margin-left: auto;
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; padding: 0 8px;
  border-radius: 10px;
  color: var(--text2); text-decoration: none;
  font-size: var(--fs-sec); line-height: var(--lh-sec);
}
.home-guide-ic { display: flex; }
.home-guide-ic .ic { width: 18px; height: 18px; }
.home-guide + .home-lang { margin-left: 0; }
.home-lang {
  flex: none; margin-left: auto;
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; padding: 0 8px;
  border: 0; border-radius: 10px; background: transparent;
  color: var(--text2);
  font-size: var(--fs-sec); line-height: var(--lh-sec);
  cursor: pointer;
}
.home-body { margin-block: auto; padding: 24px 0; }
/* 온보딩의 첫 화면과 같은 헤드라인 크기다(--fs-lg) — 다음 화면이 그 말을
   그대로 잇는다. */
.home-head {
  font-size: var(--fs-lg); line-height: var(--lh-lg);
  letter-spacing: -0.01em; color: var(--text);
  text-wrap: balance;
}
/* 헤드라인 밑의 한 단락 — 읽는 글이다. 그 밑은 구역이 제 숨을 가진다. */
.home-about {
  margin: 16px 0 0;
  color: var(--text2);
}
/* 소개 밑의 행동 한 줄 — 본문 크기의 글자 링크에 앞으로 가는 화살표. 카드는
   맨 아래 하나뿐이고 이것은 같은 곳으로 가는 지름길이다. */
.home-start { margin: 20px 0 0; }
.home-start .lnk {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--fs-body); line-height: var(--lh-body);
}
.home-start .ic { width: 18px; height: 18px; }
/* 원칙 §4 — 글은 구역 일곱이고(마지막이 「시작하기」), 계단은 텍스트 페이지와
 * 같은 셋뿐이다: 소제목(--fs-title 600) · 문단 · 조용한 줄. 숨도 새로 만들지
 * 않는다 — 구역 사이 40, 소제목 밑 10, 증거 위 20 이다. 행동 자리도 제 구역의
 * 증거이므로 같은 20 에 선다. */
.home-sec { margin-top: 40px; }
.home-sec-hd {
  margin: 0;
  font-size: var(--fs-title); line-height: var(--lh-title);
  font-weight: 600; letter-spacing: -0.01em; color: var(--text);
}
.home-sec-tx { margin: 10px 0 0; color: var(--text2); }
/* 원칙 §4 — 행동 자리는 카드 하나다. 증거 카드와 같은 모서리(16)이되 바탕이
 * 다르다: 증거는 surface2 이고 이것은 강조색을 옅게 깐 --ptint 라, 읽는 글
 * 안에서 「여기를 누른다」가 먼저 읽힌다. 안은 다른 구역과 같은 계단
 * (소제목 · 조용한 한 줄)에 채움 버튼 하나이고, 숨은 제 것이라(위 40)
 * 헤드라인 밑이든 구역 사이든 같은 블록을 한 벌 더 세우면 그만이다.
 * 안 여백은 증거 카드보다 한 단계 넉넉한 24 이고, 아주 좁은 화면(≤360)에서만
 * 20 으로 줄인다. */
.cta {
  margin-top: 40px;
  padding: 24px;
  border-radius: 16px;
  background: var(--ptint);
}
/* 화면당 하나뿐인 채움 버튼이다(원칙 §5). 시트의 주 동작(`.bbt`)과 같은 색이되
 * 한 단계 크다 — 이 페이지에서 누를 것은 이것 하나다. 좁은 화면(≤560)에서는
 * 카드의 안쪽 폭을 다 쓰고, 넓어지면 제 폭(최소 240)으로 왼쪽 선에 선다. */
.cta-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%;
  min-height: 52px;
  margin-top: 20px;
  padding: 0 20px;
  border-radius: 14px;
  background: var(--primary); color: var(--onPrimary);
  font-size: var(--fs-title); line-height: var(--lh-title); font-weight: 600;
  text-decoration: none;
}
@media (min-width: 561px) {
  .cta-btn { width: auto; min-width: 240px; }
}
@media (max-width: 360px) {
  .cta { padding: 20px; }
}
/* 원칙 §4 — 증거는 저마다 카드 하나 안에 선다. 주장은 페이지의 글이고 증거는
 * 제품의 화면이라, 둘이 같은 바탕에 이어 서면 어디까지가 말이고 어디부터가
 * 화면인지 읽는 사람이 알 수 없다. 카드는 로그인 카드와 같은 어휘다(surface2 ·
 * 모서리 16, 안 여백은 20 · 한 단 폭)이고 그림자도 테두리도 더하지 않는다.
 * 아주 좁은 화면(≤360)에서는 안 여백만 16 으로 줄여 말풍선이 설 자리를 준다.
 * 스크립트가 채우기 전의 빈 카드는 서지 않는다 — 빈 상자가 자리를 잡아먹느니
 * 없는 편이 낫다. */
.demo-card {
  margin-top: 20px;
  padding: 20px;
  border-radius: 16px;
  background: var(--surface2);
}
.demo-card:empty { display: none; }
/* 그림 둘은 한 구역의 증거 둘이라 서로 더 가깝다. */
.demo-card + .demo-card { margin-top: 12px; }
@media (max-width: 360px) {
  .demo-card { padding: 16px; }
}
/* 원칙 §4 — 카드 안의 증거. 방의 말풍선을 그대로 재생하므로 조각도 토큰도
 * 여기서 새로 정하지 않는다: 정하는 것은 세로 흐름(방의 `.list` 와 같은 12 —
 * 발화자가 바뀌면 `.m + .m` 이 4 를 더해 16 이 된다)과, 누를 것이 없다는 것뿐이다.
 * 폭도 정하지 않는다 — 말풍선은 `.col` 의 80% 규칙으로 접히므로 320 에서도
 * 사이드패널에서도 방과 똑같이 선다. */
.home-demo { display: flex; flex-direction: column; gap: 12px; }
.home-demo .b { cursor: default; }
/* 방의 바닥은 bg 이고 상대의 말풍선이 surface2 다 — 카드가 그 surface2 라
   말풍선이 카드에 그대로 녹는다. 카드 안에서는 그 둘이 자리를 맞바꾼다:
   카드가 바닥이 되고 상대의 말풍선이 bg 를 쓰므로, 두 색의 차이는 방에서와
   똑같고 무엇이 상대의 말인지도 그대로 보인다. */
.demo-card .b.op { background: var(--bg); }
/* 원칙 §4 — 「이렇게 씁니다」의 증거는 채팅 목록 그 자체다. 행도 원판도 시각도
 * 목록의 것을 그대로 쓴다. 카드 안에서는 행이 제 안쪽 여백(12)을 그대로 쓰고
 * 밖으로 물리지 않는다 — 카드가 이미 왼쪽 선을 그어 주기 때문이다. */
.home-list .rows-inner { padding: 0; margin: 0; }
.home-list .r { cursor: default; }
/* 대화 모델 §6 — 「내가 있는 곳에서」 밑의 그림 둘. 사진이 아니라 이 화면의
 * 토큰으로 그린 것이라 두 테마와 글자 크기를 그대로 따르고, 읽기 그림의 패널
 * 칸에는 방의 진짜 말풍선이 선다. 한 줄에 [전 · 키 · 후]이고, 좁아지면(≤560)
 * 위에서 아래로 같은 순서로 선다 — 320 에서도 가로로 넘치지 않는다.
 * 상자는 어느 사이트에나 있는 그 입력창 하나다: 테두리 1px 과 모서리 12 뿐이고
 * 도구 띠도 이름도 로고도 없다. */
/* `figure` 는 브라우저가 제 여백(1em 40px)을 주므로 지우고, 카드의 숨을 그대로
   쓴다 — 증거 위 20, 카드 사이 12(`.demo-card`). 그림 둘도 다른 구역과 같은
   숨으로 문단 밑에 선다. */
.flow { margin: 20px 0 0; }
/* 두 그림은 같은 판이다: 왼쪽 상자 · 키 · 오른쪽 상자가 두 줄에서 같은 폭으로
   선다(오른쪽은 패널 폭 240 까지, 왼쪽이 남는 폭). 그래야 「전 → 후」가 두 그림
   사이에서 같은 자리를 가리킨다. */
.flow-row {
  display: grid; align-items: center; gap: 12px;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 240px);
}
.flow-box {
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface);
  font-size: var(--fs-sec); line-height: var(--lh-sec); color: var(--text);
}
/* 드래그한 자리 — 방의 선택색 그대로다. */
.flow-sel {
  background: var(--selection); border-radius: 3px;
  box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
/* 키 하나. 사용법의 문장이 괄호 안에 적는 그 키를, 그림에서는 글자 대신
   눌리는 것의 모양으로 세운다(맥이면 글리프, 아니면 그대로 적는다). */
.flow-key {
  padding: 4px 8px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface2); color: var(--text2);
  font-family: inherit;
  font-size: var(--fs-cap); line-height: var(--lh-cap);
  white-space: nowrap;
}
/* 패널 칸 — 넓은 화면에서도 240 을 넘지 않아 옆의 「전」과 균형이 서고, 바탕은
   패널과 같은 맨바닥(bg)이다. */
.flow-panel {
  min-width: 0;
  padding: 12px;
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg);
}
/* 이 칸에서만 말풍선이 원판 옆의 남은 폭을 다 쓴다. 방에서 폭을 깎는 둘은 여기서
   깎을 이유가 없다 — 36 은 포인터 기기에서 ⋮ 가 설 자리인데 이 그림에는 누를
   것이 없고(방의 최소 여백은 0 이다), 80% 는 옆에 다음 말풍선이 설 자리를 남기는
   규칙인데 240 칸에서는 그 여백이 글을 두세 낱말로 접는다. 원판도 여백(12)도
   말풍선의 안 여백(--bpad-*)도 방의 것 그대로다. */
.flow-panel .m { padding-right: 0; padding-left: 0; }
.flow-panel .col, .flow-panel .m.me .col, .flow-panel .b { max-width: 100%; }
/* 그림 위의 한 줄 — 무슨 일이 일어날지 먼저 말한다. 구역의 조용한 줄과 같은
   판이고, 아무도 말해 주지 않은 그림은 수수께끼라서 밑이 아니라 위에 선다. */
.flow-cap {
  margin: 0 0 12px;
  font-size: var(--fs-sec); line-height: var(--lh-sec); color: var(--text2);
}
@media (max-width: 560px) {
  /* 좁아지면 [전 · 키 · 후] 가 같은 순서로 위에서 아래로 선다. */
  .flow-row { grid-template-columns: minmax(0, 1fr); }
  .flow-key { justify-self: start; }
}
/* 대화 모델 §6 — 그림 둘 앞에 서는 첫 카드(`.flow-ext`, `main.js`). 확장이
   여기 있는지 말하는 자리다. 그림과 같은 카드(`.demo-card`)라 바탕도 모서리도
   안 여백도 숨도 거기서 오고, 여기서 정하는 것은 안의 세로 흐름뿐이다: 상태
   한 문장(그림의 캡션과 같은 판)과, 있을 때만 그 밑 12 에 서는 길 하나.
   폭은 정하지 않는다 — 문장은 320 에서도 접히고 길은 제 글만큼만 넓다. */
.flow-ext { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
.flow-ext .flow-cap { margin: 0; }
/* 문장 밑의 한 줄 — 스토어로, 사용법으로, 단축키 설정으로 가는 글자 링크. 세
   상태가 같은 줄을 쓰므로 카드 높이가 답에 따라 바뀌지 않는다. */
.flow-line { margin: 0; font-size: var(--fs-cap); line-height: var(--lh-cap); }
.flow-line .lnk-still { color: var(--text2); }
/* 키가 비어 있을 때만 문장 밑에 고치러 가는 길 하나가 붙는다(띠의 그 링크다). */
.flow-ext .lnk, .gtx .lnk, .list-card .lnk {
  display: inline-flex; align-items: center; gap: 4px;
}
/* 원칙 §4 — 로그인 한 장의 카드. 이 페이지에서 할 일이 전부 이 안에 있으므로
 * 상자는 하나다: 초대의 네 상태도 이 카드의 내용이고 제 테두리를 따로 갖지
 * 않는다(상자 안의 상자는 없고, 제목도 카드의 것 하나뿐이다).
 * 어휘는 증거 카드와 같되(surface2 · 모서리 16)
 * 읽는 글이 아니라 한 장의 화면이라 단보다 좁고(440) 안 여백이 넉넉하다
 * (28 · ≤360 에서는 20). 가로는 단 안에서 가운데이고, 세로는
 * 띠와 푸터 사이의 남는 공간을 `.home-body` 의 auto 마진이 위아래로 똑같이
 * 나눠 준다. 패널이 연 탭의 끝 화면도 같은 카드다. */
.login-card {
  width: 100%; max-width: 440px; margin-inline: auto;
  padding: 28px;
  border-radius: 16px;
  background: var(--surface2);
}
@media (max-width: 360px) {
  .login-card { padding: 20px; }
}
/* 원칙 §4 — 카드의 제목은 하나이고, 지금 상태의 이름이다(main.js 의
 * `setLoginHead`). 이름이 있는 초대에서는 부른 사람의 원판(32)이 그 왼쪽에
 * 같이 서므로 제목 자리는 줄이 아니라 행이고, 제목이 두 줄이 되어도 원판은 첫
 * 줄에 맞춰 선다. */
.login-hd { display: flex; align-items: flex-start; gap: 12px; }
.login-hd .login-title { min-width: 0; }
/* 카드의 제목. 홈의 헤드라인이 아니라 이 한 장의 이름이라 한 단계 작다
   (--fs-h1). 그 밑이 곧 행동 자리다. */
.login-title {
  margin: 0;
  font-size: var(--fs-h1); line-height: var(--lh-h1);
  font-weight: 600; letter-spacing: -0.01em; color: var(--text);
}
/* 제목 밑의 조용한 한 줄 — 패널이 연 탭의 끝 화면 하나가 쓴다(패널에서 이어
   하라는 한 줄). 계단은 카드의 것이다(제목 밑 10). */
.login-sub { margin: 10px 0 0; color: var(--text2); }
/* 카드 밑의 조용한 한 줄 — 로그인 없이 읽을 수 있는 곳은 홈 하나뿐이라 링크도
   하나다. 카드와 같은 가운데에 선다. 초대·확인·코드와 패널에서는 서지 않는다:
   한 흐름의 중간에 빠져나갈 곳을 두지 않기 때문이다(main.js 의 paintInvite). */
.login-about {
  margin: 20px 0 0;
  text-align: center;
  font-size: var(--fs-sec); line-height: var(--lh-sec);
  color: var(--text2);
}
.login-about a { color: inherit; text-decoration: none; }
.login-about a:hover { text-decoration: underline; }
/* 원칙 §4 — 행동 자리는 제목 밑 20 에 선다. 버튼은 Google 이 그리는 iframe
 * 이고, 이 카드의 안쪽 폭에서 계산한 폭으로 그려진다
 * (index.html — 400 은 천장일 뿐이다). 다크에서 페이지는
 * `color-scheme: dark` 인데 그 iframe 은 라이트라 크롬이 투명 배경을 흰색으로
 * 칠한다 — 이 상자만 라이트로 맞춰 두면 투명이 그대로 남는다. */
.gis-button { display: flex; margin-top: 20px; min-height: 44px; color-scheme: light; }
.home-beta { margin: 10px 0 0; font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2); }
/* 원칙 §4 — 패널에서는 로그인이 탭에서 일어나므로 이 자리가 구글 버튼 대신
 * 버튼 하나 + 캡션 한 줄이 된다(숨 20 은 구글 버튼과 같다). 탭을 열고 난
 * 기다림도 같은 블록이다 — 자리가 바뀌는 것이 아니라 그 둘의 내용이 바뀐다. */
.home-panel { margin-top: 20px; }
/* 같은 자리를 웹에서는 구글이 그리므로 그 판을 그대로 따른다 — 흰 바탕 ·
 * 1px 회색 테두리 · 4 모서리 · 40 높이, 그리고 `.gis-button` 과 같은 폭(글의
 * 왼쪽 선에서 단 끝까지). 글씨만 우리 것이다(Roboto 가 아니다). 판이 두 테마
 * 모두 흰색이라 그 위에 서는 색은 토큰이 아니라 라이트의 값을 적어 둔다. */
.panel-signin {
  /* 라이트의 primary — 흰 판 위에 서는 마크와 포커스 테두리의 색이다. */
  --plate-primary: #3D5A99;
  display: flex;
  align-items: center;
  width: 100%;
  max-width: 400px; /* GIS 버튼의 천장(index.html 의 width)과 같다. */
  height: 40px;
  padding: 0 12px;
  border: 1px solid #dadce0;
  border-radius: 4px;
  background: #fff;
  color: #1f1f1f;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 500;
  line-height: 20px;
  cursor: pointer;
}
/* 구글의 G 가 앉는 자리에 우리 마크가 앉는다: 왼쪽 12 안쪽, 20 크기. */
.panel-signin .ic { width: 20px; height: 20px; color: var(--plate-primary); }
/* 판의 높이가 40 으로 고정이므로 라벨은 접히지 않는다 — 아주 좁은 패널에서는
   줄이 늘어나는 대신 말줄임이 된다. */
.panel-signin-tx {
  flex: 1 1 auto; min-width: 0;
  text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 원칙 §5 — 크기가 변하지 않는 1px primary 테두리, 입력 필의 포커스와 같다. */
.panel-signin:focus-visible { border-color: var(--plate-primary); }
/* 원칙 §4 — 탭을 열고 난 뒤의 같은 판. 자리도 모양도 그대로이고 누를 수 없다는
 * 것만 다르다: 마크 자리에 채팅과 같은 점 셋이 서고 라벨이 기다림의 한 줄로
 * 바뀌며, 글씨는 판 위의 조용한 회색이 된다(판이 두 테마 모두 흰색이라 점도
 * 토큰이 아니라 이 회색이다 — `--text2` 는 다크에서 흰 판 위에 흰 점이 된다).
 * `disabled` 가 아니라 `aria-disabled` 다: 이 판이 지금 화면이 하는 말이라
 * 읽히지 않고 건너뛰어지면 안 된다. */
.panel-signin[aria-disabled] { color: #5f6368; cursor: default; }
.panel-signin .dots { flex: none; padding-left: 0; }
.panel-signin .dots i { background: #5f6368; }
/* 기다리는 동안의 캡션 자리는 조용한 링크 둘이다 — 탭을 다시 여는 진짜 링크와
   이 화면을 버튼으로 되돌리는 「취소」. 캡션과 같은 크기·색이고 밑줄로만 누를 수
   있음을 말한다(버튼 모양을 한 번 더 세우지 않는다). */
.panel-hint a, .panel-hint button {
  padding: 0; border: 0; background: none;
  color: inherit; font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
/* 원칙 §4 — 그 탭의 끝 화면은 방금 지나온 로그인 페이지 그대로다: 같은 띠,
 * 같은 카드, 계단도 카드의 것(제목 밑 10, 행동 자리 20). 갈 곳이 없으므로 띠의
 * 화면 언어도 맨 아래 푸터도 없다. 버튼은 카드의 주 동작과 같은 크기다. */
.done-action { display: flex; margin-top: 20px; }
/* 닫히지 않는 탭의 캡션은 버튼 자리를 그대로 물려받는다. */
.done-action .home-beta { margin: 0; }
/* 원칙 §4 — 초대의 네 상태는 카드가 아니라 로그인 카드의 내용이다: 부른
 * 사람의 이름으로 맞는 환영, 그 이름으로 가입을 묻는 확인, 코드를 묻는 물음,
 * 다 쓴 링크. 상자 안에 상자를 만들지 않으므로 바탕도 모서리도 없고, 제목도
 * 하나뿐이라 굵은 줄을 다시 세우지 않는다 — 상태의 제목은 카드의 제목이 되고
 * 여기 남는 것은 조용한 한 줄부터다. 그래서 이 블록은 제목 밑 10(카드의
 * 계단)에서 시작하고, 그 밑 16 에 누를 것이 온다. */
.invite { margin-top: 10px; }
.invite-sub {
  margin: 0;
  font-size: var(--fs-sec); line-height: var(--lh-sec); color: var(--text2);
}
/* 글 밑의 구글 버튼은 16 이고, 10분이 지나 다시 로그인해야 할 때는 그 블록 밑
   같은 간격에 선다. */
.invite .gis-button, .invite + .gis-button { margin-top: 16px; }
/* 가입 확인의 두 버튼. 「확인」이 채움 · 「취소」가 그 밑의 조용한 한 줄이고 둘
   다 카드의 안쪽 폭을 쓴다. 이메일 줄이 없으면(쿠키가 없을 때) 이 자리가 제목
   바로 밑이라 버튼의 숨(20)으로 선다. */
.invite-act { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.invite-act:first-child { margin-top: 10px; }
/* 원칙 §5 — 조용해도 누르는 자리다: 포커스는 앱의 것과 같은 1px primary
   테두리이고(테두리가 아니라 안쪽 그림자라 크기가 변하지 않는다), hover 는 위의
   규칙이 맡는다. */
.login-card .btn-quiet:focus-visible { box-shadow: inset 0 0 0 1px var(--primary); }
/* 확인이 실패했을 때의 한 줄. 코드 물음의 줄과 달리 카드의 마지막 줄이라, 할 말이
   없을 때까지 자리를 잡아 두면 「취소」 밑이 빈 채로 남는다 — 빈 동안은 자리를
   차지하지 않고 카드는 「취소」 밑에서 제 여백으로 끝난다. */
.invite-msg:empty { margin: 0; min-height: 0; }
/* 코드 한 줄 — 필드가 늘어나고 버튼은 제 폭을 지킨다. 폰의 라벨(「시작하기」)은
 * 320 에서도 한 줄이고, 키를 단 긴 라벨(「Empezar (Ctrl↩)」)이 좁게 끌어 놓은
 * 데스크톱 창에서 필드를 코드 열 자리 밑으로 밀면 버튼이 다음 줄로 내려간다 —
 * 코드가 스크롤되는 것보다 낫다. */
.home-code { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 16px; }
.home-code .field {
  flex: 1 1 auto; min-width: 11ch;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.12em;
}
/* 원칙 §4 — 라벨이 제 키를 달고 있으므로(「시작하기 (⌘↩)」 · 손가락 화면에서는
 * 「시작하기」) 폭은 그 말의 길이를 따르고, 가장 짧은 말에서도 6.5em 밑으로는
 * 줄지 않는다(em 이라 텍스트 크기를 따라간다). 맞는 코드를 받으면 라벨이 체크
 * 하나로 바뀌는데, 그때 폭이 따라 줄면 답이 아니라 흔들림으로 읽히므로 바꾸기
 * 직전의 폭을 main.js 가 잠근다. 버튼은 줄어들지 않는다 — 필드가 먼저 줄고, 코드
 * 열 자리가 안 남으면 줄을 바꾼다(위 `.home-code`). */
.home-code .btn {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 6.5em;
}
/* 말할 것이 없어도 자리는 늘 한 줄이다 — 오류가 떠도 카드 높이가 변하지 않는다. */
.home-msg {
  margin: 6px 0 0; min-height: var(--lh-cap);
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
}
.home-msg.bad { color: var(--danger); }
/* 원칙 §4 — 버튼 밑에는 아무것도 없다. 개인정보·약관·문의 링크는 화면의 맨
 * 아래, 페이지 푸터가 서는 자리다(문의는 서버가 채우는 mailto). */
.home-legal {
  margin: 24px auto 0;
  font-size: var(--fs-cap); line-height: var(--lh-cap);
  color: var(--text2);
}
.home-legal a { color: inherit; text-decoration: none; }
.home-legal a:hover { text-decoration: underline; }
.home-legal a:active { opacity: 0.7; }

.btn {
  min-height: 44px;
  padding: 0 14px;
  border: 0;
  border-radius: 12px;
  background: var(--surface2);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-body);
  cursor: pointer;
}
.btn:disabled { opacity: 0.45; cursor: default; }
.btn-primary { background: var(--primary); color: var(--onPrimary); font-weight: 600; }
.btn-quiet { background: transparent; color: var(--primary); }
.btn-wide { width: 100%; }

/* 원칙 §5 — 입력은 한 가지 모양이다: bg 채움, 10 모서리, 1px 헤어라인,
 * 포커스에서 크기가 변하지 않는 1px primary 테두리 (.afld 도 같은 규칙). */
.field, .afld {
  width: 100%;
  min-height: 44px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
.field:focus, .afld:focus { outline: none; border-color: var(--primary); }
.field::placeholder, .afld::placeholder { color: var(--text2); }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  height: 32px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: var(--surface2);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  cursor: pointer;
}
.chip.is-active { background: var(--primary); color: var(--onPrimary); font-weight: 600; }
/* Language chips share one width so the order badges line up (TEST_FEEDBACK). */
.chip.fx { min-width: 104px; }
.chip .bg {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; margin-left: 6px; padding: 0 4px;
  border-radius: 8px; background: var(--selection); color: var(--primary);
  font-size: var(--fs-cap); line-height: 16px; font-weight: 600;
}
.chip.is-active .bg { background: var(--onPrimary); }
.form-error { color: var(--danger); font-size: var(--fs-sec); line-height: var(--lh-sec); }

/* --- shell ----------------------------------------------------------------- */

/* 원칙 §3.2 가상 키보드 규칙 — 껍데기는 뷰포트 높이 하나만 차지하고, 그 안에서
 * 스크롤하는 것은 메시지 목록뿐이다. 안드로이드에서 키보드가 뜨면 dvh 가 줄어
 * 헤더는 위에, 입력 필은 키보드 바로 위에 남는다. */
.shell {
  position: relative; display: flex; flex-direction: column;
  height: 100vh; height: 100dvh;
  overflow: hidden;
}
/* 판들이 사는 상자. 껍데기가 세로 흐름이 되면서 「가로로 늘어선 판」은 이
 * 한 겹 안으로 들어왔다 — 좁은 화면의 절대 배치 판이 기준으로 삼는 것도, 최상단
 * 띠가 밀어 내리는 것도 이 상자다. */
.panes {
  position: relative; display: flex;
  flex: 1 1 auto; min-height: 0;
  overflow: hidden;
}
/* 대화 모델 §6 — 단축키가 비어 있다고 말하는 최상단 띠(사이드패널에서만 선다).
 * 판 위의 형제라 본문이 통째로 제 높이만큼 내려오고, 판 안에 떠 있는 헤더도
 * 입력 필도 같이 내려온다 — top 계산이 없다. 고장이 아니라 알림이라 danger 가
 * 아니라 warn 을 통째로 깐 띠이고, 모서리도 그림자도 없다: 캡션 한 줄에 위아래
 * 8, 밑으로 헤어라인 하나. 왼쪽이 말이고 오른쪽이 두 링크다. */
.band {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 12px;
  padding: 8px 12px;
  background: var(--warn); color: var(--surface);
  font-size: var(--fs-cap); line-height: var(--lh-cap); font-weight: 500;
}
/* 눈에 띄어야 하는 띠다: warn 을 섞지 않고 통째로 깐다 — 다크는 밝은 앰버 위에
 * 짙은 글자, 라이트는 짙은 앰버 위에 종이색 글자(--surface 가 두 테마에서 그
 * 반대색이다). 왼쪽은 말뿐이고(띠 색이 이미 주의를 말한다), 링크는 같은
 * 잉크·같은 굵기에 제 선 글리프(톱니·되돌리기)를 앞세워 구분된다. */
.band .lnk {
  color: inherit; font-weight: inherit;
  display: inline-flex; align-items: center; gap: 4px;
}
.band > span { flex: 1 1 auto; min-width: 0; }
.band .lnk { flex: 0 0 auto; }
.pane {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  background: var(--bg);
  overflow: hidden;
}
/* 원칙 §5 — 판마다 단은 하나다: 채팅 판은 720, 설정·사용법은 다른 페이지와
 * 같은 560(:root 의 기본값). 목록 판은 넓은 화면에서 320 한 열뿐이라 단이
 * 따로 없다. */
.pane-room { --col: var(--col-chat); }
.pane-list { --col: none; }

/* One screen at a time: only the pane the shell is on is on screen. */
@media (max-width: 639px) {
  .pane { position: absolute; inset: 0; display: none; }
  body[data-screen="list"] .pane-list,
  body[data-screen="room"] .pane-room,
  body[data-screen="settings"] .pane-settings,
  body[data-screen="guide"] .pane-guide { display: flex; }

  /* Principles section 5: 200ms ease-out (the reduced-motion rule wins). */
  .pane.is-push { animation: pane-push 0.2s ease-out; }
  .pane.is-pop { animation: pane-pop 0.2s ease-out; }
}
@keyframes pane-push { from { transform: translateX(100%); } to { transform: none; } }
@keyframes pane-pop {
  from { transform: translateX(-26%); opacity: 0.65; }
  to { transform: none; opacity: 1; }
}

/* 원칙 §5 — 사이드바 분기점은 모바일 폭(<640) 여부다. */
@media (min-width: 640px) {
  .pane-list {
    position: relative;
    flex: 0 0 320px;
    width: 320px;
    border-right: 1px solid var(--border);
  }
}
/* The right-hand pane is the room, or the settings screen or the guide in its
 * place (원칙 §4 — 사용법도 설정과 같은 자리에 서는 페이지다). */
body:not([data-screen="settings"]) .pane-settings,
body:not([data-screen="guide"]) .pane-guide,
body[data-screen="settings"] .pane-room,
body[data-screen="guide"] .pane-room { display: none; }

/* --- floating header (원칙 §4 — 텔레그램 상단처럼 떠 있는 세 조각) ------------
 *
 * 경계선도 바탕 띠도 없다. 하단 플로팅 바와 같은 토큰을 쓰는 세 조각이 화면
 * 위에 떠 있고(높이 --pill-h, surface 채움, 1px 헤어라인, 같은 그림자, 좌우
 * 12, 위 12 + safe-area), 메시지 목록은 그 밑으로 지나간다. 목록·설정·사용법도
 * 같은 어휘를 쓴다: [마크 원판] [검색 필] [+ 원판] · [뒤로 원판] [제목 필].
 * 스크롤 영역은 --hbar-h 만큼 위를 비워 두므로 첫 말풍선이 가리지 않는다. */

.hbar {
  position: absolute; z-index: 5;
  /* 바는 그 페이지의 단 안에 선다 (원칙 §5): 판이 단보다 넓으면 단 폭에서
     멈추고 가운데에 서고, 그보다 좁으면 좌우 12 만 남긴 판 폭 그대로다.
     스크롤바 자리(--sbw)는 더하지 않는다 — 떠 있는 것은 판의 폭에 선다. */
  left: 12px; right: 12px;
  max-width: var(--col); margin-inline: auto;
  top: calc(12px + env(safe-area-inset-top));
  display: flex; align-items: center; gap: 8px;
  /* 조각 사이의 틈으로는 밑의 목록이 그대로 스크롤된다. */
  pointer-events: none;
}
.hbar > * { pointer-events: auto; }

/* 원판: 뒤로 · ⋮ · + 는 모두 플로팅 바와 같은 높이의 원이다 (원칙 §4). */
.hd {
  flex: 0 0 var(--pill-h); width: var(--pill-h); height: var(--pill-h);
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 50%;
  background: var(--surface); color: var(--primary);
  box-shadow: 0 3px 14px var(--shade);
  font: inherit; padding: 0; cursor: pointer;
  transition: background 0.12s ease-out, color 0.12s ease-out,
    opacity 0.12s ease-out, transform 0.12s ease-out;
}
.hd.pl .ic { width: 22px; height: 22px; }
.hd.is-on { background: var(--selection); }

/* 제목 필: 가운데, 한 줄 말줄임 (원칙 §4). */
.htt {
  flex: 1 1 auto; min-width: 0; height: var(--pill-h);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 0 14px;
  border: 1px solid var(--border); border-radius: var(--pill-r);
  background: var(--surface);
  box-shadow: 0 3px 14px var(--shade);
}
.htt b {
  max-width: 100%;
  font-size: var(--fs-title); line-height: var(--lh-title); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 원칙 §4 — 목록 헤더의 왼쪽 원판은 제품의 마크다. 누를 것이 아니므로 버튼이
   아니고(탭 순서에도 없다), 손가락에도 포인터에도 답하지 않는다 — hover 는
   `button.hd` 의 것이고 눌림 피드백은 여기서 되돌린다. */
div.hd { cursor: default; }
div.hd:active { opacity: 1; transform: none; }
.hd.hm .ic { width: 22px; height: 22px; }

/* 원칙 §4 — 가운데 조각은 제목 필과 같은 모양이지만 필드로 읽힌다: 돋보기와
   낱말이 왼쪽에 서고 색은 메타 색이다. 누르면 검색 시트가 열린다. */
button.htt.hsr {
  flex-direction: row; align-items: center; justify-content: flex-start; gap: 8px;
  padding: 0 14px; color: var(--text2); cursor: pointer;
  font: inherit; font-size: var(--fs-body); line-height: var(--lh-body);
  text-align: left;
}
.hsr span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- chat list -------------------------------------------------------------- */

.rows {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  background: var(--bg);
  /* 스크롤바 자리는 넘치든 아니든 비워 둔다 — 막대가 생기는 순간 글이 옆으로
     튀지 않고, --sbw 도 그대로 남는다 (원칙 §5). */
  scrollbar-gutter: stable;
  /* 떠 있는 헤더 밑으로 지나가되 첫 행이 가리지 않게 그만큼 비워 둔다 —
     본문은 어느 페이지든 헤더 밑 20 에서 시작한다 (원칙 §4). */
  padding-top: calc(var(--hbar-h) + 20px);
  padding-bottom: calc(var(--pill-h) + 48px + env(safe-area-inset-bottom));
}
/* 원칙 §4 (v0.11) — 경계선도 바탕 띠도 없다: 맨바닥(bg) 위에 6px 세로 간격으로
 * 놓인 12 라운드 블록이고, 쉴 때는 투명하다. 포인터가 올라오면 surface2,
 * 넓은 화면에서 열려 있는 채팅은 selection 으로 채워진다. 좌우 12 는 플로팅
 * 필·바와 같은 여백이다. */
.rows-inner {
  display: flex; flex-direction: column; gap: 6px;
  padding: 0 12px;
}
/* 대화 모델 §6 — 행들 위에 서는 설치 카드 하나(`chatlist.js`). 상자는 홈의 증거
 * 카드 그대로이고(`.demo-card` — surface2 · 모서리 16 · 안 여백 20) 여기서 정하는
 * 것은 자리뿐이다: 좌우는 행과 같은 12, 첫 행까지 12, 위는 스크롤러가 이미 헤더
 * 밑 20 을 비워 두었다. 안은 제목 한 줄 · 문장 한 줄 · 길의 줄 하나이고, 닫기는
 * 오른쪽 위 모서리에 뜬다 — 행의 ✕ 와 같은 상자(44)라 제목이 그만큼 비워 준다. */
.list-card {
  position: relative;
  display: flex; flex-direction: column; gap: 8px;
  margin: 0 12px 12px;
}
.list-card .lc-h {
  margin: 0; padding-right: 32px;
  font-size: var(--fs-body); line-height: var(--lh-body); color: var(--text);
}
.list-card .flow-cap { margin: 0; }
/* 길 둘은 한 줄에 서고, 좁아지면 같은 순서로 접힌다. */
.list-card .flow-line { display: flex; flex-wrap: wrap; gap: 4px 16px; }
.list-card .rx { position: absolute; top: 6px; right: 6px; margin: 0; }
.r {
  display: flex; align-items: center; gap: 12px;
  width: 100%; height: var(--row-h); padding: 0 12px;
  border: 0; border-radius: 12px;
  background: transparent; color: inherit; font: inherit;
  text-align: left; cursor: pointer;
}
/* 원칙 §4 — 행의 메뉴가 열려 있는 동안은 포인터가 올라온 것과 같은 표면색으로
   눌린 채 남는다. 열려 있는 채팅은 그대로 selection 이 이긴다. */
.r.is-on { background: var(--surface2); }
.r.is-active { background: var(--selection); }
/* 원판의 바탕도 selection 이라, 고른 행 위에서는 글자 없는 원판(아직 말이 없는
   채팅)이 사라진다 — 그 행에서만 강조색을 한 단 더 섞는다. */
.r.is-active .disc { background: color-mix(in srgb, var(--primary) 26%, var(--selection)); }
/* 원칙 §4 — 행 맨 앞은 그 채팅의 마지막 발화자 원판이다. 말풍선 옆에서 첫 줄에
 * 중심을 맞추던 위 여백 6 은 여기 없다: 행 높이 한가운데에 선다. */
.r .av { margin-top: 0; }
.rc { flex: 1 1 auto; min-width: 0; }
.rc b { display: block; font-size: var(--fs-lt); line-height: var(--lh-lt); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc span { display: block; font-size: var(--fs-ls); line-height: var(--lh-ls); color: var(--text2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 원칙 §4 · 대화 모델 §6 — 행의 오른쪽은 메타 열이다: 제목 줄에 시각, 그 밑에
 * 확장이 채팅을 연 페이지의 사이트(등록 도메인). 둘 다 캡션 크기·메타 색으로
 * 오른쪽에 맞춰 서고 행 폭의 35% 까지만 쓴다 — 나머지는 제목과 미리보기의
 * 것이다. 줄 높이를 왼쪽 두 줄과 같게(--lh-lt / --lh-ls) 두어 두 열이 같은
 * 리듬으로 서고, .r 의 세로 가운데 정렬이 둘을 맞춘다. 시각도 사이트도 없는
 * 행(검색 결과)에는 이 열이 아예 없다. */
.rm {
  flex: none; max-width: 35%; min-width: 0;
  /* 두 줄 높이를 언제나 차지한다 — 사이트가 없는 행에서도 시각은 제목 줄에 선다. */
  height: calc(var(--lh-lt) + var(--lh-ls));
  display: flex; flex-direction: column; align-items: flex-end;
  text-align: right;
}
.rm .rt {
  font-size: var(--fs-cap); line-height: var(--lh-lt);
  color: var(--text2); white-space: nowrap;
}
.rm .rp {
  max-width: 100%;
  font-size: var(--fs-cap); line-height: var(--lh-ls);
  color: var(--text2); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
/* 원칙 §4 — 빈 화면은 안내 문구 + 명시적 "새 채팅" 버튼. 바닥을 탭해서
 * 채팅이 만들어지는 일은 없다. 방(.list)에서는 남는 높이 한가운데에 선다. */
.empty {
  flex: 1 1 auto; width: 100%;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 8px;
  padding: 40px 16px; text-align: center; color: var(--text2);
  font-size: var(--fs-body); line-height: var(--lh-body);
}
.tbtn {
  min-height: 44px; padding: 0 14px;
  border: 0; border-radius: 12px; background: transparent;
  color: var(--primary); font: inherit;
  font-size: var(--fs-body); font-weight: 600; cursor: pointer;
}

/* 원칙 §4 — 하단 플로팅 바의 높이·여백·그림자는 접힌 입력 필과 같다(--pill-h).
 * 탭은 셋이고 그 옆에는 아무것도 없다 — 검색은 목록 헤더로 올라갔다. */
.fbar {
  /* 스크롤바 자리(--sbw)와 무관하게 좌우 12 — 떠 있는 바는 목록의 폭이 아니라
     판의 폭에 선다. 언어가 바뀌어도 바의 폭은 같다. */
  position: absolute; left: 12px; right: 12px;
  bottom: calc(16px + env(safe-area-inset-bottom));
  display: flex; gap: 8px; align-items: center; z-index: 4;
}
.fb {
  flex: 1 1 auto; min-width: 0; height: var(--pill-h); border-radius: var(--pill-r);
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: 0 3px 14px var(--shade);
  /* 세 탭이 바를 똑같이 셋으로 나눈다. 칸의 폭이 글자와 무관하므로 고른 탭이
     굵어져도 아무것도 밀리지 않는다. */
  display: flex; align-items: stretch; padding: 0;
  transition: height 0.2s ease-out, border-radius 0.2s ease-out;
}
/* 탭은 바 높이를 그대로 쓰고(터치 타깃), 활성 표시만 --pill-pad 만큼 안으로
   들어온 채움으로 그린다 — 입력 필 안의 언어 원판과 같은 안쪽 여백이다. */
.ft {
  position: relative; z-index: 0;
  flex: 1 1 0; min-width: 0; padding: 0 8px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--pill-r); background: transparent;
  /* 글자만 선다: 아이콘을 두면 긴 언어(튀르키예어·러시아어)가 320 폭에 들어가지
     않고, 세 단어는 그 자체로 읽힌다. */
  font: inherit; font-size: var(--fs-ls); line-height: var(--lh-ls); color: var(--text2); cursor: pointer;
  white-space: nowrap;
}
.ft.on { color: var(--primary); font-weight: 600; }
.ft.on::before {
  content: ""; position: absolute; z-index: -1; inset: var(--pill-pad);
  border-radius: 999px; background: var(--surface2);
}
/* 라벨은 접힐 때 사라지는 쪽이므로 아이콘과 형제인 자기 요소를 가진다. */
.ft em { font-style: normal; overflow: hidden; text-overflow: ellipsis; }
/* 스크롤이 내려가면 0.8 배로 접힌다 — 글자는 남고 작아진다. */
.fbar.is-mini .fb {
  height: calc(var(--pill-h) * 0.8); border-radius: calc(var(--pill-r) * 0.8);
}
.fbar.is-mini .ft { font-size: var(--fs-cap); line-height: var(--lh-cap); }
.fbar.is-mini .ft.on::before { inset: calc(var(--pill-pad) * 0.8); }

/* --- message list ----------------------------------------------------------- */

/* 원칙 §3.2 — 껍데기 안에서 스크롤하는 것은 이 목록 하나뿐이다. 떠 있는 헤더와
 * 입력 필 밑으로 지나가므로 위아래로 그만큼 자리를 비워 둔다. 아래 여백은
 * 떠 있는 입력창의 실제 높이(--cp-h, room.js 가 ResizeObserver 로 넣는다)에서
 * 나오므로 답장 스트립이 서거나 필이 자라도 마지막 말풍선이 가려지지 않는다.
 * 값이 아직 없을 때는 접힌 필 하나(--pill-h)로 친다. */
.list {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  background: var(--bg);
  /* 스크롤바 자리는 넘치든 아니든 비워 둔다 — 막대가 생기는 순간 글이 옆으로
     튀지 않고, --sbw 도 그대로 남는다 (원칙 §5). */
  scrollbar-gutter: stable;
  padding: calc(var(--hbar-h) + 20px) 12px
    calc(var(--cp-h, var(--pill-h)) + 28px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 12px;
  /* 바탕과 스크롤은 판 전체가 그대로 쓰고, 글만 단 안에 선다 (원칙 §5). */
  align-items: center;
}
/* 말풍선 행만 단 폭을 받는다 — 안에서 내 말풍선이 오른쪽으로 붙는 자리가
   이 행이다. 날짜 캡션과 시스템 줄은 제 폭으로 가운데에 서므로 단의 가운데와
   같은 자리이고, 빈 화면은 판을 그대로 쓴다. */
.list > .m { width: 100%; max-width: var(--col); }
/* iOS 에서 목록은 키보드 뒤까지 그대로 있으므로, 마지막 말풍선이 키보드 위로
 * 올라올 만큼 아래를 더 비워 둔다. 아래쪽 여백만 늘어나므로 이미 보이는 글은
 * 한 픽셀도 움직이지 않는다 (원칙 §3.2). */
body.kb-open .list {
  padding-bottom: calc(var(--cp-h, var(--pill-h)) + 28px + var(--kb, 0px));
}
/* 원칙 §5 — 채팅이 없으면 입력 필도 없으므로 그만큼의 자리를 비워 두지 않는다. */
.list.nc { padding-bottom: 16px; }
.day { align-self: center; font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2); margin: 2px 0; }
.m { display: flex; gap: 8px; align-items: flex-start; }
.m.me { justify-content: flex-end; }
/* 말풍선 사이는 12, 발화자가 바뀌면 16 (원칙 §5). */
.m + .m { margin-top: 4px; }
.m.sp-me + .m.sp-me, .m.sp-op + .m.sp-op, .m.sp-st + .m.sp-st { margin-top: 0; }
/* F-H3 — a failed send does not just vanish: the pending dots and (when the
   turn never landed) the optimistic bubble sink and fade together before the
   system line takes their place. `prefers-reduced-motion`'s global override
   further down drops the transition, so the class just jumps straight to its
   end state — room.js's fallback timer is what removes the node then. */
.is-leaving { opacity: 0; transform: translateY(4px); transition: opacity 0.3s ease-out, transform 0.3s ease-out; }
.col {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  max-width: min(80%, 720px); min-width: 0;
}
/* 원칙 §5 — 말풍선과 그 밑 메타 줄 사이는 10 이라 액션 글리프가 말풍선에
   붙어 보이지 않는다. 메타 줄끼리(남은 말 · 맥락 힌트)는 3 그대로다. */
.col > .bw + .meta { margin-top: 7px; }
/* 원칙 §5 — **단의 폭은 말풍선과 그 바로 밑 메타 줄의 것이다.** 그 밑에 서는
   조용한 줄들(판정 · 남은 말 · 같은 이름 · 넣지 못함 · 규칙으로 기억했어요)은 긴
   문장이라, 폭을 재는 자리에 그대로 서면 단을 80% 까지 밀어 메타 줄의 글리프가
   말풍선 오른쪽 끝에서 떨어져 나간다. 그래서 잴 때는 0 으로 서고(단의 폭에 아무
   말도 보태지 않는다) 정해진 폭을 100% 로 받아 그 안에서 접힌다. 짧은 말풍선이
   메타 줄만큼은 넓어지는 것은 그대로다 — 그 줄은 여기서 빠진다. */
.col > .bw + .meta ~ .meta,
.col > .bw + .meta ~ .cap { width: 0; min-width: 100%; }
/* 원칙 §3.8 — 그중 판정·남은 말의 줄은 글이 흐르는 줄이라 메타 줄과 달리 flex 가
   아니다: 크기·색은 메타의 것이고, 나가는 길(「바로잡기 · 번역 안 함」)은 마지막
   줄의 글 끝에 한 칸 띄어 이어 선다. 줄이 접히면 링크는 낱말처럼 통째로 다음 줄로
   넘어간다 — 글자 사이에서 잘리지 않는다. */
.meta.ql { display: block; }
.meta.ql .lnk { display: inline-block; white-space: nowrap; }
/* 원칙 §3.8 — 판정 줄 밑의 참고 한 줄: 등록부가 이 사람의 언어로 읽지 못해 버린
   옮긴 말 그대로다. 같은 가족의 한 걸음 옅은 색이고 그 글은 누를 것이 아니므로
   hover 도 눌림도 없다(글은 고를 수 있다). 마지막 줄이면 나가는 길이 여느 줄처럼
   그 끝에 이어 서고, 누를 수 있는 것은 그 링크뿐이다. */
.meta.ql.qa { color: color-mix(in srgb, var(--text2) 70%, var(--bg)); }
/* 원칙 §2 — 상대의 말풍선 위 한 줄은 그 글을 쓴 사람의 이름이다: 1:1 채팅에서도
   언제나 서고, 길면 말줄임 없이 접힌다. 누를 것이 아니므로 hover 도 없다 —
   「누가 썼나」에 답하는 것은 원판이다. 말풍선 글자와 같은 선에 서고(짧은 한 줄이라
   안여백을 나눠 써도 잃는 폭이 없다), `.col` 의 gap 3 에 1 을 더해 말풍선과 4 를
   띄운다(메타 줄 간격은 그대로다). */
.col > .nm {
  max-width: 100%; margin-bottom: 1px; padding-left: var(--bpad-x);
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
  overflow-wrap: anywhere;
}
.m.me .col { align-items: flex-end; max-width: min(78%, 720px); }
.b {
  border-radius: 14px; padding: var(--bpad-y) var(--bpad-x);
  font-size: var(--fs-body); line-height: var(--lh-body);
  overflow-wrap: anywhere; white-space: pre-wrap;
}
/* 원칙 §2 — 말풍선 하나에 탭 하나: 내 것과 어시스턴트는 복사, 상대의 것은
   답하기. 셋 다 눌리는 것이므로 셋 다 손 모양이다. */
.b { cursor: pointer; }
.b.me { background: var(--bubbleMe); color: var(--onBubbleMe); }
.b.st { background: var(--surface); }
/* 앱이 스스로 연 첫 채팅의 안내 말풍선 (`docs/VOICE_MODEL.md` §7). 어시스턴트의
   말풍선과 같은 종이·같은 마크이되 메시지가 아니다: 누를 것도 메뉴도 없으므로
   손 모양을 쓰지 않는다. 줄마다 한 문장이고, 문장 안의 글리프는 글자와 같은
   줄에 선다(톤 줄의 아이콘과 같은 규칙). */
/* 도움말 말풍선 — 어시스턴트의 판이되 한 단 작고 한 단 조용하다. 첫 줄은 이끄는
   말, 나머지는 점으로 시작하는 목록. 단축키는 인라인 코드처럼 선다. */
.b.gd {
  cursor: default; white-space: normal;
  font-size: var(--fs-sec); line-height: var(--lh-sec); color: var(--text2);
}
.b.gd .gl + .gl { margin-top: 6px; }
.b.gd .gl.li { padding-left: 1em; }
.b.gd .gl.li::before { content: '·'; display: inline-block; width: 1em; margin-left: -1em; }
.b.gd .ic { display: inline-block; vertical-align: -0.25em; width: 1.15em; height: 1.15em; }
.b.gd .kk {
  padding: 0 5px; border-radius: 5px;
  background: color-mix(in srgb, var(--text2) 18%, transparent);
  color: var(--text); font: inherit; font-size: 0.92em; white-space: nowrap;
}
.mk.hlp { color: var(--text2); }
.b.op { background: var(--surface2); }
.b.pr { box-shadow: inset 0 0 0 999px rgba(127, 127, 127, 0.10); }
/* 원칙 §5 — 피드백은 즉시 들어오고, 빠질 때만 400ms ease-out.
   The tint and the press both land on the frame they are asked for: the
   transition lives on the resting state, so only the way back is eased. */
.b { transition: background 0.4s ease-out, box-shadow 0.4s ease-out; }
.b.fl { background: var(--selection); }
/* 내 말풍선은 바탕이 selection 과 거의 같은 색이라(두 테마 모두) 같은 플래시로는
 * 아무 일도 없어 보인다. primary 를 섞어 한 톤 밝게(다크)·짙게(라이트) 튄다 — 내
 * 말풍선에는 글리프가 없어 이 플래시가 복사의 유일한 대답이다 (원칙 §2). */
.b.me.fl { background: color-mix(in srgb, var(--primary) 35%, var(--bubbleMe)); }
.b.fl, .b.pr { transition: none; }
.tf .ic { display: inline-block; vertical-align: -0.25em; margin-right: 5px; }
.tf {
  margin: 8px calc(-1 * var(--bpad-x)) calc(-1 * var(--bpad-y));
  padding: 8px var(--bpad-x) var(--bpad-y);
  border-top: 1px solid var(--border);
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
  white-space: normal;
}
/* 원칙 §3.7 — 기다리는 동안 점 셋(또는 흘러든 글자) 밑에 서는 한 줄. 톤 메모와
   같은 캡션 크기·색이되 헤어라인은 없다 — 기다림은 말풍선을 나누지 않는다. 답이
   서면 말풍선이 통째로 바뀌므로 이 줄은 남지 않는다. */
.b.st .wn {
  margin-top: 6px;
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
  white-space: normal;
}
/* 원칙 §2 — 마크는 두 글자다(한중일은 앞 두 자, 라틴은 앞 두 낱말의 이니셜).
   두 CJK 글자가 들어가야 하므로 지름은 32 이고 글자는 캡션 크기다. 위 여백 6 은
   원판의 중심을 말풍선 첫 줄의 중심에 맞춘다. */
.mk, .av {
  flex: 0 0 32px; width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; font-size: var(--fs-cap); line-height: 1;
  font-weight: 600; margin-top: 6px;
}
.mk { background: var(--selection); color: var(--primary); }
.av { background: var(--ctint); color: var(--counterpart); }
/* 글자가 아니라 아이콘이 서는 원판(어시스턴트의 제품 마크)은 글자 크기를
   따르지 않고 원판에 맞춘다. */
.mk .ic, .av .ic { width: 20px; height: 20px; }
/* 대화 모델 §2 — 인바운드 말풍선의 마크는 버튼이다: 같은 원판에 눌림 피드백만
   더한다. `.sm` 은 답장 스트립이 쓰는 작은 판. */
button.av { padding: 0; border: 0; font: inherit; font-size: var(--fs-cap); font-weight: 600; cursor: pointer; }
.av.sm {
  flex: 0 0 24px; width: 24px; height: 24px; margin-top: 0;
  font-size: var(--fs-cap);
}
.av.sm .ic { width: 15px; height: 15px; }
/* "현재 상대" 시트 맨 위의 큰 원판 — 이름을 묻기 전에 누구인지부터 말한다.
   두 글자가 서므로 제목 크기다. */
.av.lg {
  flex: 0 0 56px; width: 56px; height: 56px; margin-top: 0;
  font-size: var(--fs-h1);
}
.av.lg.two { font-size: var(--fs-title); }
.av.lg .ic { width: 26px; height: 26px; }
/* 「새 상대」 행의 원판 — 두 글자가 아니라 `+` 하나라 캡션 크기로는 작다. */
.av.pls .ic { width: 18px; height: 18px; }
/* 원칙 §5 — 말풍선 밑 줄들은 말풍선의 양 끝에 붙는다: 왼쪽 글자는 왼쪽 가장자리,
   오른쪽 끝 글리프는 오른쪽 가장자리다. 320 폭에서 좌우 안여백 14 를 또 비우면 줄이
   너무 좁아진다. */
.meta {
  display: flex; align-items: center; flex-wrap: wrap; width: 100%;
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
  overflow-wrap: anywhere;
}
.lnk {
  background: none; border: 0; padding: 0; color: var(--primary); font: inherit;
  font-size: var(--fs-cap); line-height: var(--lh-cap); cursor: pointer;
}
.meta .sep { padding: 0 5px; }
/* 원칙 §2 — 액션은 어시스턴트의 것이다: 도구는 메타 줄 오른쪽 끝의 작은
 * 글리프뿐이고 사람 말풍선은 맨몸이다. 띠도, 버튼 바탕도 없다 — 메타와 같은
 * 크기·색으로 서 있다가 hover·눌림에서만 본문 색으로 올라온다.
 * 손가락이 닿는 상자는 40 이고 음수 마진이 그만큼 되당기므로, 줄 높이도 자리
 * 폭도 16px 글리프 그대로다 (원칙 §5 — 터치 타깃): 마지막 글리프의 오른쪽 끝이
 * 곧 말풍선의 오른쪽 가장자리다. 글리프 사이 12 는 gap 이 준다. */
.mact { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; margin-left: auto; padding-left: 14px; }
.ma {
  flex: 0 0 auto; width: 40px; height: 40px; padding: 0; margin: -12px;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: none; color: var(--text2); opacity: 0.8;
  cursor: pointer;
}
/* 눌림의 불투명도·스케일은 위의 공용 규칙에서 오고, 색만 여기서 올라온다. */
.ma { transition: opacity 0.12s ease-out, color 0.12s ease-out, transform 0.12s ease-out; }
.ma:active { color: var(--text); }
/* 원칙 §3.11 — 액션은 그 자리에서 답한다: 잠시 원판이 선다. 복사는 ok 색 체크,
   답하기는 primary 색 화살표이고 그 밖에는 같다. 노드를 갈아 끼우지 않는다 — 원판과
   원래 글리프가 한 칸에 겹쳐 서고 호스트의 `is-copied`·`is-replied` 만 오가므로
   상자 크기는 변하지 않는다. 글리프가 빠지는 동안 원판이 자라 오르고, 돌아올 때는
   원판이 조용히 사라지기만 하며 크기는 보이지 않는 뒤에서 되돌아간다: 말풍선 플래시와
   같은 규칙이다 — 들어올 때 말하고 빠질 때는 말하지 않는다 (원칙 §5).
   `prefers-reduced-motion` 은 아래 전역 규칙이 전환만 걷어내고, 바뀐 모양은 그대로
   남는다. 원판이 서는 자리는 셋이다 — 말풍선의 메타 줄, 행 끝(초대 코드·링크,
   사용법의 주소), 그리고 글리프가 없는 원본 말풍선의 ⋮ 자리(아래).
   두 클래스는 이 답 말고는 쓰이지 않는다. */
.ma[data-act="copy"], .irow .cpy { display: inline-grid; place-items: center; }
.ma[data-act="copy"] > .ic, .irow .cpy > .ic { grid-area: 1 / 1; }
.ma[data-act="copy"] > .ic:not(.stamp),
.irow .cpy > .ic:not(.stamp) { transition: opacity 0.16s ease-out; }
.is-copied > .ic:not(.stamp) { opacity: 0; }
/* 원판은 제 색을 지닌다 — 복사는 ok, 답하기는 primary 다. 호스트의 색은 건드리지
   않으므로 사라지는 복사 글리프가 물들지 않는다. 클릭은 언제나 호스트가 받는다.
   자리는 `--sx`·`--sy` 하나로만 다르다: 글리프 위에 겹쳐 서면 0 이고, 말풍선 밖
   ⋮ 자리에 서면 아래에서 그 값을 받는다. */
.ic.stamp {
  opacity: 0; transform: translate(var(--sx, 0), var(--sy, 0)) scale(0.7);
  transform-origin: center; pointer-events: none;
  transition: opacity 0.2s ease-out, transform 0s linear 0.2s;
}
.ic.stamp.st-cp { color: var(--ok); }
.ic.stamp.st-rp { color: var(--primary); }
.is-copied > .ic.stamp.st-cp, .is-replied > .ic.stamp.st-rp {
  opacity: 1; transform: translate(var(--sx, 0), var(--sy, 0));
  transition: opacity 0.2s ease-out, transform 0.2s ease-out;
}
.ma.is-copied { opacity: 1; }
/* 원칙 §2 — 메뉴 진입로는 홀드(터치)와, 포인터 기기에서 말풍선 우측(내 말풍선은
 * 좌측) 바깥에 나타나는 ⋮ 다. 메타 줄에는 없다. 터치 기기에는 아예 없다. */
.bw { position: relative; display: flex; max-width: 100%; }
.bdot { display: none; }
@media (hover: hover) and (pointer: fine) {
  /* ⋮ 가 설 자리를 미리 비워 둔다 — 말풍선 폭이 그만큼 줄고 가로 스크롤은 없다. */
  .m { padding-right: 36px; }
  .m.me { padding-right: 0; padding-left: 36px; }
  .bdot {
    position: absolute; top: 50%; right: -36px; margin-top: -16px;
    width: 32px; height: 32px;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%; background: transparent;
    color: var(--text2); cursor: pointer;
    opacity: 0; pointer-events: none;
    transition: opacity 0.12s ease-out, background 0.12s ease-out;
  }
  .m.me .bdot { right: auto; left: -36px; }
  .m:hover .bdot, .bdot:focus-visible, .m.is-menu .bdot {
    opacity: 1; pointer-events: auto;
  }
  .bdot:hover { background: var(--surface2); color: var(--text); }
  .m.is-menu .bdot { background: var(--selection); color: var(--primary); }
  /* 원판이 선 동안에는 ⋮ 가 비켜선다 — 둘의 자리가 같다 (원칙 §3.11). */
  .bw.is-copied .bdot, .bw.is-replied .bdot { opacity: 0; pointer-events: none; }
}
/* 원칙 §2 · §3.11 — 원본 말풍선에는 글리프가 없으므로 복사도 답하기도 ⋮ 의 자리에
 * 답한다: 말풍선 바깥, ⋮ 원판의 정중앙(-36 + 32/2 = 20)이다 — 내 말풍선은 왼쪽,
 * 상대의 말풍선은 오른쪽. 서는 것은 원판 크기의 버튼이 아니라 글리프 하나이고,
 * 크기는 늘 그렇듯 제가 선 줄의 글자 크기를 따른다(원칙 §5) — ⋮ 글리프와 같은 20 이다.
 * 크로스페이드는 위의 공용 규칙이 하고 여기서는 자리만 정한다. 포인터 기기에는 ⋮ 가
 * 설 36 의 여백이 이미 비어 있고, 터치 기기에는 그 여백이 없어도 말풍선 바깥은
 * 비어 있다(단은 80% 에서 멈춘다). */
.m .bw > .ic.stamp { position: absolute; top: 50%; --sy: -50%; }
.m.me .bw > .ic.stamp { left: -20px; --sx: -50%; }
.m:not(.me) .bw > .ic.stamp { right: -20px; --sx: 50%; }
.dots { display: inline-flex; gap: 3px; align-items: center; padding-left: 2px; vertical-align: 2px; }
.dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--text2); animation: bl 1.2s linear infinite; }
.dots i:nth-child(2) { animation-delay: 0.16s; }
.dots i:nth-child(3) { animation-delay: 0.32s; }
@keyframes bl { 0%, 60%, 100% { opacity: 0.3 } 30% { opacity: 1 } }
.cap { font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2); }

/* 원칙 §5 — 화자 없는 시스템 문구. 말풍선이 아니라 채팅 바닥의 가운데 정렬
 * 캡션이다: 아이콘도, 아바타도, 표면도 없다. 저장되지 않으므로 채팅을 바꾸면
 * 사라진다. "다시 시도" 는 그 줄 안의 링크(.lnk)다. */
.sys {
  align-self: center; max-width: min(90%, 520px);
  margin: 8px 0; padding: 0 8px;
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  text-align: center;
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
  overflow-wrap: anywhere;
  transition: opacity 0.2s ease-out;
}
.sys .sep { padding: 0 5px; }
/* spec §7 — 다 쓴 한도 옆의 주소는 같은 줄의 「다시 시도」와 같은 링크다: <a> 가
   들고 오는 기본 밑줄을 지워 hover 의 밑줄이 다시 피드백이 되게 한다. 320 폭에서
   긴 주소는 가운데 정렬을 깨는 대신 줄 안에서 끊는다. */
.sys .lnk { text-decoration: none; overflow-wrap: anywhere; }
/* 시스템 줄 안의 둘째 줄 — 사실(창·시각) 밑에 상황과 길(베타·연락)이 제 줄로 선다. */
.sys .sys-row { flex-basis: 100%; }
/* F-H3 — the line only ever lands once the failed bubbles above it are gone
   (room.js), so it gets its own brief fade-in instead of just appearing. */
.sys.is-entering { opacity: 0; }
/* F-M6 (원칙 §2) — 터치 화면에서 말풍선을 길게 누르는 것은 "메뉴"이지 "선택"이
 * 아니다. iOS 의 콜아웃과 텍스트 선택이 그 위에 겹치지 않도록 거친 포인터에서만
 * 끈다 — 복사는 메뉴의 첫 항목이고 still 말풍선은 탭만 해도 복사된다. 마우스가
 * 있는 화면에서는 선택이 그대로 살아 있다. 홀드 중에만 클래스로 켜고 끄면
 * 스타일 재계산이 스크롤 컨테이너를 다시 앵커링해 홀드 자신이 취소된다.
 * 채팅 목록 행도 같은 이유로 같이 끈다 (원칙 §4 — 행의 홀드는 그 채팅의 메뉴다). */
@media (pointer: coarse) {
  .m .b, .r {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
}
/* F-L8 — 터치 기기의 말풍선은 탭 정지점이다: 포커스 링은 다른 곳과 같은 1px. */

/* --- composer (원칙 §3.2 — 하단에 떠 있는 둥근 입력 필 하나) ------------------- */

/* 입력창은 방 위에 떠 있는 필 하나다. 하단 박스도, 위를 자르는 헤어라인도 없다.
 * 여백·그림자·높이는 목록의 플로팅 바와 같은 변수(--pill-h)에서 나온다:
 * 좌우 12, 아래 16(+safe-area). 보내기 원은 v0.9 에서 필 안으로 들어와,
 * 필의 오른쪽 끝 반원과 같은 중심에 선다 — 필 바깥에는 아무것도 없다.
 * 키보드가 떠도(iOS) --kb 만큼 올라와 키보드 바로 위에 남는다 (원칙 §3.2). */
.cp {
  position: absolute; left: 12px; right: calc(12px + var(--sbw, 0px));
  /* 헤더·목록과 같은 단 안이다 (원칙 §5) — 세 조각의 좌우 선이 같다. */
  max-width: var(--col); margin-inline: auto;
  bottom: calc(16px + env(safe-area-inset-bottom) + var(--kb, 0px));
  z-index: 4;
}
/* 키보드가 가린 만큼 올라온 뒤에는 홈 인디케이터 자리를 또 비워 둘 이유가 없다. */
body.kb-open .cp { bottom: calc(16px + var(--kb, 0px)); }
.pill {
  position: relative;
  display: flex; align-items: flex-end; gap: 8px;
  min-height: var(--pill-h);
  padding: var(--pill-pad);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--pill-r);
  box-shadow: 0 3px 14px var(--shade);
}
/* 원칙 §5 — 포커스는 크기가 변하지 않는 1px primary 테두리. 필 자체가 그 필드다. */
.pill:focus-within { border-color: var(--primary); }

/* 언어 원판: 32 원, surface2, 2자 코드 13 semibold, 캐럿 없음 (원칙 §3.2).
 * --pill-pad 덕분에 접힌 필에서 원판의 중심이 필의 중심선(--pill-r)에 온다. */
.ldisc {
  flex: 0 0 32px; width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: var(--surface2); color: var(--text);
  font: inherit; font-size: 13px; line-height: 1; font-weight: 600;
  letter-spacing: 0.02em; cursor: pointer;
  transition: background 0.12s ease-out, color 0.12s ease-out,
    opacity 0.12s ease-out, transform 0.12s ease-out;
}
.ldisc.is-on { background: var(--selection); color: var(--primary); }

.fldwrap { position: relative; flex: 1 1 auto; min-width: 0; }
/* 필 안의 텍스트에는 자기 배경도 테두리도 없다 — 필이 곧 필드다.
 * 한 줄일 때 32(원판과 같은 높이)로 서고, 여섯 줄까지 자란 뒤 안에서 스크롤한다. */
textarea.fld {
  display: block; width: 100%;
  min-height: 32px;
  max-height: calc(var(--lh-body) * 5 + 32px);
  /* 오른쪽으로 보내기 원(36) + 8 만큼을 비워 둔다. 우상단 펼치기 글리프도 그
   * 폭 안에 들어오므로 글자는 어느 쪽에도 닿지 않는다. */
  padding: calc((32px - var(--lh-body)) / 2) 44px calc((32px - var(--lh-body)) / 2) 0;
  background: transparent; border: 0;
  color: var(--text);
  font: inherit; font-size: var(--fs-body); line-height: var(--lh-body);
  resize: none; overflow-y: hidden;
}
textarea.fld::placeholder { color: var(--text2); }
textarea.fld:focus-visible { outline: none; }
textarea.fld.gr { overflow-y: auto; }
.snd {
  position: relative;
  flex: 0 0 var(--snd-h); width: var(--snd-h); height: var(--snd-h);
  border: 0; border-radius: 50%; background: var(--primary); cursor: pointer;
}
/* 원칙 §3.2 (v0.9) — 필 안 오른쪽 끝. 지름이 36 으로 언어 원판(32)보다 크므로
 * 같은 --pill-pad 를 쓰면 중심선이 2px 위로 어긋난다(v0.10 에서 잡음):
 * --snd-pad 는 필의 안쪽 높이에서 36 을 빼고 반으로 나눈 값이라, 접힌 필에서
 * 보내기 원의 중심이 언어 원판과 같은 --pill-r 높이에 정확히 온다. 가로도 같은
 * 값이므로 필 오른쪽 끝의 반원과 동심원이 된다. 필이 자라도 우하단에 남는다. */
.pill .snd { position: absolute; right: var(--snd-pad); bottom: var(--snd-pad); }
.snd:disabled { opacity: 0.4; cursor: default; }
.snd::before {
  content: ""; position: absolute; left: 50%; top: 11px; width: 9px; height: 9px; margin-left: -4.5px;
  border-left: 2px solid var(--onPrimary); border-top: 2px solid var(--onPrimary); transform: rotate(45deg);
}
.snd::after {
  content: ""; position: absolute; left: 50%; top: 12px; margin-left: -1px; width: 2px; height: 14px;
  border-radius: 1px; background: var(--onPrimary);
}
/* 원칙 §5 — 입력창은 잠기지 않는다: 응답을 기다리는 동안 바뀌는 것은 이 원
 * 하나뿐이다. 화살표가 고리로 바뀌어 돌 뿐, 원은 그대로 눌린다(두 번째 전송은
 * 그냥 두 번째 요청이다). 모션을 줄이는 설정에서는 위 규칙이 회전을 끄므로
 * 고리가 조용히 서 있는 상태로 남는다. */
.snd.is-busy::before { content: none; }
.snd.is-busy::after {
  left: 50%; top: 50%; width: 16px; height: 16px; margin: -8px 0 0 -8px;
  background: none; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--onPrimary) 30%, transparent);
  border-top-color: var(--onPrimary);
  animation: snd-spin 0.7s linear infinite;
}
@keyframes snd-spin { to { transform: rotate(360deg); } }

/* S-M3 의 화면 쪽 짝 — 서버가 받는 길이의 90% 를 넘으면 필드 곁에 조용히
 * 서고, 상한에서 warn 으로 바뀐다. 숫자는 tabular. */
.lcnt {
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
  text-align: right; padding: 0 12px 4px;
  font-variant-numeric: tabular-nums;
}
.lcnt.warn { color: var(--warn); }
.sheet .lcnt, .qs .lcnt, .arow .lcnt, .onb-in .lcnt { padding: 2px 2px 0; }
.editor .lcnt { flex: 0 0 auto; padding: 0 16px 4px; }
/* spec §7 의 사용량 힌트 — 길이 카운터와 같은 자리·같은 크기·같은 색이다.
 * 필드 곁의 조용한 줄은 언제나 하나뿐이라 카운터가 서면 이 줄은 물러선다
 * (원칙 §5). 숫자가 아니라 문장이므로 왼쪽으로 붙는 것만 다르다. */
.lcnt.usg { text-align: left; }
/* 세 줄 이상일 때만 필의 우상단에 나타나는 펼치기 글리프 (원칙 §3.2, v0.10).
 * 20 아이콘이 필의 위·오른쪽 가장자리에서 10 씩 떨어져 서고(보내기 원과는 무관),
 * 그 둘레의 32 상자가 터치 타깃이다: 32 상자 안에서 아이콘은 6 안쪽에 있으므로
 * 상자는 필의 패딩 박스에서 10 − 1(테두리) − 6 = 3 만큼 들어온다. */
.pill .exp {
  position: absolute; right: 3px; top: 3px; width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--text2); cursor: pointer;
}

/* 답장 스트립 (대화 모델 §3, §6) — 입력 필 바로 위에 서고 표면·헤어라인·
 * 그림자가 같아 입력창의 일부로 읽힌다: 답하기 아이콘 · 대상의 작은 마크 ·
 * 한 줄 말줄임 미리보기 · ✕. 인바운드가 없는 채팅에서는 아예 없다.
 * 글자는 본문보다 한 단계 작다(--fs-sec). */
.rst {
  display: flex; align-items: center; gap: 8px;
  min-height: calc(var(--lh-sec) + 12px);
  margin: 0 0 6px; padding: 0 6px 0 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--pill-r);
  box-shadow: 0 3px 14px var(--shade);
  font-size: var(--fs-sec); line-height: var(--lh-sec);
}
.rst > .ic { color: var(--text2); }
.rsx {
  flex: 1 1 auto; min-width: 0; color: var(--text2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 보이는 크기는 28 이고 터치 타깃은 그 둘레로 44 까지 넓힌다 (원칙 §5). */
.rsc .ic { width: 16px; height: 16px; }
.rsc {
  position: relative;
  flex: 0 0 28px; width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--text2); font: inherit; font-size: var(--fs-cap); cursor: pointer;
}
.rsc::after { content: ""; position: absolute; inset: -8px; }
/* 편집기 안에서도 같은 자리(발치 바로 위)에 선다. */
.editor .rsth { flex: 0 0 auto; padding: 0 12px; }

/* 전체 화면 편집기 (원칙 §3.2) — 헤더도 타이틀도 없다. 위에 고정된 뒤로 원판,
 * 그 사이를 스크롤하는 텍스트, 그리고 전환 전과 같은 자리(보이는 뷰포트 하단,
 * 키보드 위)에 남는 언어 원판과 보내기 원. 세 조각은 세로 flex 로 서므로
 * 텍스트만 스크롤한다. 언어 시트(z 50)가 위에 뜨도록 z 는 40. */
.editor {
  position: fixed; left: 0; right: 0; top: 0; bottom: auto;
  height: 100vh; height: 100dvh;
  z-index: 40; background: var(--bg);
  display: flex; flex-direction: column;
}
/* iOS 사파리는 키보드가 떠도 레이아웃 뷰포트가 줄지 않는다: 보이는 높이로
 * 줄이고 밀린 만큼 내려 준다 (원칙 §3.2 — state.js 가 값을 넣는다). */
body.kb-open .editor { top: var(--vvtop, 0px); height: var(--vvh, 100dvh); }
.editor-head {
  flex: 0 0 auto;
  padding: calc(12px + env(safe-area-inset-top)) 12px 0;
}
.ebk {
  width: var(--pill-h); height: var(--pill-h); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: var(--surface);
  color: var(--primary); box-shadow: 0 3px 14px var(--shade);
  padding: 0; cursor: pointer;
  transition: background 0.12s ease-out, color 0.12s ease-out,
    opacity 0.12s ease-out, transform 0.12s ease-out;
}
.edt {
  flex: 1 1 auto; min-height: 0; width: 100%;
  background: transparent; border: 0; padding: 16px;
  color: var(--text); font: inherit; font-size: var(--fs-body); line-height: var(--lh-body);
  resize: none; overflow-y: auto;
}
.edt:focus-visible { outline: none; }
.edt::placeholder { color: var(--text2); }
/* 원판의 왼쪽 끝과 보내기 원의 오른쪽 끝이 입력 필에서와 같은 자리에 온다:
 * 12(필 여백) + 1(필 테두리) + 각자의 안쪽 여백. 세로도 같은 식이라 두 원의
 * 중심선이 접힌 필에서와 똑같이 맞는다. */
.editor-foot {
  flex: 0 0 auto;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;
  padding: 0 12px calc(16px + env(safe-area-inset-bottom));
}
body.kb-open .editor-foot { padding-bottom: 16px; }
.editor-foot .ldisc {
  margin-left: calc(1px + var(--pill-pad));
  margin-bottom: calc(1px + var(--pill-pad));
}
.editor-foot .snd {
  margin-right: calc(1px + var(--snd-pad));
  margin-bottom: calc(1px + var(--snd-pad));
}

/* --- sheets, popover, menu ---------------------------------------------------- */

.sheet-host { position: fixed; inset: 0; z-index: 50; }
.scrim { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.35); border: 0; width: 100%; }
.scrim.light { background: rgba(0, 0, 0, 0.2); }
.sheet {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: var(--surface); border-radius: 16px 16px 0 0;
  padding: 8px 16px calc(22px + env(safe-area-inset-bottom));
  box-shadow: 0 -4px 18px var(--shade);
  max-height: 86dvh; overflow: hidden;
  display: flex; flex-direction: column;
}
/* 원칙 §5 — 시트는 스스로 스크롤하지 않는다: 모서리와 안여백은 시트가 갖고,
 * 넘치는 것은 그 안의 상자(`.sheet-scroll`)가 맡는다. 막대를 그려서 안으로
 * 들이는 것이 아니라 **스크롤하는 상자를 안쪽에 두는 것**이다 — 상자는 시트의
 * 좌우 안여백보다 8 씩 넓게 서고(음수 마진) 제 안여백으로 글을 제자리에
 * 돌려놓으므로, 막대는 시트 가장자리에서 8 안쪽에 서고 글의 좌우는 대칭이다.
 * 위로는 제목 띠가, 아래로는 시트의 아래 안여백(22+)이 둥근 모서리를 비켜 준다.
 * 제목과 ✕ 는 상자 밖이라 스크롤해도 제자리에 남는다. 상자는 세로로만 스크롤한다 —
 * 안의 것이 상자의 안여백 밖으로 나가지 않게 짓고, 가로는 숨겨 둔다. */
.sheet-scroll {
  flex: 1 1 auto; min-height: 0;
  overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
  margin: 0 -8px; padding: 0 8px;
}
/* 원칙 §5 — 시트를 닫는 길은 우상단의 원형 ✕ 하나다(바닥에 취소는 없다).
 * 보이는 크기는 32 이고 터치 타깃은 그 둘레로 44 까지 넓힌다. 스크롤하는 것은
 * 그 밑의 상자이므로 ✕ 는 글이 흘러도 제자리에 남는다. */
.shx .ic { width: 18px; height: 18px; }
.shx {
  position: absolute; top: 10px; right: 10px;
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; background: var(--surface2);
  color: var(--text2); font: inherit; font-size: var(--fs-sec); line-height: 1; cursor: pointer;
}
.shx::after { content: ""; position: absolute; inset: -6px; }
.grab { width: 36px; height: 5px; border-radius: 3px; background: var(--border); margin: 0 auto 10px; }
/* 원칙 §5 — 모든 시트는 머리띠로 열린다: ✕(위 10 · 높이 32)의 높이를 띠가
   보장하므로 제목이 없든 첫 조각이 넓든 ✕ 밑으로 들어가지 않는다. 제목은
   가운데 정렬이라 좌우를 ✕ 만큼 비운다. */
.sh {
  display: flex; flex-direction: column; justify-content: center;
  min-height: 32px; padding: 0 36px; margin-bottom: 12px;
  font-size: var(--fs-title); line-height: var(--lh-title); font-weight: 600; text-align: center;
}
/* 확인 시트의 한 줄 — 제목이 묻고 이 줄이 무엇이 사라지는지 말한다. 제목과
   같은 가운데 정렬이되 크기·색은 메타의 것이라 질문 하나만 굵게 남는다. */
.shn {
  margin: 0; padding: 0 8px;
  font-size: var(--fs-sec); line-height: var(--lh-sec); color: var(--text2); text-align: center;
}
/* 시트 안의 구역 제목 — 설정의 그룹 제목과 같은 크기·색으로 행 위에 선다. */
.shc {
  margin: 0 0 8px; padding: 0 4px;
  font-size: var(--fs-sec); line-height: var(--lh-sec); font-weight: 400; color: var(--text2);
}
.quo {
  background: var(--surface2); border-radius: 12px; padding: 10px 12px; margin: 12px 0;
  font-size: var(--fs-sec); line-height: var(--lh-sec); color: var(--text2);
}
.quo i {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  max-height: calc(var(--lh-sec) * 2); overflow: hidden; font-style: normal; overflow-wrap: anywhere;
}
/* 원칙 §3.8 — 재작성 시트 안, 원문 밑의 판정 줄: 말풍선 밑에 섰던 바로 그 줄이라
 * 크기·색도 메타 줄의 것(`--fs-cap` · `text2`)이고, 오른쪽 끝에 그것을 치우는 ✕
 * 하나가 선다. 보여 줄 뿐이라 시트가 보내는 것은 지시 한 줄 그대로다. */
.dbr { display: flex; align-items: flex-start; gap: 6px; margin: 0 0 12px; }
.dbt {
  flex: 1 1 auto; min-width: 0; padding-top: 4px;
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
  overflow-wrap: anywhere;
}
/* 그 밑의 참고 한 줄 — 말풍선 밑의 그 줄과 같고, 시트의 바탕 위에서 옅어진다. */
.dba { display: block; color: color-mix(in srgb, var(--text2) 70%, var(--surface)); }
/* 보이는 크기는 24 이고 터치 타깃은 그 둘레로 넓힌다 (원칙 §5) — 오른쪽으로는 시트의
 * 스크롤 상자의 안여백(8)까지만: 그 밖으로 나가면 상자에 가로 스크롤이 생긴다. */
.dbx .ic { width: 14px; height: 14px; }
.dbx {
  position: relative;
  flex: 0 0 24px; width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--text2); font: inherit; font-size: var(--fs-cap); cursor: pointer;
}
.dbx::after { content: ""; position: absolute; inset: -10px -8px -10px -10px; }
/* 입력 필은 두 줄에서 시작해 쓴 만큼 자라고(sheets.js 가 높이를 잰다) 여섯 줄에서
   멈춘 뒤 안에서 스크롤한다. 지우면 두 줄까지 다시 줄어든다 — 바닥과 천장이 여기
   있으므로 잰 높이가 그 밖으로 나가도 필은 두 값 사이에 선다. 안여백 10+10 과
   테두리 1+1 이 22 다. */
.sfl {
  display: block; width: 100%;
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 14px; margin-bottom: 14px;
  color: var(--text); font: inherit; font-size: var(--fs-body); line-height: var(--lh-body);
  resize: none; overflow-y: auto;
  min-height: calc(var(--lh-body) * 2 + 22px);
  max-height: calc(var(--lh-body) * 6 + 22px);
}
.sfl:focus { outline: none; border-color: var(--primary); }
.sfl::placeholder { color: var(--text2); }
.orow {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 44px; padding: 0 10px; border: 0; border-radius: 10px;
  background: transparent; color: inherit; font: inherit;
  font-size: var(--fs-body); line-height: var(--lh-body);
  text-align: left; cursor: pointer;
}
.orow.sel { background: var(--selection); }
.orow .av, .orow .mk { margin-top: 0; }
.orow em { margin-left: auto; font-style: normal; color: var(--primary); font-size: var(--fs-body); font-weight: 600; }
/* 원칙 §3.8 「번역 안 함」 — 줄의 낱말은 잘리지 않는다: 길면 말줄임 없이 접힌다.
   줄들은 한 카드 안에 서고 가는 선으로 나뉘며, 고른 줄은 표식이 말한다. */
.orow .wlb { min-width: 0; overflow-wrap: anywhere; }
.olist { background: var(--surface2); border-radius: 12px; overflow: hidden; margin-bottom: 18px; }
.olist .orow { border-radius: 0; min-height: 48px; padding: 0 14px; }
.olist .orow + .orow { border-top: 1px solid var(--border); }
/* 고른 줄은 selection 바탕이고 낱말이 키 컬러로 선다. 줄 끝의 동그라미는 빈 고리에서
   키 컬러 고리 안의 체크로 바뀐다 — 하나의 표식이 두 상태를 갖고, 채운 원판은 없다
   (언어 시트의 체크와 같은 어휘: 키 컬러는 선과 글리프에만). */
.olist .orow.sel { background: var(--selection); color: var(--primary); }
.olist .orow em.ck {
  flex: 0 0 auto; width: 22px; height: 22px; margin-left: auto; border-radius: 50%;
  box-shadow: inset 0 0 0 1.5px var(--text2); background: transparent;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--primary); font-size: 13px; font-weight: 700; line-height: 1;
}
.olist .orow.sel em.ck { box-shadow: inset 0 0 0 1.5px var(--primary); }
.orow.danger { color: var(--danger); }
.hl { height: 1px; background: var(--border); margin: 6px 10px; }
.tgr { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.tgs {
  flex: 0 0 44px; width: 44px; height: 26px; border-radius: 13px;
  background: var(--surface2); border: 0; position: relative; cursor: pointer;
  transition: background 0.2s ease-out;
}
.tgs i { position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: var(--surface); transition: left 0.2s ease-out; }
.tgs[aria-pressed="true"] { background: var(--primary); }
.tgs[aria-pressed="true"] i { left: 21px; background: var(--onPrimary); }
.tgt { font-size: var(--fs-body); line-height: var(--lh-body); }
.tgt i { display: block; font-style: normal; font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2); }
/* 시트 제목 밑의 조용한 줄들 — 도움말 말풍선의 목록과 같은 점 하나짜리 판이다. */
.shl {
  margin: 0 0 14px; padding: 0; list-style: none;
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
}
.shl li { padding-left: 1em; }
.shl li + li { margin-top: 4px; }
.shl li::before { content: '·'; display: inline-block; width: 1em; margin-left: -1em; }
.bbt {
  display: flex; align-items: center; justify-content: center; width: 100%;
  height: 44px; border: 0; border-radius: 12px; margin-top: 14px;
  background: var(--primary); color: var(--onPrimary);
  font: inherit; font-size: var(--fs-body); font-weight: 600; cursor: pointer;
}
/* 아직 바꾼 것이 없으면 저장할 것도 없다 (원칙 §5 — 상태는 색만으로 말하지
   않는다: 버튼은 눌리지 않고 눌림 피드백도 없다). */
.bbt:disabled { background: var(--surface2); color: var(--text2); cursor: default; }
.bbt:disabled:active { opacity: 1; transform: none; }
/* 원칙 §5 — 질문 하나에 답이 둘인 시트의 둘째 답. 채움 버튼은 시트에 하나뿐이라
   같은 폭·같은 높이의 틴트 글씨로 선다 — 덜한 답이 아니라 다른 답이다. */
.qbt {
  display: flex; align-items: center; justify-content: center; width: 100%;
  height: 44px; border: 0; border-radius: 12px; margin-top: 6px;
  background: transparent; color: var(--primary);
  font: inherit; font-size: var(--fs-body); font-weight: 600; cursor: pointer;
}
/* 대화 모델 §2 — 시트 위쪽은 현재 상대(원판 · 이름 · 수정), 그 밑은 구역 제목
   하나와 상대를 바꾸는 행들이다. */
.pmk { display: flex; justify-content: center; margin: 14px 0 4px; }
.sgap { height: 18px; }
/* 채움 버튼은 시트당 하나이므로 나머지 동작은 틴트 텍스트다 (원칙 §5). */
.orow.act { color: var(--primary); }
/* 대화 모델 §2 — 상대를 바꾸는 행은 눌리는 것으로 보인다: 표면을 채운 카드다. */
.orow.card {
  min-height: 48px; padding: 0 12px; border-radius: 12px; background: var(--surface2);
}
.orow.card + .orow.card { margin-top: 8px; }
.seg { display: flex; gap: 4px; background: var(--surface2); border-radius: 12px; padding: 4px; }
.seg button {
  flex: 1; min-height: 36px; border: 0; border-radius: 9px;
  background: transparent; color: var(--text2); font: inherit; font-size: var(--fs-sec); cursor: pointer;
}
.seg button[aria-pressed="true"] { background: var(--surface); color: var(--text); font-weight: 600; }
.cmn {
  position: fixed; z-index: 55; width: 250px;
  border-radius: 12px; background: var(--surface);
  box-shadow: 0 8px 24px var(--shade); overflow: hidden;
}
.cmn button {
  display: flex; align-items: center; gap: 12px; width: 100%; height: 44px; padding: 0 14px;
  border: 0; border-bottom: 1px solid var(--border);
  background: transparent; color: inherit; font: inherit; font-size: var(--fs-body);
  text-align: left; cursor: pointer;
}
/* 원칙 §5 — 아이콘은 메타 색으로 왼쪽에, 라벨은 본문 색으로 그 오른쪽에.
   삭제 항목만 아이콘과 라벨이 함께 위험 색을 쓴다 — 자리가 아니라 그 항목이
   위험한 것이므로, 언어처럼 고르기만 하는 메뉴의 마지막 줄은 물들지 않는다. */
.cmn button:not(.danger) .ic { color: var(--text2); }
.cmn button:last-child { border-bottom: 0; }
.cmn button.danger { color: var(--danger); }
/* 고른 항목의 표시는 시트의 옵션 행과 같다: selection 바탕에 오른쪽 끝 체크. */
.cmn button.sel { background: var(--selection); }
.cmn button em {
  margin-left: auto; font-style: normal; font-weight: 600; color: var(--primary);
}

/* 원칙 §4 — 홈의 「두 번 설명하지 않습니다」 안에 서는 그 시트다. 시트의 판을
 * 그대로 쓰되 화면 위로 뜨지 않고 데모 안에 그냥 선다: 자리를 잡는 것들(고정
 * 위치 · 변환 · 스크롤 · 그림자)만 풀고 나머지는 시트의 것이다. ✕ 가 절대
 * 위치라 상자는 relative 로 남는다. 안의 모든 것은 `inert` 라 눌리지 않으므로
 * 손가락 모양도 서지 않는다.
 * 폭은 시트가 폰에서 갖는 그 폭이다 — 폰의 시트는 화면 폭 그대로라 좁은 폰
 * 320 이 곧 시트의 폭이고, 그래서 320 을 천장으로 카드 안에 가운데 선다.
 * 「작은 시트가 하나 떴다」로 읽혀야지 데모만 한 덩어리로 읽히면 안 된다.
 * 320 은 천장일 뿐이라 좁은 화면(≤360)에서는 카드의 안쪽 폭을 그대로 쓴다.
 * 안의 글자 크기는 시트의 것 그대로다. */
.sheet.is-still {
  position: relative; left: auto; right: auto; top: auto; bottom: auto;
  transform: none; width: 100%; max-width: 320px; margin-inline: auto;
  max-height: none; overflow: visible;
  border-radius: 16px; box-shadow: none;
  padding: 8px 16px 16px;
}
.sheet.is-still .shx, .sheet.is-still .tgs, .sheet.is-still .bbt,
.sheet.is-still .sfl { cursor: default; }

@media (min-width: 640px) {
  .sheet {
    left: 50%; right: auto; bottom: auto; top: 50%;
    width: 420px; max-width: calc(100vw - 32px);
    transform: translate(-50%, -50%);
    border-radius: 16px;
    box-shadow: 0 8px 34px var(--shade);
    padding-bottom: 22px;
  }
  .grab { display: none; }
}

/* --- toast -------------------------------------------------------------------- */

.toast-host {
  position: fixed; left: 0; right: 0;
  bottom: calc(var(--pill-h) + 32px + env(safe-area-inset-bottom));
  z-index: 70; display: flex; justify-content: center;
  padding: 0 16px; pointer-events: none;
}
.toast {
  max-width: min(92vw, 420px);
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 8px 16px; font-size: var(--fs-sec); line-height: var(--lh-sec);
  box-shadow: 0 1px 4px var(--shade);
  white-space: pre-wrap; text-align: center;
  transition: opacity 0.2s ease-out;
}
.toast.is-out { opacity: 0; }
/* 토스트도 입력 필 위에 있어야 하므로 키보드만큼 함께 올라간다 (원칙 §5). */
body.kb-open .toast-host { bottom: calc(var(--pill-h) + 32px + var(--kb, 0px)); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* the destructive confirm shares the primary button's shape */
.bbt.danger { background: var(--danger); color: var(--onPrimary); }

/* --- grouped inset lists (principles sections 4 and 5) ----------------------
 * The settings screen, the persona editor, the rule and term lists and the
 * restyled onboarding all use the same three pieces: a quiet group title, an
 * inset card on the page background, and 44px rows separated by hairlines. */

.sset {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  background: var(--bg);
  /* 스크롤바 자리는 넘치든 아니든 비워 둔다 — 막대가 생기는 순간 글이 옆으로
     튀지 않고, --sbw 도 그대로 남는다 (원칙 §5). */
  scrollbar-gutter: stable;
  padding: calc(var(--hbar-h) + 20px) 16px calc(28px + env(safe-area-inset-bottom));
}
.sset-in { max-width: var(--col); margin: 0 auto; }
/* 원칙 §4 — 개인정보·약관은 설정의 행이 아니라 탭의 맨 아래 캡션 한 줄이고,
 * 로그인 화면의 푸터(.home-legal)와 같은 크기·색·구분점이다. 가운데로만 선다. */
.sset-legal { margin: 2px auto 0; text-align: center; }
/* 원칙 §4 — 설정의 네 탭. 헤더 바로 밑, 그룹과 같은 한 단 폭이고 붙어 있지
 * 않으므로 내려가면 그룹과 함께 흐른다. */
.stabs { margin: 0 0 18px; }
.grp { margin: 0 0 22px; }
.gt {
  font-size: var(--fs-sec); line-height: var(--lh-sec); font-weight: 400;
  color: var(--text2); padding: 0 12px 6px;
}
.ins { background: var(--surface); border-radius: 12px; overflow: hidden; }
.gf { padding: 6px 12px 0; margin: 0; font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2); }

.irow {
  display: flex; align-items: center; gap: 12px;
  width: 100%; min-height: 44px; padding: 7px 14px;
  border: 0; border-bottom: 1px solid var(--border);
  background: transparent; color: inherit; font: inherit;
  font-size: var(--fs-body); line-height: var(--lh-body);
  text-align: left;
}
.irow:last-child { border-bottom: 0; }
button.irow { cursor: pointer; }
.irow > span.lb { flex: 0 0 auto; }
.irow .val {
  margin-left: auto; min-width: 0; color: var(--text2); font-size: var(--fs-body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 여러 낱말이 쉼표로 이어진 값(프로필의 대상 언어)은 말줄임이 되면 읽을 수
 * 없으므로 둘째 줄로 흐른다 — 오른쪽 정렬은 그대로다. */
.irow .val.wrap { white-space: normal; overflow: visible; text-align: right; }
.irow .chev { flex: 0 0 auto; color: var(--text2); font-size: var(--fs-title); line-height: 1; }
.irow .val + .chev { margin-left: 6px; }
.irow > span.lb + .chev { margin-left: auto; }
.irow em { margin-left: auto; font-style: normal; color: var(--primary); font-weight: 600; }
.irow.danger { color: var(--danger); justify-content: center; }
/* 누르는 행(로그아웃 · 새 링크 만들기) — 삭제 행과 같은 가운데 정렬, 색만 강조색이다. 왼쪽에 라벨
   색으로 서면 값 없는 라벨로 읽힌다. */
.irow.act { color: var(--primary); justify-content: center; }
/* 초대 링크 행 끝의 복사 글리프 — 메타 줄의 것과 같은 색·크기이고, 복사에도 같은
   답을 한다: 겹쳐 선 체크 원판으로 크로스페이드한다 (원칙 §3.11 · §5). 겹쳐 세우는
   일은 그 규칙이 하므로 여기에 display 는 없다. */
.irow .cpy { flex: 0 0 auto; margin-left: 6px; color: var(--text2); }
/* 초대 코드는 받아 적는 것이라 홈페이지의 필드와 같은 자간·숫자 폭으로 선다. */
.irow .val.code { letter-spacing: 0.12em; font-variant-numeric: tabular-nums; }
/* 원칙 §4 — 설정의 사용량 행: 두 줄이다. 윗줄은 왼쪽에 윈도 이름, 오른쪽 끝에
 * 캡션 크기의 「4% · 월 09:00 초기화」(베이스라인으로 맞춘다), 아랫줄은 행의
 * 폭을 그대로 쓰는 알약 막대. 막대가 양끝까지 가므로 세 행의 끝이 나란하다 —
 * 이름 옆에 끼워 넣던 막대가 행마다 다른 데서 끝나 미완성으로 보이던 자리다.
 * **읽기만 하는 행끼리는 줄을 긋지 않는다**: 헤어라인은 누를 것들을 나누는
 * 것이지 숫자 셋을 나누는 것이 아니다 (원칙 §5). 44 높이도 없고, 행 사이는
 * 자기 패딩이 준다. */
.irow.usg { display: block; padding: 10px 14px 12px; border-bottom: 0; min-height: 0; }
.usgl { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
/* 이름은 다른 행의 라벨과 같은 크기·색이고, 좁으면 말줄임이 된다. */
.usgl > .lb { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 값은 캡션이다 — 이름보다 한 단 작고 조용해서 왼쪽과 다투지 않는다. 숫자 폭이
 * 고르고, 어떤 폭에서도 접히지 않는다(좁아지면 이름 쪽이 줄어든다). */
.irow.usg .val {
  flex: 0 0 auto; margin-left: 0; overflow: visible; white-space: nowrap;
  font-size: var(--fs-cap); line-height: var(--lh-cap);
  font-variant-numeric: tabular-nums;
}
/* 트랙은 계기의 바닥이지 줄이 아니므로 헤어라인(--border)과 확실히 다른 색이어야
 * 한다 — 다크에서 --surface2 와 --border 는 거의 같은 색이라 막대가 카드에
 * 묻혔다. 글자색을 옅게 섞으면 두 테마 모두에서 바탕과 채움 사이에 선다. */
.ubar {
  margin-top: 8px; height: 6px; border-radius: 999px; overflow: hidden;
  background: color-mix(in srgb, var(--text2) 18%, transparent);
}
/* 아무것도 쓰지 않은 윈도(0%)도 왼쪽 끝에 점 하나로 선다 — 텅 빈 트랙은 막대가
 * 고장 난 것처럼 보인다. */
.ubar > i {
  display: block; height: 100%; min-width: 6px; border-radius: inherit;
  background: var(--primary);
}
.ubar.over > i { background: var(--danger); }

.irow.stk { display: block; padding: 10px 14px; }
.irow.stk > .lb { display: block; font-size: var(--fs-sec); line-height: var(--lh-sec); color: var(--text2); margin-bottom: 8px; }
.irow.stk .seg { max-width: none; }

/* 원칙 §4 — 텍스트 페이지(사용법의 글). 설정과 같은 스크롤러(.sset) 안의 같은
 * 한 단(560)이고 상자도 카드도 없다. 단계는 셋뿐이다: 소제목(--fs-title · 600 ·
 * 위 28 아래 10), 문단(본문 크기에 읽기용 행간 --lh-read · 사이 16), 조용한 줄
 * (문단 크기에 --text2). 목록은 항목 사이 8 · 들여쓰기 24. 굵은 머리가 붙은
 * 줄은 홈페이지의 세 줄과 같은 규칙이라 이름이 곧 불릿이다(점이 없다). 본문
 * 속 카드(주소 한 행)는 설정 행과 같은 카드이고 위 16 아래 20 이다. */
.gtx {
  font-size: var(--fs-body); line-height: var(--lh-read); color: var(--text);
}
.gtx p { margin: 0 0 16px; }
/* 구역 제목은 본문보다 한 단 크다(28 › 22 › 16). 증상으로 시작하는 제목을
   훑어 읽는 페이지라 크기로 갈린다. 긴 제목은 좁은 폭에서 두 줄이 되어도
   그대로 둔다 — 등록부의 언어를 모두 한 줄로 맞출 수는 없다. */
.gtx h2 {
  margin: 32px 0 8px; font-size: var(--fs-h1); line-height: var(--lh-h1);
  font-weight: 600; letter-spacing: -0.01em; text-wrap: balance;
}
.gtx > :first-child { margin-top: 0; }
.gtx ol { margin: 0 0 16px; padding-left: 24px; }
.gtx ul { margin: 0 0 16px; padding-left: 0; list-style: none; }
.gtx li + li { margin-top: 8px; }
.gtx ul b { font-weight: 600; }
.gtx a { color: var(--primary); }
/* 아직 말할 것이 없는 줄(심사 중 · 준비 중)은 조용한 한 줄이다. */
.gtx .gq { color: var(--text2); }
/* 복사되는 주소 한 행 — 초대 링크 행과 같은 카드·같은 글리프다. */
.gtx .gadr { margin: 16px 0 20px; }
.gtx .gadr .cpy { margin-left: auto; }
/* 대화 모델 §6 — 주제 맨 위의 흐름 그림 둘. 홈의 그것 그대로이므로(`flow.js`)
   카드도 좁은 폭 규칙도 여기서 다시 정하지 않는다 — 정하는 것은 글 앞에 서는
   숨뿐이다: 맨 위라 위는 0, 밑은 문단과 같은 20 이다. */
.gtx .gflow { margin: 0 0 20px; }
.gtx .gflow .flow:first-child { margin-top: 0; }

/* 원칙 §4 — 패널 밖의 사용법은 계정 없이 읽히는 한 장이라 홈과 같은 껍데기
 * 안에 선다: 떠 있는 헤더도 제 스크롤러도 없고(스크롤은 `.view-home` 이 한다),
 * 홈의 헤드라인과 같은 제목 하나가 목록이나 글 위에 선다. 주제에서 목록으로
 * 돌아가는 길은 그 제목 위의 조용한 한 줄이다 — 뒤로 원판이 뜰 자리가
 * 여기에는 없기 때문이다. */
/* 사용법은 읽는 페이지라 위에 붙는다(홈·로그인은 가운데). 뒤로 한 줄은 목록에서
   비어 있어도 자리를 지켜, 목록과 주제 사이를 오갈 때 제목이 흔들리지 않는다. */
#view-guide .home-body { margin-block: 0; }
.gpg-back { margin: 0 0 8px; min-height: var(--lh-sec); font-size: var(--fs-sec); line-height: var(--lh-sec); }
.gpg-back .lnk { display: inline-flex; align-items: center; gap: 4px; }
.gpg-hd { margin: 0 0 20px; }

/* the disc that stands for me, one recipe: the settings profile row and the
   list row whose last word was mine.
   A person's disc is .av, in the counterpart tint */
.disc {
  flex: 0 0 32px; width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-sec); font-weight: 600;
  background: var(--selection); color: var(--primary);
}
/* rule / term rows: text, where it came from, and the way to drop it */
.rrow {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 8px 10px 14px; border-bottom: 1px solid var(--border);
  transition: transform 0.2s ease-out;
}
.rrow:last-child { border-bottom: 0; }
.rrow.is-swiped { transform: translateX(-10px); }
.rtx { flex: 1 1 auto; min-width: 0; font-size: var(--fs-body); line-height: var(--lh-body); overflow-wrap: anywhere; }
.rtx i {
  display: block; font-style: normal; margin-top: 3px;
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
}
.rtx .tag {
  display: inline-flex; align-items: center; height: 20px; padding: 0 8px;
  margin-left: 6px; border-radius: 10px;
  background: var(--surface2); color: var(--text2); font-size: var(--fs-cap); line-height: var(--lh-cap);
}
.rx {
  flex: 0 0 44px; width: 44px; height: 44px; margin: -10px -8px -10px 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--text2);
  line-height: 1; cursor: pointer;
}
/* 원칙 §4 — 빈 그룹 카드도 완성된 카드다: 조용한 한 줄이 행과 같은 높이·같은
   위아래 여백을 지킨다 (`p` 의 기본 아래 마진이 카드를 아래로 늘리던 자리). */
.ins-empty {
  display: flex; align-items: center; min-height: 44px;
  margin: 0; padding: 10px 14px;
  color: var(--text2); font-size: var(--fs-body); line-height: var(--lh-body);
}

/* 원칙 §5 — 사람의 행 하나. 상대를 "누구인가"로 보이는 자리 셋이 이 행을 쓴다:
   설정의 상대 화면(앞자리는 마크 원판, 바깥의 `.rrow` 가 ⋮ 와 스와이프를 더한다),
   「누구와 합칠까요?」(같은 원판, 카드 옷), 「같은 사람인가요?」(원판 자리에 고르는
   고리, 둘째 줄 오른쪽 끝에 「채팅 보기 ›」). 세 줄이다: 이름과 그 줄 오른쪽 끝의
   자리·언어, 마지막으로 말한 채팅, 말풍선 글리프와 거기서의 마지막 글. 어느 줄도
   접히지 않고 넘치면 … 로 끝나므로 320 폭에서도 행은 제 높이를 지킨다. 위아래
   여백이 셋 다 10 이라 같은 글자 크기에서 세 화면의 행 높이는 하나다 (원칙 §4). */
.prow { position: relative; flex: 1 1 auto; min-width: 0; }
/* 시트의 행은 눌리는 것으로 보인다: 표면을 채운 카드다 (대화 모델 §2). */
.prow.card {
  border-radius: 12px; background: var(--surface2); padding: 10px 12px;
}
.prow.card + .prow.card { margin-top: 8px; }
/* 시트의 목록 안에서는 목록의 간격이 대신한다. */
.sh-res .prow.card + .prow.card { margin-top: 0; }
/* 고른 행은 selection 바탕이 함께 답한다 (원칙 §4). */
.prow.sel { background: var(--selection); }
.prw {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 0;
  border: 0; background: transparent; color: inherit; font: inherit;
  text-align: left; cursor: pointer;
}
/* 카드 어디를 눌러도 그 행이다 — 링크만 제 자리를 지킨다. */
.prow.card .prw::after { content: ''; position: absolute; inset: 0; border-radius: 12px; }
.prw .av { margin-top: 0; }
/* 하나만 고르는 자리의 고리: 쉴 때는 빈 테두리, 고른 뒤에는 채워진 원에 체크.
   세 줄의 세로 가운데, 마크 원판과 같은 자리에 선다. */
.prk {
  flex: 0 0 22px; width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--text2); border-radius: 50%; color: transparent;
}
.prk .ic { width: 14px; height: 14px; }
.prow.sel .prk {
  border-color: var(--primary); background: var(--primary); color: var(--onPrimary);
}
.ptx { flex: 1 1 auto; min-width: 0; }
.pn { display: flex; align-items: baseline; gap: 8px; }
.pn b {
  flex: 0 1 auto; min-width: 0;
  font-size: var(--fs-lt); line-height: var(--lh-lt);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 자리와 언어는 이름 줄의 오른쪽 끝에서 메타 색으로 선다 — 읽은 것이지 고른 것이
   아니다. 좁아지면 이름이 먼저 … 로 끝나고, 자리 이름이 아무리 길어도 줄의
   반을 조금 넘는 데서 멈춘다 — 이름이 사라지는 행은 없다. */
.pn i {
  flex: 0 1 auto; max-width: 60%; margin-left: auto; font-style: normal;
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pt {
  display: block; font-size: var(--fs-ls); line-height: var(--lh-ls);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 마지막 글: 메타 색 한 줄. */
.pv {
  display: block; font-size: var(--fs-ls); line-height: var(--lh-ls); color: var(--text2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 핸들이 이미 쓰이는 중이면 시트는 닫히지 않고 필드 밑에 한 줄이 선다. */
.ferr {
  margin: 4px 2px 0; color: var(--danger);
  font-size: var(--fs-cap); line-height: var(--lh-cap);
}
.flds { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }

/* 원칙 §4 — "번역하지 않을 단어" 한 줄 추가: the one add form in settings.
 * The field itself is styled with .field above — the row only insets it. */
/* 세로로 쌓는다 — 필드 밑에 길이 카운터가 설 자리가 필요하다. */
.arow { padding: 10px 14px; flex-direction: column; align-items: stretch; gap: 0; }

/* --- 온보딩: 한 화면에 한 걸음 (원칙 §4 · `docs/VOICE_MODEL.md`) --------------
 *
 * 세로 세 조각이다: 맨 위에 고정 헤더(가운데 걸음 제목, 왼쪽에 두 번째 걸음부터
 * 뒤로 원판), 가운데 스크롤하는 질문 하나, 발치에 조용한 걸음 표시("2 / 6")와
 * 채움 버튼 하나. 가상 키보드가 떠도 버튼은 그 위에 남는다 — 전체 화면 편집기와
 * 같은 규칙(dvh + --vvh / --vvtop)을 쓴다. "내 글 더 넣기"의 층(.rtn)도 같은
 * 조각으로 서고, 한 화면뿐이라 걸음 표시가 없다. */

.view-col { height: 100vh; height: 100dvh; display: flex; flex-direction: column; }
/* iOS 사파리는 키보드가 떠도 레이아웃 뷰포트가 줄지 않는다 (원칙 §3.2). */
body.kb-open #view-onboarding {
  position: fixed; left: 0; right: 0; top: var(--vvtop, 0px);
  height: var(--vvh, 100dvh);
}
.onb { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.onb-top {
  flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
  padding: calc(12px + env(safe-area-inset-top)) 16px 0;
  /* 헤더도 글과 같은 단 안이다 (원칙 §5) — 좌우 16 이 안쪽 여백이므로 단에
     그만큼을 더한 것이 이 바의 폭이고, 그래야 안쪽이 .onb-in 과 같은 선에 선다.
     스크롤러가 비워 둔 스크롤바 자리(--sbw)는 여기서도 오른쪽에 그대로 둔다. */
  max-width: calc(var(--col) + 32px + var(--sbw, 0px)); margin-inline: auto; width: 100%;
  padding-right: calc(16px + var(--sbw, 0px));
}
/* 헤더는 세 칸이다 — 양쪽 칸이 원판 하나만큼이라 제목이 화면 가운데에 선다.
   첫 걸음은 왼쪽 칸이 비어 있고 줄 높이는 그대로다. */
.onb-side { flex: 0 0 var(--pill-h); height: var(--pill-h); display: flex; }
.onb-title {
  flex: 1 1 auto; min-width: 0; margin: 0;
  font-size: var(--fs-title); line-height: var(--pill-h); font-weight: 600;
  text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 걸음 표시는 버튼 바로 위 한 줄, 캡션 크기의 조용한 글씨다. 키 안내는 버튼이
   제 라벨로 말하므로(`withKey`) 이 줄에는 걸음 수뿐이다. */
.onb-at {
  margin: 0 0 10px; text-align: center;
  font-size: var(--fs-cap); line-height: var(--lh-cap); color: var(--text2);
}
.onb-scroll {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  /* 스크롤바 자리는 넘치든 아니든 비워 둔다 — 막대가 생기는 순간 글이 옆으로
     튀지 않고, --sbw 도 그대로 남는다 (원칙 §5). */
  scrollbar-gutter: stable;
  padding: 20px 16px 8px;
}
.onb-in { max-width: var(--col); margin: 0 auto; }
.onb-foot {
  flex: 0 0 auto;
  padding: 12px 16px calc(16px + env(safe-area-inset-bottom));
  /* 발치의 버튼도 글과 같은 선에 선다 — 스크롤러가 비워 둔 자리만큼 오른쪽을
     비운다 (원칙 §5). */
  padding-right: calc(16px + var(--sbw, 0px));
}
body.kb-open .onb-foot { padding-bottom: 16px; }
/* 원칙 §5 — 마지막 걸음의 한 번의 호출이 나가 있는 동안에는 걸음 전체가 잠긴다:
 * 뒤로 원판도 칩도 필드도 채움 버튼과 같은 말을 한다(`.btn:disabled` 의 값 그대로).
 * 포인터도 받지 않으므로 hover 로도 답하지 않는다. */
.onb.is-busy :disabled { opacity: 0.45; cursor: default; pointer-events: none; }
/* 기다림은 채팅의 그것과 같은 점 셋이다 — 채움 버튼 위에 서므로 점의 색은 라벨의
 * 색이다(`.dots i` 의 `--text2` 는 primary 위에서 보이지 않는다). */
.onb-foot .btn-primary .dots i { background: currentColor; }
.onb-h1 {
  margin: 0 0 10px; font-size: var(--fs-lg); line-height: var(--lh-lg);
  font-weight: 600; letter-spacing: -0.01em;
}
.onb-sub { margin: 0 0 28px; color: var(--text2); }
/* 한 화면의 질문 한 줄. 그룹 라벨(.lb)보다 크고 헤드라인보다 작다. */
.onb-ask {
  margin: 0 0 14px;
  font-size: var(--fs-title); line-height: var(--lh-title); font-weight: 600;
}
/* 질문 밑 한 줄 — 무엇을 넣는 자리인지만 말한다. */
.onb-help {
  margin: 0 0 14px;
  font-size: var(--fs-sec); line-height: var(--lh-sec); color: var(--text2);
}
.onb-ask + .onb-help { margin-top: -8px; }
/* 질문 밑 두 줄(어디서 가져오면 되나 · 무엇이든 좋다)은 한 덩어리로 붙어 선다. */
.onb-help + .onb-help { margin-top: -8px; }
.qs { margin: 0 0 24px; }
/* 무대 칩 밑의 "한 줄" — 칩과 붙지 않게 한 칸 띄우고 밑에는 여백이 없다. */
.onb-desc { margin: 22px 0 0; }
.qs > .lb { display: block; margin-bottom: 8px; font-size: var(--fs-sec); line-height: var(--lh-sec); color: var(--text2); }
.onb-in > .field { margin-top: 12px; }
.onb-ta { min-height: 132px; resize: none; }
/* "내가 쓴 글"은 글 하나에 상자 하나다 (원칙 §4) — 상자마다 카운터가 딸리므로
   필드와 함께 묶여 서고, 새 상자는 마지막 상자 밑에 붙는다. */
.onb-box .field { margin-top: 12px; }
/* 마지막 상자 밑의 조용한 한 줄. 채움 버튼은 발치의 하나뿐이므로(원칙 §5)
   여기는 틴트 텍스트이고, 손가락 상자는 그대로 44 다. */
.onb-add {
  margin-top: 4px; padding: 0 2px;
  font-size: var(--fs-sec); line-height: var(--lh-sec);
}

/* 무대의 인바운드와 그 이해 말풍선. 채팅의 말풍선 그대로라 이해 말풍선 안의
 * 톤 줄(.tf)도 방에서와 같은 모양이고, 여기에는 누를 것도 메타도 없다. */
.onb-conv { display: flex; flex-direction: column; gap: 4px; margin-bottom: 22px; }
.onb-conv .col { max-width: min(88%, 720px); }
.onb-conv .b { cursor: default; }

/* 설정의 "내 글 더 넣기" 맨 위 — 아직 답하지 않은 상황들 (`docs/VOICE_MODEL.md`).
 * 상황 하나가 온보딩의 답장 걸음과 같은 조각(인바운드 두 말풍선 + 상자)이고,
 * 그 밑에 붙여넣기 질문이 이어지므로 헤어라인 하나가 둘을 가른다. 답하지 않은
 * 상황이 없으면 이 블록 자체가 없다. */
.onb-sits {
  margin-bottom: 30px; padding-bottom: 26px;
  border-bottom: 1px solid var(--border);
}
.onb-sit + .onb-sit { margin-top: 26px; }
.onb-sit .onb-conv { margin-bottom: 0; }
.onb-sit .field { margin-top: 12px; }

/* 원칙 §4 — "내 글 더 넣기"는 전체 화면 일시 층이다: 편집기와 같은 자리에
 * 서고(z 40, 언어 시트가 위에 뜬다) 뒤로가기가 닫는다. */
.rtn {
  position: fixed; left: 0; right: 0; top: 0;
  height: 100vh; height: 100dvh;
  z-index: 40; background: var(--bg);
  display: flex; flex-direction: column;
}
body.kb-open .rtn { top: var(--vvtop, 0px); height: var(--vvh, 100dvh); }

/* words to leave untranslated: chips under a field that stays one line */
.dchips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.dchips:empty { display: none; }
.dchip {
  display: inline-flex; align-items: center; gap: 2px;
  height: 28px; padding: 0 4px 0 10px; border-radius: 14px;
  background: var(--surface2); color: var(--text); font-size: var(--fs-sec); line-height: var(--lh-sec);
}
.dchip .x .ic { width: 14px; height: 14px; }
.dchip .x {
  width: 22px; height: 22px; display: inline-flex;
  align-items: center; justify-content: center;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--text2); font: inherit; font-size: var(--fs-cap); cursor: pointer;
}

/* --- search ------------------------------------------------------------------- */

mark { background: var(--selection); color: inherit; border-radius: 3px; padding: 0 1px; }
/* 원칙 §4 — 검색 결과는 채팅 목록 그 자체다: 같은 행(.r), 같은 --row-h,
 * 같은 6px 세로 간격, 같은 글자 단계(--fs-lt / --fs-ls). 헤어라인도 카드도
 * 없고 간격만으로 나뉘며, hover·선택은 둥근 surface2 채움이다. 목록은 시트를
 * 늘리지 않고 자기 안에서 스크롤한다. */
.sh-fld { margin: 12px 0 8px; }
.sh-res {
  display: flex; flex-direction: column; gap: 6px;
  max-height: min(46dvh, 340px);
  overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
}
.sh-res .r, .sh-res .prow { flex: 0 0 auto; }
/* 결과가 없을 때도 행 하나의 높이를 지키는 조용한 한 줄 (원칙 §4). */
.sh-empty {
  display: flex; align-items: center; justify-content: center;
  min-height: var(--row-h); margin: 0; padding: 0 12px;
  color: var(--text2); font-size: var(--fs-ls); line-height: var(--lh-ls);
}

/* iOS Safari zooms into fields under 16px on focus; keep the composer and full-screen editor at 16px on touch devices. */
@media (pointer: coarse) {
  textarea.fld, textarea.edt { font-size: max(var(--fs-body), 16px); }
}
