/* =========================================================================
 * app.css — 골격 + 컴포넌트 14종. 정본은 DESIGN-SYSTEM.md §2·§5.
 * 2026-08-11 재디자인: Claude 디자인 언어(크림·코랄·넉넉한 여백·부드러운 곡률).
 *
 * ⚠️ 유지: 1024px 단일 분기 · 카드 고정높이 금지(rem) · 모션 150~200ms 스프링 금지 ·
 *          3상태 체크 · 추모 무채·모션0 · safe-area.
 * ========================================================================= */

* { margin: 0; padding: 0; box-sizing: border-box; }
/* ⚠️ 가로로는 절대 안 움직인다 — 2026-08-13 사용자 지시("폰으로 볼 때 슬라이딩이나
   화면 전체가 움직이는 상황은 최대한 없애줘"). 이건 **마지막 안전망**이지 해결책이
   아니다: 원인(가로 스크롤 칩·넘치는 그리드)은 각자 자리에서 고쳤고, 이 줄은 앞으로
   누가 넓은 요소를 넣어도 화면이 옆으로 밀리지 않게 잡아 두는 용도다.
   ⚠️ overflow-y 는 건드리지 않는다 — 세로 스크롤까지 막으면 긴 화면을 못 읽는다. */
html, body { max-width: 100%; overflow-x: hidden; overscroll-behavior-x: none; }
html { font-size: 16px; -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--sans);
  background: var(--bg); color: var(--ink);
  line-height: 1.65;
  min-height: 100dvh;
  padding-bottom: calc(4rem + env(safe-area-inset-bottom));
  -webkit-font-smoothing: antialiased;
  /* ⚠️ 한글은 어절 중간에서 끊으면 안 된다 — "바 뀝니다"·"이 안내는 초 안이에요" 방지.
     실제로 깨져 있던 것을 2026-08-12 전역으로 고침. */
  word-break: keep-all;
  overflow-wrap: break-word;   /* 단, 긴 URL·영문 연속은 넘치지 않게 */
  letter-spacing: -.003em;     /* Pretendard 는 아주 살짝 좁혀야 결이 산다 */
}
/* ⚠️ **`hidden` 이 우리 규칙에 지는 것을 막는다** (2026-08-16 사용자 제보 — 끝말잇기에서
   입력줄과 "다른 가족이 이어 줄 차례예요"가 **한꺼번에** 떴다).
   `hidden` 속성이 감추는 힘은 브라우저 기본 스타일의 `[hidden]{display:none}` 하나뿐이라,
   같은 요소에 `display` 를 주는 우리 규칙(`.word-send{display:flex}`) 하나면 조용히 뚫린다.
   **속성은 그대로 붙어 있는데 화면엔 보인다** — 그래서 `getAttribute('hidden')` 로 재는
   시험은 통과한다(실제로 통과했다).
   ⚠️ 이 사고가 **두 번째**다. 처음엔 `.emoji-pad` 에서 나서 그 자리만 막았는데(§이모지판),
      다음 컴포넌트에서 똑같이 났다. 컴포넌트마다 막는 대신 여기서 한 번에 막는다.
   ⚠️ `!important` 를 쓴다. 이건 취향이 아니라 **`hidden` 의 뜻이 "무조건 감춘다"**이기
      때문이다 — 여기서 지면 감춘 게 아니다. */
[hidden] { display: none !important; }
a { color: var(--primary); text-decoration: none; }
button { font: inherit; cursor: pointer; }
img { max-width: 100%; height: auto; }
/* ⚠️ 입력칸은 기본적으로 "20글자쯤"의 고유 너비를 가져서, flex:1 을 줘도 그 아래로는
   안 줄어든다. 좁은 폰에서 입력 두 개를 한 줄에 두면(기록·일정 화면이 그렇다) 줄이
   화면 밖으로 삐져나가 가로 스크롤이 생겼다. min-width:0 이 그걸 푼다. */
input, select, textarea { min-width: 0; max-width: 100%; }
/* ⚠️ **`<select>` 만 앱 글꼴을 안 물려받고 있었다**(2026-08-23 실측: 옆 입력칸 16px 인데
   select 는 UA 기본 13.33px). `button` 은 42줄에서 이미 `font: inherit` 를 받고 있었는데
   select 만 빠져 있어, 같은 줄에 선 「진료 ▾」와 「무엇」의 글자 크기가 눈에 띄게 달랐다. */
select { font: inherit; }

/* ══ 고르기 칸(.pick) ══ 2026-08-23 신설.
   ⚠️ **왜 만들었나**: 사장님 제보 *"진료 기록에 진료를 누르면 팝업이 이상한 곳에서 떠"*.
      네이티브 `<select>` 는 **OS 가 목록을 그린다** — 안드로이드는 화면 가운데 대화상자,
      iOS 는 아래쪽 휠이다. **CSS 로 위치를 못 옮긴다. 없애는 것만이 해결이다.**
   ⚠️ 이 앱의 다른 고르기는 전부 눌러서 칠하는 방식이다(요일 `.tt-pick`, 자녀 `.chips`) —
      사장님이 시간표에서 고르신 *"체크 말고 터치로"* 그것이다. select 만 결이 달랐다.
   ⚠️ `:has()` 를 안 쓴다 — `.tt-pick-day` 와 같은 이유(옛 웹뷰). `input:checked + span` 으로 칠한다.
   ⚠️ 라디오를 **투명하게 겹쳐** 둔다(안 감춘다) — 키보드 초점과 스크린리더가 그대로 산다. */
/* ⚠️ **접히지 않고 줄에 남는 단추**(2026-08-23). 줄을 터치하면 고치기·빼기가 나오는데,
   「냈어요」·「끝남」처럼 **그 줄에서 가장 자주 누르는 동작**까지 접으면 두 번 눌러야 한다.
   ⇒ 자주 쓰는 하나는 밖에 두고, 가끔 쓰는 것(고치기·빼기)만 접는다.
   ⚠️ `rowactions.js` 는 단추·폼·입력을 누르면 접힘을 토글하지 않는다 — 여기가 그 규약을 쓴다. */
.row-keep { margin: 0; flex: 0 0 auto; }

.pick { display: flex; flex-wrap: wrap; gap: .25rem; }
.pick-opt { flex: 1 1 4.5rem; display: block; position: relative; margin: 0; }
.pick-opt input {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  opacity: 0; cursor: pointer; -webkit-appearance: none; appearance: none;
}
.pick-opt span {
  display: block; text-align: center; padding: .7rem .5rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink-2); font-size: .9rem;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.pick-opt input:checked + span {
  background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600;
}
.pick-opt input:active + span { transform: scale(.93); }
.pick-opt input:focus-visible + span { outline: 2px solid var(--primary); outline-offset: 2px; }
.num { font-variant-numeric: tabular-nums; }

/* 2026-09-07 #733: 위 여백을 변수로 뺐다 — 아래 「참여중 줄」 보정(body.watchline-on)이
   이 값을 그대로 더해야 하는데, 두 곳에 같은 숫자(1.25rem)를 따로 적으면 언젠가 갈린다.
   폴백을 적어 둔 이유는 inc/app_auth.php 의 `<div class="wrap">` 처럼 이 변수를 모르는
   문맥(자기 인라인 CSS만 쓰는 페이지)에서도 지금 값 그대로 나오게 하기 위함이다. */
:root { --wrap-pad-top: 1.25rem; }
.wrap { max-width: 34rem; margin: 0 auto; padding: var(--wrap-pad-top, 1.25rem) 1.25rem 2rem; }

/* ⚠️ 탈퇴 화면 전용(2026-09-06) — 하단 탭바(아래 .tabbar: 4rem+safe-area)가
   "탈퇴하기" 단추를 가렸다. 이 화면은 카드+폼뿐이라 콘텐츠 끝이 뷰포트 끝과
   거의 겹쳐서, `.wrap` 의 기본 여백(2rem)만으로는 탭바 높이를 못 뺀다.
   ⚠️ `.wrap` 전역을 못 올린다 — 다른 화면(대화·초성게임 등)은 이미 자기
   여백을 따로 잡고 있어(`body.chat-page`·`body.word-page` 참고) 전역을 올리면
   그 화면들 아래에 불필요한 빈틈이 생긴다. 그래서 이 화면 클래스에만 건다.
   탭바와 **같은 식**을 쓴다 — 탭바 높이를 고치면 여기도 같이 움직인다. */
body.withdraw-page main.wrap { padding-bottom: calc(4rem + env(safe-area-inset-bottom) + 1rem); }

/* ── 하단 탭바 ── */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  /* ⚠️ **개수를 박지 않는다**(2026-08-16). `repeat(5, 1fr)` 로 박아 뒀더니 게임 탭이
     늘면서 여섯째가 첫 줄 밖으로 밀려났다. 링크로 들어온 아이는 대화가 없어 하나 적기도
     하다 — 탭 수는 이미 상황마다 다르다(ns_tabs·ns_tabs_kid). 자동으로 나눈다. */
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  height: calc(4rem + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: saturate(140%) blur(12px);
  border-top: 1px solid var(--line);
}
/* 아이콘 — Solar(선형) 한 세트만. 획 두께는 1.5 로 고정(세트를 섞으면 여기가 어긋난다) */
.ico { width: 1.5rem; height: 1.5rem; flex: 0 0 auto; }

.tabbar a {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .18rem;
  /* ⚠️ ink-3 는 11.5px 라벨에서 3.63:1(AA 미달, 실측 2026-08-12) — 탭 라벨은 ink-2 하한 */
  font-size: .72rem; color: var(--ink-2); min-height: var(--tap);
  position: relative; transition: color var(--dur) var(--ease);
}
/* ⭐ **누르는 순간 반응한다** ── 2026-08-23 사장님 지시 *"메뉴 탭 이동시 부드러운 효과"*.
   ⚠️ **여기가 가장 큰 자리다.** 탭을 눌러도 새 화면이 올 때까지 **아무 일도 안 일어나서**,
      안 눌린 줄 알고 한 번 더 누르게 된다(줄 단추에서 배운 것과 같은 이유 — 탭 하이라이트를
      끄면 우리가 대신 줘야 한다).
   ⚠️ 아이콘만 살짝 줄인다. 글자까지 줄이면 라벨이 흔들려 지저분하다.
   ⚠️ 색은 **바로** 강조색으로 — 어디를 눌렀는지가 먼저 보여야 한다. */
.tabbar a { -webkit-tap-highlight-color: transparent; }
.tabbar a:active { color: var(--primary); }
.tabbar a .ico, .tabbar a .emo { transition: transform var(--dur) var(--ease); }
.tabbar a:active .ico, .tabbar a:active .emo { transform: scale(.88); }

/* ⚠️⚠️ **탭 이모지 — 켜진 탭을 어떻게 알리나**(2026-08-23 사장님 지시로 탭바도 이모지).
   SVG 는 `currentColor` 라 켜진 탭이 코랄로 물들었다. **이모지는 색을 못 바꾼다** —
   그게 앞서 두 번 「SVG 로 두자」고 올렸던 이유다. 그래서 셋으로 대신한다:
     ① 위쪽 코랄 막대(`.tabbar a.on::before`) — 원래 있던 것, 가장 강한 표시
     ② 라벨이 코랄 + 굵게 — 원래 있던 것
     ③ **안 켜진 탭의 이모지를 옅게** — 여기서 더한다
   ⚠️ 너무 옅게 하면 「고장난 탭」으로 보인다. .62 면 무엇인지는 읽히고 대비는 산다.
   ⚠️ `filter` 는 GPU 를 태우고 고정 요소라 늘 그려진다 — `opacity` 하나만 쓴다. */
.tabbar a .emo { font-size: 1.4rem; line-height: 1; opacity: .62;
                 transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease); }
.tabbar a.on .emo { opacity: 1; }
.tabbar a.on { color: var(--primary); font-weight: 600; }
.tabbar a.on::before {   /* 상단 코랄 인디케이터 — Claude 특유의 절제된 표식 */
  content: ""; position: absolute; top: 0; width: 1.5rem; height: 2px;
  border-radius: 999px; background: var(--primary);
}

/* ── 데스크톱 좁은 창: **본문이 스크롤한다** ──
   2026-08-14 사용자 지적: "달력·대화에서 오른쪽 스크롤이 설정 옆으로까지 넘어온다".
   창이 스크롤하면 스크롤바가 **창 높이 전체**를 차지해서, 아래에 고정된 탭바 옆으로
   그대로 내려온다. 스크롤바는 브라우저가 그리는 것이라 우리 요소로 덮을 수도 없다.
   → 스크롤 주체를 **본문(body)** 으로 옮기고 그 높이를 탭바 위까지로 자른다.
      그러면 스크롤바가 탭바 **위에서 끝난다.**

   ⚠️ **마우스 쓰는 좁은 창에서만** 건다.
      · 폰(터치)은 그대로 창이 스크롤한다 — 안쪽 스크롤로 바꾸면 주소창 자동 숨김과
        관성 스크롤이 깨지고, 애초에 폰 스크롤바는 겹쳐 뜨는 방식이라 문제가 없다.
      · 1024px 이상은 탭바가 **왼쪽 사이드바**라 아래를 자를 이유가 없다.
   ⚠️ 스크롤 주체가 바뀌면 `window.scrollY` 를 쓰던 코드가 조용히 멈춘다 —
      대화(chat.js)가 그랬다. 거기도 같이 고쳤다(assets/chat.js 의 sc() 참고). */
@media (hover: hover) and (pointer: fine) and (max-width: 1023.98px) {
  html { height: 100%; overflow: hidden; }
  body {
    height: calc(100dvh - 4rem);   /* 4rem = 탭바 높이 */
    /* ⚠️ min-height 를 반드시 풀어 준다 — 기본값이 `min-height:100dvh` 라 그대로 두면
       **min 이 이겨서 높이가 안 줄어든다**(스크롤바가 그대로 탭바 옆까지 내려온다).
       2026-08-14 실측으로 잡았다: 창 907 일 때 843 이어야 할 body 가 907 로 나왔다. */
    min-height: 0;
    overflow-y: auto;
    padding-bottom: 1rem;          /* 아래 padding 은 이제 탭바 자리가 아니다 */
  }
  /* 탭바가 없는 화면(로그인 등)은 자를 이유가 없다 */
  body.no-tabs { height: auto; overflow: visible; }

  /* ⚠️ 대화는 **탭바 위에 보내기 칸이 하나 더** 있다(.chat-send 는 bottom:4rem 고정).
     탭바만 빼면 스크롤바가 보내기 칸 뒤로 지나가 거기까지 내려온 것처럼 보인다
     (2026-08-14 사용자 지적 "대화의 스크롤은 보내기 버튼 틀 위까지 되도록").
     ⚠️ 보내기 칸 높이는 **글이 길어지면 늘어난다**(textarea 최대 7.5rem) — 상수로 적으면
        여러 줄을 쓰는 순간 어긋난다. 그래서 chat.js 가 실제 높이를 재서 --compose-h 로 준다.
     ⚠️ 기본값(4.25rem)은 한 줄일 때의 높이다 — JS 가 막혀도 얼추 맞게. */
  body.chat-page { height: calc(100dvh - 4rem - var(--compose-h, 4.25rem)); }
  /* ⚠️ 본문 아래 여백을 줄이는 규칙은 **이 파일 뒤쪽**에 있다(.chat-page 여백 항목).
     여기 두면 뒤쪽의 같은 특정도 규칙(body.chat-page main.wrap)에 져서 **한 번도 안 먹는다** —
     실제로 그랬다(2026-08-15 실측: PC 에서 88px 죽은 여백). CSS 는 특정도가 같으면
     **나중 것이 이긴다** — 그래서 순서가 곧 규칙이다. */
}

/* 데스크톱 — 사이드바(1024px 단일 분기) */
@media (min-width: 1024px) {
  body { padding-bottom: 0; padding-left: 15rem; }
  /* ⚠️ **사이드바가 없는 화면은 그 자리를 비워 두면 안 된다.** 위 padding-left 는
     탭바(사이드바) 자리인데, 탭바를 안 그리는 화면(로그인·가입·아이 화면)에도 240px 이
     그대로 잡혀 내용이 **오른쪽으로 120px 밀려** 있었다(2026-08-14 데스크톱 실측:
     1440px 에서 main 이 400px 이 아니라 520px 에서 시작).
     ⚠️ 폰에서는 안 보이는 결함이다 — 이 media query 자체가 1024px 부터라서.
     ⚠️ `body.auth` 로 가르면 안 된다 — 온보딩·탈퇴처럼 **로그인한 auth 화면에는
        탭바가 있다.** 판정은 `ns_has_tabbar()` 한 곳(inc/view.php)이 하고,
        없을 때 `no-tabs` 를 붙인다. */
  body.no-tabs { padding-left: 0; }
  .tabbar {
    left: 0; right: auto; top: 3.75rem; bottom: 0; width: 15rem; height: auto;
    display: flex; flex-direction: column; padding: 1.25rem .75rem 0;
    border-top: none; border-right: 1px solid var(--line); background: var(--surface);
    backdrop-filter: none;
  }
  .tabbar a {   /* 데스크톱 사이드바 — 아이콘과 라벨을 가로로 */
    flex-direction: row; justify-content: flex-start; gap: .75rem;
    padding: .7rem 1rem; font-size: 1rem;
    border-radius: var(--radius); margin-bottom: .25rem;
  }
  .tabbar a.on { background: var(--primary-l); }
  .tabbar a.on::before { display: none; }
  /* padding-top 을 직접 안 쓰고 변수만 바꾼다(2026-09-07 #733) — 아래 「참여중 줄」
     보정(body.watchline-on)이 이 변수를 그대로 따라와야 데스크톱 폭에서도 안 겹친다. */
  .wrap { max-width: 40rem; --wrap-pad-top: 2rem; }
  /* 데스크톱은 탭바가 왼쪽 사이드바로 바뀐다(위 .tabbar 재정의) — 바닥을
     안 가리므로 위 모바일 보정을 되돌린다. 안 되돌리면 탈퇴 화면 아래에
     불필요한 빈 공간이 남는다. */
  body.withdraw-page main.wrap { padding-bottom: 2rem; }
}

/* ── 타이포 스케일 ── */
h1 { font-size: 1.55rem; font-weight: 700; line-height: 1.3; letter-spacing: -.015em; }
h2 { font-size: 1.15rem; font-weight: 600; line-height: 1.4; letter-spacing: -.01em; }
h3 { font-size: .8rem; font-weight: 600; line-height: 1.4; color: var(--ink-2);
     text-transform: none; letter-spacing: .01em; }
.display {
  font-family: var(--serif); font-size: 2rem; font-weight: 600;
  line-height: 1.2; letter-spacing: -.02em;
}
small, .caption { font-size: .78rem; line-height: 1.5; color: var(--ink-2); }

/* ── 버튼 — 부드러운 곡률, 코랄 액션 ── */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap); padding: 0 2rem;   /* 명세: 좌우 32px — 손에 잡히는 크기 */
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink); font-weight: 500;
  white-space: nowrap;   /* ⚠️ 버튼 라벨은 절대 줄바꿈 금지("기 록"·"올 리 기" 방지) */
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.btn:hover { background: var(--surface-2); }
.btn:active { transform: scale(.98); }
.btn-primary {
  background: var(--primary); border-color: var(--primary); color: #fff;
  box-shadow: 0 1px 2px rgba(35,32,27,.08);
}
.btn-primary:hover { background: var(--primary-d); border-color: var(--primary-d); }
.btn-danger { background: var(--surface); border-color: var(--danger); color: var(--danger); }

/* ── 탈퇴 화면(withdraw.php) 전용 ── 2026-09-06 사장님 지시.
   "그만두기"가 43.8×15px 잔글씨 링크였다 — 되돌릴 수 없는 삭제 옆에서 물러날
   유일한 길이 애플 최소 탭 영역(44×44pt, 이 앱 기준 --tap 48px)에 한참 못 미쳤다.
   dream21 회원탈퇴 화면과 같은 판단: **되돌릴 수 없는 동작 앞에서는 물러날 길이
   더 눈에 띄어야** 사고가 준다 — 그래서 여기서만 무게를 뒤집는다.
   ⚠️ 전역 `.btn.btn-danger`(아래 confirm-dlg 용, 사진·일기 삭제 확인 모달)는 안
   건드린다 — 이름을 분리해 그 모달들이 안 딸려오게 한다. */
.withdraw-danger {
  background: var(--surface); border-color: var(--danger); color: var(--danger);
}
.withdraw-danger:hover { background: var(--surface-2); }
.withdraw-cancel { display: flex; width: 100%; margin-top: .6rem; }

/* 다크모드에서 「그만두기」 글자가 안 읽히는 것 — 2026-09-06, kidstep-review 가 실측(대비 1.7:1).
   왜: .btn-primary 는 글자를 흰색으로 고정하는데, 다크 토큰의 --primary 는 «밝은» 살구색
   (#FFB4A5)이다. 밝은 바탕에 흰 글자라 라벨이 배경에 묻힌다(같은 화면 「탈퇴하기」는 8.9:1).
   이 화면에선 「그만두기」가 «물러날 유일한 길»이라 라벨이 안 읽히면 색을 뒤집은 취지가 무너진다.
   ⚠️ var(--ink) 를 쓰면 안 된다 — 다크에서 밝은 색(#F8EFEA)으로 뒤집혀 «더» 나빠진다.
      어두운 값을 직접 준다.
   ⚠️ 전역 .btn-primary 는 안 건드린다 — 「사용법 보기」 등 다른 화면 단추가 다 바뀐다.
   ⚠️ 다크 정의가 두 벌이다(tokens.css 머리말) — 「어둡게」를 고른 사람(:root[data-theme=dark])과
      「시스템」인 사람(media) 양쪽에 다 넣어야 화면이 안 갈라진다. */
:root[data-theme="dark"] .withdraw-cancel { color: #1E1B18; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .withdraw-cancel { color: #1E1B18; }
}

/* ── 카드 — 크림 위 아이보리 + 층을 준 표면 ──
   ⚠️ 밋밋한 "1px 회색 테두리 + 검은 그림자" 금지. 경계는 배경색과 같은 계열로 두고,
   윗면에 아주 옅은 하이라이트(inset)를 얹어 빛이 위에서 오는 것처럼 만든다. */
.card {
  background: var(--surface);
  /* ⚠️ 명세 §Elevation — 카드에 그림자를 쓰지 않는다. 깊이는 색조 차이와 옅은 경계로 만든다.
     경계는 20~40% 불투명도라 "딱딱한 모서리"가 생기지 않는다. */
  border: 1px solid color-mix(in srgb, var(--line) 40%, transparent);
  border-radius: var(--radius-l); padding: 1.5rem; margin-bottom: 1rem;
  box-shadow: none;
}
.card .caption { display: block; margin-bottom: .35rem; color: var(--ink-2);
                 font-weight: 500; letter-spacing: .01em; }
.card h2 { margin-bottom: .35rem; font-family: var(--serif); font-weight: 500; }
/* quiet(기록 없음) — 그림자·경계 없이 면으로만. 다크에선 경계로 */
.card-quiet { background: var(--surface-2); border-color: transparent; box-shadow: none; }
:root[data-theme="dark"] .card-quiet { background: var(--surface); border-color: var(--line); }

/* ── 화면 제목 ── 2026-08-13. 모든 내용 화면이 같은 자리·같은 크기로 시작한다.
   ⚠️ 세리프 .display 를 안 쓴다 — 그건 로그인·등록완료 같은 순간용이다.
   매일 여는 화면마다 큰 세리프를 쓰면 강조가 흔해져 아무 데도 강조가 안 된다. */
.page-title { font-size: 1.55rem; font-weight: 700; line-height: 1.3;
              letter-spacing: -.015em; margin: .25rem 0 0; }
.page-sub { margin: .3rem 0 0; }
/* 제목 바로 뒤에 오는 칩은 위 여백을 줄인다(제목과 한 덩어리로 읽히게) */
.page-sub + .chips, .page-title + .chips { padding-top: .75rem; }
/* 제목 다음 첫 섹션 헤더는 위 여백을 줄인다 — 기본값(64px)은 제목과 너무 떨어진다 */
.page-title + .sec-head, .page-sub + .sec-head, .chips + .sec-head { margin-top: 1.5rem; }

/* 안내 본문 제목 — 화면 제목(h1)이 위에 따로 있어 이건 h2 다.
   다만 이 화면의 주인공이라 h2 기본값보다 크게 준다. */
.guide-title { font-size: 1.35rem; font-weight: 700; line-height: 1.35;
               letter-spacing: -.015em; margin-top: 1.25rem; }

/* ── 달력(월 격자) ── 2026-08-13.
   ⚠️ 7칸을 `1fr` 로만 나눈다(최소폭 금지) — 320px 폰에서 한 칸이 40px 남짓인데
   min-width 를 주는 순간 가로로 밀린다("가로로는 안 움직인다" 원칙). */
.cal-head { display: flex; align-items: center; justify-content: center; gap: 1rem; margin: .5rem 0 .75rem; }
.cal-head strong { font-size: 1.05rem; font-weight: 600; }
.cal-nav { display: inline-flex; align-items: center; justify-content: center;
           width: 2.5rem; height: 2.5rem; border-radius: 999px; color: var(--ink-2); font-size: 1.3rem; }
.cal-nav:hover { background: var(--surface-2); }

/* ⚠️ `1fr` 이 아니라 `minmax(0,1fr)` 이다. `1fr` 은 최소값이 auto(=콘텐츠 크기)라
   좁은 폰에서 앞쪽 칸들이 제 콘텐츠 폭을 우선 차지하고 **마지막 칸만 찌그러진다**
   (320px 실측: 40px×6 + 25.7px). minmax(0,…) 가 그 최소값을 0으로 풀어 7칸을 똑같이 나눈다.
   ⚠️ 요일 머리글은 **격자를 따로 쓴다** — 같은 격자에 넣으면 날짜 칸 행 높이가
   머리글과 엮여 한 줄만 키가 달라진다(실측: 한 칸이 96px). */
.cal, .cal-wds { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: .15rem; }
.cal { grid-auto-rows: 2.75rem; }
.cal-wd { text-align: center; font-size: .72rem; color: var(--ink-3); padding: .25rem 0 .4rem; }
.cal-wd.sun, .cal-cell.sun .num { color: var(--primary); }
/* ── 공휴일 (2026-08-23) ─────────────────────────────────────────────────
   사용자 지시: "달력에 공휴일이 있었으면 좋겠어".
   ⚠️ **일요일과 같은 빨간색**을 쓴다. 색을 새로 만들면 "이 빨강과 저 빨강이 뭐가 다르지"가
      되고, 실제로 뜻이 같다(쉬는 날).
   ⚠️ 이름은 칸이 좁아 **반드시 한 줄로 자른다.** 안 자르면 "부처님오신날"이 칸을 밀어
      그 주 전체가 높아진다 — 「참여중」 띠에서 겪은 것과 같은 자리(2026-08-22). */
.cal-cell.hol .num { color: var(--primary); }
.cal-hol {
  max-width: 100%; font-size: .55rem; line-height: 1.1; color: var(--primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cal-cell.sel .cal-hol { color: #fff; }      /* 고른 날은 채워지니 흰 글씨 */
.cal-cell {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .12rem; min-height: 2.75rem; border-radius: var(--radius);
  color: var(--ink); font-size: .9rem; min-width: 0;
}
.cal-cell.empty { pointer-events: none; }
.cal-cell:hover { background: var(--surface-2); }
/* 오늘 = 은은한 테두리 / 고른 날 = 채움. 둘이 겹쳐도 읽힌다 */
.cal-cell.today { box-shadow: inset 0 0 0 1px var(--line); font-weight: 600; }
/* 기간이 걸친 날(시험·방학) — 점이 아니라 **면**으로 칠한다.
   ⚠️ 6주 방학을 점으로 찍으면 한 달이 점으로 뒤덮여 "무슨 날인지" 구분이 사라진다.
   ⚠️ .sel 보다 **먼저** 선언한다 — 고른 날은 채움색이 이겨야 한다. */
.cal-cell.span { background: var(--accent-l); }
.cal-cell.sel { background: var(--primary); color: #fff; font-weight: 600; }
.cal-cell.sel .num, .cal-cell.sel.sun .num { color: #fff; }
/* 점 — 할 게 남았으면 강조색, 다 끝났으면 옅게. ⚠️ 개수를 숫자로 쓰지 않는다(재촉이 된다) */
.cal-dot { width: .3rem; height: .3rem; border-radius: 999px; background: var(--accent); }
.cal-dot.done { background: var(--line); }
.cal-cell.sel .cal-dot { background: #fff; }

/* 안내 목록(학사 일정 등) — 카드 안에서 읽는 줄글. 점은 옅게, 줄간격은 넉넉히 */
.note-list { margin: .5rem 0 0; padding-left: 1.1rem; }
.note-list li { margin-bottom: .4rem; color: var(--ink-2); font-size: .92rem; line-height: 1.6; }
.note-list li::marker { color: var(--line); }

/* ── 자녀 열람 화면 ── 부모 화면의 축소판이 아니다. 탭바가 없어 아래 여백을 직접 준다. */
body.kid { padding-bottom: 3rem; }
.kid-wrap { max-width: 34rem; margin: 0 auto; }

/* ── 섹션 헤더 ── */
.sec-head { display: flex; align-items: baseline; gap: .75rem;
            margin: var(--stack-lg) .25rem 1rem; }
.sec-head h2 { font-size: .8rem; color: var(--ink-2); flex: 1; font-weight: 600;
               text-transform: uppercase; letter-spacing: .06em; }
.sec-act { font-size: .78rem; color: var(--primary); min-height: auto; font-weight: 500; }

/* ── 생년월일 년·월·일 드롭다운 ── 2026-08-13.
   모바일 기본 달력이 오늘 달부터 열려 먼 과거를 못 고른다는 제보로 신설(view.php 머리말). */
.ymd { display: flex; gap: .5rem; }
.ymd select {
  flex: 1; min-width: 0; min-height: 3rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink); font: inherit;
}
.ymd select:first-child { flex: 1.4; }   /* 연도는 네 자리라 조금 넓게 */

/* ⚠️ 2026-08-19 사장님 지적("1982 뒤의 년과 10 뒤의 월이 잘려", 갤럭시 실물).
   원인 둘:
   ① **세기 싸움에서 졌다.** 위 `.ymd select` 와 아래 `.field select`(633줄 근처)는
      세기가 같은데(클래스 1 + 요소 1) `.field select` 가 **뒤에 있어 이긴다.**
      그래서 여백이 의도한 .5rem 이 아니라 **1.1rem** 으로 잡혔다(실측 17.6px).
      ⚠️ 위 규칙만 고치면 또 진다 — 세기를 올려야 한다.
   ② **기본 드롭다운 화살표가 글자를 덮는다.** 화살표는 여백 안쪽에 그려지는데
      브라우저마다 크기가 다르다 — 데스크톱 Chromium 에선 안 잘리고 갤럭시에서만 잘렸다.
      그래서 **오른쪽에 화살표 자리를 따로 비운다**(1.7rem). 왼쪽은 좁혀 글자 자리를 번다.
   ⚠️ 내 화면에서 멀쩡하다고 넘어가면 안 되는 자리다. 사장님 폰이 정본이다. */
.field .ymd select,
.ymd select {
  /* ⚠️ **기본 화살표를 안 쓴다**(appearance:none) — 2026-08-19 두 번째 손질.
     처음엔 오른쪽 여백만 넓혀 화살표 자리를 비웠는데, 그건 **화살표 크기를 짐작한 것**이다.
     기본 화살표는 브라우저·기기마다 다르게 그려지고 **내 화면에선 재현이 안 된다** —
     사장님 갤럭시에선 잘리는데 데스크톱 Chromium 에선 멀쩡했다.
     짐작으로 여백을 맞추는 대신 **변수를 없앤다**: 우리가 그린 화살표만 쓴다.
     그러면 어느 기기에서 열든 같은 그림이고, 자리도 정확히 우리가 아는 만큼만 먹는다. */
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding: 0 2rem 0 .75rem;     /* 오른쪽 2rem = 우리 화살표(.6rem) + 여백 */
  font-size: 1rem;
  width: auto;                  /* .field select 의 width:100% 를 되돌린다(flex 가 정한다) */
  text-overflow: ellipsis;      /* 그래도 좁으면 조용히 자르지 말고 …으로 알린다 */
  background-image: var(--sel-arrow);
  background-repeat: no-repeat;
  background-position: right .7rem center;
  background-size: .62rem .62rem;
}
/* ⚠️ 화살표 색은 테마마다 다르다 — 배경 이미지엔 currentColor 가 안 먹어서 두 벌을 둔다.
   ⚠️ 한쪽만 고치면 다른 테마에서 화살표가 배경에 묻힌다(밝은 화면의 흰 화살표처럼). */
:root { --sel-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><path d='M2 4.5L6 8.5L10 4.5' fill='none' stroke='%237E6763' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>"); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --sel-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><path d='M2 4.5L6 8.5L10 4.5' fill='none' stroke='%23A08D87' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>"); }
}
:root[data-theme="dark"] { --sel-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><path d='M2 4.5L6 8.5L10 4.5' fill='none' stroke='%23A08D87' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>"); }

/* ── 아이콘 타일 그리드(앱 화면) ──
   2026-08-13: "태아부터 청소년까지 통합할 수 있는 메뉴" 요청 + 코레일톡 이동·편의
   탭 참고 이미지. ⚠️ 하단 탭 5개는 그대로 두고(IA-SCREENS "탭 고정") 이 그리드는
   탭 안이 아니라 앱 화면 하나에서 기능 입구를 한눈에 모아 보여주는 용도다.
   (처음엔 더보기에 뒀다가 2026-08-13 앱 탭으로 옮겼다.) */
/* ⚠️ 4열이 아니라 3열이다 — 좁은 폰(320px)에서 4열은 타일이 눌려 라벨이 깨지고
   가로 스크롤을 만들 수 있다. 3열이면 타일 6개가 정확히 2줄로 떨어지기도 한다. */
.tilegrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem .5rem; margin: .5rem 0 var(--stack-lg); }
.tile { display: flex; flex-direction: column; align-items: center; gap: .45rem; text-align: center; color: var(--ink); }
.tile-ico {
  width: 3.25rem; height: 3.25rem; border-radius: 999px; background: var(--surface-2);
  display: flex; align-items: center; justify-content: center; color: var(--primary);
  transition: background var(--dur) var(--ease);
}
.tile:active .tile-ico { background: var(--primary-l); }
/* 이모지 타일 — 2026-08-23. ⚠️ **글꼴이 정하는 여백을 우리가 못 정한다**(SVG 와 다른 점).
   `line-height:1` 로 눌러 두고 크기만 맞춘다. 안 그러면 원 안에서 아래로 처진다.
   ⚠️ 기기마다 그림이 다르다(애플·삼성·구글이 각자 그린다) — 그게 이 갈래의 대가다. */
/* ⚠️⚠️ **세기 싸움에서 진 자리다**(2026-08-23 실측으로 잡았다). `.tile-emo`(클래스 1)는
   바로 위 `.tile span`(클래스 1 + 요소 1)에 **밀린다** — 그래서 이모지가 26px 가 아니라
   **라벨 크기 12.5px** 로 그려지고 있었다. 화면만 봐선 "작네" 로 지나칠 자리다.
   ⇒ `.tile-ico .tile-emo`(클래스 2)로 올린다. `!important` 를 쓰지 않는 이유는
      이 파일이 이미 같은 싸움을 `.ymd select` 에서 겪고 세기로 푼 전례가 있어서다. */
.tile-ico .tile-emo {
  font-size: 1.65rem; line-height: 1; font-style: normal;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
.tile span { font-size: .78rem; color: var(--ink-2); line-height: 1.3; }
/* 타일 설명 한 줄 — 2026-08-14 사용자 지시("메뉴에 대한 설명이 있었으면").
   ⚠️ 라벨보다 **작고 옅게**. 같은 크기면 두 줄짜리 라벨로 읽혀 무엇이 이름인지 흐려진다.
   ⚠️ 3열 그리드라 길면 접힌다 — 짧게 쓰는 건 문구 쪽 규칙이고(apps.php 주석),
      그래도 넘칠 때를 대비해 줄바꿈은 허용한다(잘라내면 뜻이 사라진다). */
/* 타일 배지(느낌표) — 2026-08-14 사용자 지시("수정사항이 있으면 오른쪽 위에 느낌표").
   ⚠️ **알림이 가리키는 화면에만** 붙인다 — 아무 데나 붙이면 배지가 흔해져 아무 뜻이 없다.
   ⚠️ 숫자를 안 쓴다. 몇 건인지는 오늘 화면의 "새로운 소식"이 말하고, 여기서는
      "볼 것이 있다"만 알리면 된다(같은 정보를 두 곳에서 세면 언젠가 어긋난다). */
.tile-ico { position: relative; }
.tile-dot {
  position: absolute; top: -.15rem; right: -.15rem;
  min-width: 1.15rem; height: 1.15rem; padding: 0 .25rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px; background: var(--primary); color: #fff;
  font-size: .72rem; font-weight: 700; line-height: 1;
  border: 2px solid var(--bg);   /* 배경과 떨어져 보이게 — 아이콘 원에 붙으면 얼룩처럼 보인다 */
}
.tile-sub { font-size: .66rem; color: var(--ink-3); line-height: 1.25; margin-top: -.2rem;
            word-break: keep-all; }
/* 타일 모양의 **동작**(로그아웃) — 2026-08-14 사용자 지시("로그아웃도 해당 모양으로").
   ⚠️ <a> 가 아니라 <button> 이다. 로그아웃은 POST + CSRF 로만 받는다 — 링크로 두면
      남의 사이트에 심은 <img src=".../logout.php"> 하나로 로그아웃당한다.
      모양은 같게, 오는 길은 다르게. */
button.tile { border: 0; background: none; font: inherit; padding: 0; width: 100%; cursor: pointer; }

/* 작은 표식(일기 공개범위·필수/선택 등) — 강조가 아니라 사실 표기라 채도를 낮게 */
.pill { font-size: .7rem; padding: .1rem .5rem; border-radius: 999px;
        background: var(--info-l); color: var(--info); font-weight: 600; }
.pill-req { background: var(--primary-l); color: var(--primary); }
.pill-opt { background: var(--surface-2); color: var(--ink-2); }

/* ── 일기 첨부 사진 ── 글에 딸린 것이라 크게 펼치지 않는다(글이 본체다) */
.diary-pics { display: flex; flex-wrap: wrap; gap: .4rem; width: 100%; margin-top: .15rem; }
.diary-pics img { width: 6.5rem; height: 6.5rem; object-fit: cover;
                  border-radius: var(--radius); border: 1px solid var(--line); }

/* 사진 한 장 + 지우기 — 2026-08-14.
   ⚠️ 사진 **위에 겹치는 X 단추를 안 쓴다.** 아이 얼굴 위에 삭제 아이콘이 얹히는 것도
      그렇고, 손가락이 큰 화면에서 사진을 보려다 잘못 누르기 쉽다. 사진 아래 조용한
      글씨 한 줄이면 실수로 눌릴 일이 없다.
   ⚠️ figure 의 기본 margin 을 0 으로 — 안 그러면 브라우저 기본 여백(1em 40px)이 붙어
      그리드가 통째로 벌어진다. */
.pic { margin: 0; display: flex; flex-direction: column; align-items: center; gap: .15rem; }
.pic-del { margin: 0; line-height: 1; }
.pic .cmt-del { font-size: .68rem; }

/* ── 일기 가족 댓글 ── 2026-08-13. 채팅이 아니라 "이 기록에 붙는 한마디"라서
   말풍선·시간·읽음표시를 만들지 않는다. 그런 걸 붙이는 순간 카톡 흉내가 된다. */
.cmts { width: 100%; margin-top: .5rem; padding-left: .75rem;
        border-left: 2px solid var(--line); display: flex; flex-direction: column; gap: .4rem; }
.cmt { display: flex; gap: .45rem; align-items: baseline; font-size: .88rem; }
.cmt-who { color: var(--ink-3); font-size: .78rem; flex: 0 0 auto; }
.cmt-body { color: var(--ink-2); flex: 1; }
.cmt-del { background: none; border: 0; color: var(--ink-3); font-size: .72rem;
           padding: 0 .2rem; text-decoration: underline; }
.cmt-form { display: flex; gap: .4rem; width: 100%; margin-top: .5rem; }
.cmt-form input {
  flex: 1; min-height: 2.5rem; padding: 0 .75rem; font-size: .88rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); color: var(--ink);
}
.cmt-form .btn { min-height: 2.5rem; padding: 0 .9rem; flex: 0 0 auto; }

/* ── 자녀 칩 ── ⚠️ 2026-08-13: 가로 스크롤(overflow-x:auto)을 걷어내고 줄바꿈으로.
   사용자 지시 "폰으로 볼 때 슬라이딩이나 화면 전체가 움직이는 상황은 최대한 없애줘".
   자녀가 여럿이면 칩이 옆으로 밀려 나가 손가락으로 밀어야 보였는데(안 밀면 뒤쪽
   자녀가 있는 줄도 모른다), 이제 다 보인다. guide.php 의 주차·시기 이동 nav 도
   같은 .chips 라 함께 고쳐진다. sticky 첫 칩도 뗀다 — 가로 스크롤이 없으면 붙일 이유가 없다. */
.chips { display: flex; flex-wrap: wrap; gap: .5rem; padding: .25rem 0 1rem; }
.chips a {
  flex: 0 0 auto; display: flex; align-items: center;
  min-height: 2.5rem; padding: 0 1.05rem;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink-2); font-size: .9rem; font-weight: 500;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.chips a.on { background: var(--primary); border-color: var(--primary); color: #fff; }

/* ── 일정 행 ── */
.sched { border: 1px solid color-mix(in srgb, var(--line) 40%, transparent);
         border-radius: var(--radius-l); background: var(--surface); overflow: hidden; }
.sched-row {
  display: flex; align-items: center; gap: .875rem;
  min-height: 3.75rem; padding: .75rem 1.25rem;
  /* 명세 §Lists — 구분선이 컨테이너를 가로지르지 않게 안쪽으로 들인다 */
  border-bottom: 1px solid transparent;
  background-image: linear-gradient(to right,
      color-mix(in srgb, var(--line) 55%, transparent),
      color-mix(in srgb, var(--line) 55%, transparent));
  background-size: calc(100% - 2.5rem) 1px;
  background-position: 1.25rem 100%;
  background-repeat: no-repeat;
}
.sched-row:last-child { background-image: none; }
.sched-row:hover { background: color-mix(in srgb, var(--surface-2) 50%, transparent); }
.sched-date { flex: 0 0 3.75rem; font-size: .82rem; color: var(--ink-2); font-weight: 500; }
.sched-title { flex: 1; }

/* ── 안내 항목 줄(달력 「안내 항목」) ── 2026-08-23 사장님 폰 실측으로 다시 짰다.
   화면엔 제목이 「성하늘 ·」「Tdap」「6차·」「일본뇌염」… 처럼 **조각조각** 접혀 있었다.
   ⚠️ 원인은 글자가 아니라 **자리다.** `체크`·`날짜` 두 단추가 `.btn` 기본 좌우여백(각 32px)
      으로 200px 을 먹어, 360px 폰에서 제목에 **70px** 밖에 안 남았다.
   ⚠️ 2026-08-21 시간표의 「한 글자씩 눌러 넣기」와 뿌리가 같다(flex 의 min-content 바닥).
      거기선 단추를 ✏️🗑️ 이모지로 줄여 한 줄을 지켰는데, **여기는 그 길이 안 통한다** —
      제목이 「Tdap 6차·일본뇌염 5차·HPV(만 11~12세)」처럼 접종 여럿이 한 항목이라 훨씬 길고,
      「체크」·「날짜」는 이모지로 바꾸면 무슨 뜻인지 알 수 없다(✏️ 와 달리 관용 기호가 없다).
   ⇒ 줄이지 말고 **세로로 쌓는다** — 이 파일의 원칙 그대로("가로로 늘어놓을 자리가 없으면
      세로로 쌓는다"). 제목이 한 줄을 통째로 쓰고, 단추 둘이 아래에서 반씩 나눠 선다.
   ⚠️ 단추를 반씩 나눈 이유: 오른쪽에 몰아 두면 폭이 글자 수 따라 들쭉날쭉해 목록이
      목록으로 안 읽힌다. 반반이면 손가락 자리도 커진다. */
.sched-plan { flex-wrap: wrap; gap: .5rem .875rem; }
/* ⚠️ `flex-basis: 100%` 라 이 줄에선 min-width:0 이 안전하다 — 혼자 한 줄을 쓰므로
   2026-08-21 의 「한 글자 폭까지 눌리는」 상황이 아예 생기지 않는다. */
.sched-plan .sched-title { flex: 1 1 100%; min-width: 0; }
.sched-plan-act { display: flex; gap: .5rem; flex: 1 1 100%; }
.sched-plan-act > .btn { flex: 1 1 0; min-width: 0; min-height: 2.5rem; padding: 0 .75rem; }
/* 설정 목록 한 줄 — "라벨 … 값 ›". 2026-08-14 개인 관리를 목록+상세로 다시 짜면서.
   ⚠️ 값이 길면(메일 주소) 줄을 넘기지 말고 **잘라서 말줄임** — 넘기면 줄 높이가
      들쭉날쭉해져 목록이 목록으로 안 읽힌다. 전체 값은 누르고 들어가면 보인다. */
/* ⚠️ 줄 전체가 <a> 라 기본 링크색(코랄)이 따라온다 — 그대로 두면 라벨 다섯 개가
   전부 강조색이 되어 **무엇이 중요한지 안 보인다**(2026-08-14 화면 실측).
   누를 수 있다는 신호는 오른쪽 화살표가 이미 하고 있다. */
a.sched-row { color: var(--ink); }
.row-label { flex: 0 0 auto; font-weight: 500; }
.row-val { flex: 1; min-width: 0; text-align: right; color: var(--ink-2);
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-val small { display: block; white-space: nowrap; }
/* 고친 말 표시 — ⚠️ **시간 앞**에 둔다(카카오톡과 같은 자리). 뒤에 두면 시간과 붙어
   "17:34 수정됨"이 한 덩어리로 읽혀 몇 시에 고쳤다는 말처럼 보인다.
   ⚠️ 옅게 — 사실 표기지 강조가 아니다. */
.chat-edited { font-size: .68rem; color: var(--ink-3); }
/* 고치는 중 — 입력칸 바로 위. 왜 옛 글이 들어와 있는지 말해 준다 */
/* ── 고치는 중 알림줄 ── 입력칸 바로 위. 카카오톡의 "메시지 수정" 줄과 같은 자리.
   ⚠️ 2026-08-14 사용자 지적("고치는 중 · 그만두기는 뭐야?")으로 다시 짰다 —
      그만두기가 **글자 링크**라 눌러야 할 것으로 안 보였고, 무엇을 고치는 중인지도
      안 보였다. 지금은 ✕ 단추로 두고, 고치는 말은 위에서 .chat-row.editing 이 짚는다. */
.chat-editing { position: fixed; left: 0; right: 0; z-index: 11;
  bottom: calc(4rem + env(safe-area-inset-bottom) + var(--compose-h, 4.25rem));
  max-width: 40rem; margin: 0 auto; padding: .35rem .5rem;
  background: var(--primary-l); color: var(--ink-2); font-size: .78rem;
  display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
/* ⚠️ 손가락으로 누르는 것이라 글자 크기와 별개로 2rem 을 확보한다 */
.chat-editing-x { flex: none; width: 2rem; height: 2rem; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-2); text-decoration: none; font-size: .9rem; line-height: 1; }
.chat-editing-x:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); }

/* 지금 고치는 중인 말 — 어느 것인지 눈으로 짚어 준다(위 알림줄과 한 쌍이다).
   ⚠️ 배경색을 바꾸지 않는다. 내 말/남의 말 색이 이미 의미를 지고 있어서, 거기에
      또 색을 얹으면 "지워진 말"·"안 읽음"과 뒤섞인다. 테두리만 두른다. */
.chat-row.editing .chat-bubble {
  outline: 2px solid var(--primary); outline-offset: 2px; }

/* ── 사진 크게 보기 ── 2026-08-14. 대화의 사진을 **떠나지 않고** 크게 본다.
   ⚠️ 배경을 아주 어둡게 한다 — 사진을 보는 자리라 주변이 밝으면 눈이 분산된다.
   ⚠️ 닫기 단추는 **오른쪽 위 고정**. 사진 크기가 제각각이라 사진에 붙이면 자리가 매번 다르다. */
dialog.lightbox {
  border: 0; padding: 0; background: transparent;
  width: 100vw; height: 100dvh; max-width: 100vw; max-height: 100dvh;
}
/* ⚠️ **`display` 는 열렸을 때만 준다.** 닫힌 <dialog> 는 브라우저 기본값
   `display:none` 으로 숨는데, 여기에 `display:flex` 를 무조건 걸면 그 기본값을 이겨서
   **닫아도 사진이 그대로 남는다**(2026-08-14 사용자 제보 "X를 눌러도 사진만 남아있어").
   ⚠️ 이모지 팔레트에서 겪은 것과 **같은 함정**이다 — 그때는 `hidden` 을 `display:grid` 가
      이겼다(.emoji-pad[hidden] 참고). 감추는 기본값을 덮어쓰는 속성은 늘 이 사고를 낸다. */
dialog.lightbox[open] { display: flex; align-items: center; justify-content: center; }
dialog.lightbox::backdrop { background: rgba(0, 0, 0, .88); }
dialog.lightbox img { max-width: 96vw; max-height: 88dvh; object-fit: contain; border-radius: var(--radius); }
.lightbox-x {
  position: fixed; top: calc(.75rem + env(safe-area-inset-top)); right: .75rem;
  width: 2.5rem; height: 2.5rem; border: 0; border-radius: 999px;
  background: rgba(255, 255, 255, .16); color: #fff; font-size: 1.1rem; line-height: 1;
}

/* ── 팝업(바텀시트) ── 2026-08-14 사용자 지시("버튼을 누를 경우 팝업이 떠서 변경").
   ⚠️ 네이티브 <dialog> 를 쓴다 — 배경 비활성·Esc·초점 가두기를 브라우저가 해 준다.
      직접 만들면 그 셋 중 하나는 반드시 빠지고, 빠진 게 접근성이다.
   ⚠️ 폰에서는 **아래에 붙인다**(바텀시트). 손가락이 닿는 곳이 아래쪽이고, 가운데에
      띄우면 키보드가 올라올 때 팝업이 위로 밀려 제목이 화면 밖으로 나간다. */
dialog.sheet {
  border: 0; padding: 0; background: transparent;
  width: 100%; max-width: 34rem; max-height: 92dvh;
  margin: auto auto 0;                 /* 아래 붙임 */
}
dialog.sheet::backdrop { background: rgba(35, 32, 27, .45); }
.sheet-box {
  background: var(--surface); color: var(--ink);
  border-radius: var(--radius-l) var(--radius-l) 0 0;
  padding: 1.25rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom));
  max-height: 92dvh; overflow-y: auto;
}
.sheet-head { display: flex; align-items: center; justify-content: space-between;
              gap: 1rem; margin-bottom: 1.25rem; }
.sheet-head h2 { font-size: 1.2rem; }
.sheet-x { border: 0; background: none; color: var(--ink-2);
           font-size: 1.1rem; line-height: 1; padding: .5rem; margin: -.5rem; }
/* ⚠️ **아래에 붙이는 것은 "폰일 때"만이다**(2026-08-14 사용자 지적 "일부러 저렇게 아래에
   뜨게 해둔거야?" — 959px 창에서 좁은 카드가 화면 아래에 붙어 있었다).
   바텀시트의 근거는 **엄지**다. 마우스로 쓰는 폭에서는 근거가 사라지고, 넓은 화면 아래쪽에
   좁은 카드만 붙어 있으면 잘린 것처럼 보인다.
   ⚠️ 이 앱은 분기를 1024px 하나로 쓰는데(DESIGN-SYSTEM) 여기만 640px 을 더 둔다 —
      1024px 은 **사이드바가 생기는 폭**이고, 이 판단의 기준은 **손이 닿는 폭**이라
      같은 선을 쓸 이유가 없다. 실제로 그 사이(640~1024)가 어긋나 있었다. */
@media (min-width: 640px) {
  dialog.sheet { margin: auto; max-width: 26rem; }
  .sheet-box { border-radius: var(--radius-l); }
}
/* 데스크톱 — 사이드바 자리를 빼고 가운데.
   ⚠️ **뷰포트가 아니라 "내용 칸" 가운데다.** 데스크톱은 왼쪽 15rem 이 사이드바라,
      뷰포트 기준으로 가운데를 잡으면 팝업이 **사이드바 쪽으로 120px 밀려** 내가 누른
      줄과 어긋난 자리에 뜬다(2026-08-14 사용자 지적 "팝업 위치가 이상한데?").
   ⚠️ 2026-08-14 로그인 화면이 오른쪽으로 밀렸던 것과 **똑같은 함정**이다 —
      그때는 사이드바 자리를 안 비워서, 이번엔 비운 자리를 안 빼서. 15rem 이 등장하는
      곳(body.padding-left · .tabbar.width · 여기)은 늘 같이 움직여야 한다.
   ⚠️ 탭바가 없는 화면(body.no-tabs)에는 그 자리가 없으니 되돌린다 — 지금 팝업은
      account.php 에만 있어 해당이 없지만, 다음에 로그인 계열 화면에 팝업을 두면 걸린다. */
@media (min-width: 1024px) {
  dialog.sheet { left: 15rem; right: 0; }
  body.no-tabs dialog.sheet { left: 0; }
}
/* 오른쪽 화살표 — i-back 을 뒤집어 쓴다(같은 세트·같은 획. icons.php 머리말 참고) */
.row-go { flex: 0 0 auto; width: 1.1rem; height: 1.1rem;
          color: var(--ink-3); transform: rotate(180deg); }
.sched-dday { font-size: .75rem; color: var(--primary); font-weight: 600;
              background: var(--primary-l); padding: .15rem .5rem; border-radius: 999px; }

/* ── 3상태 체크 ── */
.check3 { display: flex; gap: .5rem; margin-top: .875rem; }
.check3 button, .check3 form button {
  flex: 1; min-height: var(--tap);
  /* 아이콘이 들어와도 글자와 같은 줄에 가운데로 — 2026-08-13 설정 화면(밝게/어둡게) */
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink-2); font-weight: 500;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
/* 버튼 안 아이콘은 글자 크기에 맞춰 조금 작게(기본 1.5rem 은 라벨을 눌러 보인다) */
.check3 button .ico, .btn .ico { width: 1.15rem; height: 1.15rem; }
/* 같은 자리에 이모지가 오면 **아이콘과 같은 크기**로(2026-08-23 밝기 고르기).
   ⚠️ 세기를 `.check3 button .emo`(클래스 2 + 요소 1)로 잡는다 — `.tile-emo` 를 클래스
      하나로 뒀다가 `.tile span` 에 밀린 적이 있다(그 주석 참고). */
.check3 button .emo, .btn .emo { font-size: 1.15rem; line-height: 1; }
.btn { gap: .4rem; }
.check3 button:hover { background: var(--surface-2); }
.check3 button.on { background: var(--primary); border-color: var(--primary); color: #fff; }

/* 라디오를 같은 모양의 버튼으로 — 개인 관리의 보호자 구분(2026-08-14).
   ⚠️ 라디오를 `display:none` 으로 감추지 않는다 — 그러면 **탭 키로 못 고른다.**
      투명하게 겹쳐 두면 초점은 그대로 받는다(그래서 focus 표시도 따로 그린다).
   ⚠️ 선택 상태는 :has() 로 그린다. 못 쓰는 브라우저에서는 테두리가 안 바뀔 뿐
      고르는 것 자체는 된다(기능이 죽지 않는다). */
/* ⚠️ `.check3 label` 이 아니라 `label.opt` 다 — 아래 `.field label { display:block }` 이
   **나중에 나와서 이깄다.** 폼 안에 넣으면 라디오가 세로로 쌓여 버린다(같은 특이도면
   순서가 이긴다). 클래스를 하나 더 붙여 특이도로 못 박는다. */
.check3 label.opt {
  flex: 1; min-height: var(--tap); position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink-2); font-weight: 500; cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
label.opt input { position: absolute; opacity: 0; width: 1px; height: 1px; }
label.opt:has(input:checked) { background: var(--primary); border-color: var(--primary); color: #fff; }
label.opt:has(input:focus-visible) { outline: 2px solid var(--primary); outline-offset: 2px; }

/* ── 출처·면책 ── */
.src-note {
  margin-top: 1.25rem; padding-top: .875rem;
  border-top: 1px solid var(--line);
  font-size: .75rem; color: var(--ink-3); line-height: 1.6;
}

/* ── 빈 상태 — 넉넉한 여백 ── */
.empty { text-align: center; padding: 3rem 1.25rem; color: var(--ink-2); }
.empty p { margin-bottom: .5rem; font-size: 1.02rem; }
.empty .btn { margin: 1.25rem auto 0; max-width: 20rem; }

/* ── 주차 막대(StatBar) — 코랄 그라데이션 ── */
.statbar { height: .5rem; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.statbar > span {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent), var(--primary));
}

/* =========================================================================
 * 인사 히어로 — 이 서비스의 얼굴. 정체성 세 요소가 여기 모인다:
 *   ① 큰 세리프 주차 숫자(감정의 무게)  ② 여정 궤적 + 켜진 노드(로고와 같은 언어)
 *   ③ 넉넉한 여백(설렘은 여백과 타이포로 — 요건 그대로)
 * 2026-08-11 신설.
 * ======================================================================= */
.hero { padding: 1.25rem .25rem 1.75rem; }
/* 날짜 — ink-3 는 크림 배경 위 4.05:1 로 AA 미달(실측). ink-2 로 5.2:1 확보 */
.hero-date { color: var(--ink-2); font-size: .82rem; letter-spacing: .02em; margin-bottom: .85rem; }
.hero-greet {
  font-size: 1.85rem; font-weight: 700; line-height: 1.25; letter-spacing: -.02em; color: var(--ink);
}
.hero-greet span { font-weight: 400; color: var(--ink-2); }   /* "…를 기다리는 중" 은 약하게 */

/* 정체성 숫자 — 세리프 큰 주차. "주" 는 중간, "N일" 은 조용히 */
.hero-week {
  display: flex; align-items: baseline; gap: .3rem;
  margin-top: 1.05rem; font-family: var(--serif);
  font-size: 1.5rem; color: var(--primary); letter-spacing: -.01em;
}
.hero-week b { font-size: 4rem; font-weight: 600; line-height: .92; }
.hero-week em {
  font-style: normal; font-family: var(--sans); font-weight: 500;
  font-size: 1.25rem; color: var(--ink-2);
}
/* "만" — 나이 앞의 한정어. ⚠️ 큰 숫자와 같은 크기로 두면 flex 한 칸을 차지해
   숫자가 왼쪽 끝에서 밀린다. 태아 화면은 주차가 왼쪽 끝에서 시작하는데, 칩으로
   아이를 바꿀 때마다 그 시작점이 어긋나 보인다. 작고 조용하게 두어 큰 숫자가
   왼쪽을 잡게 한다(2026-08-16). */
.hero-week i {
  font-style: normal; font-family: var(--sans); font-weight: 500;
  font-size: .95rem; color: var(--ink-2); align-self: center;
}

/* "전체" 화면의 **무엇까지인가** — 큰 숫자 위에 조용히 놓인다(2026-08-16).
   ⚠️ 아이별 화면의 `.hero-sub`(숫자 아래)와 자리가 반대다. 여기선 "콩이 · 만날 날까지"를
      먼저 읽어야 아래 숫자가 무슨 뜻인지 안다 — 숫자부터 보면 137이 뭔지 모른다. */
.hero-next { margin-top: 1.05rem; color: var(--ink-2); font-size: .9rem; }
.hero-next + .hero-week { margin-top: .15rem; }

.hero-bar { margin-top: 1.15rem; height: .4rem; }
.hero-sub { margin-top: .95rem; color: var(--ink-2); font-size: .9rem; }

/* 여정 궤적의 '켜진 노드' — 로고와 같은 언어. 홈 히어로와 guide 상세가 공유(.trail).
   overflow 를 열어 노드가 막대 밖으로 나온다. */
.statbar.trail { overflow: visible; position: relative; }
.statbar.trail > span { position: relative; overflow: visible; }
.statbar.trail > span::after {
  content: ""; position: absolute; right: -.3rem; top: 50%; transform: translateY(-50%);
  width: .9rem; height: .9rem; border-radius: 999px;
  background: var(--primary); border: 3px solid var(--surface);
  box-shadow: 0 0 0 1px var(--line), 0 2px 7px rgba(187,90,56,.4);
}
/* guide 상세의 여정 궤적 — 캡션과 제목 사이 */
.trail-line { margin: .9rem 0 .3rem; }
.trail-cap { color: var(--ink-2); font-size: .8rem; margin-bottom: 1.25rem; }

/* 추모 모드 — 궤적 노드의 빛(그림자) 제거, 무채로 */
.memorial .statbar.trail > span::after { box-shadow: 0 0 0 1px var(--line); }

/* ── 피처 카드 본문(이번 시기) — 이 아이의 '다음 걸음'을 가장 또렷하게 ── */
.feature-body { margin-top: .3rem; color: var(--ink-2); line-height: 1.6; }
.feature-link {
  display: inline-flex; align-items: center; gap: .15rem;
  margin-top: .95rem; color: var(--primary); font-weight: 600; font-size: .9rem;
}

/* ── 안내 블록·플래시 ── */
.note-info {
  background: var(--info-l); border-left: 3px solid var(--info);
  padding: .875rem 1.15rem; border-radius: 0 var(--radius) var(--radius) 0;
  font-size: .86rem; margin: 1.25rem 0; line-height: 1.6;
}
.flash {
  background: var(--primary-l); color: var(--primary-d);
  padding: .875rem 1.15rem; border-radius: var(--radius); margin-bottom: 1.25rem;
  font-weight: 500; box-shadow: var(--shadow);
}

/* ── 폼 ── */
.field { margin-bottom: 1.5rem; }
.field label { display: block; font-weight: 600; margin-bottom: .5rem; font-size: .95rem; }
.field input, .field select {
  width: 100%; min-height: 3.5rem; padding: 0 1.1rem;
  font-size: 1.05rem; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.field input:focus, .field select:focus {
  /* 명세: 두꺼운 글로우 금지 — 테두리만 테라코타로 두꺼워진다 */
  outline: none; border-color: var(--accent); border-width: 2px;
  box-shadow: none;
}
/* ── 음력·윤달 체크칸 (2026-08-18) ──────────────────────────────────────────
   ⚠️ 사장님 지적: "본인의 생년월일을 알려주세요 밑에 음력과 윤달 체크가 이상하게 표기돼".
   원인은 이 위젯(`ns_lunar_fields()`)이 `<div class="field">` **안에** 있는데
   그 안의 규칙이 **체크박스에까지 걸린 것**이다:
     · `.field input { width:100%; min-height:3.5rem; border… }` → 체크박스가 **화면 폭짜리 상자**
     · `.field label { display:block; font-weight:600 }`         → 라벨이 굵게 **세로로** 쌓임
   `.check` 에는 CSS 가 **하나도 없어서**(kidlogin.php 만 인라인 style 로 때우고 있었다)
   아무도 이걸 되돌리지 않고 있었다. 여기서 되돌린다.
   ⚠️ `.field ...` 선택자를 같이 두는 이유 — `.field input` 이 더 구체적이라 그것보다 세야 한다. */
.lunar-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin-top: .6rem; }
.lunar-pick label.check,
.field .lunar-pick label.check {
  display: inline-flex; align-items: center; gap: .45rem;
  margin: 0; font-weight: 400; font-size: .95rem; cursor: pointer;
}
.lunar-pick input[type="checkbox"],
.field .lunar-pick input[type="checkbox"] {
  width: 1.15rem; height: 1.15rem; min-height: 0;
  padding: 0; margin: 0; flex: 0 0 auto; accent-color: var(--primary);
}
/* 설명 줄은 두 체크칸 아래로 — 같은 줄에 끼면 셋이 뒤엉킨다 */
.lunar-pick .hint { flex: 1 0 100%; margin-top: .1rem; }

/* ⚠️ .field 안팎 어디서든 쓴다 — 새 폼(일기·측정)은 .field 로 감싸지 않는다 */
.hint, .field .hint { font-size: .78rem; color: var(--ink-3); margin-top: .45rem; }
.field .live {
  margin-top: .875rem; padding: .875rem 1.15rem;
  background: var(--accent-l); color: var(--primary-d);
  border-radius: var(--radius); font-weight: 600; font-variant-numeric: tabular-nums;
}

/* ── 인증 화면 ── Earth and Cradle: 조용하게, 화면 세로 중앙에 둔다.
   ⚠️ 배경 사진·일러스트를 두지 않는다 — 첫 화면이 조용해야 서비스의 성격이 전해진다.
   2026-08-13: 고정 상단 여백(14dvh) 대신 진짜 세로 중앙 정렬로 — 화면 높이·내용
   길이(에러 문구 유무 등)가 달라도 항상 가운데 있어야 한다는 사용자 지시. */
/* ⚠️ 세로 중앙 정렬은 **로그인·추모 화면에만** 건다(`auth-center`).
   2026-08-13: 처음엔 `body.auth` 전체에 걸었는데("로그인 페이지는 세로 중앙"이라는
   지시를 auth 계열 전부로 넓혀 버렸다), 그 바람에 **온보딩·아이 등록 화면의 내용이
   화면 한가운데까지 밀려 내려갔다**(사용자 폰 스크린샷으로 확인).
   내용이 짧은 입력 화면은 위에서 시작해야 한다 — 다른 탭 화면(오늘·달력…)과 같은 틀이다. */
body.auth-center main.wrap { min-height: 100dvh; display: flex; flex-direction: column; justify-content: center; }
.auth-box { max-width: 22rem; margin: 0 auto; padding: 0 .5rem; }
/* ⚠️ 온보딩 입력 화면(보호자·내 정보·아이 등록) — 로그인보다 넓고 왼쪽 정렬이다.
   2026-08-13: 화면마다 `style="text-align:left; max-width:26rem;"` 을 손으로 반복하고
   있었고 첫 화면(보호자)만 그게 빠져 **혼자 좁았다**. 클래스 하나로 모은다. */
.auth-form { max-width: 26rem; margin: 0 auto; padding: 0 .5rem; text-align: left; }
.auth-mark { display: block; margin: 0 auto 1.25rem; }
.auth-title {
  font-family: var(--serif); font-size: 2.3rem; font-weight: 600;
  letter-spacing: -.02em; text-align: center; color: var(--ink);
}
.auth-sub {
  color: var(--ink-2); margin: .6rem 0 2.5rem; font-size: 1rem;
  text-align: center; line-height: 1.6;
}
.auth-btns { display: flex; flex-direction: column; gap: .75rem; }
.auth-err {
  color: var(--danger); font-size: .9rem; margin-bottom: 1.25rem; text-align: center;
  background: color-mix(in srgb, var(--danger) 7%, transparent);
  border-radius: var(--radius); padding: .75rem 1rem;
}
.btn-naver  { background: #03C75A; border-color: #03C75A; color: #fff; }
.btn-google { background: var(--surface); }

/* ── 접근성 ── */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ── 로그인 유지 체크 ── 기본 꺼짐이라 눈에 띄되 강요하지 않게 */
.keep-row {
  display: flex; align-items: flex-start; gap: .6rem;
  margin: 0 0 1.25rem; cursor: pointer; font-size: .95rem;
}
.keep-row input { width: 1.15rem; height: 1.15rem; margin-top: .18rem; accent-color: var(--primary); }
.keep-row em {   /* 공용기기 경고 — 체크 옆에 붙어 있어야 의미가 있다 */
  display: block; font-style: normal; font-size: .78rem; color: var(--ink-2); margin-top: .1rem;
}

/* =========================================================================
 * Earth and Cradle 시그니처 — "The Breath" (2026-08-12)
 * 명세 §Special Component: 기능 버튼이 하나도 없는 조용한 자리.
 * 대비를 낮추고 타이포만 크게 둔다. **이 서비스가 재촉하지 않는다는 증거**가 되는 칸이다.
 * ⚠️ 여기에 버튼·링크·수치를 넣지 말 것. 넣는 순간 성격이 사라진다.
 * ======================================================================= */
.breath {
  background: color-mix(in srgb, var(--info-l) 45%, transparent);
  border-top: 1px solid color-mix(in srgb, var(--line) 35%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--line) 35%, transparent);
  border-radius: var(--radius-l);
  padding: 2rem 1.75rem;
  margin: var(--stack) 0;
  text-align: center;
}
.breath p {
  font-family: var(--serif); font-size: 1.15rem; font-weight: 500;
  line-height: 1.85; color: var(--ink-2); margin: 0;
}

/* ── 주차 여정 막대의 양 끝 라벨 ── 사장님이 좋다고 한 요소.
   "시작 · 40주 여정 중 23주째 · 40주" — 지금 어디쯤인지 한눈에 */
.trail-head {
  display: flex; justify-content: space-between; align-items: flex-end;
  margin: 0 .25rem .5rem; font-size: .75rem; color: var(--ink-3);
}
.trail-head .now { color: var(--primary); font-weight: 600; font-size: .82rem; }

/* ── 카드 소제목에 붙는 작은 아이콘(아기·엄마) ── 면 색이라 --accent 를 쓴다 */
.card h2 .ico, .sec-ico {
  width: 1.35rem; height: 1.35rem; color: var(--accent);
  vertical-align: -.2rem; margin-right: .45rem;
}

/* ── 소셜 로그인 버튼(전체폭) ── 2026-08-12: 유일한 로그인 수단이 되면서
   작은 아이콘 원에서 라벨 있는 큰 버튼으로. 뭘 눌러야 하는지 한눈에 보여야 한다. */
.social-col { display: flex; flex-direction: column; gap: .625rem; }
/* ⚠️ 2026-08-14 실측: 아이콘이 130/124/124px 로 **어긋나 있었다**(가운데 정렬 +
   라벨 길이가 달라서). 세 버튼이 흐트러져 보이던 진짜 원인이 이것이다.
   → 3칸 격자로 바꿔 **아이콘은 왼쪽에 고정**하고, 오른쪽에 아이콘 폭만큼 빈 칸을 둬
     라벨이 진짜 가운데 오게 한다(구글·애플 로그인 버튼의 표준 배치). */
.social-btn {
  display: grid; grid-template-columns: 1.35rem 1fr 1.35rem;
  align-items: center; gap: .6rem;
  min-height: 3.25rem; padding: 0 1.125rem; border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
  font-weight: 600; font-size: .98rem; letter-spacing: -.01em;
  transition: transform var(--dur) var(--ease);
}
.social-btn:active { transform: scale(.98); }
.social-btn .ico { width: 1.35rem; height: 1.35rem; }
.social-btn span { text-align: center; }
/* ⚠️ 구글 버튼은 흰색(#FFF)인데 바탕도 크림(#FFF8F5)이라 **테두리가 없으면 사라진다.**
   브랜드 규정상 배경색은 못 바꾸므로 테두리를 조금 더 준다. */
.social-btn[style*="#FFFFFF"] { border-color: color-mix(in srgb, var(--line) 95%, transparent); }
/* ⚠️ 아직 못 쓰는 곳은 흐리게 — 눌러도 되는 것처럼 보이면 안 된다 */
.social-btn.off { opacity: .45; }

/* ── 로그인 동그라미 (2026-08-18 사용자 지시) ─────────────────────────────
   "구글/네이버/카카오로 시작하기를 동그란 모양으로, 동그라미 안에 아이콘이 있도록.
    그리고 아이 로그인 동그란 버튼이 있어서 누르면 아이 id pw 입력."

   ⚠️ **아이콘만 남기면 글자 없는 단추가 된다** — 화면낭독기에는 아무것도 안 들린다.
      그래서 ①동그라미마다 `aria-label` 을 달고 ②동그라미 **아래에 작은 이름**을 남긴다.
      이름을 지우면 색으로만 구분해야 하는데, 색각이 다른 사람에겐 넷이 같은 원이다.
   ⚠️ 손가락이 닿는 크기를 3.75rem 으로 잡았다 — 권장 최소(44px)보다 넉넉하다.
      아이가 누르는 단추라 더 크게 잡을 이유가 있다. */
.auth-circles {
  display: flex; justify-content: center; align-items: flex-start;
  gap: 1.1rem; flex-wrap: wrap; margin: .25rem 0 1.5rem;
}
.circle-item { display: flex; flex-direction: column; align-items: center; gap: .45rem; width: 4.25rem; }
.circle-btn {
  width: 3.75rem; height: 3.75rem; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
  transition: transform var(--dur) var(--ease);
  cursor: pointer;
}
.circle-btn:active { transform: scale(.94); }
.circle-btn .ico { width: 1.8rem; height: 1.8rem; }
/* 구글은 흰 동그라미라 크림 바탕에서 사라진다 — 테두리를 조금 더(위 .social-btn 와 같은 이유) */
.circle-btn[style*="#FFFFFF"] { border-color: color-mix(in srgb, var(--line) 95%, transparent); }
.circle-btn.off { opacity: .4; }
.circle-cap { font-size: .76rem; line-height: 1.3; color: var(--ink-2); text-align: center; }

/* 아이 동그라미 — ⚠️ 2026-08-18 스크린샷이 잡았다: **색이 없으면 '준비 안 됨'으로 읽힌다.**
   소셜 셋이 브랜드 색을 쓰니 맨 동그라미 하나만 혼자 꺼져 보였다.
   앱 색(--primary)을 줘서 **넷 중 유일하게 '우리 것'** 으로 보이게 한다. */
.kid-circle {
  background: var(--primary); color: #fff;
  border-color: var(--primary);
}
.kid-circle[aria-expanded="true"] { box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 25%, transparent); }

/* ── 로고 로크업(로그인 화면) ── */
/* ⚠️ 2026-08-12: 15rem 이던 걸 키웠다 — 소셜 버튼(전체폭 21rem)보다 로고가
   작아 보인다는 지적. 컨테이너 폭(22rem)에 맞춰 로고가 화면의 주인공이 되게 한다.
   ⚠️ 2026-08-14 "세련되게" 요청으로 다시 봤을 때 **크기는 그대로 뒀다**(사용자 결정).
      실측: 화면 폭의 86%·세로의 28%. 문제는 크기가 아니라 **그 아래가 전부 작아서
      무게가 2단계(거대↔작음)뿐**이었던 것 — 그래서 문구를 중간 무게로 올렸다. */
/* ⚠️ 실측 4px 이었다 — 문구가 **로고의 일부**처럼 읽혔다. 로고와 문구는 다른 층위라
   (그림 / 말) 숨을 한 번 줘야 각자로 읽힌다. 이미지 자체 여백을 감안해 1rem. */
.auth-logo { display: block; width: 100%; max-width: 21rem; height: auto;
             margin: 0 auto 1rem; }

/* ── 로그인 리드 문구 ── 2026-08-14.
   ⚠️ 이 문구가 **이 서비스의 정체성 그 자체**다("지금 이 아이의 다음 걸음").
      그런데 16px 회색이라 로고 밑 각주처럼 읽혔다. 크기·색을 올려 로고와 버튼 사이의
      중간 무게를 만든다.
   ⚠️ 색은 --ink 다. --ink-2 로 두면 다시 각주가 된다.

   ⚠️⚠️ **--serif 를 지정하지만 한글에는 기대하지 말 것**(2026-08-14 실측).
      Literata·Georgia 는 **라틴 전용이라 한글 글리프가 없다** — 한글은 기기의 기본
      대체 글꼴로 떨어지고, 그게 명조인지 고딕인지는 **기기마다 다르다**(실제 렌더에서
      산세리프로 나왔다). 그러니 이 화면의 위계는 **세리프가 아니라 크기·굵기·색**이
      만든다. 라틴이 섞이면 그때만 세리프가 보인다.
      ⚠️ 이건 이 화면만의 문제가 아니다 — `.display`·`.auth-title`·`.breath p` 도
         같은 상태다. 한글 명조를 정말 쓰려면 웹폰트를 추가해야 하고, 그건 브랜드
         결정이라 여기서 혼자 정하지 않는다. */
/* ⚠️ 2026-08-14 문구가 한 줄("오늘의 한 걸음")로 짧아지면서 크기를 올렸다.
   두 줄일 땐 1.6rem 이 한계였다(더 키우면 세 줄로 넘어갔다) — 한 줄이면 그 제약이
   없어서, 이 화면의 위계를 **크기가** 만들 수 있다(한글에 세리프가 안 걸려 서체로는
   못 만든다, 위 주석). 로고 다음으로 큰 것이 이 한 줄이고 그다음이 로그인 단추다.
   ⚠️ letter-spacing 을 더 조인다 — 글자가 커질수록 자간이 벌어져 보인다. */
.auth-lead {
  font-family: var(--serif); font-size: 2rem; font-weight: 500;
  line-height: 1.35; letter-spacing: -.025em; color: var(--ink);
  text-align: center; margin: 0 0 var(--stack-lg);
  text-wrap: balance;                     /* 줄바꿈이 어색하게 갈리지 않게 */
}

/* ── 온보딩 진행 표시 ── 2026-08-13.
   ⚠️ 숫자("3/4")가 아니라 점이다 — 숫자는 세게 읽혀 재촉처럼 느껴진다.
   지난 단계는 옅게 채우고 지금만 길게 — 색은 하나(primary)만 쓴다. */
.steps { display: flex; gap: .3rem; align-items: center; margin: 0 0 1.25rem; }
.steps span {
  width: .375rem; height: .375rem; border-radius: 999px; background: var(--line);
  transition: width var(--dur) var(--ease), background var(--dur) var(--ease);
}
.steps span.done { background: var(--primary-l); }
.steps span.on   { width: 1.25rem; background: var(--primary); }

/* ── 온보딩 뒤로가기 ── auth 화면엔 상단 헤더가 없어 본문 안에 둔다 */
.onboard-back {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; margin: 0 0 .75rem -.6rem;
  border-radius: 999px; color: var(--ink-2);
  transition: background var(--dur) var(--ease);
}
.onboard-back:hover { background: var(--surface-2); }
.onboard-back .ico { width: 1.4rem; height: 1.4rem; }

/* ── 보호자 선택 — 버튼 자체가 폼(누르면 즉시 다음으로) ── */
.guardian-pick { display: flex; gap: 1rem; margin-top: 2rem; }
.guardian-pick form { flex: 1; }
.guardian-btn {
  width: 100%; min-height: 6rem; border-radius: var(--radius-l);
  border: 1px solid var(--line); background: var(--surface);
  font-size: 1.15rem; font-weight: 600; color: var(--ink);
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.guardian-btn:active { transform: scale(.97); }
.guardian-btn:hover { border-color: var(--accent); }
.guardian-btn.on { border-color: var(--primary); background: var(--primary-l); color: var(--primary-d); }

/* ── 개발 서버 전용 신규가입 시험 버튼 ── 라이브엔 PHP 단에서부터 안 뜬다 */
.dev-signup { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px dashed var(--line); }
.dev-signup-cap { text-align: center; font-size: .72rem; color: var(--ink-3);
                   text-transform: uppercase; letter-spacing: .06em; margin-bottom: .6rem; }

/* ── 동의 체크 줄 (2026-08-13) ──────────────────────────────────────────────
   ⚠️ 필수/선택을 눈으로 갈라야 한다(개인정보 보호법 제22조 동의 구분). 한 덩어리로
      보이면 "선택을 거부할 자유"가 화면에서 사라진다. 그래서 줄마다 테두리를 준다. */
.consent-row { display: flex; gap: .625rem; align-items: flex-start;
  padding: .875rem 1rem; margin-top: .5rem; font-size: .9rem; line-height: 1.5;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.consent-row input[type="checkbox"] { width: auto; min-height: auto; margin-top: .2rem; flex: 0 0 auto; }
.consent-row a { text-decoration: underline; }

/* ── 법률 문서 ──────────────────────────────────────────────────────────────
   ⚠️ 읽으라고 만든 화면이라 본문 폭·행간을 앱 화면보다 넉넉하게 준다. */
.legal { font-size: .92rem; line-height: 1.75; color: var(--ink-2); }
.legal h2 { font-size: 1.02rem; color: var(--ink); margin: 1.75rem 0 .5rem; }
.legal h3 { font-size: .95rem; color: var(--ink); margin: 1.1rem 0 .35rem; }
.legal p, .legal ul { margin-bottom: .75rem; }
.legal ul { padding-left: 1.1rem; list-style: disc; }
.legal li { margin-bottom: .3rem; }
.legal strong { color: var(--ink); }
.legal a { text-decoration: underline; }
/* ⚠️ 표는 좁은 화면에서 가로로 밀리면 안 된다 — 칸을 쌓지 말고 폭을 나눈다 */
.legal-t { width: 100%; border-collapse: collapse; margin: .5rem 0 1rem; font-size: .87rem; }
.legal-t th, .legal-t td { border: 1px solid var(--line); padding: .5rem .625rem;
  text-align: left; vertical-align: top; word-break: keep-all; }
.legal-t th { background: var(--surface-2); color: var(--ink); font-weight: 600; width: 32%; }
/* 4열 표(방침 4절 앱 알림 전달, 2026-08-25). 위 32% 는 2열(항목/목적)용이라 4열에선 128% 가 돼
   브라우저가 비율로 눌러 담는다 — 깨지진 않지만 「보관」 열이 다섯 줄로 접힌다(business-dev-lead 지적). */
.legal-t.legal-t4 th { width: auto; }
.legal-note { padding: .75rem 1rem; border-radius: var(--radius);
  background: var(--surface-2); font-size: .87rem; }
.legal-ver { margin-top: 2rem; text-align: right; }

/* ── 앱 안 알림 (2026-08-13) ────────────────────────────────────────────────
   ⚠️ 알릴 게 없으면 아예 안 그린다(PHP 쪽에서). 빈 알림함은 소음이다.
   ⚠️ 색을 강하게 쓰지 않는다 — 이 앱은 재촉하지 않는 게 원칙이라, 알림이
      "빨간 뱃지"처럼 굴면 그 원칙과 충돌한다. 조용히 눈에 띄는 정도. */
.noti { margin: 0 0 1.25rem; border: 1px solid var(--line); border-radius: var(--radius-l);
        background: var(--surface); overflow: hidden; }
.noti-head { display: flex; align-items: center; justify-content: space-between;
             padding: .625rem 1rem; font-size: .82rem; font-weight: 600; color: var(--ink-2);
             background: var(--surface-2); }
.noti-head form button { background: none; border: 0; padding: 0; }
/* ⚠️ 줄이 **눌러서 가는 것**임을 보인다 — 머리의 "모두 읽음"과 하는 일이 달라서,
   화살표가 없으면 둘 다 그냥 글씨로 보인다(2026-08-14 제보의 절반이 이 혼동이었다). */
.noti-go { width: 1rem; height: 1rem; flex: 0 0 auto; color: var(--ink-3);
           transform: rotate(180deg); margin-left: auto; }
.noti-row { display: flex; align-items: center; gap: .5rem;
            padding: .75rem 1rem; font-size: .9rem; color: var(--ink);
            border-top: 1px solid color-mix(in srgb, var(--line) 45%, transparent); }
.noti-row:first-of-type { border-top: 0; }
.noti-row:hover { background: color-mix(in srgb, var(--surface-2) 50%, transparent); }

/* ── 로그인 화면 마무리 (2026-08-14 "세련되게") ─────────────────────────────
   ⚠️ 여기서 "세련"은 화려함이 아니라 **덜어냄과 위계**다. 이 앱의 성격이
      "재촉하지 않음·조용함"이라, 그라데이션·그림자·모션을 더하면 성격과 싸운다.
      바꾼 것은 넷뿐: ①문구를 중간 무게로 ②아이콘 정렬 ③덩어리 사이 여백 ④군더더기 축소. */

/* 약관 줄 — ⚠️ 지우지 않는다(가입 전에 읽고 결정하는 문서다). 다만 두 줄 + 부연은
   첫 화면에서 무겁다. 한 줄로 줄이고 가장 낮은 대비로 내린다. */
.auth-legal {
  margin-top: var(--stack); text-align: center;
  font-size: .78rem; line-height: 1.7; color: var(--ink-3);
}
.auth-legal a { color: var(--ink-2); text-decoration: underline;
                text-underline-offset: .15em; }

/* 개발 서버 블록 — ⚠️ 라이브엔 안 뜬다. 그래도 **개발 중 첫인상을 매일 본다**.
   점선 상자에 큰 버튼이라 화면의 무게 중심을 가져갔다. 글자 링크 수준으로 낮춘다. */
/* ⚠️ 위(586줄)에 옛 규칙이 남아 있어 점선 테두리가 그대로 그려졌다 —
   여기서 명시적으로 끈다(둘을 합치지 않는 이유: 옛 규칙은 다른 화면에도 걸릴 수 있다). */
.dev-signup { margin-top: var(--stack); padding-top: 0; border-top: 0;
              text-align: center; opacity: .5; }
.dev-signup:hover { opacity: 1; }
.dev-signup-cap { font-size: .7rem; color: var(--ink-3); letter-spacing: .04em;
                  margin: 0 0 .35rem; }
.dev-signup .btn {
  min-height: auto; padding: .4rem .9rem; font-size: .8rem; font-weight: 500;
  background: none; border: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
  color: var(--ink-3); width: auto !important;
}

/* ── 가족 대화 (2026-08-14) ────────────────────────────────────────────────
   ⚠️ 말풍선을 화려하게 만들지 않는다. 이 앱은 조용한 앱이고, 대화창이 갑자기
      메신저처럼 굴면 나머지 화면과 다른 앱이 된다. 색은 두 가지(내 말/상대 말)뿐이고
      그림자·모션은 없다.
   ⚠️ 입력칸은 **고정**이다 — 화면 순서 규약("입력은 맨 아래")의 의도적 예외.
      대화는 읽으면서 바로 답하는 것이라 매번 스크롤해 내려가면 쓸 수가 없다. */
/* ⚠️ 고정 입력칸에 마지막 말이 가리지 않게 아래 여백. **대화가 없을 때도** 필요해서
   화면(body)에 건다 — 빈 화면의 안내 문구가 입력칸에 겹치던 것.
   ⚠️ **상수(5.5rem)로는 모자랐다**(2026-08-15 폰 폭 실측: 마지막 말이 66px 가려졌다).
      비켜야 할 것이 둘이다 — 탭바(4rem, .chat-send 의 bottom 값)와 **입력칸 자신의 높이**.
      입력칸 높이는 글이 길어지면 늘어나므로(textarea 최대 7.5rem) 상수로 적을 수가 없다.
      그래서 chat.js 가 재서 주는 --compose-h 를 그대로 쓴다(위 미디어쿼리와 같은 값).
   ⚠️ 기본값 4.25rem 은 한 줄일 때의 높이 — JS 가 막혀도 얼추 맞게. */
/* ⚠️ **산수를 버리고 잰 값을 쓴다.** 예전엔 `4rem(탭바) + 입력칸 높이` 로 계산했는데
   실기기에서 안 맞았다(2026-08-15 사용자 사진 — 마지막 말이 입력칸에서 한참 떴다).
   안전영역·브라우저 주소창·기기 내비게이션 바가 기기마다 다르게 끼어들기 때문이다.
   `--compose-gap` 은 chat.js 가 **화면 바닥부터 입력줄 윗변까지**를 실제로 잰 값이라
   그 전부가 이미 들어 있다. 여기에 숨 쉴 틈 .5rem 만 더한다.
   ⚠️ 기본값(8.5rem)은 JS 가 막혔을 때만 쓰인다 — 넉넉한 쪽으로 잡는다(가리는 것보다 낫다). */
body.chat-page main.wrap { padding-bottom: calc(var(--compose-gap, 8.5rem) + .5rem); }
/* ⚠️ **데스크톱 좁은 창에서는 그 여백이 죽은 공간이다.** 거기서는 body 를
   `100dvh - 탭바 - 보내기칸` 으로 이미 잘라 놨기 때문에(위 미디어쿼리), 5.5rem 이
   그대로 남아 마지막 말과 입력칸 사이에 **88px 빈 자리**가 생긴다
   (2026-08-15 사용자 지적 "보내고 나면 글이 이상한 자리에 보인다" — PC 에서 재현).
   ⚠️ **조건은 위 미디어쿼리와 같아야 한다.** 한쪽만 고치면 자른 화면과 여백이 어긋난다.
   ⚠️ 폰(hover 없음)에서는 5.5rem 이 **맞다** — 거기선 입력칸이 본문 위에 떠 있어서
      그 여백이 없으면 마지막 말이 입력칸에 가린다. 지우면 안 된다. */
@media (hover: hover) and (pointer: fine) and (max-width: 1023.98px) {
  body.chat-page main.wrap { padding-bottom: 1rem; }
}

/* ── 말이 적으면 **아래에서부터** 쌓인다 (2026-08-15 사용자 사진) ──────────────
   ⚠️ 여기까지 고친 건 전부 "가리지 않게 하는" 여백이었는데, 사용자가 보낸 사진의
      문제는 **정반대**였다 — 말이 몇 개뿐이라 위쪽에 붙어 있고 입력칸은 화면 바닥에
      고정이라, 그 사이가 통째로 빈 것이다("마지막 글이 보내기 버튼이랑 너무 멀어").
      여백을 아무리 잘 재도 이건 안 고쳐진다. **정렬의 문제**지 여백의 문제가 아니다.
   ⚠️ 메신저(카카오톡)는 전부 아래 정렬이다 — 대화는 **최근 것이 눈높이**에 있어야 한다.
   방법: 화면(body)을 세로 flex 로 두고 본문이 남는 자리를 다 먹게(flex:1) 한 뒤,
        말 목록에 `margin-top:auto` 를 준다. 말이 화면보다 길어지면 남는 자리가 없어
        이 규칙은 **저절로 꺼진다**(스크롤 동작은 그대로).
   ⚠️ 고정 요소(.chat-send·.emoji-pad·.tabbar·.devmark)는 flex 항목이 아니다 —
      position:fixed 는 흐름에서 빠지므로 이 정렬에 끼어들지 않는다.
   ⚠️ `padding-bottom:0` 이 필요하다: 화면 기본값이 탭바 자리 4rem 인데, 아래 여백은
      이미 `--compose-gap`(바닥부터 입력줄 윗변까지, 탭바를 **포함해** 잰 값)이 다 갖고
      있다. 둘을 겹쳐 두면 그만큼(실측 72px) 또 뜬다. */
body.chat-page { display: flex; flex-direction: column; padding-bottom: 0; }
/* ⚠️ `width:100%` 가 꼭 있어야 한다. 화면(body)을 세로 flex 로 바꾸는 순간, `margin:0 auto`
   가 붙은 본문은 **stretch 를 잃고 내용 폭으로 쪼그라든다**(flex 규칙: 가로 auto 마진이
   있으면 늘리지 않는다). 1440px 실측에서 1024px 이어야 할 칼럼이 **505px** 로 좁아져
   있었다 — 아래 정렬을 넣으면서 조용히 생긴 부작용이고, 폰에서는 어차피 화면이 좁아
   안 보였다. `width:100%` 로 예전처럼 "채우고 max-width 로 자르기"를 되돌린다. */
body.chat-page main.wrap { flex: 1 1 auto; display: flex; flex-direction: column; width: 100%; }
body.chat-page .chat-log { margin-top: auto; }
/* ⚠️ **아이 대화는 한 겹이 더 있다** (2026-08-16 사용자 지시 "지금 어른 대화창과 동일한
   여백으로"). 아이 화면은 `<main class="wrap">` 안에 `<section class="kid-wrap">` 을 한 겹
   더 두는데, 위 두 줄이 `main.wrap` 과 `.chat-log` 만 잡아서 **가운데 한 겹이 흐름을 끊었다** —
   `.chat-log` 의 부모가 flex 가 아니니 `margin-top:auto` 가 아무 일도 안 했고, 말이 위에
   붙은 채 밑이 통째로 비었다. 여백을 아무리 재도 안 보이던 이유다(가로는 실측상 같았다).
   ⚠️ `max-width` 도 같이 맞춘다 — 아이 기본값은 34rem, 어른 대화는 40rem 이라 **넓은
      화면에서만** 칼럼이 어긋났다. 폰에서는 둘 다 화면보다 넓어 티가 안 난다.
   ⚠️ 대화일 때만이다. 다른 아이 화면은 34rem 이 맞다(읽는 화면이라 좁은 게 낫다). */
body.chat-page .kid-wrap {
  flex: 1 1 auto; display: flex; flex-direction: column; width: 100%; max-width: 40rem;
}

/* ── 좌우 여백을 줄인다 (2026-08-15 사용자 지적 "왼쪽 오른쪽 가로 너비가 너무 많아") ──
   ⚠️ 대화는 **다른 화면과 여백 규칙이 다르다.** 나머지 화면은 읽는 화면이라 넉넉한
      여백이 글을 읽기 좋게 하지만, 대화는 **말풍선 자체가 여백을 만든다**(오른쪽 말은
      왼쪽이 비고, 왼쪽 말은 오른쪽이 빈다). 거기에 화면 여백까지 얹으면 두 겹이 된다.
   실측(390px 폰, 고치기 전): 긴 말의 말풍선이 **250px = 화면의 64%** 밖에 못 썼고
   왼쪽에 120px 이 통째로 비었다. 메신저는 보통 75% 안팎이다.
   ⚠️ 셋을 **같이** 고쳐야 한다 — 하나만 줄이면 줄 끝이 서로 어긋나 더 지저분해진다:
      ① 본문 여백(1.25rem→.75rem) ② 말풍선 상한(82%→88%) ③ 입력줄 여백(--gap 24px→.75rem)
   ⚠️ 본문 상한을 40rem 으로 올린 이유는 미감이 아니다 — `.chat-send` 가 이미 40rem 이라,
      544~1024px 구간에서 **입력줄만 넓고 말은 좁은** 어긋남이 있었다. 둘을 같은 값으로. */
/* ⚠️ 좌우는 **카카오톡 수준으로** 좁힌다(.5rem=8px). 여기서 더 줄이면 말풍선이 화면
   가장자리에 붙어 답답하고, 늘리면 말풍선이 쓸 자리가 그만큼 준다. */
body.chat-page main.wrap { max-width: 40rem; padding-left: .5rem; padding-right: .5rem; }
body.chat-page .chat-row { max-width: 90%; }
/* ③(입력줄·고치는중 줄·이모지판)은 **각자의 규칙 안에서** 고쳤다 — 여기에 적으면
   뒤에 오는 `padding` 단축 속성에 져서 안 먹는다(이 파일에서 이미 한 번 겪은 함정). */

.chat-log { display: flex; flex-direction: column; gap: .5rem; }
/* ⚠️ #369 스크롤 튐 — 자리 잡기 전엔 숨긴다(inc/chat.php ns_chat_scroll_guard 가 켠다).
   `display:none` 이 아니라 `visibility:hidden` — 자리를 그대로 차지해야 chat.js 의
   toBottom() 이 실제 높이를 잰다(자리가 0이면 감춘 채로 엉뚱한 곳에 스크롤해 두고
   드러나는 순간 또 튄다). */
.chat-log.positioning { visibility: hidden; }

/* 날짜 구분 — ⚠️ 대화는 시간에 붙어 있어 "언제 한 말인지"가 내용만큼 중요하다 */
.chat-day { display: flex; align-items: center; gap: .75rem; margin: 1rem 0 .25rem; }
.chat-day::before, .chat-day::after {
  content: ''; flex: 1; height: 1px;
  background: color-mix(in srgb, var(--line) 45%, transparent);
}
.chat-day span { font-size: .75rem; color: var(--ink-3); }

/* ⚠️ 카카오톡식 배치(2026-08-14): 이름은 **묶음의 첫 줄에만**, 시간·읽음은 말풍선
   옆 아래. 연속으로 쓴 말은 이름을 생략한다 — 한 사람이 세 줄 쓰면 이름이 세 번 떠
   **세 사람이 말한 것처럼** 보이기 때문이다(미감이 아니라 읽기 속도의 문제). */
.chat-row { display: flex; flex-direction: column; max-width: 82%; }
.chat-row.me { align-self: flex-end; align-items: flex-end; }
.chat-row.grouped { margin-top: -.35rem; }   /* 묶인 줄은 더 붙인다 */
.chat-who { font-size: .74rem; color: var(--ink-2); margin: 0 0 .2rem .25rem; }
/* 말풍선 + 시간/읽음을 한 줄에 — 읽음은 말풍선 **바깥**이다(카톡과 같은 자리) */
.chat-line { display: flex; align-items: flex-end; gap: .35rem; }
.chat-row.me .chat-line { flex-direction: row-reverse; }
.chat-meta { display: flex; flex-direction: column; align-items: flex-end;
             gap: .05rem; flex: 0 0 auto; padding-bottom: .1rem; }
.chat-row.me .chat-meta { align-items: flex-start; }
/* 안 읽은 수 — 카카오톡의 "1". ⚠️ 0 이면 아예 안 그린다(0 을 보여주면 소음이다) */
.chat-unread { font-size: .68rem; color: var(--primary); font-weight: 700; line-height: 1; }
.chat-bubble {
  padding: .625rem .875rem; border-radius: 1rem;
  background: var(--surface); border: 1px solid color-mix(in srgb, var(--line) 45%, transparent);
  font-size: .94rem; line-height: 1.6; color: var(--ink);
  word-break: break-word; overflow-wrap: anywhere;   /* 긴 주소가 가로로 밀지 않게 */
}
/* 내 말 — ⚠️ --primary 면 글자가 흰색이라 대비는 되지만 화면이 강해진다.
   연한 primary-l 로 두면 "내 말"은 구분되면서 조용함이 유지된다. */
.chat-row.me .chat-bubble { background: var(--primary-l); border-color: transparent; }
/* 지워진 말 — 본문은 안 나온다. 자리만 남겨 흐름을 잇는다. */
.chat-bubble.gone { background: none; border-style: dashed; color: var(--ink-3);
                    font-size: .85rem; font-style: normal; }
.chat-time { font-size: .68rem; color: var(--ink-3); flex: 0 0 auto; margin-bottom: .15rem; }
/* 지우기 — ⚠️ 말풍선 바깥, 시간 옆. JS 가 있으면 평소엔 감춰 두고 내 말풍선을 누를
   때만 나온다(chat.js). display 를 주지 않는다 — 주면 hidden 이 안 먹는다(위 .emoji-pad 참고). */
.chat-del { margin: 0 0 .15rem; }
.chat-row .cmt-del { font-size: .68rem; }

/* 이전 대화 더 보기 — ⚠️ 단추처럼 안 만든다. 대화 위에 얹힌 조용한 한 줄이면 된다. */
.chat-more { text-align: center; margin: .25rem 0 .5rem; }
.chat-more a { font-size: .8rem; color: var(--ink-3); text-decoration: underline; }

/* 보내기 — 하단 탭바 바로 위에 고정 */
.chat-send {
  position: fixed; left: 0; right: 0;
  /* ⚠️ 탭바와 **같은 식**을 쓴다(.tabbar: 4rem + safe-area). 상수로 적으면 탭바 높이를
     고칠 때 여기만 남아 입력칸이 탭바에 겹치거나 뜬다. */
  bottom: calc(4rem + env(safe-area-inset-bottom));
  display: flex; gap: .5rem; align-items: flex-end;
  /* ⚠️ 좌우는 본문(main.wrap)과 **같은 값**이어야 한다(.75rem) — 다르면 말풍선 줄 끝과
     입력줄 끝이 어긋난다. 2026-08-15 --gap(24px) 에서 내렸다. */
  padding: .625rem .5rem; background: var(--bg);
  border-top: 1px solid color-mix(in srgb, var(--line) 45%, transparent);
  /* ⚠️ 화면이 넓어져도 본문 폭 안에 머물게 — 데스크톱에서 좌우로 늘어지면 흉하다 */
  max-width: 40rem; margin: 0 auto;
}
.chat-send textarea {
  flex: 1; min-height: 2.75rem; max-height: 7.5rem; resize: none;
  padding: .625rem .875rem; font: inherit; font-size: .94rem; line-height: 1.5;
  border: 1px solid var(--line); border-radius: 1.25rem;
  background: var(--surface); color: var(--ink);
}
/* ⚠️ 보내기는 **작게**(2026-08-15 사용자 지적 "보내기 버튼이 너무 커").
   390px 폰에서 재 보니 75px 로 입력줄의 19% 를 먹고 있었다(입력칸은 163px).
   이 줄의 주인공은 **입력칸**이다 — 단추가 넓으면 쓸 자리가 그만큼 줄어든다.
   ⚠️ **높이는 안 줄인다**(2.75rem 유지). 이유 둘:
      ① 입력칸 min-height 와 같아야 두 칸이 나란히 맞는다.
      ② 이 줄 높이가 `--compose-h` 로 대화 스크롤 계산에 들어간다(app.css 위쪽) —
         높이를 건드리면 오늘 맞춘 스크롤이 다시 어긋난다.
   ⚠️ 그래서 **폭과 글자만** 줄인다. 손가락이 닿는 면적은 높이가 지킨다. */
.chat-send .btn {
  min-height: 2.75rem; padding: 0 .7rem; flex: 0 0 auto;
  font-size: .88rem; border-radius: 1.25rem;   /* 입력칸(1.25rem)과 같은 둥글기 */
}

/* ── 대화: 사진·이모지 (2026-08-14) ────────────────────────────────────────
   ⚠️ 사진만 보낸 말은 말풍선 배경을 안 씌운다(.pic-only) — 사진 둘레에 색면이 생기면
      액자처럼 보여 대화 흐름이 끊긴다. 카카오톡도 사진은 맨몸으로 둔다. */
.chat-bubble.pic-only { background: none; border: 0; padding: 0; }
.chat-pic { display: block; }
.chat-pic img {
  display: block; max-width: 100%; width: auto; max-height: 15rem;
  border-radius: .75rem; border: 1px solid color-mix(in srgb, var(--line) 40%, transparent);
}
/* 사진 + 글이 같이 온 경우 — 사진 아래에 글 */
.chat-bubble .chat-pic + span { display: block; margin-top: .4rem; }

/* 입력줄의 사진·이모지 단추 — ⚠️ 보내기보다 작고 조용하게. 입력이 주인공이다 */
.chat-attach, .chat-emoji {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 2.5rem; height: 2.75rem; padding: 0; border: 0; background: none;
  color: var(--ink-3); cursor: pointer; font-size: 1.25rem; line-height: 1;
}
.chat-attach:active, .chat-emoji:active { color: var(--primary); }
.chat-attach .ico { width: 1.4rem; height: 1.4rem; }
.chat-picname {
  position: fixed; left: 0; right: 0; bottom: calc(7.5rem + env(safe-area-inset-bottom));
  margin: 0 auto; max-width: 40rem; padding: .35rem var(--gap);
  font-size: .78rem; color: var(--ink-2); background: var(--surface-2);
}

/* 이모지 팔레트 — ⚠️ 전체 픽커를 안 만든다. 폰 키보드에 이미 있고, 다시 만들면
   수백 개를 스크롤하게 된다. 육아 대화에서 실제로 쓰는 것 스물넷만. */
.emoji-pad {
  position: fixed; left: 0; right: 0; bottom: calc(7.25rem + env(safe-area-inset-bottom));
  margin: 0 auto; max-width: 40rem; z-index: 19;
  display: grid; grid-template-columns: repeat(8, 1fr); gap: .15rem;
  padding: .5rem .5rem; background: var(--surface);
  border-top: 1px solid color-mix(in srgb, var(--line) 45%, transparent);
}
.emoji-pad .emoji {
  border: 0; background: none; font-size: 1.4rem; line-height: 1;
  padding: .35rem 0; cursor: pointer; border-radius: .5rem;
}
.emoji-pad .emoji:active { background: var(--primary-l); }
/* ⚠️ **`display` 를 준 요소는 `hidden` 속성이 안 먹는다** — 브라우저 기본
   `[hidden]{display:none}` 보다 위 `.emoji-pad{display:grid}` 가 더 구체적이라
   이긴다. 그래서 팔레트가 **처음부터 펼쳐진 채로** 떠 있었다(2026-08-14 실측).
   display 를 주는 자리마다 이 짝을 같이 둘 것. */
.emoji-pad[hidden] { display: none; }

/* 아이가 체크한 줄 — 2026-08-14.
   ⚠️ 취소선을 **연하게만** 긋는다. 진하게 그으면 "지워진 것"처럼 보이는데, 아이가
      한 일은 지워진 게 아니라 **해낸 것**이다. 색도 --ink-3 까지만 내린다. */
.sched-title.done { color: var(--ink-3); text-decoration: line-through;
                    text-decoration-color: color-mix(in srgb, var(--ink-3) 45%, transparent); }

/* 비밀번호 보기 토글 — 2026-08-14 사용자 지시("uiux 기본은 다 만들어야").
   ⚠️ 왜 기본기인가: 가려진 칸은 **오타의 원인을 못 찾게 한다.** 아이는 왜 로그인이
      안 되는지 모르고, 부모는 자기가 뭘 쳤는지 모른다. 확인 재입력과 한 쌍이다.
   ⚠️ 칸 안에 겹쳐 놓지 않고 **오른쪽에 나란히** 둔다 — 겹치면 긴 비밀번호가 단추 밑으로
      들어가고, 폰에서 손가락이 칸을 누르려다 단추를 누른다. */
.pw-row { display: flex; gap: .5rem; align-items: stretch; }
.pw-row input { flex: 1; }
.pw-see {
  flex: 0 0 auto; min-width: 3.25rem; padding: 0 .6rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink-2); font-size: .8rem;
}
.pw-see:active { background: var(--surface-2); }

/* 사람 확인(캡챠) — 2026-08-14.
   ⚠️ 이미지와 "다시"를 한 줄에, 입력칸은 그 아래. 셋을 한 줄에 넣으면 좁은 폰에서
      입력칸이 손톱만 해진다. */
.cap-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .5rem; }
.cap-img { border-radius: var(--radius); border: 1px solid var(--line); display: block; }
.cap-text { display: inline-flex; align-items: center; justify-content: center;
            width: 150px; height: 52px; background: var(--surface-2);
            font-size: 1.4rem; letter-spacing: .3em; color: var(--primary); }
.cap-again { font-size: .8rem; color: var(--ink-2); text-decoration: underline; }

/* ── 개발 표시 ── 2026-08-14. 스테이징에만 그려진다(inc/view.php 의 NS_STAGING).
   ⚠️ **자리를 안 먹는다**(fixed) — 흐름에 넣으면 오늘 맞춘 대화 화면 스크롤 높이가
      어긋난다. ⚠️ pointer-events:none — 밑에 있는 것을 못 누르게 만들면 안 된다.
   ⚠️ 처음엔 모서리에 **비스듬한 리본**으로 넣었는데, 회전시키니 글자가 화면 밖으로
      잘려 "개발"이 안 읽혔다(실측 top=-15 left=-28). 딱지는 읽히라고 다는 것이라
      **회전을 버리고 납작한 라벨**로 바꿨다 — 멋보다 읽히는 게 먼저다.
   ⚠️ 왼쪽 위. 오른쪽 위는 화면마다 단추가 오는 자리라 겹친다.
   ⚠️ `--danger` 를 안 쓴다. 그 토큰은 **파괴적 동작 전용**이라고 tokens.css 에 못
      박혀 있고(지우기·탈퇴), 무대 표시는 파괴가 아니다. 더구나 다크모드에서 --danger 는
      연분홍(#FFB4AB)이라 흰 글씨가 묻힌다 — 값을 직접 박아 두 테마에서 같게 만든다. */
.devmark {
  position: fixed; top: 0; left: 0; z-index: 60; pointer-events: none;
  padding: .2rem .6rem .25rem; border-bottom-right-radius: .5rem;
  background: #8E1B1B; color: #fff;
  font-size: .7rem; font-weight: 700; letter-spacing: .06em; line-height: 1;
  box-shadow: 0 1px 4px rgba(0,0,0,.25);
}

/* ── 게임 시간 (2026-08-15) ─────────────────────────────────────────────────
   ⚠️ 이 화면의 주인공은 **숫자 하나**다. 카드·설명·단추가 시계보다 크면 "몇 분 남았지"에
      한 박자가 더 걸린다 — 그 한 박자를 없애려고 만든 화면이다.
   ⚠️ 숫자는 **자리가 안 흔들려야 한다**(tabular-nums). 안 그러면 1초마다 폭이 달라져
      옆 글자가 들썩인다. */
.game-card { margin-top: 1.25rem; text-align: center; }
.game-clock {
  font-size: 3.2rem; font-weight: 600; line-height: 1.1; color: var(--primary);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; margin: .1rem 0 .2rem;
}
/* 아직 시작 전 — ⚠️ 옅게 둔다. 안 도는 시계가 도는 것처럼 보이면 안 된다 */
.game-clock.off { color: var(--ink-3); }
/* 마지막 1분 — ⚠️ 소리를 못 듣는 상황이 실제로 많아 **눈으로도** 알린다.
   ⚠️ `--danger` 를 쓰지 않는다(tokens.css: 파괴적 동작 전용). 시간이 얼마 안 남은 것은
      경고가 아니다 — 아이 화면에서 빨강은 "잘못했다"로 읽힌다. 굵기로만 말한다. */
.game-clock.soon { font-weight: 700; }
.game-clock.over { color: var(--ink-3); }
.game-done { margin-top: .5rem; font-weight: 600; color: var(--primary); }
/* 빠른 단추 — ⚠️ 4칸 고정. 개수가 늘면 여기 숫자도 같이 고쳐야 한다(줄이 접힌다) */
.game-presets { display: grid; grid-template-columns: repeat(4, 1fr); gap: .5rem; }
.game-preset { padding: 0 .25rem; font-size: .9rem; }

/* ── 알림 받기 상자 (2026-08-15) ──
   ⚠️ 눈에 띄되 **재촉하지 않는다** — 알림 허락은 한 번 거절당하면 되돌리기 어려워서
      (브라우저가 다시 안 물어본다) 크게 강조해 얼떨결에 누르게 만들면 안 된다. */
.pushbox { margin-top: .5rem; }
.pushbox .btn { width: 100%; }
.pushbox .push-say { margin-top: .35rem; min-height: 1.1em; }

/* ── 데스크톱: 대화가 창을 채운다 (2026-08-16 사용자 지적 "좌우 여백이 너무 넓어, 카톡처럼") ──
   두 가지가 걸려 있었다.
   ⚠️ ① **입력줄이 말풍선과 안 맞았다.** `.chat-send` 는 `position:fixed` 라 body 의
        사이드바 자리(padding-left:15rem)를 **못 본다** — 본문은 사이드바 오른쪽에서
        가운데를 잡고, 입력줄은 **창 전체**에서 가운데를 잡았다.
        1440px 실측: 말풍선 587~1093 vs 입력줄 400~1040 — **187px 어긋남.**
        고정 요소는 흐름 밖이라 부모의 padding 이 안 먹는다는 걸 잊으면 늘 이렇게 된다.
   ⚠️ ② 대화가 **좁은 띠**로 남았다(1440 에서 505px). 대화는 읽는 화면이 아니라 주고받는
        화면이라, 나머지 화면과 같은 폭 규칙(읽기 좋은 34~40rem)이 안 어울린다.
        메신저는 창을 채운다.
   ⚠️ 대신 **말풍선은 따로 묶는다** — 칼럼을 넓히면서 말풍선까지 같이 넓히면 한 줄이
      900px 이 되어 눈이 줄을 못 따라간다. 넓어지는 건 '자리'고, 말풍선은 읽는 폭을 지킨다. */
@media (min-width: 1024px) {
  body.chat-page main.wrap { max-width: 64rem; }
  /* ⚠️ 셋 다 같이 옮긴다 — 하나만 두면 고치는 중 알림줄·이모지판이 딴 데 뜬다 */
  body.chat-page .chat-send,
  body.chat-page .chat-editing,
  body.chat-page .emoji-pad { left: 15rem; max-width: 64rem; }
  /* 창은 넓어져도 한 줄은 읽을 수 있는 길이로.
     ⚠️ 70% = 1008px 칼럼에서 말풍선 ~660px ≈ 한글 50자 안팎이다(읽기 좋은 45~75자 안).
        60% 로 뒀더니 칼럼의 절반밖에 못 써서 여백이 다시 넓어 보였다(실측 50%). */
  body.chat-page .chat-row { max-width: 70%; }
}

/* ── 수유 기록 (2026-08-16) ──
   ⚠️ 이 화면의 주인공은 **"마지막으로 먹은 지 얼마나 됐나"** 한 줄이다. 게임 시간의
      시계와 같은 자리·같은 크기 — 새벽에 한 팔로 안고 보는 화면이라 한 박자라도 줄여야 한다.
   ⚠️ 다만 **초를 세지 않는다**(게임 시간과 다른 점). 1초마다 바뀌면 재촉처럼 보인다. */
.feed-card { margin-top: 1.25rem; text-align: center; }
.feed-ago {
  font-size: 2.4rem; font-weight: 600; line-height: 1.15; color: var(--primary);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; margin: .1rem 0 .2rem;
}
.feed-ago.off { font-size: 1.5rem; color: var(--ink-3); }
/* ⚠️ 한 손으로 누른다 — 폭을 다 쓰고 높이도 넉넉히 */
.feed-now { width: 100%; min-height: 3.5rem; font-size: 1.05rem; }

/* ══ 끝말잇기 입력줄 ══ 2026-08-16 사용자 지시("이어진 말이 계속될수록 글 넣는 창이
   계속 밑으로 내려가지 않도록"). 말이 쌓이면 폼이 화면 밖으로 밀려 매번 스크롤해야 했다.
   ⚠️ **대화(.chat-send)와 같은 식**을 쓴다 — 같은 문제를 이미 그렇게 풀었고, 두 벌로
      갈라 두면 탭바 높이를 고칠 때 한쪽만 남는다.
   ⚠️ 좌우·최대폭도 본문과 같은 값이어야 줄 끝이 안 어긋난다(대화에서 겪은 그대로). */
.word-send {
  position: fixed; left: 0; right: 0;
  bottom: calc(4rem + env(safe-area-inset-bottom));   /* 탭바와 같은 식 */
  display: flex; gap: .5rem; align-items: center;
  padding: .625rem 1.25rem; background: var(--bg);
  border-top: 1px solid color-mix(in srgb, var(--line) 45%, transparent);
  max-width: 34rem; margin: 0 auto; z-index: 15;
}
.word-send .field { flex: 1 1 auto; margin: 0; min-width: 0; }
.word-send .field label { display: block; margin-bottom: .15rem; }
/* ── 보내기 단추를 입력칸에 **붙인다** (2026-08-23 사장님 지시) ──────────────
   "게임 채팅박스 옆에서 보내기 버튼이 너무 커!" · "붙이기!"
   ⚠️ 왜 커 보였나 — 둘이다.
     ① `.btn` 의 기본 좌우 여백이 **2rem**(명세의 손에 잡히는 크기)인데, 좁은 폰에서
        입력칸 옆에 서면 그 여백이 화면 폭을 크게 먹는다.
     ② `.word-send` 가 `align-items:center` 라 단추가 **라벨+입력칸 전체의 가운데**에
        맞춰졌다 — 입력칸과 줄이 안 맞아 더 커 보였다.
   ⇒ 좌우 여백을 줄이고, **입력칸 아래선에 맞추고**, 사이를 붙여 한 덩이로 만든다.
   ⚠️ 높이는 줄이지 않는다 — 입력칸과 같은 3.5rem 이라 손가락 크기(명세 §Touch)를 넘는다.
      «작게»는 **가로**를 줄이라는 말이지 누르기 어렵게 하라는 말이 아니다. */
.word-send { align-items: flex-end; gap: 0; }
.word-send .field input {
  border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: 0;
}
.word-send .btn {
  flex: 0 0 auto; padding: 0 1.1rem; min-height: 3.5rem;
  border-top-left-radius: 0; border-bottom-left-radius: 0;
}
/* ⚠️ 입력칸에 focus 가 오면 테두리가 2px 로 굵어진다 — 붙은 단추도 같이 굵어져야
   한 덩이로 남는다. 안 그러면 이음매에서 어긋나 보인다. */
.word-send .field input:focus + .btn,
.word-send .field:focus-within ~ .btn { border-color: var(--accent); border-width: 2px; }
/* ⚠️ 고정 줄에 가리지 않게 본문 아래를 비운다. 라벨이 한 줄 더 있어 대화보다 조금 높다. */
body.word-page main.wrap { padding-bottom: 9rem; }
@media (min-width: 1024px) {
  /* ⚠️ position:fixed 는 사이드바를 모른다 — 대화와 같은 값으로 비켜 준다 */
  body.word-page .word-send { left: 15rem; max-width: 40rem; }
  body.word-page main.wrap { padding-bottom: 8rem; }
}

/* ══ 초성게임 ══ 2026-08-17. 결재 #75 1순위.
   ⚠️ **초성이 화면의 주인공**이다. 글자 몇 개가 문제의 전부라, 작으면 화면이 무슨 화면인지
      알 수가 없다. (처음엔 "온 가족이 한 화면을 본다"가 이유였는데 2026-08-17 온라인화로
      각자 폰을 보게 됐다 — 그래도 크기는 그대로가 맞다.)
   ⚠️ **점수·별·등수 자리가 아직 없다.** 막던 원칙 9 는 2026-08-18 폐기됐으니 이제 금지가
      아니라 **안 만든 것**이다 — 붙이려면 여기에 클래스를 새로 만들게 되고, 그때 이 줄을 본다. */
.cho-board {
  text-align: center; padding: 2rem 1rem; margin-top: 1rem;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.cho-letters {
  font-size: 3rem; font-weight: 600; letter-spacing: .35em;
  /* ⚠️ letter-spacing 은 **오른쪽에** 붙는다 — 안 밀어 주면 가운데가 왼쪽으로 치우친다 */
  text-indent: .35em;
  line-height: 1.2; color: var(--ink);
}
.cho-answer { font-size: 2rem; font-weight: 600; color: var(--primary); margin-top: .75rem; }
/* ⚠️ 단추 둘을 나란히. "정답 보기"가 없을 때도 "다음 문제"가 같은 자리에 있어야
   손이 헤매지 않는다(그래서 flex-end 가 아니라 stretch 로 나눈다). */
.cho-acts { display: flex; gap: .5rem; margin-top: 1rem; }
.cho-acts form { flex: 1 1 0; }
.cho-acts .btn { width: 100%; }

/* ⚠️ 스무고개 — 질문 줄에 붙는 예·아니요 단추. 2026-08-17 온라인화.
   ⚠️ 단추가 셋이라 줄바꿈이 생기기 쉽다 — 질문이 길어도 단추가 **한 덩이로** 붙어 있게
      wrap 을 막고, 대신 질문 쪽이 줄어든다(읽는 건 두 줄이 돼도 되지만 단추가 흩어지면
      어느 질문의 답인지 알 수 없다). */
.say-ans { display: flex; gap: .25rem; flex: 0 0 auto; white-space: nowrap; }
.say-ans .cmt-del { padding: .2rem .5rem; }

/* ⚠️ 스무고개의 **출제자 이름**은 초성이 아니다. `.cho-letters` 를 그대로 썼더니
   자간 .35em 이 먹어 "아 빠"로 벌어졌다(2026-08-17 화면 실측) — 초성은 글자를 하나씩
   떼어 보여야 하고 이름은 붙어 있어야 한다. 크기만 물려받고 자간은 되돌린다. */
.guess-who { font-size: 2.2rem; font-weight: 600; line-height: 1.2; color: var(--ink);
             letter-spacing: -.02em; text-indent: 0; }

/* ⚠️ 놀이 입력줄의 단추가 **입력칸과 안 맞았다**(2026-08-18 사장님 지적 "넣기 사이즈가
   이상해"). 실측(390px): 입력칸 56px 높이인데 단추는 48px, 게다가 `align-items:center` 라
   **라벨까지 포함한 한가운데**에 떠 있어 위로 10px 어긋나 있었다 — 입력칸 옆에 있는데
   입력칸과 아귀가 안 맞으니 크기가 이상해 보인다.
   ⚠️ 라벨이 한 줄이라 눈에 잘 안 띄는 종류다. 두 화면(끝말잇기·떠오르는 말 잇기)이 같은
      `.word-send` 를 써서 한 번에 고쳐진다. */
.word-send .btn { align-self: flex-end; min-height: 3.5rem; }

/* ⚠️ 맞힌 말 — **눈에 띄되 상 같지는 않게**(확정 원칙 9(2026-08-18 폐기)). 색만 살짝 바뀐다.
   별·트로피·크기 키우기를 안 쓰는 것이 그 선이다. */
.say-hit { background: color-mix(in srgb, var(--primary) 8%, transparent); border-radius: var(--radius); }

/* ══ 이어쓰기 이야기 ══ 2026-08-18.
   ⚠️ **이야기는 목록이 아니라 글이다.** 다른 놀이처럼 줄마다 칸을 나누면(누가·무엇) 읽을 때
      이야기가 안 이어진다 — 문장을 잇대어 놓고 이름은 작게 옆에 둔다.
   ⚠️ 그래서 `.sched` 를 안 쓴다. 같은 폴링 코드가 여기만 다른 모양으로 그린다
      (assets/wordchain.js 의 KIND === 'story'). 한쪽만 고치면 **폴링으로 온 문장만
      모양이 다르다** — 새로고침하면 멀쩡해지는, 제일 찾기 어려운 종류의 어긋남이다. */
.story-log { line-height: 1.9; font-size: 1.05rem; }
.story-line { margin: 0 0 .35rem; }
.story-who { font-size: .75rem; color: var(--ink-3); white-space: nowrap; }

/* ══ 복약 ══ 2026-08-18. 진료에서 떼어내며 신설.
   ⚠️ **오늘 칸이 이 화면의 주인공**이다 — 여기 온 사람의 질문은 "오늘 아침 약 먹였나?"
      하나다. 그래서 칸이 크고, 누른 것과 안 누른 것이 멀리서도 갈린다.
   ⚠️ **누른 칸을 상처럼 꾸미지 않는다** — 색만 바뀐다. 별·체크마크·크기 키우기를 안 쓴다.
      이건 성취가 아니라 사실이다(약을 먹인 것은 잘한 일이 아니라 한 일이다).
   ⚠️ 아이 화면은 같은 모양을 **span 으로** 그린다(누를 수 없다) — 눌러도 아무 일이
      없으면 그게 더 답답해서, 아예 단추가 아니게 만든다. */
.med-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .875rem 1rem; margin-bottom: .625rem;
}
.med-head { display: flex; align-items: baseline; justify-content: space-between; gap: .5rem; }
.med-name { font-weight: 600; }
/* ⚠️ 칸이 여섯까지 온다(하루 6번) — 줄바꿈을 막지 않는다. 좁은 폰에서 한 줄에 욱여넣으면
   글자가 잘리고, 약 이름이 잘리는 것보다 나쁜 것은 시각이 잘리는 것이다. */
.med-slots { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .5rem; }
/* ⚠️ 2026-08-18 사장님 지적("아침 점심 저녁 칸이 너무 작게 되어있어").
   원인은 **칸을 나눠 갖는 것이 단추가 아니라 폼**이라는 것이었다 —
   단추마다 제 `<form>` 으로 감싸 놓아서(CSRF·act 를 각각 실어야 한다),
   `.med-slots` 의 flex 자식은 폼이다. 폼엔 flex 규칙이 없어 **글자 폭(42px)** 으로
   쪼그라들었고, 아래 `.med-slot { flex: 1 1 4.5rem }` 은 아무 일도 안 하고 있었다.
   (칸은 316px 인데 단추 셋이 42px 씩만 쓰고 있었다.)
   ⚠️ 하루 세 번 누르는 단추다 — 아이를 안은 채 한 손으로 누른다. 크기가 곧 쓸모다. */
.med-slots > form { flex: 1 1 4.5rem; display: flex; margin: 0; }
.med-slots > form > .med-slot { width: 100%; }
.med-slot {
  flex: 1 1 4.5rem; min-height: 3.5rem; padding: .5rem .5rem;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); color: var(--ink-2); font-size: 1rem; line-height: 1.2;
}
.med-slot small { font-size: .72rem; color: var(--ink-3); margin-top: .15rem; }
.med-slot.on { background: color-mix(in srgb, var(--primary) 12%, var(--surface));
               border-color: var(--primary); color: var(--ink); }
.med-slot.on small { color: var(--ink-2); }
button.med-slot { cursor: pointer; }
.med-end { text-align: right; margin-top: .5rem; }

/* ── 약 먹는 시각 ── 2026-08-20 사장님 지적("시간설정 버튼의 가로 넓이가 너무 길어서
   화면 옆으로 나가"). 아침·점심·저녁을 **가로로 셋 늘어놓던 것을 세로로 쌓는다.**
   ⚠️ 원인은 폭 지정이 아니라 **flex 항목의 최소폭**이었다. `<label style="flex:1">` 은
      flex-basis:0 이라 줄어들 것 같지만 `min-width` 기본값이 `auto` 라 **속 내용의
      min-content 아래로는 안 줄어든다.** 속에 든 `<input type="time">` 은 네이티브
      컨트롤이라 고유폭이 있고(ko-KR 은 "오전 08:00" 이라 **101px**), 거기에 좌우
      padding 32px 가 붙어 한 칸이 133px 이 됐다. 390px 화면에서 한 칸에 쓸 수 있는 폭은
      (390 - 40 - 16) / 3 = **111px** 이라 셋째 칸이 45px 밖으로 나갔다.
      `width:100%` 는 이 계산을 못 막는다 — min-content 를 잴 때 백분율은 auto 로 친다.
   ⚠️ **잘려서 더 나빴다.** 부모에 overflow 가 걸려 가로 스크롤조차 안 생겼고(문서
      scrollWidth 는 정상이었다), 저녁 칸은 밀려나간 채 **누를 수도 없었다.**
      칸 하나만 재는 시험은 이걸 못 잡는다 — 폭과 오른쪽 끝을 같이 잰다.
   ⚠️ **padding 을 줄여 맞추는 길은 없다.** 컨트롤 고유폭 101px 는 못 줄이고,
      101×3 + gap 16 + 화면여백 40 = 359px 라 320px 폰에서는 padding 을 0 으로 해도
      산수가 안 맞는다. 세로로 쌓는 것이 모든 폭에서 안 깨지는 유일한 답이다.
   ⚠️ 세로로 길어지는 대가가 작은 자리다 — **가정이 한 번 정하고 마는 설정**이고
      (약마다 안 받는다) 이 화면의 주인공은 위쪽 「오늘」 칸이다.
   집 안에 이미 있는 「라벨 … 값」 설정 줄(.sched-row/.row-label)과 같은 꼴로 맞춘다. */
.medtime { display: flex; flex-direction: column; gap: .5rem; }
.medtime label { display: flex; align-items: center; gap: .875rem; }
/* 라벨 폭을 고정해 셋의 입력칸 왼쪽 끝이 **한 줄로 선다** — 들쭉날쭉하면 목록으로 안 읽힌다 */
.medtime label > .caption { flex: 0 0 2.5rem; color: var(--ink); font-size: .95rem; font-weight: 500; }
/* ⚠️ min-width:0 이 있어야 위에 적은 최소폭 함정을 다시 안 밟는다 */
.medtime input[type="time"] { flex: 1 1 auto; min-width: 0; }

/* ══ 숙제 ══ 2026-08-20 신설(사장님 지시).
   ⚠️ **늦은 줄만 색이 다르다.** 나머지는 평범하게 둔다 — 다 강조하면 아무것도 강조가 아니고,
      숙제 목록은 원래 대부분이 "아직 안 낸 것"이라 전부 빨개진다. */
.hw-row { gap: .625rem; }
/* 과목 — 작고 조용하게. ⚠️ 제목보다 눈에 띄면 "수학"이 "문제집 12~15쪽"을 이긴다 */
.hw-subject { flex: 0 0 auto; font-size: .75rem; color: var(--ink-2);
              background: var(--surface-2); border-radius: 999px; padding: .15rem .5rem; }
.hw-due { flex: 0 0 auto; }
/* .hw-act 는 2026-08-24 에 없앴다 — 숙제 줄이 공용 `ns_hw_row()` 로 가면서
   `.row-keep`(같은 규칙: flex:0 0 auto; margin:0)을 쓴다. 규칙이 같아 화면은 안 달라졌다. */
.hw-late { background: color-mix(in srgb, var(--danger) 7%, transparent); }
.hw-late .hw-due { color: var(--danger); font-weight: 600; }

/* ══ 하루 시간표 ══ 2026-08-20 신설.
   ⚠️ 「지금」 칸이 이 화면의 주인공이라 카드로 띄운다 — 목록과 같은 무게로 그리면
      스무 줄 중 한 줄이 되어 안 보인다. */
.tt-now { background: color-mix(in srgb, var(--primary) 10%, var(--surface));
          border: 1px solid var(--primary); border-radius: var(--radius-l);
          padding: 1rem 1.125rem; margin-bottom: .75rem; }
.tt-now-label { font-size: .8rem; color: var(--ink-2); }
.tt-now-title { font-size: 1.35rem; font-weight: 700; line-height: 1.3; margin-top: .15rem; }
.tt-next { margin-top: .625rem; padding-top: .625rem; font-size: .85rem; color: var(--ink-2);
           border-top: 1px solid color-mix(in srgb, var(--line) 60%, transparent); }

/* 요일 고르기 — 일곱이 한 줄에 선다. ⚠️ 320px 에서도 들어와야 해서 글자 하나짜리다 */
.tt-days { display: flex; gap: .375rem; }
.tt-day { flex: 1 1 0; min-width: 0; text-align: center; padding: .5rem 0;
          border: 1px solid var(--line); border-radius: var(--radius);
          background: var(--surface); color: var(--ink-2); font-size: .9rem; }
.tt-day.today { font-weight: 700; color: var(--ink); }
.tt-day.on { background: var(--primary); border-color: var(--primary); color: #fff; }

/* ── 요일 탭을 부드럽게 ── 2026-08-22 사장님 지시 "날짜 바꿀 때도 부드럽게 바뀌도록".
   ⚠️ 탭은 **페이지를 새로 여는 링크**라 색이 뚝 바뀐다 — 누른 순간의 반응과, 새 화면이
      들어오는 결 둘 다 필요하다. 여기서는 그 둘을 준다(즉시 전환은 구조가 달라진다).
   ⚠️ 누를 때 반응은 줄(.tt-row)과 **같은 방식**으로 맞춘다 — 한 화면에서 누르는 느낌이
      자리마다 다르면 그게 더 어색하다. tap highlight 를 끄고 우리가 대신 준다. */
.tt-day {
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.tt-day:active { transform: scale(.94); }

/* 새 요일의 내용이 **살짝 떠오르며** 들어온다 — 새로고침이 "툭" 끊기지 않게.
   ⚠️ 짧아야 한다(--dur-in). 길면 요일을 연달아 누를 때 화면이 계속 출렁인다.
   ⚠️ 움직임 줄이기 설정은 위쪽 `prefers-reduced-motion` 이 통째로 끈다(한 곳에만 둔다). */
@keyframes ttDayIn { from { opacity: 0; transform: translateY(.4rem); } to { opacity: 1; transform: none; } }
/* ⚠️ **형제 선택자(`~`)를 쓰지 않는다.** 처음엔 `.tt-days ~ .sec-head` 로 걸었는데
   `~` 는 뒤에 오는 형제를 **전부** 잡아 아래 「시간표에 넣기」 제목까지 같이 떠올랐다.
   움직여야 할 것은 그날 목록 하나뿐이다 — timetable.php 가 `.tt-daybox` 로 경계를 긋는다. */
.tt-daybox { animation: ttDayIn var(--dur-in) var(--ease) both; }
.tt-time { flex: 0 0 3rem; font-variant-numeric: tabular-nums; color: var(--ink-2); font-size: .9rem; }
.tt-row.on .tt-time { color: var(--primary); font-weight: 700; }
.tt-row.on { background: color-mix(in srgb, var(--primary) 8%, transparent); }
.tt-rep { flex: 0 0 auto; }
.row-act { flex: 0 0 auto; margin: 0; }
.sched-title small { display: block; font-size: .78rem; color: var(--ink-3); margin-top: .1rem; }

/* ⚠️⚠️ 네이티브 입력을 나란히 세우는 자리 — **둘까지만**이다.
   셋을 세우면 320~390px 에서 화면 밖으로 나간다(고유폭 101px + padding 32px 를 못 줄인다).
   자세한 산수는 위 `.medtime` 머리말. 여기서도 min-width:0 을 꼭 준다 —
   없으면 flex 항목이 속 내용의 min-content 아래로 안 줄어든다. */
.hw-new, .tt-new { display: flex; gap: .5rem; }
.hw-new > *, .tt-new > label { flex: 1 1 0; min-width: 0; }
.tt-new > label { display: flex; flex-direction: column; gap: .25rem; }
.hw-new > input, .tt-new input { width: 100%; min-width: 0; }

/* ── 요일 고르기 ── 2026-08-22 사장님 지적 "구조가 너무 복잡해"(사진).
   ⚠️ **전에는 네모 체크박스 + 글자**가 `flex-wrap` 으로 놓여 있었다. 좁은 폰에서
      **여섯 개가 첫 줄, 「토」 하나가 둘째 줄에서 폭을 다 먹는** 모양이 됐다(실측 사진).
      체크박스 일곱이 자리를 다 먹어 접는 상자 안이 통째로 무거워 보였다.
   ⇒ **일곱 칸 격자 한 줄**로 못 박고, 체크박스는 **칩 자체가 되게** 한다.
      네모를 지운 게 아니라 **칸 전체가 네모**다 — 누를 자리가 커지고 줄이 안 깨진다.
   ⚠️ `:has()` 를 안 쓴다 — 안 되는 기기에서 **고른 게 안 보인다.** 대신
      `input:checked + span` 이라는 오래된 선택자를 쓴다(어디서나 된다).
   ⚠️ 체크박스를 `display:none` 으로 지우지 않는다 — 그러면 키보드·읽어 주는 기기에서
      사라진다. 투명하게 덮어 두면 **누르는 것도 읽는 것도 원래대로** 된다. */
.tt-pick { display: grid; grid-template-columns: repeat(7, 1fr); gap: .25rem; }
.tt-pick-day { display: block; position: relative; margin: 0; }
.tt-pick-day input[type="checkbox"] {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  opacity: 0; cursor: pointer; -webkit-appearance: none; appearance: none;
}
.tt-pick-day span {
  display: block; text-align: center; padding: .55rem 0;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink-2); font-size: .85rem;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.tt-pick-day input:checked + span {
  background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600;
}
.tt-pick-day input:active + span { transform: scale(.93); }
/* 키보드로 옮겨 다닐 때 지금 어느 요일인지 보여야 한다 — 체크박스가 투명해서 더 필요하다 */
.tt-pick-day input:focus-visible + span { outline: 2px solid var(--primary); outline-offset: 2px; }

/* ══ 일기 공개 범위 ══ 2026-08-20 신설(아이가 자기 글을 「나만 보기」로 둘 수 있다).
   ⚠️ 고르는 칸을 **크고 평범하게** 둔다 — 작게 숨겨 두면 부담을 던다는 뜻이 안 살고,
      너무 강조하면 "숨겨야 하나?"를 묻는 화면이 된다. 기본이 「보여주기」인 게 답이다. */
.vis-pick { display: flex; gap: .5rem; }
.vis-pick label { flex: 1 1 0; min-width: 0; display: flex; align-items: center; justify-content: center;
                  gap: .375rem; min-height: 3rem; padding: 0 .5rem; font-size: .95rem;
                  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
/* ⚠️ 라디오가 화면 폭짜리 상자가 되지 않게 못 박는다(`.field input` 이 width:100% 를 거는 자리) */
.vis-pick input[type="radio"] { width: 1rem; height: 1rem; min-width: 1rem; margin: 0; }
.vis-pick label:has(input:checked) { border-color: var(--primary);
                                     background: color-mix(in srgb, var(--primary) 10%, var(--surface)); }
.vis-lock { flex: 0 0 auto; }
/* 부모 화면에서 가려진 자리 — ⚠️ 빈 줄로 두지 않는다. 무엇이 왜 안 보이는지 말해 준다 */
.vis-hidden { color: var(--ink-3); font-style: italic; }

/* 시간표 한 줄을 **그 자리에서** 고친다 — 2026-08-20.
   ⚠️ 줄이 세로 폼으로 바뀌므로 sched-row 의 가로 배치를 끈다. */
/* ── 고치는 중인 줄 ── 2026-08-22 사장님 지적 "뭘 수정하는지 표시가 제대로 안돼" →
   "색으로 구분했으면 좋겠어".
   ⚠️ 그전엔 `.tt-edit` 이 **모양만 세로로** 바꿨고 **색 구분이 아예 없었다** — 여러 줄 중
      어느 것을 연 건지 알 수 없었고, 제목을 지우는 순간 단서가 통째로 사라졌다.
   ⚠️ 왼쪽 굵은 선 + 옅은 바탕으로 **한눈에 이 줄**임을 보인다. 배경만 칠하면 좁은 폰에서
      옆 줄과 구별이 약하다 — 선이 경계를 만든다.
   ⚠️ 색은 `--primary` 를 옅게 섞어 쓴다(새 색을 만들지 않는다) — 이 앱은 색이 브랜드다. */
.tt-edit {
  flex-direction: column; align-items: stretch;
  background: color-mix(in srgb, var(--primary) 7%, transparent);
  border-left: 3px solid var(--primary);
  border-radius: var(--radius);
  padding-left: calc(1.25rem - 3px);
}
/* 「고치는 중 · 09:00 · 수학」 — 원래 제목을 그대로 남긴다(입력칸 값이 아니라) */
.tt-editnow { margin: 0 0 .25rem; font-size: .85rem; color: var(--ink-2); }
.tt-editbadge {
  display: inline-block; padding: .1rem .45rem; margin-right: .35rem;
  border-radius: 999px; background: var(--primary); color: #fff;
  font-size: .72rem; font-weight: 600; vertical-align: middle;
}
.tt-editform { display: flex; flex-direction: column; gap: .5rem; width: 100%; margin: 0; }
.tt-editbtn { display: flex; gap: .5rem; }
.tt-editbtn > * { flex: 1 1 0; min-width: 0; display: flex; align-items: center; justify-content: center; }
/* 「고치기」는 <a> 라 단추 모양을 맞춰 준다(폼 안의 button 과 나란히 선다) */
a.row-act { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
/* ⚠️ 단추 둘이 나란히 서므로 좌우 여백을 줄인다 — 320px 에서 시각·제목까지 넣으면
   기본 padding 으로는 안 들어간다(2026-08-20 실측 70px 초과).
   ⚠️ 2026-08-21 수정 — 이 규칙이 「고치기」(그 자신이 .row-act) 엔 걸렸지만 「빼기」는
   `<form class="row-act">` 안의 `<button class="btn">` 라 이 규칙이 못 닿고 전역 `.btn`
   (좌우 32px)을 그대로 썼다. 그래서 글자 수가 더 적은 「빼기」가 더 넓었다
   (사장님 폰 실측·2026-08-21). `.btn` 을 가진 요소 자신에게 걸리도록 선택자를 넓힌다. */
.tt-row .row-act.btn, .tt-row .row-act .btn { padding: 0 .6rem; font-size: .85rem; }
.row-actions { display: flex; gap: .5rem; flex: 0 0 auto; }
/* ⚠️⚠️ 2026-08-21 — `.tt-row .sched-title { min-width: 0 }` 이 있으면 제목이 바닥없이
   줄어든다. 한글은 공백이 없어 min-content 가 **글자 하나** 폭이라, 좁은 화면에서
   flex 엔진이 "줄바꿈 대신 한 글자씩 눌러 넣기"를 골랐다(2026-08-21 사장님 폰 실측 —
   "도서관가기"가 다섯 줄). 최소 4.5글자 폭은 지키게 하고, 그래도 자리가 없으면
   **제목이 아니라 단추 묶음(.row-actions)이 통째로 다음 줄로** 내려간다 — 이 파일 위쪽
   주석의 원칙과 같다("가로로 늘어놓을 자리가 없으면 세로로 쌓는다", 요일을 제목
   아래로 내린 이유와 동일). 제목을 말줄임(ellipsis)으로 자르는 대신 이 길을 골랐다 —
   제목엔 요일·메모까지 두 줄로 붙어 있어 한 줄 말줄임 CSS 가 그 구조와 잘 안 맞고,
   글자 하나만 잘려도 "무슨 시간인지" 자체를 잃기 때문이다. */
/* ⚠️⚠️ **2026-08-22 — 전제가 바뀌어서 이 결정을 뒤집는다.**
   위 판단은 단추가 **「고치기」「빼기」 글자**였을 때 것이다(둘이 130px 넘게 먹었다).
   오늘 사장님 지시로 ✏️🗑️ **이모지**가 되면서 둘이 **88px** 로 줄었다.
   실측 계산(320px 기준): 320 − 좌우여백40 − 시각60 − gap28 − 단추88 = **제목에 104px(6.5em)**.
   ⇒ 이제 셋이 한 줄에 들어간다. 그런데 `flex-wrap: wrap` 이 남아 있어 **제목이 조금만 길면
     단추가 통째로 다음 줄로** 내려갔다(사장님 제보: "영어 자기소개카드 다음에 줄바꿈").

   ⇒ **줄바꿈을 끄고, 대신 제목이 스스로 여러 줄로 접히게 한다.**
   ⚠️ 그래도 `min-width: 0` 은 **절대 안 된다** — 2026-08-21 에 겪은 그것이다. 한글은 공백이
      없어 min-content 가 **글자 하나** 폭이라, flex 엔진이 "줄바꿈 대신 한 글자씩 눌러 넣기"를
      골라 "도서관가기"가 다섯 줄이 됐다. **바닥(4.5em)은 그대로 지킨다** — 그 아래로는
      못 줄이니 제목은 그 폭 안에서 정상적으로 줄바꿈된다.
   ⚠️ 말줄임(ellipsis)은 여전히 안 쓴다 — 제목 아래 요일·메모가 두 줄로 붙어 있어 구조가
      안 맞고, 글자 하나만 잘려도 "무슨 시간인지"를 잃는다(2026-08-21 판단 그대로). */
.tt-row { flex-wrap: nowrap; }
.tt-row .sched-title { min-width: 4.5em; flex: 1 1 auto; }
/* 제목이 여러 줄이 되면 단추가 가운데로 뜬다 — 첫 줄에 맞춰 위로 붙인다 */
/* ⚠️ 이 둘은 **시간표 전용**으로 남긴다 — 시간표 줄만 제목 아래 요일이 한 줄 더 붙어
   세로가 길다(다른 화면 줄은 한 줄이라 가운데가 맞다). 접는 동작 자체는 위에서 공용이다. */
.tt-row .row-actions { align-self: flex-start; margin-top: .15rem; }

/* ── 터치하면 나오는 고치기·빼기 ── 2026-08-22 사장님 지시:
   *"앱이니까 일정을 터치하면 수정 삭제 이모지가 나오는 방법도 좋고!"*
   ⚠️⚠️ **2026-08-23: `.tt-row` 를 뗐다.** 이 결을 화면 아홉 곳에 더 붙이면서
      (기념일·학교·용돈·신체측정·복약·수유·진료·할일습관·달력) 시간표 전용 클래스가
      붙어 있으면 **다른 화면은 단추가 늘 펼쳐진 채**로 남는다. 이제 `[data-rowact]` 만 본다.
   ⚠️ **`.js-rowact` 가 있을 때만 접는다.** 그 클래스는 assets/rowactions.js 가 켠다 —
      JS 가 막히면 이 규칙이 아예 안 걸려 단추가 **늘 보인다.** 서버는 `hidden` 을 안 박는다.
      (JS 없이도 고치기·빼기에 손이 닿아야 한다 — wordchain.js 머리말의 같은 규약.)
   ⚠️ `display:none` 이 아니라 폭을 접는다 — 열고 닫을 때 줄 높이가 안 튀어야 옆 줄이
      아래위로 밀리지 않는다.
   ⚠️ 열린 줄은 눌린 것이 보이게 살짝 배경을 준다 — 어느 줄을 열었는지 모르면
      **엉뚱한 일정을 지운다.** */
/* ⚠️⚠️ **흐름에서 뺀다(absolute).** 처음엔 폭만 접었는데, 열면 단추가 자리를 차지해
   **제목이 좁아지고 줄이 늘어나 줄 높이가 72→98px 로 튀었다**(2026-08-22 시험이 잡음).
   그러면 아래 줄들이 26px 씩 밀려서, 누르려던 줄이 손가락 밑에서 움직인다 —
   **엉뚱한 일정의 「빼기」를 누르는 길**이다. 접든 열든 제목 폭이 안 변해야 한다.
   ⚠️ 떠 있으므로 배경을 깐다 — 안 깔면 제목 글자 위에 이모지가 겹쳐 둘 다 안 읽힌다. */
/* ⭐ **눌리는 느낌**(2026-08-22 사장님 지시 "시간표를 터치 눌렀을 때도 부드럽게 터치되는 느낌으로").
   ⚠️ `-webkit-tap-highlight-color` 를 끈다 — 안 끄면 안드로이드 웹뷰의 **기본 파란 네모**가
      우리 효과 위에 겹쳐 번쩍인다. ⚠️ **끄는 순간 우리가 대신 줘야 한다** — 아무 반응이 없으면
      아이는 "안 눌렸나?" 하고 **두 번 누른다.** 그래서 아래 `:active` 가 짝이다.
   ⚠️ 아주 살짝만 줄인다(.985). 크게 줄이면 손가락 밑에서 줄이 달아나는 느낌이 든다.
   ⚠️ `transform` 은 흐름을 안 건드리니 옆 줄이 안 밀린다(위 .row-actions 와 같은 이유). */
.js-rowact [data-rowact] {
  cursor: pointer; position: relative;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.js-rowact [data-rowact]:active { transform: scale(.985); }
.js-rowact [data-rowact] .row-actions {
  position: absolute; right: 1.25rem; top: 50%;
  padding: .15rem .3rem; border-radius: var(--radius-m, .5rem);
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--line) 40%, transparent);
  /* ⚠️ **`visibility` 까지 끈다.** `opacity:0` 만으로는 **키보드 탭이 그대로 잡고**,
     읽어 주는 기기도 읽는다 — 접혀 있는데 "고치기"가 들리면 그게 더 헷갈린다.
     ⚠️ 그런데 `visibility` 를 바로 끄면 사라지는 동작이 안 보인다 — **나갈 때만 늦춘다**
        (`transition-delay`). 들어올 때는 0 이라 곧바로 보인다. */
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-50%) translateX(.45rem) scale(.9);
  transition: opacity var(--dur) var(--ease),
              transform var(--dur) var(--ease),
              visibility 0s linear var(--dur);
}
/* ⭐ 부드럽게 등장한다(2026-08-22 사장님 지시 "아이콘이 부드럽게 등장할 수는 없어?").
   ⚠️ 자리를 옮기는 게 아니라 **살짝 밀려 들어오며 커진다** — absolute 라 흐름을 안 건드리니
      아무리 움직여도 옆 줄이 안 밀린다.
   ⚠️ 움직임을 싫어하는 설정은 이 파일 위쪽 `@media (prefers-reduced-motion: reduce)` 가
      `transition: none !important` 로 통째로 끈다 — 여기 따로 안 적는다(한 곳에만 둔다). */
.js-rowact [data-rowact].rowact-open .row-actions {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(-50%) translateX(0) scale(1);
  transition: opacity var(--dur) var(--ease),
              transform var(--dur-in) var(--ease),
              visibility 0s linear 0s;
}
/* 두 이모지가 **아주 살짝 시차**를 두고 뜬다 — 동시에 나오는 것보다 눈이 편하다.
   ⚠️ 시차를 크게 주면 "느리다"로 느껴진다. 40ms·100ms 면 의식은 못 하고 결만 남는다. */
.js-rowact [data-rowact] .row-act {
  opacity: 0; transform: scale(.82);
  transition: opacity var(--dur) var(--ease), transform var(--dur-in) var(--ease);
}
.js-rowact [data-rowact].rowact-open .row-act { opacity: 1; transform: scale(1); }
.js-rowact [data-rowact].rowact-open .row-act:nth-child(1) { transition-delay: .04s; }
.js-rowact [data-rowact].rowact-open .row-act:nth-child(2) { transition-delay: .10s; }
.js-rowact [data-rowact].rowact-open { background: color-mix(in srgb, var(--surface-2) 60%, transparent); }
/* 눌렀다 뗐을 때 배경이 뚝 끊기지 않게 — 위 transition 이 background 를 같이 물고 있다 */
.js-rowact [data-rowact]:active.rowact-open { transform: scale(.985); }
/* 키보드로 옮겨 다닐 때 지금 어느 줄인지 보이게 — role=button 을 줬으니 초점 표시가 필요하다 */
.js-rowact [data-rowact]:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

/* ══ 오늘의 주인공 ══ 2026-08-18.
   ⚠️ **질문과 답이 한 덩이로 읽혀야 한다** — 다른 놀이처럼 한 줄에 칸을 나누면
      "누가 무엇을 물었고 뭐라고 답했나"가 흩어진다. 질문 아래 답을 들여쓴다. */
.star-row { padding: .625rem 0; border-bottom: 1px solid color-mix(in srgb, var(--line) 45%, transparent); }
.star-row:last-child { border-bottom: 0; }
.star-q { margin: 0; }
.star-a { margin: .35rem 0 0 1.25rem; padding-left: .625rem;
          border-left: 2px solid var(--primary); color: var(--ink); }
.star-reply { display: flex; gap: .4rem; margin: .35rem 0 0 1.25rem; }
.star-reply input[name="text"] {
  flex: 1 1 auto; min-height: 2.75rem; padding: 0 .75rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
}
.star-reply .btn { flex: 0 0 auto; min-height: 2.75rem; padding: 0 .9rem; }

/* ══ 짝 맞추기 ══ 2026-08-18. 놀이 여덟 중 **유일한 "진짜 게임"**.
   ⚠️ 카드가 **손가락으로 눌리는 크기**여야 한다 — 짝 맞추기는 아이가 하는 게임이고,
      작으면 옆 카드를 누른다. 4쌍이면 8장, 8쌍이면 16장이 한 화면에 들어가야 해서
      grid 로 자동 배치하고 카드는 정사각(aspect-ratio)으로 둔다.
   ⚠️ **뒷면이 빈 칸이면 "안 눌리는 곳"으로 보인다** — 무늬를 준다.
   ⚠️ 뒤집기 애니메이션을 **짧게**(150ms) — 길면 아이가 기다리다 두 번 누른다. */
.match-hud {
  display: flex; gap: .75rem; align-items: baseline; margin: .75rem 0 .5rem;
}
.match-hud #mtime { font-size: 1.5rem; font-weight: 600; color: var(--primary); }
.match-board {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: .5rem;
}
.mcard {
  position: relative; aspect-ratio: 1 / 1; padding: 0; border: 0; background: none;
  border-radius: var(--radius); overflow: hidden; cursor: pointer;
}
.mcard .mback, .mcard .mface {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius); transition: opacity 150ms var(--ease);
}
.mcard .mback {
  opacity: 1; background: var(--surface-2);
  border: 1px solid var(--line);
  /* 무늬 — ⚠️ 빈 칸으로 두면 안 눌리는 곳처럼 보인다 */
  background-image: radial-gradient(circle at 50% 50%,
    color-mix(in srgb, var(--primary) 18%, transparent) 18%, transparent 19%);
  background-size: 1.5rem 1.5rem;
}
.mcard .mface { opacity: 0; background: var(--surface); border: 1px solid var(--primary); }
.mcard .mface img { width: 100%; height: 100%; object-fit: cover; }
.mcard .mtxt { font-size: 1.1rem; font-weight: 600; color: var(--ink); }
.mcard.on .mback, .mcard.done .mback { opacity: 0; }
.mcard.on .mface, .mcard.done .mface { opacity: 1; }
/* ⚠️ 맞힌 카드는 **없애지 않고 눕힌다** — 없애면 판이 흔들려 다음 카드를 놓친다 */
.mcard.done { cursor: default; }
.mcard.done .mface { opacity: .45; border-color: var(--line); }
@media (max-width: 380px) { .match-board { grid-template-columns: repeat(3, 1fr); } }

/* ══ 그림 맞히기 ══ 2026-08-19. 아홉째 놀이 — 이 앱에서 **유일하게 캔버스를 쓴다.**
   ⚠️ **그림판 바탕은 늘 흰 종이다**(토큰을 안 쓴다). 다크모드(설정에서 켠다)에서 바탕이
      어두워지면 검정 선이 안 보인다 — 종이는 어느 화면에서나 종이여야 한다.
   ⚠️ **붓이 켜진 칸만 touch-action:none** 이다. 보는 사람 화면까지 막으면 큰 칸 위에서
      손가락으로 스크롤이 안 돼 화면에 갇힌다.
   ⚠️ 칸은 **정사각**(aspect-ratio)이다 — 좌표를 0~1000 한 칸으로 주고받으므로 화면 비율이
      다르면 아빠가 그린 동그라미가 아이 화면에서 찌그러진다. */
.draw-wrap { position: relative; margin: .75rem 0 .5rem; }
.draw-pad {
  display: block; width: 100%; aspect-ratio: 1 / 1;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
}
.draw-pad.on { touch-action: none; cursor: crosshair; }
/* ⚠️⚠️ **`touch-action:none` 만으로는 부족했다**(2026-08-20 사장님 지적 "그림을 폰으로
   그리면 화면이 터치에 따라 함께 움직여").
   원인이 둘이다 —
    ① **문서가 창보다 길다**(실측 1508 vs 844). 캔버스가 스크롤을 안 받아도 iOS 는
       **고무줄 튕김(rubber-band)** 을 하는데 그건 `touch-action` 이 안 막는다.
       `overscroll-behavior: none` 이 그걸 막는 유일한 CSS 다.
    ② **앱(WebView)의 `bounces`** 가 안 꺼져 있다 — iOS 기본값이 켜짐이라 UIScrollView
       자체가 튕긴다. **이건 CSS 로 못 막고 앱을 고쳐야 한다**(app-ops 소관).
   ⚠️ 그림 화면에만 건다 — 전 화면에 걸면 당겨서 새로고침 같은 것까지 죽는다.
   ⚠️ 헤들리스 크로미움에서는 원래 안 움직였다(실측). **폰에서만 나는 결함**이라
      여기 고친 것도 실기기로 봐야 확인된다. */
body.draw-page, html:has(body.draw-page) { overscroll-behavior: none; }
/* 획을 긋는 동안만 문서 스크롤을 잠근다 — draw.js 가 붙였다 뗀다.
   ⚠️ `overflow:hidden` 을 안 쓴다(스크롤 위치가 튄다). 터치만 막는다. */
html.ns-drawing, html.ns-drawing body { touch-action: none; overscroll-behavior: none; }
.draw-wait {
  position: absolute; inset: 0; margin: 0; display: flex; align-items: center;
  justify-content: center; color: var(--ink-3); font-size: .95rem; pointer-events: none;
}
.draw-tools { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; }
.draw-colors, .draw-widths, .draw-acts2 { display: flex; align-items: center; gap: .4rem; }
.draw-acts2 { margin-left: auto; }
/* ⚠️ 색·굵기 단추도 **손가락 크기**(44px)여야 한다 — 아이가 누르는 것들이다 */
.draw-color {
  width: 2.75rem; height: 2.75rem; padding: 0; border-radius: 50%;
  border: 2px solid var(--line); cursor: pointer;
}
.draw-color.on { border-color: var(--ink); box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ink-3); }
/* ⚠️ 이 여섯 값은 draw.js 의 COLORS 와 **같아야 한다**(한쪽만 고치면 고른 색과 그려진
   색이 달라진다 — 화면에서는 이유를 알 수 없는 결함이 된다). */
.draw-color.c0 { background: #26262b; } .draw-color.c1 { background: #e5484d; }
.draw-color.c2 { background: #3e63dd; } .draw-color.c3 { background: #30a46c; }
.draw-color.c4 { background: #f5a524; } .draw-color.c5 { background: #8e4ec6; }
.draw-width {
  width: 2.75rem; height: 2.75rem; padding: 0; display: flex; align-items: center;
  justify-content: center; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); cursor: pointer;
}
.draw-width.on { border-color: var(--primary); background: var(--primary-l); }
.draw-width span { display: block; border-radius: 50%; background: var(--ink); }
.draw-width.w0 span { width: .25rem; height: .25rem; }
.draw-width.w1 span { width: .5rem;  height: .5rem; }
.draw-width.w2 span { width: .9rem;  height: .9rem; }
.draw-acts2 .btn {
  /* ⚠️ 도구는 **한 화면에 들어와야** 한다 — 색 여섯 줄 아래로 굵기와 단추가 각각 한 줄씩
     내려가면 그림판 아래가 세 줄이 되어 단추가 아래 탭에 가린다(2026-08-19 실측).
     기본 단추(3.5rem)를 여기서만 낮춰 굵기와 한 줄에 세운다. */
  min-height: 2.75rem; padding: 0 .75rem; font-size: .9rem;
}
.draw-state { margin: .25rem 0 .5rem; }
/* 지난 그림 — ⚠️ 작아도 **무엇을 그린 것인지 읽혀야** 해서 두 칸까지만 줄인다 */
.draw-past { display: grid; grid-template-columns: repeat(2, 1fr); gap: .75rem; margin-top: .5rem; }
.draw-thumb { margin: 0; }
.draw-mini {
  display: block; width: 100%; aspect-ratio: 1 / 1;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
}
.draw-thumb figcaption { margin-top: .25rem; font-size: .9rem; color: var(--ink); }

/* ══ 숫자 놀이 ══ 2026-08-19. 열·열한째 놀이(숫자 맞히기·숫자야구).
   ⚠️ **적은 수는 자릿수가 맞아 보여야 한다** — 추리 놀이라 위아래로 훑으며 비교한다.
      비례폭 글꼴에서는 1 과 8 의 폭이 달라 세로줄이 어긋나고, 그러면 눈으로 못 좁힌다.
   ⚠️ 화면 구조는 초성게임 것을 그대로 쓴다(.cho-board·.sched·.word-send) — 새 뼈대를
      만들지 않는다. 여기서 더하는 것은 **숫자를 읽기 좋게** 하는 것뿐이다. */
.num-page #nummask { letter-spacing: .15em; }
.num-try { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
.num-try .pill { margin-left: .35rem; }

/* ── 눌러서 복사되는 줄 (2026-08-18) ─────────────────────────────────────
   ⚠️ 눌리는 것처럼 **보여야** 누른다 — 그냥 글자면 아무도 안 누른다.
      그래서 테두리·손가락 커서를 주고, 오른쪽에 "누르면 복사"를 늘 띄워 둔다. */
.copy-line {
  display: flex; align-items: center; gap: .6rem; cursor: pointer;
  margin: .35rem 0 0; padding: .7rem .85rem;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.copy-line:hover, .copy-line:focus-visible { border-color: var(--accent); background: var(--surface); }
.copy-line:focus-visible { outline: none; }
.copy-url  { flex: 1 1 auto; word-break: break-all; font-size: .85rem; }
/* 알림칸은 폭이 고정이라야 글자가 바뀔 때 주소가 안 밀린다 */
.copy-hint { flex: 0 0 auto; font-size: .75rem; color: var(--ink-3); white-space: nowrap; }

/* ── 사용법(manual.php)의 접히는 항목 ── 2026-08-19 ────────────────────────
   ⚠️ 한 화면에 스무 항목을 다 펼치면 "쉬운" 매뉴얼이 아니게 된다 — 제목만 훑다가
      궁금한 것만 연다. <details> 라 JS 가 필요 없다.
   ⚠️ 눌리는 줄이라는 걸 **화살표로** 말한다. 기본 삼각형은 브라우저마다 달라 직접 그린다. */
.manual-item { border: 1px solid var(--line); border-radius: var(--radius);
               background: var(--surface); margin-bottom: .5rem; overflow: hidden; }
.manual-item > summary {
  list-style: none; cursor: pointer; padding: 1rem 1.1rem;
  font-weight: 600; color: var(--ink); display: flex; align-items: center; gap: .6rem;
}
.manual-item > summary::-webkit-details-marker { display: none; }
/* 화살표 — 열리면 아래를 가리킨다 */
.manual-item > summary::before {
  content: ''; flex: 0 0 auto; width: .5rem; height: .5rem;
  border-right: 2px solid var(--ink-3); border-bottom: 2px solid var(--ink-3);
  transform: rotate(-45deg); transition: transform var(--dur) var(--ease);
}
.manual-item[open] > summary::before { transform: rotate(45deg); }
.manual-item > summary:hover { background: var(--surface-2); }
.manual-body { padding: 0 1.1rem 1.1rem; color: var(--ink-2); line-height: 1.65; }
.manual-body p { margin: 0 0 .6rem; }
.manual-body p:last-child { margin-bottom: 0; }
.manual-body ul { margin: 0 0 .6rem; padding-left: 1.1rem; }
.manual-body li { margin-bottom: .35rem; }
.manual-body strong { color: var(--ink); }

/* 아이용은 글자를 키운다 — 같은 장치라도 읽는 사람이 다르다(2026-08-19) */
.manual-kid > summary { font-size: 1.1rem; padding: 1.15rem 1.2rem; }
.manual-kid .manual-body { font-size: 1.02rem; line-height: 1.7; }

/* ── 처음 오신 분 안내 ── 2026-08-19 ─────────────────────────────────────────
   ⚠️ 경고처럼 보이면 안 된다 — 붉은 테두리·느낌표를 안 쓴다. 반가운 인사에 가깝다.
   ⚠️ 두 단추의 무게를 다르게 준다: 보러 가기는 채운 단추, "다음에"는 테두리만.
      둘이 같아 보이면 사람은 아무것도 안 누르고 지나간다. */
.firstrun { border: 1px solid var(--primary); background: var(--surface); }
.firstrun-t { margin: 0 0 .3rem; font-weight: 700; color: var(--ink); }
.firstrun-b { margin: 0 0 .9rem; color: var(--ink-2); font-size: .92rem; line-height: 1.6; }
.firstrun-row { display: flex; gap: .5rem; }
.firstrun-row > * { flex: 1 1 0; }
.firstrun-row .btn { width: 100%; }

/* ── "지난번" 표시 ── 2026-08-19 ────────────────────────────────────────────
   ⚠️ 크게 만들지 않는다. 이건 **거드는 말**이지 경고가 아니다 —
      소셜 이름보다 커지면 무엇을 누르는 화면인지가 흐려진다. */
.circle-badge {
  display: inline-block; margin-left: .3rem; padding: .05rem .35rem;
  font-size: .68rem; font-weight: 600; line-height: 1.5;
  color: var(--primary); background: color-mix(in srgb, var(--primary) 12%, transparent);
  border-radius: 999px; vertical-align: middle;
}
/* 지난번에 쓴 동그라미에 얇은 테를 둘러 눈에 먼저 들어오게 한다 */
.circle-btn.circle-last { box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 35%, transparent); }

/* 아이디 확인 결과 — 2026-08-19. ⚠️ 색만으로 말하지 않는다(글자가 이미 말한다).
   색맹·흑백 화면에서도 "이미 쓰는 아이디예요"라는 **문장**이 정보를 다 담는다. */
.hint.ok { color: var(--primary); font-weight: 600; }
.hint.no { color: var(--danger); font-weight: 600; }

/* ── 확인 모달 ── 2026-08-19 ────────────────────────────────────────────────
   ⚠️ `.sheet` 뼈대를 그대로 빌려 쓴다 — 개인 관리·사진 크게 보기와 **같은 모양**이어야
      "이 앱의 창"으로 읽힌다. 확인창만 다르게 생기면 낯선 것이 끼어든 것처럼 보인다.
   ⚠️ 지우는 단추는 **빨갛게**(btn-danger). 되돌릴 수 없는 것과 그냥 끝내는 것은
      눌리는 무게가 달라야 한다 — 색이 그 말을 대신한다. */
.confirm-msg { color: var(--ink-2); line-height: 1.65; margin: 0 0 1.25rem; }
.confirm-row { display: flex; gap: .5rem; }
.confirm-row .btn { flex: 1 1 0; width: auto; }
/* ⚠️ 위험한 단추는 **주 색이 아니라 경고 색**이다.
   ⚠️ 그런데 `var(--danger)` 를 그대로 쓰면 안 된다 — 어두운 테마에서 그 값은
      **연분홍(#FFB4AB)** 이라 흰 글씨가 묻힌다(이 파일 위쪽에서 이미 한 번 겪은 함정이다).
      그래서 **값을 박아** 두 테마에서 같게 만든다. 진한 붉은 바탕 + 흰 글씨는 양쪽 다 읽힌다. */
.btn.btn-danger { background: #BA1A1A; border-color: #BA1A1A; color: #fff; }
.btn.btn-danger:hover { background: #9F1414; border-color: #9F1414; }

/* ── 놀이 「○○ 참여중」 ── 2026-08-22 사장님 지시:
   ① "제일 잘 보이는 곳에 놓고싶어" → 화면 위로 올렸다.
   ② "**스크롤과 상관없이 계속 보일수 있는 곳**" → `sticky` 를 버리고 **`fixed`** 로 바꿨다.

   ⚠️ **왜 sticky 를 버렸나.** sticky 는 "내 컨테이너 안에서만" 붙는다 —
      조상 중 하나라도 `overflow: hidden/auto` 면 **조용히 죽는다.** 이 앱은 화면 폭과
      입력수단에 따라 스크롤 주체가 바뀐다(아래 @media 참고: 데스크톱 좁은 창에서는
      `html{overflow:hidden}` + `body{overflow-y:auto}`). 그런 데서 sticky 는 믿을 게 못 된다.
      `fixed` 는 **뷰포트 기준**이라 스크롤 주체가 무엇이든 상관이 없다.
   ⚠️ 대신 fixed 는 흐름에서 빠져 **밑에 있는 것을 가린다** — 특히 화면 맨 위의
      뒤로 단추(.onboard-back).
      ⚠️⚠️ **2026-09-07 집무실 #733 — 여기서 실제로 가려졌다.** 처음엔 fixed 줄과
      "같은 높이의 빈 자리(.watchline-gap)" 를 흐름에 같이 넣어 밑을 미는 방식이었다.
      **그런데 화면들은 전부 `ns_back()` 을 먼저 부르고 이 함수를 뒤에 부른다**
      (놀이·아이 화면 22곳 전부, grep 으로 확인). 빈 자리는 **자기 뒤에 오는 것만** 밀 수
      있는데 DOM 에서 이미 `.onboard-back` **뒤**에 놓이니, 정작 가려진 뒤로 단추는
      못 밀고 그 아래 아무 상관 없는 내용만 밀었다(실측: 뒤로 top=20 · 줄 bottom=35 —
      15px 겹침). ⇒ 미는 일을 **바깥 상자**(`main.wrap` 의 위 여백, 아래
      `body.watchline-on main.wrap`)로 옮겼다 — 상자의 위 여백이면 안에 무엇이 몇 겹이든
      (아이 화면의 `.kid-wrap` 포함) 통째로 내려가고 **호출 순서와 무관**해진다.
      `.watchline-gap`/`#wordwatchgap` 은 더는 안 쓴다(inc/wordchain.php·watchline.js 도
      같이 고쳤다).
   ⚠️ 높이는 변수 하나로 묶는다 — 줄과 보정 여백이 어긋나면 화면이 위아래로 튄다.
   ⚠️ z-index 는 탭바(20)보다 위, 개발 딱지(60)보다 아래로 둔다.
   ⚠️ 아이 화면도 같은 클래스를 쓴다 — 두 벌로 두면 갈린다. */
/* ⚠️ **높이를 못 박지 않는다**(2026-08-22 사장님 제보 "성용 참여중이 글씨가 잘려").
   처음엔 `height: 2.1rem` 이었는데, 이 앱은 `* { box-sizing: border-box }` 라
   **안전영역 여백(노치)과 아래 실선이 그 2.1rem 안을 파고든다** — 글자 자리가 그만큼
   줄어 잘린다. 노치가 큰 기기일수록 심하다.
   ⇒ `min-height` 로 바꾸고 **여백을 따로 준다**. 내용이 커지면 줄도 같이 커진다.
   ⚠️ `display:flex` 도 뺐다 — flex 컨테이너에는 `text-overflow: ellipsis` 가 **안 먹는다**.
      블록으로 두어야 이름이 길 때 …로 줄어든다. */
:root { --watchline-pad: .45rem; --watchline-h: 2.2rem; }
.watchline {
  position: fixed; top: 0; left: 0; right: 0; z-index: 30;
  min-height: calc(var(--watchline-h) + env(safe-area-inset-top, 0px));
  padding: var(--watchline-pad) 1rem;
  padding-top: calc(var(--watchline-pad) + env(safe-area-inset-top, 0px));
  text-align: center; font-size: .82rem; line-height: 1.35; color: var(--ink-2);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid color-mix(in srgb, var(--line) 35%, transparent);
  /* 이름이 길면 줄을 넘기지 않고 …로 줄인다 — 고정 줄이 두 줄이 되면 자리가 어긋난다 */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.watchline[hidden] { display: none; }

/* ⭐ 2026-09-07 집무실 #733 「참여중 줄이 뒤로가기 단추를 가린다」
   ⚠️ 전에는 같은 높이의 빈 <div class="watchline-gap"> 를 흐름에 넣어 밀었다. 그런데
      화면들이 ns_back() 을 **먼저** 부르고 ns_game_watch_line() 을 뒤에 부른다(호출부
      22곳 전부). 빈 자리는 뒤로 단추 **뒤**에 놓이니 밀 수 있는 것은 그 아래뿐이었고,
      정작 가려진 단추는 못 밀었다(실측: 뒤로 top=20 · 줄 bottom=35 · 빈 자리 top=76).
   ⇒ 미는 일을 **바깥 상자**가 한다. main.wrap 의 위 여백이면 안에 무엇이 몇 겹이든
      (아이 화면의 .kid-wrap 포함) 통째로 내려가고 **호출 순서와 무관**해진다.
   ⚠️ 줄과 **같은 식**을 쓴다 — 높이(--watchline-h)나 안전영역을 고치면 여백도 같이
      움직인다. */
body.watchline-on main.wrap {
  padding-top: calc(var(--wrap-pad-top, 1.25rem) + var(--watchline-h) + env(safe-area-inset-top, 0px));
}

/* ── 하루 시간표 단추(이모지) ── 2026-08-22 사장님 지시: "고치기 빼기를 이모지로 바꿔주고 작게".
   ⚠️ **보이는 것만 작게 하고 손가락이 닿는 크기는 안 줄인다.** 아이도 쓰는 화면이라
      2.5rem(=40px) 아래로 내리면 옆 단추를 누른다(명세 §Touch). 그래서 min-width/height 는
      그대로 두고 **글자 자리(padding)** 만 줄였다.
   ⚠️ 이모지는 글꼴에 따라 크기가 들쭉날쭉하다 — font-size 를 고정해 둘이 같아 보이게 한다. */
.row-ico {
  min-width: 2.5rem; min-height: 2.5rem;
  padding: 0; font-size: 1.05rem; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}

/* ── 시간표 넣기 폼 ── 2026-08-22 "너무 어렵게 되어있는 것 같아".
   ⚠️ `<details>` 를 **접는 상자처럼** 보이게만 한다 — 여닫는 동작·키보드·읽어 주는 기기는
      브라우저가 이미 한다. 우리가 다시 만들지 않는다.
   ⚠️ 기본 삼각형(marker)을 지우고 우리 표시를 단다 — 기기마다 모양이 달라 들쭉날쭉하다. */
/* ⚠️ **`width:100%` 를 빠뜨려 시각 칸이 절반만 차 있었다**(2026-08-22 사장님 지적
   "시간의 가로를 넓히는게 깔끔하지 않을까?"). 넣기 폼을 단순화하면서 `.tt-new` 를
   `.tt-at` 으로 바꿨는데, 옛 규칙(`.tt-new input { width:100% }`)을 **안 가져왔다.**
   `input[type=time]` 은 스스로 정한 폭이 있어 세로 flex 에서도 안 늘어난다 —
   못 박아 주지 않으면 왼쪽에 붙고 오른쪽이 빈다.
   ⚠️ 클래스 이름을 바꿀 때는 **그 이름에 걸려 있던 규칙을 다 옮겼는지** 센다.
      한 줄만 빠져도 화면이 어긋나고, 그건 문법 검사에 안 걸린다. */
.tt-at { display: flex; flex-direction: column; gap: .25rem; }
.tt-at input { width: 100%; min-width: 0; }
.tt-more { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.tt-more > summary {
  list-style: none; cursor: pointer; padding: .7rem 1rem;
  font-size: .9rem; color: var(--ink-2);
  display: flex; align-items: center; justify-content: space-between;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur) var(--ease);
}
.tt-more > summary::-webkit-details-marker { display: none; }
.tt-more > summary::after { content: '▾'; transition: transform var(--dur) var(--ease); }
.tt-more[open] > summary::after { transform: rotate(180deg); }
.tt-more > summary:active { background: var(--surface-2); }
.tt-more-body { display: flex; flex-direction: column; gap: .5rem; padding: 0 1rem 1rem; }

/* ══ 화면이 바뀔 때 ══ 2026-08-23 사장님 지시 *"메뉴 탭 이동시 부드러운 효과"*.

   ⚠️ **이 앱은 화면마다 서버에서 새로 그린다**(SPA 가 아니다). 그래서 탭을 옮기면 화면이
      **뚝 끊겼다가** 새로 나타난다 — 앱처럼 안 느껴지는 가장 큰 이유였다.
   ⇒ `@view-transition` 으로 **문서 사이**를 잇는다. 브라우저가 옛 화면을 붙잡아 두고 새
      화면과 겹쳐 준다. **JS 가 한 줄도 없다** — 이 저장소 규약(JS 가 막혀도 그대로)과 맞는다.
   ⚠️ **모르는 브라우저는 그냥 무시한다** — 옛 웹뷰에선 지금까지처럼 뚝 바뀐다(안 깨진다).
      그래서 아래 `@supports not` 로 **그쪽에만** 등장 결을 따로 준다. 둘 다 걸면 두 번 움직인다. */
/* ⚠️⚠️ **2026-08-23 껐다.** 110ms 로 줄인 뒤에도 사장님이 *"동일"* 하다고 하셨다.
   길이를 줄여도 **전환 자체의 비용**은 남는다 — 브라우저가 옛 화면과 새 화면을 **통째로
   찍어(snapshot)** 겹쳐야 하고, 이 앱은 고정 탭바에 `backdrop-filter: blur(12px)` 가 걸려
   있어 그 찍기가 폰에서 특히 비싸다. 그런데 **가릴 빈틈이 애초에 없다**(화면이 23~49ms).
   ⇒ 값이 없는 곳에 비용만 남으므로 **끈다.** 되살리려면 아래 한 줄의 주석만 풀면 된다.
   ⚠️ **누르는 순간 반응(.tabbar a:active)은 그대로 둔다** — 그게 실제로 없던 것이고,
      비용이 0 이다. 「부드러움」의 대부분은 사실 그쪽이다.
@view-transition { navigation: auto; } */

/* ⚠️⚠️ **탭바는 같이 페이드되면 안 된다.** 이름을 주면 브라우저가 「같은 것」으로 알아보고
   양쪽 화면에 걸쳐 **가만히 둔다**. 안 주면 화면 전체와 함께 사라졌다 나타나서, 손가락이
   올라가 있는 바로 그 자리가 깜빡인다 — 가장 어지러운 그림이다.
   ⚠️ 개발 표시(.devmark)도 같은 이유로 고정한다. */
.tabbar { view-transition-name: tabbar; }
.devmark { view-transition-name: devmark; }

/* 본문만 **아주 짧게** 겹쳐 바뀐다.
   ⚠️⚠️ **여기서 한 번 틀렸다**(2026-08-23 사장님 제보 *"메뉴 탭 눌렀는데 반응 느림"*).
      처음엔 `--dur-in`(420ms)에 `translateY(.5rem)` 까지 줬는데, 이 앱은 화면이 **30ms 만에**
      온다 — **없는 빈틈을 메우려고 420ms 를 앞에 세운 셈**이었다. 전환은 새 화면이 준비될
      때까지 옛 화면을 붙잡아 두므로, 누른 뒤 그만큼 **아무 일도 안 일어나는 것처럼 보인다.**
   ⚠️ **움직임(translateY)을 뺐다** — 글자가 미끄러지면 읽히는 시점이 그만큼 더 늦다.
      겹쳐 바뀌기만 하면 결은 남고 지연은 안 는다.
   ⚠️ 시간은 `--dur-page` 한 곳에 있다(tokens.css) — 더 줄일 일이 생기면 그 값만 만진다. */
::view-transition-old(root) { animation: vtOut var(--dur-page) linear both; }
::view-transition-new(root) { animation: vtIn  var(--dur-page) linear both; }
@keyframes vtOut { to   { opacity: 0; } }
@keyframes vtIn  { from { opacity: 0; } }

/* ══ 도착할 때만 살짝 ══ 2026-08-23 — 전환을 끈 뒤 사장님 *"전환이 부드럽지 않은데"*.

   ⭐ **이건 화면 전환이 아니다.** 옛 화면을 **붙잡지 않는다** — 새 화면이 제 속도로 오고,
      온 뒤에 **자기만** 살짝 떠오른다. 그래서 **기다림이 0 이다**(전환은 옛 화면을 붙잡아
      두는 그 시간이 곧 지연이었다).
   ⚠️ **투명도 0 에서 시작하지 않는다**(.45 부터). 0 이면 다 칠해질 때까지 글자가 안 보여
      그만큼 늦게 읽힌다 — 「부드러움」을 얻으려다 「느림」을 다시 만드는 자리다.
   ⚠️ **움직임(translate)을 안 준다** — 글자가 미끄러지면 읽는 눈이 따라가야 한다.
   ⚠️ **탭바는 이 결에 안 든다**(main.wrap 밖이다) — 손가락이 올라가 있는 자리는 안 움직인다.
   ⚠️ 거슬리면 `--dur-page`(tokens.css) 하나만 줄이면 된다. 0 이면 사실상 꺼진다. */
@keyframes pageIn { from { opacity: .45; } to { opacity: 1; } }
/* ⚠️ `linear` → `--ease`(강한 ease-out). **같은 길이인데 더 물렁하게 느껴진다** —
   끝으로 갈수록 천천히 잦아들어서다. 시간을 안 늘리고 얻는 것이라 먼저 이걸 썼다. */
main.wrap { animation: pageIn var(--dur-page) var(--ease) both; }

/* ⚠️ **움직임 줄이기 설정을 여기서 또 본다.** 위쪽 `* { animation: none }` 은 `*` 가
   **의사요소를 안 잡아** `::view-transition-*` 에 안 걸린다. 한 곳에 두자는 규칙의
   예외라 이유를 적어 둔다 — 안 적으면 다음 사람이 중복으로 보고 지운다. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none !important; }
  main.wrap { animation: none !important; }
}

/* ── 「함께하기 요청」 (2026-08-23) ────────────────────────────────────────
   사장님 지시로 **자동 알림을 걷어내고** 사람이 누르는 단추로 옮겼다.
   ⚠️ 참여중 띠(.watchline)가 상단 고정이라, 이 단추는 **흐름 안**에 둔다 — 둘 다 고정하면
      좁은 폰에서 놀이 공간이 두 줄 줄어든다(8/22 에 띠 하나로도 잘렸던 자리).
   ⚠️ 손가락 크기 2.5rem 아래로 내리지 않는다(명세 §Touch). 아이가 누른다. */
.invite-wrap {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin: .75rem 0 .25rem;
}
.invite-btn {
  min-height: 2.5rem; padding: 0 .9rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface-2); color: var(--ink);
  font-size: .9rem; cursor: pointer;
  transition: transform .12s ease, background .12s ease, opacity .12s ease;
}
.invite-btn:active { transform: scale(.97); background: var(--accent-l); }
.invite-btn:disabled { opacity: .55; cursor: default; transform: none; }
.invite-msg { font-size: .82rem; color: var(--muted); min-height: 1.1em; }
.invite-msg.ok { color: var(--accent); }

/* ── 일정: 아이 여럿 고르기 (2026-08-23) ──────────────────────────────────
   사장님 지시로 하나 고르기(select)에서 여럿 고르기(체크)로 바꿨다.
   ⭐ **모양은 새로 만들지 않았다** — `.pick`/`.pick-opt`(health.php 등이 쓰는 것)를
      그대로 쓴다. 손가락 크기·고른 표시·`input:checked + span`(옛 웹뷰에서도 되는
      선택자)이 이미 다 들어 있다. 새로 짜면 그 셋을 또 맞춰야 하고 언젠가 갈린다.
   ⚠️ 여기선 **너비만** 다르다 — 아이 이름은 요일보다 길어 4.5rem 로 고정하면 잘린다. */
/* ⚠️ 2026-08-23 사장님 폰 실측 — 아이 셋(성하영·성하늘·성하린)이 왼쪽에만 뭉치고
   오른쪽 절반이 비어 있었다. `flex: 0 1 auto` 라 **글자 폭만큼만** 서기 때문이다
   (3~4글자 이름이면 한 칸 71px × 3 = 213px, 320px 줄에서 105px 이 죽는다).
   ⇒ `grow: 1` 로 **남는 자리를 나눠 갖게** 한다. 셋이면 셋으로 나눠 서고, 여럿이면
     줄을 넘겨 그 줄을 채운다 — 바로 위 「체크·날짜」를 반씩 세운 것과 같은 결이다.
   ⚠️ **basis 는 auto 로 둔다**(0 이 아니다). 0 으로 하면 이름이 긴 아이가 있을 때
      줄바꿈 판정이 안 서서 칸 밖으로 글자가 삐져나온다 — `.pick` 의 기본값
      `flex: 1 1 4.5rem` 을 그대로 쓰지 않는 이유이기도 하다(이름은 4.5rem 보다 짧다). */
.pick-opt.pick-kid { flex: 1 1 auto; }
.pick-opt.pick-kid span { padding-left: .9rem; padding-right: .9rem; white-space: nowrap; }
.ev-kids-hint { margin: .4rem 0 0; font-size: .78rem; color: var(--muted); }

/* ── 자라는 모양(선 그래프) ── 2026-08-23 신설. 그리는 쪽은 `inc/chart.php`.
   ⚠️ **색을 여기서 안 박는다** — 선 색은 PHP 가 항목마다 넘긴다(키·몸무게·머리둘레가
      달라야 한다). 여기는 **어느 테마에서나 읽히는 뼈대**만 맡는다.
   ⚠️ svg 에 `height: auto` 를 준다 — viewBox 320×150 을 폭에 맞춰 늘린다. 글자도 같이
      커지지만 `.wrap` 이 34rem 에서 멈추므로 최대 1.7배다(폰에서는 1:1). */
.charts { display: flex; flex-direction: column; gap: 1rem; }
.chart { margin: 0; padding: .75rem .5rem .25rem;
         border: 1px solid color-mix(in srgb, var(--line) 40%, transparent);
         border-radius: var(--radius-l); background: var(--surface); }
.chart figcaption { display: flex; align-items: baseline; justify-content: space-between;
                    gap: .5rem; padding: 0 .5rem .25rem; font-size: .85rem; color: var(--ink-2); }
.chart-last { font-size: 1.05rem; font-weight: 600; color: var(--ink); }
/* 마지막으로 잰 날 — ⚠️ 값과 같은 크기로 두면 날짜가 값처럼 읽힌다 */
.chart-last small { font-size: .75rem; font-weight: 400; color: var(--ink-3); }
.chart svg { display: block; width: 100%; height: auto; overflow: visible; }
.chart-grid { stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 3; opacity: .7; }
.chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* ⚠️ 점 테두리를 바탕색으로 준다 — 선과 점이 겹쳐 뭉치는 것을 끊는다(값이 촘촘할 때) */
.chart-dot { stroke: var(--surface); stroke-width: 1.2; }
.chart-ax { fill: var(--ink-3); font-size: 9px; }

/* ── 「입력 중」 ── 2026-08-24 사장님 지시(카톡처럼).
   ⚠️ **자리를 늘 차지하지 않는다**(hidden). 빈 줄로 늘 서 있으면 대화가 그만큼 밀린다.
   ⚠️ 말풍선 목록 **밖**이라 여기 색·크기는 말풍선과 같은 결로만 맞춘다(같은 것처럼 보이면
      「지워진 말」처럼 읽힌다 — 일부러 작고 옅게 둔다). */
.chat-typing {
  padding: .25rem 1.25rem .4rem;
  font-size: .82rem; color: var(--ink-3);
  display: flex; align-items: center; gap: .15rem;
}
/* ⚠️⚠️ **이 한 줄이 없으면 `hidden` 이 안 먹는다.** 브라우저 기본값 `[hidden]{display:none}`
   보다 바로 위 `.chat-typing{display:flex}` 가 세서, 아무도 안 치는데도 입력칸 위에
   **빈 줄이 늘 떠 있었다**(2026-08-24 시험 전 발견). 요소를 숨기는데 `display` 를 준
   자리에서는 늘 이 짝이 필요하다. */
.chat-typing[hidden] { display: none; }
.chat-typing .who { color: var(--ink-2); font-weight: 600; }
/* 점 셋이 차례로 떠오른다 — ⚠️ 크기가 아니라 **투명도**로 움직인다.
   크기를 바꾸면 그 줄 높이가 미세하게 흔들려 대화가 들썩인다. */
.chat-typing .dots { display: inline-flex; gap: 2px; margin-left: 3px; }
.chat-typing .dots i {
  width: 4px; height: 4px; border-radius: 50%; background: var(--ink-3);
  opacity: .25; animation: typingDot 1.2s infinite;
}
.chat-typing .dots i:nth-child(2) { animation-delay: .2s; }
.chat-typing .dots i:nth-child(3) { animation-delay: .4s; }
@keyframes typingDot { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }
/* ⚠️ 움직임을 줄여 달라고 한 사람에겐 안 움직인다 — 점은 그대로 보인다(뜻이 안 사라진다) */
@media (prefers-reduced-motion: reduce) {
  .chat-typing .dots i { animation: none; opacity: .55; }
}

/* 링크처럼 보이는 단추 — 로그아웃 같은 「상태를 바꾸는 링크」는 <a> 가 아니라 POST 폼이어야 해서(me.php 주석) 모양만 링크로. 2026-08-25 */
.linklike { background: none; border: 0; padding: 0; font: inherit; color: inherit; text-decoration: underline; cursor: pointer; }

/* ══ 오목 ══ 2026-09-06. **판 놀이의 첫 놀이** — 이 앱에서 처음으로 «판»이 화면에 뜬다.
   ⚠️ **판 색은 토큰을 안 쓴다.** 그림판 바탕과 같은 판단(위 draw-pad) — 바둑판은 어느
      화면에서나 바둑판이어야 한다. 다크모드에서 판이 어두워지면 검은 돌이 사라진다.
   ⚠️ **칸은 25px 안팎이라 이 앱의 손가락 규칙(--tap: 48px)을 못 지킨다.** 그래서 확정을
      칸이 아니라 **확인 단추**(48px)가 맡는다(assets/omok.js «미리 놓기»). 판을 9줄로
      줄이면 칸은 커지지만 오목이 너무 빨리 끝난다 — 규칙을 살리고 확정을 옮겼다. */
.omok-board {
  display: grid;
  /* ⚠️ 줄 수는 **PHP 상수(OMOK_SIZE)가 인라인으로 준다** — 여기 13을 박으면 판 크기를
     바꾼 날 CSS 만 13줄로 남아 판이 어긋난다(표에도 size 칸을 둔 이유와 같다). */
  grid-template-columns: repeat(var(--osize, 13), 1fr);
  /* ⚠️⚠️ **행도 못 박는다**(2026-09-08, 집무실 #736 — 사장님 이미지 제보 「장기판과 체스판의
     간격이 동일하지 않아」). 열만 정하면 행은 암묵적으로 `auto` 라 **줄 높이가 내용물로 정해진다** —
     말이 있는 줄은 알(span) 만큼 부풀고 **말 없는 줄은 찌그러진다.** 장기 초기 배치에서
     완전히 빈 두 줄(兵과 卒 사이)이 실측 45.5px 대 11.5px 로 갈렸다. 체스도 가운데 네 줄이
     58.1 대 28.9 였다. `aspect-ratio` 는 판 «전체» 높이만 정할 뿐 그 안을 고르게 나누지 않는다. */
  grid-template-rows: repeat(var(--osize, 13), 1fr);
  aspect-ratio: 1 / 1;
  width: 100%;
  margin: .25rem 0 .75rem;
  background: #E3BE83;                       /* 나무판 — 두 테마 공통 */
  border: 1px solid #A9814A;
  border-radius: var(--radius);
  /* ⚠️ **격자 둘레에 여백을 둔다**(2026-09-08 #739). 줄이 칸 한가운데를 지나므로 맨 바깥
     교차점은 판 끝에서 «반 칸» 뿐인데, 거기 놓인 돌은 반지름이 반 칸에 가까워
     **둥근 귀(border-radius)에 잘린다**(실제로 모서리 돌이 잘렸다). 진짜 바둑판도
     격자 둘레에 여백이 있다. `box-sizing: border-box` 라 안쪽만 줄고 판은 정사각 그대로다. */
  padding: 3.5%;
  overflow: hidden;
  /* ⚠️ 판 위에서 손가락으로 스크롤은 **되게 둔다**(그림판과 다르다) — 여긴 끌 일이
     없고, 막으면 판이 화면을 거의 채우는 폰에서 아래로 못 내려간다. */
}
/* ⚠️⚠️ **줄은 칸 한가운데를 지난다 — 모서리가 아니다.** (2026-09-08, 집무실 #737,
   사장님 제보 「바둑판 선 위에 놓는게 아니라 선과 선 사이에 놓게 되어있어」)
   오목은 **줄이 만나는 「점」에 놓는 놀이**다. 처음엔 칸의 오른쪽·아래 모서리에 선을 그어
   («모눈종이») 돌이 칸 한가운데 = **선과 선 사이**에 앉았다. 규칙이 아는 자리(13×13 점)와
   눈에 보이는 자리가 어긋나 있었다.
   ⚠️ **장기(`.jcell`)는 이걸 제대로 하고 있었다** — 이틀 뒤에 만들면서 바로잡았는데
      **먼저 만든 오목으로 그 본이 돌아오지 않았다.** 판을 하나 더 만들면 여기부터 볼 것.
   ⚠️ **줄을 `::before`/`::after` 가 아니라 배경으로 그린다** — 장기와 다른 점이다.
      오목은 두 가짜 요소를 이미 쓴다(`::after` 돌 · `::before` 마지막 수 표시)고,
      `assets/omok.js` 가 **클래스만 토글**하지 요소를 만들지 않는다. 배경으로 그리면
      HTML·JS 를 하나도 안 건드리고 줄만 옮길 수 있다.
   ⚠️ 가장자리는 **CSS 변수로** 반 칸 끊는다 — 모서리 칸(`o-x0`+`o-y0`)은 두 규칙이 겹치는데,
      변수는 각각 물려받아 섞이지만 `background-size` 를 통째로 덮어쓰면 한쪽이 사라진다. */
.ocell {
  position: relative;
  padding: 0; margin: 0; cursor: pointer; border: 0;
  /* ⚠️⚠️ **`background-color: transparent` 를 빠뜨리지 말 것.** 이 칸은 `<button>` 이라
     브라우저 기본 배경색(회색)이 있다. 예전엔 `background: none` 단축이 그걸 같이 껐는데,
     #739 에서 줄을 배경으로 옮기며 `background-image` 만 쓰자 **기본 회색이 되살아나
     나무판 색을 통째로 덮었다**(2026-09-08 실제로 그랬다 — 시험 20개는 다 통과했는데
     그림을 보고서야 알았다. 「구조가 맞나」와 「보기에 맞나」는 다른 질문이다). */
  background-color: transparent;
  --oline: rgba(90, 60, 20, .45);
  --ov-len: 100%; --ov-at: 0;          /* 세로줄 — 길이·시작(위/아래 끝에서 반 칸) */
  --oh-len: 100%; --oh-at: 0;          /* 가로줄 — 길이·시작(좌/우 끝에서 반 칸) */
  background-image: linear-gradient(var(--oline), var(--oline)),
                    linear-gradient(var(--oline), var(--oline));
  background-size: 1px var(--ov-len), var(--oh-len) 1px;
  background-position: 50% var(--ov-at), var(--oh-at) 50%;
  background-repeat: no-repeat;
}
/* ⚠️ **반 칸에 0.5px 를 얹는다.** 칸 너비가 26.769px 처럼 소수라 정확히 50% 로 끊으면
   옆 칸의 온전한 줄과 만나는 자리에 **머리카락 같은 틈**이 생긴다(2026-09-08 6배로 확대해
   실제로 보였다 — 1배에서는 「선이 끊어진 조각」처럼 읽힌다). 겹쳐 그려 없앤다. */
.ocell.o-x0 { --oh-len: calc(50% + .5px); --oh-at: 100%; }  /* 왼쪽 끝 — 가로줄은 오른쪽 반만 */
.ocell.o-xz { --oh-len: calc(50% + .5px); --oh-at: 0; }     /* 오른쪽 끝 — 왼쪽 반만 */
.ocell.o-y0 { --ov-len: calc(50% + .5px); --ov-at: 100%; }  /* 맨 위 — 세로줄은 아래 반만 */
.ocell.o-yz { --ov-len: calc(50% + .5px); --ov-at: 0; }     /* 맨 아래 — 위쪽 반만 */
.ocell:disabled { cursor: default; }
/* 돌 — ⚠️ **줄이 만나는 점 위에 얹히는 알**이다(2026-09-08 #737). 칸의 84%.
   ⚠️ 꽉 채우면 격자가 안 보여 «어디에 놓았는지»를 못 읽는다. 반대로 너무 작으면
      점 위에 얹힌 것이 아니라 «칸에 찍힌 자국»처럼 보인다. */
.ocell::after {
  content: ''; position: absolute; inset: 8%;
  border-radius: 50%;
}
.ocell.o-b::after { background: #241A10; box-shadow: inset -1px -1px 2px rgba(255,255,255,.18); }
.ocell.o-w::after { background: #FDFBF7; box-shadow: inset 1px 1px 2px rgba(0,0,0,.22); }
/* 미리 놓은 자리 — ⚠️ **아직 둔 게 아니라는 것이 한눈에** 보여야 한다(점선 + 반투명).
   색을 내 돌 색으로 칠하면 이미 놓인 돌과 구별이 안 된다. */
.ocell.o-pre::after {
  background: rgba(255, 255, 255, .35);
  border: 2px dashed var(--primary);
}
/* 마지막 수 — 어디에 뒀는지 놓치지 않게. ⚠️ 판에 **하나뿐**이다(omok.js 가 옮겨 붙인다)
   ⚠️ **점을 작게 둔다(칸의 1/5).** 처음에 30% 로 뒀더니 표시가 돌을 거의 덮어 **흰 돌이
      밝은 테마에서 검은 돌처럼 보였다**(2026-09-06 실물 화면에서 발견 — 밝은 테마의
      --primary 가 어두운 색이라 그렇다). 어디에 뒀나만 알면 되는 표시다. */
.ocell.o-last::before {
  content: ''; position: absolute; inset: 40%;
  border-radius: 50%; background: var(--primary);
  z-index: 1;
}
.omok-state { text-align: center; margin: .25rem 0; font-weight: 600; }
/* 확인 줄 — ⚠️ **화면 아래에 붙여 둔다.** 판이 커서 위쪽 칸을 누르면 확인 단추가 화면
   밖으로 밀린다(끝말잇기 입력줄을 아래에 붙인 것과 같은 이유). */
.omok-confirm {
  position: sticky; bottom: .5rem; z-index: 2;
  display: flex; align-items: center; gap: .5rem;
  padding: .5rem; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--line);
}
.omok-confirm[hidden] { display: none; }     /* ⚠️ display 를 준 요소엔 늘 이 짝이 필요하다 */
.omok-confirm .btn { min-height: var(--tap); flex: 1; }
.omok-confirm .caption { flex: 0 0 auto; }
.omok-acts { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .75rem; }
.omok-acts form { flex: 1 1 8rem; }
.omok-acts .btn { width: 100%; }
.omok-level { margin-top: .5rem; }
.omok-level .btn { width: 100%; }

/* ══ 체스 ══ 2026-09-07. 판 놀이 둘째.
   ⚠️ **오목의 판을 안 물려받는다.** 저긴 격자 위 «칸»이고 여긴 밝고 어두운 «판»이라 그린
      방식이 다르다. 대신 **판 밖의 것들(상태 한 줄·단추 줄·세기)은 오목 것을 그대로 쓴다**
      (`.omok-state`·`.omok-acts`·`.omok-level`) — 두 놀이가 같은 자리에서 같아 보여야 한다.
   ⚠️ 칸이 8줄이라 폰(360px)에서 **45px** 다. 오목(25px)과 달리 손가락 규칙에 거의 닿아서
      「미리 놓기」를 안 넣었다 — 대신 «말 고르기»가 한 단계 앞에 있다. */
.chess-board {
  display: grid; grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);   /* #736 — 위 오목 판 머리말 참고 */
  aspect-ratio: 1 / 1; width: 100%; margin: .25rem 0 .75rem;
  border: 1px solid #6B4A2A; border-radius: var(--radius); overflow: hidden;
}
.ccell {
  position: relative; padding: 0; margin: 0; border: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  /* ⚠️ 글자 크기를 **칸에 맞춰 준다**(vw). px 로 박으면 큰 화면에서 말이 콩알만 해진다. */
  font-size: min(7.5vw, 2rem); line-height: 1;
}
/* 판 색 — ⚠️ 토큰을 안 쓴다(그림판·오목판과 같은 판단: 판은 어느 테마에서나 판이다) */
.ccell.c-light { background: #EFD9B4; }
.ccell.c-dark  { background: #B98A5E; }
.ccell:disabled { cursor: default; }
/* 말 — ⚠️ **흰 말도 «속 찬» 글자**에 색을 입힌다. 속 빈 글자(♔)는 폰에 따라 안 그려진다. */
.ccell .c-w { color: #FFFDF8; text-shadow: 0 0 1px #241A10, 0 0 2px #241A10, 0 1px 1px rgba(0,0,0,.45); }
.ccell .c-b { color: #241A10; text-shadow: 0 0 1px rgba(255,255,255,.35); }
/* 고른 말 */
.ccell.c-sel { box-shadow: inset 0 0 0 3px var(--primary); }
/* 갈 수 있는 자리 — ⚠️ 빈 칸은 **점**, 잡는 자리는 **테두리**. 둘을 같게 그리면 «거기 뭐가
   있는지»가 안 보인다(아이가 실수로 자기 말을 잡으려 든다). */
.ccell.c-dest::after {
  content: ''; position: absolute; width: 30%; height: 30%;
  border-radius: 50%; background: rgba(40, 25, 10, .55);
}
.ccell.c-take { box-shadow: inset 0 0 0 3px rgba(200, 60, 40, .85); }
/* 마지막 수 — 어디서 어디로 갔는지 두 칸에 남긴다(오목의 점과 같은 뜻, 모양만 다르다) */
.ccell.c-last { box-shadow: inset 0 0 0 2px rgba(90, 140, 60, .8); }
/* ⚠️⚠️ **겹칠 때 무엇이 이기나를 못박는다.** 셋 다 box-shadow 라 나중에 적은 것이 이기는데,
   그러면 «방금 움직인 말을 잡을 수 있는» 칸이 초록(마지막 수)으로만 보여 **잡을 수 있다는
   표시가 사라진다**(2026-09-07 화면을 찍어 보고 발견 — 실제로 b5 가 그랬다).
   순서: 고른 칸 > 잡을 자리 > 마지막 수. 지금 손이 갈 자리가 위다. */
.ccell.c-last.c-take { box-shadow: inset 0 0 0 3px rgba(200, 60, 40, .85); }
.ccell.c-sel, .ccell.c-last.c-sel, .ccell.c-take.c-sel { box-shadow: inset 0 0 0 3px var(--primary); }

/* ══ 장기 ══ 2026-09-07. 판 놀이 셋째.
   ⚠️ **줄이 만나는 「점」에 놓는 놀이**라 체스처럼 칸을 칠하지 않는다 — 칸 한가운데를
      지나도록 줄을 긋고(`::before`·`::after`), 말은 그 위에 동그란 알로 얹는다.
   ⚠️ 바깥 줄은 **반 칸에서 끊는다**(`.j-x0`·`.j-x8`·`.j-y0`·`.j-y9`) — 안 끊으면 줄이
      판 밖으로 반 칸씩 삐져나가 판이 커 보인다.
   ⚠️ 궁성 대각선은 **판 위에 덧그린다**(`.jpal`) — 점과 점을 잇는 선이라 칸 단위로는
      그릴 수가 없다. 자리는 «(3,y) 한가운데 → (5,y+2) 한가운데»다. */
.janggi-wrap { width: 100%; }
.janggi-board {
  position: relative; display: grid; grid-template-columns: repeat(9, 1fr);
  grid-template-rows: repeat(10, 1fr);  /* #736 — 위 오목 판 머리말 참고 */
  aspect-ratio: 9 / 10; width: 100%; margin: .25rem 0 .75rem;
  background: #E8C88A;                        /* 판 색은 토큰을 안 쓴다(오목·체스와 같은 판단) */
  border: 1px solid #8A6A3A; border-radius: var(--radius);
}
.jcell {
  position: relative; padding: 0; margin: 0; border: 0; background: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.jcell:disabled { cursor: default; }
.jcell::before {                              /* 세로 줄 */
  content: ''; position: absolute; left: 50%; top: 0; bottom: 0;
  width: 1px; margin-left: -.5px; background: rgba(90, 60, 20, .55);
}
.jcell::after {                               /* 가로 줄 */
  content: ''; position: absolute; top: 50%; left: 0; right: 0;
  height: 1px; margin-top: -.5px; background: rgba(90, 60, 20, .55);
}
.jcell.j-x0::after { left: 50%; }
.jcell.j-x8::after { right: 50%; }
.jcell.j-y0::before { top: 50%; }
.jcell.j-y9::before { bottom: 50%; }
/* 궁성 대각선 — 위·아래 둘 */
.jpal {
  position: absolute; pointer-events: none;
  left: calc(100% * 3.5 / 9); width: calc(100% * 2 / 9); height: calc(100% * 2 / 10);
  background-image:
    linear-gradient(to bottom right, transparent calc(50% - .5px), rgba(90,60,20,.55) calc(50% - .5px),
                    rgba(90,60,20,.55) calc(50% + .5px), transparent calc(50% + .5px)),
    linear-gradient(to bottom left,  transparent calc(50% - .5px), rgba(90,60,20,.55) calc(50% - .5px),
                    rgba(90,60,20,.55) calc(50% + .5px), transparent calc(50% + .5px));
}
.jpal-top { top: calc(100% * 0.5 / 10); }
.jpal-bot { top: calc(100% * 7.5 / 10); }
/* 말 — 동그란 알. ⚠️ 초는 초록, 한은 빨강(실제 장기판 그대로). 글자는 한자다. */
.jcell span {
  position: relative; z-index: 1;
  width: 88%; aspect-ratio: 1 / 1; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: #F4E3C1; border: 2px solid; font-weight: 700;
  font-size: min(4.6vw, 1.1rem); line-height: 1;
}
.jcell .j-cho { color: #1E6B34; border-color: #1E6B34; }   /* 초(楚) */
.jcell .j-han { color: #B5232B; border-color: #B5232B; }   /* 한(漢) */
/* 갈 수 있는 점 — 빈 점은 **점**, 잡는 점은 **테두리**(체스와 같은 말) */
.jcell.j-dest {
  background: radial-gradient(circle at 50% 50%,
    rgba(40, 25, 10, .55) 0 17%, transparent 18%);
}
.jcell.j-take span { box-shadow: 0 0 0 3px rgba(200, 60, 40, .9); }
/* 마지막 수 · 고른 말 — ⚠️ 겹칠 때의 순서를 못박는다(체스에서 겪은 그것: 나중 규칙이
   이겨서 「잡을 수 있다」가 가려졌다). 고른 말 > 잡는 점 > 마지막 수. */
.jcell.j-last { box-shadow: inset 0 0 0 2px rgba(90, 140, 60, .8); }
.jcell.j-sel span, .jcell.j-last.j-sel span, .jcell.j-take.j-sel span {
  box-shadow: 0 0 0 3px var(--primary);
}
