  /* 디자인 토큰(:root)은 tokens.css로 옮겼다 — 값의 단일 소스다. 값이 두 벌이
     되면 한쪽이 조용히 낡는다(예전에 별도 저장소로 분리된 관리자 콘솔의
     --ink-faint가 대비 미달인 옛 값으로 남아 있었다).
     index.html이 이 파일보다 먼저 tokens.css를 링크한다. */
  *{box-sizing:border-box;}
  html{
    background:var(--paper);
    -webkit-text-size-adjust:100%;
    text-size-adjust:100%;
  }
  body{
    margin:0;
    min-height:100%;
    background:var(--paper);
    color:var(--ink);
    font-family:var(--sans);
    -webkit-font-smoothing:antialiased;
    /* 한국어는 단어 단위로만 줄바꿈 — "보여 / 요."처럼 단어가 찢어지는
       줄바꿈이 화면을 급해 보이게 한다는 피드백(2026-07-18). 클래스마다
       keep-all을 다시 걸지 말 것(전역이 이미 담당). overflow-wrap은
       띄어쓰기 없는 긴 문자열(URL·코드)이 칸을 밀고 나가는 것만 막는
       안전핀. */
    word-break:keep-all;
    overflow-wrap:break-word;
  }
  /* 입력 글꼴 통일(2026-07-18): 쓰는 자리(입력·텍스트영역·셀렉트)는 전부
     세리프 — 책 제목·독후감은 세리프인데 검색창·일정 폼·한 줄 입력은
     산세리프라 화면마다 섞여 보인다는 피드백. 크기·굵기는 각자 유지,
     글씨체만 한 결로.
     특이도 주의(2026-08-02): 여기서 (0,3,1)을 받는 건 :not 셋이 붙은 input
     가지뿐이다. textarea·select 가지는 (0,0,1)이라 어떤 클래스 규칙에도 진다 —
     예전 주석은 셋 다 이긴다고 적어 뒀고, 그 전제 때문에 #chat-input(textarea)과
     .settings-provider-select·.exchange-control(select)이 산세리프로 새 있었다.
     그래서 규칙을 뒤집었다: **입력 요소의 개별 규칙은 font-family를 아예 선언하지
     않는다.** 글꼴은 이 한 줄만 정한다(test/typography.test.js가 지킨다). */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  textarea,
  select{font-family:var(--serif);}
  /* 조작(버튼·탭·칩·필터)은 전부 산세리프(branding/marginalia-branding.md §3.1).
     입력 요소와 같은 모양으로 전역 한 줄이 바닥을 깐다 — 이유도 같다. 버튼은
     UA 스타일시트가 `font: 400 13.333px Arial`을 얹어서 body의 --sans를 상속받지
     않는다. 그래서 클래스에서 font-family를 빠뜨리면 조용히 Arial로 나가고,
     선언이 없으니 grep으로도 안 보인다. 실제로 .library-share-btn과
     .placard-review-btn이 그렇게 새고 있었다(2026-08-03, npm run scan:render).
     둘 다 구경 모드에선 아이콘 옆 글자가 보이는 버튼이라 첫 화면에 그대로 나갔다.
     이 줄은 (0,0,1)이라 개별 규칙의 var(--sans) 선언에 지므로 기존 선언은
     그대로 둬도 무해하다 — 빠뜨린 자리만 이 줄이 받는다. */
  button, [role="button"]{font-family:var(--sans);}
  .app{
    max-width:920px;
    margin:0 auto;
    padding:
      calc(clamp(36px, 23px + 3.4vw, 56px) + env(safe-area-inset-top))
      calc(clamp(20px, 15px + 1.4vw, 28px) + env(safe-area-inset-right))
      calc(clamp(40px, 32px + 2vw, 64px) + env(safe-area-inset-bottom))
      calc(clamp(20px, 15px + 1.4vw, 28px) + env(safe-area-inset-left));
    box-sizing:border-box;
    /* 로그인 직후 데이터가 아직 없을 때는 콘텐츠가 뷰포트보다 짧을 수 있는데,
       iOS Safari는 페이지가 스크롤 안 될 만큼 짧으면 하단 고정(position:fixed;
       bottom:0) 요소를 주소창 높이를 감안한 위치로 잘못 계산해 탭바가 화면
       맨 아래에서 살짝 뜬 것처럼 보인다. 데이터가 로드돼 콘텐츠가 늘어나
       스크롤 가능해지면 그제서야 재계산되어 정상화된다 — 처음부터 뷰포트를
       꽉 채워서 이 재계산 타이밍에 좌우되지 않게 한다. */
    min-height:100vh;
    min-height:100dvh;
    display:none;
  }

  /* ---------- Boot spinner ---------- */
  /* 로그인 상태 확인 + Firebase SDK 로딩이 끝날 때까지는 login-view/.app이
     둘 다 display:none이라 화면이 완전히 비어 보인다. 그 사이를 채우는
     최소한의 로딩 표시 — JS 없이 HTML만으로 첫 페인트부터 바로 보이고,
     로그인/앱 화면 중 하나가 뜨는 순간 app.js가 hidden 처리한다. */
  .boot-spinner{
    position:fixed;
    inset:0;
    display:flex;
    align-items:center;
    justify-content:center;
    background:var(--paper);
    z-index:1;
  }
  .boot-spinner[hidden]{display:none;}
  .boot-spinner-ring{
    width:28px;
    height:28px;
    border:2.5px solid var(--line);
    border-top-color:var(--ink-soft);
    border-radius:50%;
    animation:boot-spin 0.8s linear infinite;
  }
  @keyframes boot-spin{ to{ transform:rotate(360deg); } }

  /* ---------- Login ---------- */
  .login-view{
    min-height:100vh;
    min-height:100dvh;
    display:flex;
    align-items:center;
    justify-content:center;
    padding:calc(24px + env(safe-area-inset-top)) calc(24px + env(safe-area-inset-right)) calc(24px + env(safe-area-inset-bottom)) calc(24px + env(safe-area-inset-left));
  }
  .login-box{
    max-width:360px;
    width:100%;
    text-align:center;
    padding: clamp(28px, 20px + 2vw, 40px) clamp(24px, 19px + 1.4vw, 32px);
    border:1px solid var(--line);
    border-radius:var(--radius);
    background:var(--card);
  }
  .login-title{font-family:'Cormorant Garamond', var(--serif); font-size:clamp(26px, 22px + 1.4vw, 32px); font-weight:500; letter-spacing:0.01em; margin-bottom:2px;}
  /* 부제: marginalia의 뜻 한 줄이 아이브로우·설명 문단을 대체 — 워드마크 철자 반복 금지 */
  .login-sub{font-family:var(--serif); font-size:12px; color:var(--ink-faint); letter-spacing:0.02em; margin:0;}
  .login-promise{
    margin:22px 0 24px; font-family:var(--serif); font-size:14px;
    line-height:1.8; color:var(--ink-soft);
  }
  .login-btn{
    display:block;
    width:100%;
    max-width:220px;
    margin:0 auto;
    padding:9.5px 16px;
    border-radius:var(--radius);
    border:1px solid var(--moss);
    background:var(--moss);
    color:var(--paper);
    font-family:var(--sans);
    font-size:12.5px;
    font-weight:600;
    cursor:pointer;
  }
  .login-btn:disabled{opacity:0.5; cursor:default;}
  /* 공유·초대로 온 방문자에게만 뜨는 조용한 맞이 — 바로 아래 예시 서재 링크로
     이어준다. 로그인 버튼과 각주 사이 여백에 세리프 한 줄로 낮게 둔다. */
  .login-welcome{
    margin:18px 0 0; font-family:var(--serif); font-size:13px;
    line-height:1.7; color:var(--ink-soft);
  }
  .login-welcome[hidden]{display:none;}
  /* 새 방문자용 조용한 미리보기 링크 — 주 동작(로그인) '위'에 놓여 '둘러보고
     시작'을 잇는다(2026-08-07). 밑줄을 떼고 여백으로 떼어 채운 로그인 버튼과
     경쟁하지 않게 낮춘다. 색은 --card 위에서 AA를 지나는 --ink-soft를 유지
     (더 옅게는 a11y 대비 검사로 확인). */
  .login-demo-btn{
    position:relative;
    display:block; margin:18px auto 16px; padding:0; border:0; background:none;
    font-family:var(--sans); font-size:12.5px; color:var(--ink-soft);
    cursor:pointer;
  }
  /* 담백한 밑줄 링크의 모양은 그대로 두고, 손끝 히트 영역만 44px로 넓힌다
     (보이지 않는 ::after — 실제 태핑 편의는 올리되 시각은 안 건드린다). */
  .login-demo-btn::after{
    content:''; position:absolute; left:50%; top:50%;
    transform:translate(-50%,-50%);
    width:100%; min-width:88px; height:44px;
  }
  /* 동의 문구는 링크까지 한 줄 — 좁은 화면에선 vw로 글자를 줄여서 줄바꿈을 막는다 */
  /* 구경(예시 서재) 모드 — 진입은 두 곳: 로그인 화면 맨 아래 조용한 링크와
     서재 빈 화면의 예시 링크. demo-only는 데모에서만 보이고, 쓰기 진입점은 일괄 숨긴다 */
  .demo-only{display:none;}
  body.demo-mode .demo-only{display:block;}
  .demo-banner{
    display:none; align-items:center; justify-content:space-between; gap:10px 14px; flex-wrap:wrap;
    padding:10px 12px 10px 14px; margin-bottom:18px;
    background:var(--moss-soft); border-radius:var(--radius);
    font-family:var(--sans); font-size:12px; color:var(--moss-ink);
  }
  body.demo-mode .demo-banner{display:flex;}
  .demo-banner span{flex:1 1 200px; text-align:left; line-height:1.5; text-wrap:balance;}
  /* 배너의 돌아가기는 밑줄 링크가 아니라 눌리는 버튼으로 — 문장에 묻히지 않게.
     흰 카드 배경은 연녹 배너 위에서 혼자 떠 보인다고 반려됨(2026-07-18) —
     진한 모스 솔리드로 배너 안에 앉힌다. */
  .demo-banner button{
    flex-shrink:0; padding:5px 12px; font-family:var(--sans); font-size:12px;
    font-weight:600; color:var(--paper); background:var(--moss);
    border:none; border-radius:var(--radius); cursor:pointer;
    /* 내 서재로 돌아가기(#demo-exit-btn)는 히트영역 규칙에서 inline-flex라 text-align이
       안 먹는다 — 두 버튼 글씨를 가운데로 두려면 justify-content로 맞춘다. */
    justify-content:center;
  }
  .demo-banner button:active{opacity:0.8;}
  .demo-banner-actions{display:flex; gap:8px; flex-shrink:0;}
  /* '둘러보기'는 보조라 채운 모스가 아니라 조용한 고스트로 — 두 버튼이 안 다투게 */
  .demo-banner .demo-banner-ghost{background:transparent; color:var(--moss-ink); border:1px solid var(--moss);}
  /* 방침·약관 변경 고지 배너(lib/policy-notice.js) — 헤더 아래 조용한 모스 틴트 한 줄.
     동작 둘을 같은 텍스트 버튼 형태로 통일하고(읽어보기=모스, 닫기=옅은 잉크) 오른쪽 정렬. */
  .policy-notice{
    display:flex; align-items:center; gap:10px; flex-wrap:wrap;
    padding:10px 12px 10px 14px; margin-bottom:18px;
    background:var(--moss-bg); border-radius:var(--radius);
    font-family:var(--sans); font-size:13px; color:var(--ink);
  }
  /* hidden 속성이 위 display:flex를 이기게 — 안 그러면 app.js가 감추기 전(또는 조건이
     안 맞을 때)에도 배너가 뜬다(scan:render가 잡은 자리). */
  .policy-notice[hidden]{display:none;}
  .policy-notice-text{flex:1 1 200px; line-height:1.5;}
  .policy-notice-actions{display:flex; align-items:center; gap:4px; flex-shrink:0; margin-left:auto;}
  .policy-notice-btn{
    font-family:var(--sans); font-size:13px; line-height:1;
    padding:7px 10px; border-radius:var(--radius); border:0; background:transparent;
    cursor:pointer; white-space:nowrap; text-decoration:none;
  }
  .policy-notice-btn.primary{color:var(--moss-ink); font-weight:600;}
  .policy-notice-btn.ghost{color:var(--ink-soft);}
  .policy-notice-btn:hover{background:rgba(35,40,33,0.05);}
  .policy-notice-btn:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  /* 모임 탭은 예시 데이터로 통째로 열어두되, 쓰기 진입점만 골라서 숨긴다 —
     만들기/가입/초대, 일정 저장, 한 줄 남기기, AI 추천 호출. 교환독서 설정 탭과
     딥다이브(나를 더 읽는 대화)는 읽기 전용으로 노출한다(아래 별도 규칙). */
  body.demo-mode #policy-notice,
  body.demo-mode .notif-wrap,
  body.demo-mode .header-settings-btn,
  body.demo-mode #settings-feedback-btn,
  body.demo-mode #feedback-modal-overlay,
  body.demo-mode .social-nickname-nudge,
  body.demo-mode .social-actions,
  body.demo-mode .group-card-ghost,
  body.demo-mode .group-space-actions,
  body.demo-mode .group-space-footer,
  body.demo-mode #member-space-remove,
  body.demo-mode .group-card-handle,
  body.demo-mode .exchange-note-form,
  body.demo-mode .exchange-note-row-invite,
  body.demo-mode #exchange-note-open-btn,
  body.demo-mode .margin-note-open,
  body.demo-mode .placard-margin-del,
  body.demo-mode .handoff-card-actions,
  body.demo-mode .handoff-gift-overlay,
  body.demo-mode #group-recommend-btn,
  body.demo-mode .exchange-setup-actions,
  body.demo-mode #review-generate-btn,
  body.demo-mode #review-galmuri,
  body.demo-mode #review-galmuri-preview,
  body.demo-mode #review-edit-btn,
  body.demo-mode #review-refine-btn,
  body.demo-mode #passages-merge-btn,
  body.demo-mode .passage-share,
  body.demo-mode .taste-report-refresh,
  body.demo-mode .taste-report-start-inline,
  body.demo-mode .wishlist-remove-btn,
  body.demo-mode #settings-recover-btn,
  body.demo-mode #settings-recover-deleted-btn,
  body.demo-mode #settings-delete-account-btn,
  body.demo-mode #milestone-modal-save,
  body.demo-mode .rec-actions{display:none !important;}
  /* 데모의 일정 패널은 날짜 선택과 입력 화면까지 체험하고, 저장만 코드에서 막는다. */
  /* 교환독서 설정 탭도 같은 결로 읽기 전용 — 시작일·주기·책 검색을 얼리고
     순서 드래그 손잡이도 잠근다(저장 버튼은 위 목록에서 이미 숨긴다). 쓰기
     핸들러 자체가 데모에선 배선되지 않는다(views/groups.js). */
  body.demo-mode .exchange-config-row .exchange-control,
  body.demo-mode .exchange-books-subsection .exchange-control,
  body.demo-mode .exchange-join-input,
  body.demo-mode .exchange-order-handle{pointer-events:none;}
  /* 딥다이브 대화는 데모에서 예시 대화를 읽기 전용으로 보여준다 — 입력창(컴포저)을
     접어 '모습'만 남긴다(대화 내용은 lib/demo-seed.js DEMO_DEEP_DIVE). */
  body.demo-mode #chat-view .chat-composer{display:none;}
  /* ---------- AI 꺼짐 상태 (키 미등록 — 유료화 후엔 미구독) ----------
     AI를 안 쓰는 계정에는 AI 표면을 통째로 접는다. 눌러야 "키를 등록해야…"
     에러가 뜨던 버튼들을 아예 보이지 않게 — 발견 경로는 취향 탭의 초대
     한 줄(.ai-invite)·설정의 키 섹션·독후감 화면의 메모 티저(.review-ai-invite,
     2026-08-13). 스위치는 app.js aiEnabled()
     하나라서, 유료화 때 그 함수의 조건만 구독 여부로 바꾸면 된다. */
  body.ai-off .ai-surface,
  body.ai-off #review-generate-btn,
  body.ai-off #review-galmuri,
  body.ai-off #review-galmuri-preview,
  body.ai-off #review-refine-btn,
  body.ai-off #settings-retag-row{display:none !important;}
  /* '함께 읽을 책'(.exchange-subsection--recommend)은 ai-off라고 통째로 숨기지 않는다 —
     키 없을 때 groups.js가 버튼·설명 대신 'AI 연결' 초대(.ai-connect-note)를 그려
     연결 진입점이 되게 한다(예전엔 여기 숨겨 모임 탭엔 연결 수단이 없었다). */
  /* 초대 한 줄 — 조용한 세리프, 링크 하나 */
  .ai-invite{
    display:flex; align-items:baseline; justify-content:space-between; gap:14px; flex-wrap:wrap;
    font-family:var(--serif); font-size:13px; color:var(--ink-faint); line-height:1.8;
    margin-top:46px; margin-bottom:28px;
  }
  .ai-invite span{flex:1; text-wrap:balance;} /* 마지막 줄이 짧게 떨어지지 않게 */
  .ai-invite[hidden]{display:none;}
  /* 초대 한 줄이 '읽은 나날'과 'AI 묶음' 사이에 온다. AI-on 계정에선 초대가
     display:none이라 .dash-group + .dash-group가 못 매칭한다(초대가 사이에 낌) —
     그래서 읽은 나날↔취향 리포트 구분선·간격을 이 규칙이 직접 낸다. AI-off면
     .ai-surface가 통째로 display:none이라 이 구분선도 함께 사라진다.
     값은 아래 .deep-dive-block(§취향 리포트↔대화)과 똑같이 맞춰 섹션 간격을 통일. */
  .ai-invite + .dash-group{margin-top:38px; border-top:1px solid var(--line); padding-top:34px;}
  /* 키 없는 AI 기능의 빈 상태 — 문구 속 'AI 연결'이 곧 링크(별도 버튼 없이 중복 제거).
     .ai-invite(섹션 사이 띠)와 달리 이건 결과가 뜨던 자리를 대신하는 좌측 한 줄이다. */
  .ai-connect-note{
    font-family:var(--serif); font-size:13px; color:var(--ink-faint); line-height:1.9;
    margin:0;  /* 줄바꿈은 전역 word-break:keep-all이 맡는다 — 한 줄에 들어가면
                  한 줄로, 넘칠 때만 어절 경계에서 */
  }
  /* 서체는 var(--serif) — 이 링크는 홀로 선 컨트롤이 아니라 세리프 문장 속 낱말이라
     산세리프(버튼 관례)면 어절 안에서 튄다. 문맥이 서체를 정하는 인라인 링크다. */
  .ai-connect-link{
    font-family:var(--serif); font-size:inherit; font-weight:500; color:var(--moss);
    background:none; border:none; padding:0; cursor:pointer;
    text-decoration:none; border-bottom:1px solid color-mix(in srgb, var(--moss) 45%, transparent);
    white-space:nowrap; /* 'AI 연결'이 어절 중간에서 쪼개지지 않게 */
  }
  .ai-connect-link:hover,
  .ai-connect-link:focus-visible{border-bottom-color:var(--moss);}
  /* 서재 빈 화면의 예시 서재 진입 링크 — empty-state의 세리프·옅은 잉크 톤을 잇는다 */
  .empty-demo-btn{
    display:block; margin:20px auto 0; background:none; border:none; padding:0;
    font-family:var(--sans); font-size:13px; color:var(--ink-soft);
    text-decoration:underline; text-underline-offset:4px; cursor:pointer;
  }
  /* "로그인 시 …" 동의 문구는 로그인 버튼의 각주 — 버튼 바로 아래 한 줄로 둔다.
     예전엔 nowrap으로 한 줄을 강제하고 글자를 clamp로 줄였는데, 360px에서
     9.2px까지 작아지고 320px에선 링크가 카드 밖으로 잘렸다(2026-07-28 실측).
     한 줄은 폰트가 아니라 문장 길이로 맞춘다 — 카드 안쪽이 360px 화면에서
     262px이고 지금 문장이 11px에서 229px이라 한 줄로 들어간다.
     nowrap은 걸지 않는다: 그보다 좁은 화면에선 잘리는 대신 두 줄로 흐르는 게 낫다.
     문구를 늘릴 일이 생기면 이 폭부터 다시 재고 늘릴 것. */
  .login-privacy-note{
    font-size:11px; color:var(--ink-faint); line-height:1.65; margin-top:12px;
  }
  .login-privacy-note a{color:var(--moss); text-underline-offset:3px;}
  /* 로그인 실패·부팅 지연 안내 — 개인정보 각주 아래·데모 링크 위. 각주(11px
     ink-faint)보다 또렷하게 읽히도록 13px ink-soft로, 하지만 팔레트에 에러색이
     없듯 요란하게 붉히지 않는다(문구가 뜻을 나른다). 비어 있으면 자리를 접어
     각주와 데모 링크가 제 간격으로 붙게 한다 — 안내가 뜰 때만 그만큼 벌어진다. */
  .login-status{margin-top:16px; font-size:13px; line-height:1.6; color:var(--ink-soft);}
  .login-status:empty{margin-top:0;}
  /* 카드 안에 또 박스를 두지 않는다 — 안내는 카드의 문구·버튼으로 그대로
     흐르게 해서 일반 로그인 화면과 같은 담백한 결을 유지한다(테두리·배경 없음). */
  .inapp-notice{text-align:center;}
  /* 강제 줄바꿈 없이 흘려보내되, balance로 줄 길이를 고르게 — 한 단어만 다음
     줄로 떨어지거나 문장이 어중간하게 잘려 보이는 걸 막는다(keep-all과 함께). */
  .inapp-notice-desc{font-size:13px; color:var(--ink); line-height:1.65; margin:0 0 14px; text-wrap:balance;}
  /* 일반 로그인 버튼(.login-btn)과 같은 크기·정렬 — 인앱 화면이 로그인 화면과 통일되게 */
  .inapp-notice-btn{
    display:block;
    width:100%;
    max-width:220px;
    margin:0 auto;
    padding:9.5px 16px;
    border-radius:var(--radius);
    border:1px solid var(--moss);
    background:var(--moss);
    color:var(--paper);
    font-family:var(--sans);
    font-size:12.5px;
    font-weight:600;
    cursor:pointer;
  }
  .inapp-notice-btn:active{opacity:0.8;}
  /* 평소엔 비어 있어(숨김) 문구+버튼만 남고, 링크 복사 후 성공 안내가 들어올 때만 뜬다 */
  .inapp-notice-hint{font-size:11px; color:var(--ink-soft); line-height:1.5; margin:10px 0 0;}
  .inapp-notice-hint:empty{display:none;}

  /* ---------- Hero ---------- */
  .hero{
    border-bottom:1px solid var(--line);
    padding-bottom:12px;
    margin-bottom:22px;
  }
  .hero-eyebrow{
    font-size:11px;
    letter-spacing:0.18em;
    color:var(--moss);
    font-weight:600;
    margin-bottom:10px;
  }
  /* 제목 행이라 좌측 정렬이 의도다 — 액션 행이 아니다(우아함 헌장 B1/C1). */
  .hero-title-row{
    display:flex;
    align-items:baseline;
    gap:16px;
    flex-wrap:wrap;
  }
  /* 종·설정·콘솔 묶음은 데스크톱에서 투명하다 — 감싼 div가 없는 것처럼 세 아이콘이
     그대로 .hero-title-row의 flex 자식이 된다(폰에서만 실제 박스로 살아난다). */
  .hero-actions{display:contents;}
  .hero h1{
    font-family:'Cormorant Garamond', var(--serif);
    font-size:clamp(38px, 32px + 1.6vw, 46px);
    font-weight:500;
    margin:0;
    letter-spacing:0.01em;
  }
  /* 워드마크: Margin<em>alia</em> — 뒷부분만 이탤릭 + 액센트 */
  .wordmark em{ font-style:italic; color:var(--moss); }
  .hero-count-inline{
    display:flex;
    align-items:baseline;
    gap:6px;
  }
  /* display:flex가 [hidden]의 기본 display:none을 이긴다(.library-toolbar[hidden]와 같은
     함정). 완독 0일 때 '0 권의 기록'을 감추려면(views/library.js renderLibrary) 명시한다. */
  .hero-count-inline[hidden]{display:none;}
  .hero-count-inline .num{
    font-size:20px;
    color:var(--ink);
    font-family:var(--serif);
  }
  .hero-count-inline .label{
    font-size:12px;
    letter-spacing:0.05em;
    color:var(--ink-faint);
  }
  .sync-status{font-size:11px;color:var(--moss);margin-top:8px;min-height:14px;}
  .notif-wrap{position:relative; margin-left:auto;}
  .notif-bell{
    position:relative;
    background:none;
    border:none;
    color:var(--ink-soft);
    cursor:pointer;
    padding:4px;
    display:inline-flex;
    align-items:center;
    justify-content:center;
  }
  /* 설정 진입점 — 탭이 아니라 알림 종 옆. 설정은 상시 목적지가 아니라
     가끔 들르는 풀스크린 화면이라 헤더 아이콘이 자리에 맞다 */
  .header-settings-btn{
    background:none;
    border:none;
    color:var(--ink-soft);
    cursor:pointer;
    padding:4px;
    display:inline-flex;
    align-items:center;
    justify-content:center;
  }
  .header-settings-btn:hover{color:var(--moss);}
  .notif-dot{
    position:absolute;
    top:2px; right:2px;
    width:8px; height:8px;
    border-radius:50%;
    background:var(--moss);
  }
  .notif-panel{
    position:absolute;
    top:calc(100% + 10px);
    right:0;
    z-index:30;
    width:min(360px, calc(100vw - 32px));
    display:flex;
    flex-direction:column;
    background:var(--card);
    border:1px solid var(--line);
    border-radius:var(--radius);
    box-shadow:var(--shadow-card);
    opacity:0;
    transform:translateY(-8px);
    pointer-events:none;
    transition:opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
  }
  .notif-panel.open{opacity:1; transform:translateY(0); pointer-events:auto;}
  .notif-item{
    padding:14px 16px;
    display:flex;
    flex-direction:column;
    gap:10px;
  }
  .notif-item[hidden]{display:none;}
  /* 구분선은 항목마다 위쪽에 두되 패널에서 처음 '보이는' 항목만 뺀다
     (updateNotifState가 .notif-first를 붙인다). 인접 선택자(+)는 #margin-banner
     래퍼와 [hidden] 형제 때문에 부유하는 hairline/누락 구분선이 생겼다. */
  .notif-item{border-top:1px solid var(--line);}
  .notif-item.notif-first{border-top:none;}
  .notif-empty{padding:14px 16px; font-size:11px; color:var(--ink-faint);}
  .notif-empty[hidden]{display:none;}
  .notif-dot[hidden]{display:none;}
  .notif-item-text{font-size:11px; color:var(--ink-soft); line-height:1.45;}
  .notif-item.tip .notif-item-text strong{color:var(--moss);}
  .notif-item-btn{
    align-self:flex-end;
    border:1px solid currentColor;
    border-radius:var(--radius-pill);
    background:transparent;
    padding:6px 14px;
    font-family:var(--sans);
    font-size:12px;
    font-weight:600;
    cursor:pointer;
    white-space:nowrap;
  }
  .notif-item.tip .notif-item-btn{color:var(--moss);}
  .margin-one{position:relative;}
  .margin-one .notif-item-text{padding-right:16px;}   /* 우상단 × 자리 */
  /* 설치 안내도 같은 × 닫기를 쓴다(여백 알림과 같은 패턴) — 안 닫으면 미설치
     사용자에게 벨 점이 영영 켜져 있다. margin-one 클래스는 뜻이 안 맞아 재사용 안 하고
     같은 위치 규칙만 명시한다. */
  #install-banner{position:relative;}
  #install-banner .notif-item-text{padding-right:16px;}
  .notif-item-dismiss{
    position:absolute; top:8px; right:10px;
    border:0; background:transparent;
    padding:4px; line-height:1;
    font-size:16px; color:var(--ink-faint);
    cursor:pointer;
  }
  .boot-error-banner{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:12px;
    position:sticky;
    top:0;
    z-index:50;
    background:var(--ink);
    color:var(--paper);
    padding:calc(10px + env(safe-area-inset-top)) calc(16px + env(safe-area-inset-right)) 10px calc(16px + env(safe-area-inset-left));
    font-family:var(--sans);
    font-size:12.5px;
    line-height:1.5;
  }
  .boot-error-banner[hidden]{display:none;}
  .boot-error-banner button{
    flex-shrink:0;
    background:var(--paper);
    color:var(--ink);
    border:none;
    border-radius:var(--radius-pill);
    padding:6px 14px;
    font-family:var(--sans);
    font-size:12px;
    font-weight:600;
    cursor:pointer;
  }
  .boot-error-banner button:disabled{opacity:0.6; cursor:default;}
  .settings-link-btn{
    background:none;
    border:none;
    padding:0;
    font-family:var(--sans);
    font-size:12px;
    color:var(--ink-faint);
    cursor:pointer;
    text-decoration:underline;
    text-underline-offset:2px;
    white-space:nowrap;
    flex:0 0 auto;
  }
  .settings-link-btn:disabled{opacity:0.4; cursor:default;}
  .settings-link-btn[hidden]{display:none;}
  .settings-list{display:flex; flex-direction:column;}
  /* 설정 맨 아래 조용한 링크 줄 — 설치 안내처럼 "가끔 찾는" 항목은 별도
     섹션(row)으로 세우면 설정 흐름을 끊어서, 푸터로 수납한다 */
  .settings-footer-links{display:flex; justify-content:center; align-items:center; flex-wrap:wrap; gap:10px 20px; margin-top:56px;}
  /* 둘째 줄 — 동작 둘(설치 안내·의견)과 버전이 한 세트. 버전이 10px로 링크(11px)보다
     작아서 baseline으로 맞춘다(center로 맞추면 글자 밑선이 어긋나 보인다). */
  .settings-footer-meta{display:flex; justify-content:center; align-items:baseline; flex-wrap:wrap; gap:8px 20px; margin-top:16px;}
  /* 버전 — 푸터에서 가장 조용하게(링크보다 더 옅게). 비어 있으면 자리 안 만든다. */
  .settings-version{margin:0; font-family:var(--sans); font-size:10px; color:var(--line);}
  .settings-version:empty{display:none;}
  .settings-footer-link{
    background:none; border:none; padding:0; cursor:pointer;
    font-family:var(--sans); font-size:11px; color:var(--ink-faint); text-decoration:none;
  }
  /* 섹션(.settings-row)은 넉넉히 띄우고 hairline으로 가른다. 한 섹션 안에 여러 항목
     (.settings-item)이 들어가고, 항목 사이는 촘촘히 둔다 — 섹션>항목 두 단 리듬. */
  .settings-row{padding:30px 0; border-bottom:1px solid var(--line);}
  .settings-row:last-child{border-bottom:none; padding-bottom:0;}
  .settings-item + .settings-item{margin-top:24px;}
  /* ④ 정리 섹션 — 구조·글씨체는 다른 섹션과 통일한다(§섹션 구조 통일: 한 섹션만 다른
     모양을 만들지 않는다). 파괴적 동작의 '조용함'은 글씨 크기를 낮춰서가 아니라 맨 아래
     배치 + 무채색 버튼(아래)만으로 신호한다. 그래서 첫 줄·항목 간격 오버라이드를 두지
     않고 기본(.settings-primary 세리프 17px, 항목 간격 24px)을 그대로 쓴다. */
  /* 설정 행의 타입 역할은 4개뿐 — 라벨(웜그레이 산세리프 11px), 첫 줄(세리프 17px),
     보조 설명(12.5px), 링크(12.5px 600, 색만 모스/저강도 구분).
     라벨은 구조 눈썹이라 액센트(모스) 자리가 아니다 — 앱의 다른 모든 눈썹
     (round-summary-label·year-shelf-label·독후감 블록)과 같은 --ink-faint로 통일
     (2026-08-27, 온보딩·연간결산 눈썹을 낮춘 08-13 방향을 이어간다). */
  .settings-row-label{
    display:flex;
    align-items:center;
    gap:8px;
    font-family:var(--sans);
    font-size:11px;
    font-weight:600;
    letter-spacing:0.1em;
    color:var(--ink-faint);
    margin-bottom:12px;
  }
  .settings-primary{font-family:var(--serif); font-size:17px; font-weight:500; color:var(--ink); line-height:1.5;}
  .settings-primary strong{font-weight:600;}
  /* "담아둔 책 / 30권"처럼 단위 중간에서 꺾이지 않게 — 줄바꿈은 · 구분점에서만 */
  .settings-primary .stat-unit{white-space:nowrap;}
  .settings-row-main{flex:1; min-width:0;}
  .settings-row-desc{font-size:12.5px; color:var(--ink-soft); line-height:1.6; margin:5px 0 0;}
  .settings-row-desc a{color:var(--moss);}
  .settings-row-desc strong{color:var(--ink); font-weight:600;}
  .settings-data-row{display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap;}
  .settings-data-row.nickname-editing{align-items:center;}
  .settings-data-row[hidden]{display:none;}
  .settings-data-row .settings-row-actions{margin-top:6px;}
  /* 설정 동작은 밑줄 없는 조용한 모스 텍스트 — 밑줄이 나란히 쌓이면(변경 해제 /
     백업 복원) 소음이 된다. 모스는 종이 위 4.96:1로 본문 대비 통과(tokens.css).
     밑줄은 hover/active에서만 돌아온다. 스코프를 #settings-view로 좁혀 챗버블이
     재사용하는 전역 .settings-link-btn(밑줄 링크)은 안 건드린다. */
  #settings-view .settings-link-btn{font-size:12.5px; font-weight:600; text-decoration:none; color:var(--moss);}
  #settings-view .settings-link-btn:hover,
  #settings-view .settings-link-btn:active{text-decoration:underline; color:var(--moss);}
  /* ④ 정리 섹션의 동작(지우기·되돌리기·삭제)은 모스로 물들이지 않는다 — 초록은
     '가라'는 신호라 파괴적 동작에 안 맞는다. 붉게 칠하지도 않는다(현 브랜드는
     파괴를 색이 아니라 격리·confirm으로 다룬다). 같은 저강도 무채색으로 조용히. */
  #settings-view .settings-row--quiet .settings-link-btn{color:var(--ink-faint);}
  #settings-view .settings-row--quiet .settings-link-btn:hover,
  #settings-view .settings-row--quiet .settings-link-btn:active{color:var(--ink-soft);}
  .settings-key-form{margin-top:4px;}
  .settings-key-form[hidden]{display:none;}
  .settings-key-form .settings-row-desc{margin:0 0 14px;}
  /* 키 발급 3스텝 안내(views/settings.js PROVIDER_INFO) — 발급처 링크 한 줄을 대신한다.
     desc(#settings-key-desc)는 이 ol/p를 담는 div다. 문구·색은 settings-row-desc와 같은 결. */
  .key-steps{margin:0 0 14px; padding:0; list-style:none; display:grid; gap:6px;}
  .key-steps li{font-size:12.5px; color:var(--ink-soft); line-height:1.55; display:flex; gap:8px;}
  .key-steps li .n{color:var(--moss); font-weight:600; flex:0 0 auto;}
  .key-steps a{color:var(--moss);}
  .key-note{font-size:12.5px; color:var(--ink-faint); line-height:1.55; margin:0 0 14px;}
  /* 무료 온램프를 폼 첫 줄로 올린다(gemini, 2026-08-24 A안). "무료"만 모스로 눌러
     앞세우고, 발급 단계는 아래 .key-steps가 잇는다. 유료 제공자는 이 줄 없이 .key-note만. */
  .key-lead{font-size:13px; color:var(--ink); line-height:1.55; margin:0 0 12px;}
  .key-lead strong{color:var(--moss-ink); font-weight:600;}
  /* 접힌 요약 desc가 none 상태에서 "무료"를 비출 때만 모스로(다른 상태는 기본 색). */
  .settings-row-desc--free{color:var(--moss-ink);}
  #settings-status{display:block; margin-top:10px;}
  .settings-key-field{display:flex; gap:10px; align-items:center; flex-wrap:wrap; flex:1;}
  .settings-key-field[hidden]{display:none;}
  .settings-key-field input{flex:1; min-width:100px; margin-bottom:0;}
  #settings-nickname-field{margin-top:10px;}
  .settings-provider-select{
    flex:0 0 auto;
    box-sizing:border-box;
    padding:10px 12px;
    border:1px solid var(--line);
    border-radius:var(--radius);
    font-size:13px;
    line-height:1.5;
    background:var(--paper);
    color:var(--ink);
    cursor:pointer;
  }
  .settings-provider-select:focus{outline:none; border-color:var(--moss);}
  .settings-provider-select:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .settings-key-field .review-keyword-input{background:var(--paper);}
  .settings-row-actions{display:flex; align-items:center; justify-content:flex-end; gap:14px; flex:0 0 auto;}
  .settings-data-status{font-size:12px; color:var(--ink-faint);}

  /* ---------- 모달 오버레이(여섯 종 공통) ---------- */
  /* confirm · invite · feedback · share-card · milestone · recover 여섯이 **선언 단위로
     같은 값**이었다(2026-08-31 대조). 흩어져 있으면 딤 농도 하나를 손볼 때 고칠 자리가
     여섯이다. 여기 모으는 것은 **평상시 공통값뿐**이고, 폰 바텀시트(align-items:flex-end)
     처럼 조건이 다른 것은 각자의 미디어쿼리에 그대로 남는다. */
  .confirm-modal-overlay,
  .invite-modal-overlay,
  .feedback-modal-overlay,
  .share-card-modal-overlay,
  .milestone-modal-overlay,
  .recover-modal-overlay{
    position:fixed; inset:0; background:var(--scrim);
    display:flex; align-items:center; justify-content:center; padding:24px;
    z-index:100; opacity:0; pointer-events:none; transition:opacity var(--dur-2) var(--ease-out);
  }
  .confirm-modal-overlay.open,
  .invite-modal-overlay.open,
  .feedback-modal-overlay.open,
  .share-card-modal-overlay.open,
  .milestone-modal-overlay.open,
  .recover-modal-overlay.open{opacity:1; pointer-events:auto;}

  /* ---------- Confirm modal ---------- */
  /* 모달을 열 때 다이얼로그 컨테이너로 포커스를 들인다(lib/overlay.js) — 컨테이너는
     클릭 대상이 아니라 포커스 링이 필요 없다. 스크립트 포커스라 :focus-visible은 어차피
     안 뜨지만 브라우저 편차에 대비해 명시적으로 끈다. */
  .confirm-modal:focus,.invite-modal:focus,.feedback-modal:focus,
  .share-card-modal:focus,.milestone-modal:focus,.recover-modal:focus{outline:none;}
  .confirm-modal{
    max-width:360px;
    width:100%;
    background:var(--card);
    border:1px solid var(--line);
    border-radius:var(--radius);
    padding:24px;
    box-shadow:var(--shadow-card);
    transform:translateY(16px);
    transition:transform var(--dur-2) var(--ease-out);
  }
  .confirm-modal-overlay.open .confirm-modal{transform:translateY(0);}
  .confirm-modal-title{font-family:var(--serif); font-size:17px; font-weight:600; color:var(--ink); margin-bottom:10px;}
  .confirm-modal-desc{font-size:13px; color:var(--ink-soft); line-height:1.6; margin:0 0 22px; white-space:pre-line;}
  /* 취소·삭제는 한 짝 — 같은 크기·모양의 버튼으로 두고 색만 나눈다
     (placard-finish 확인/취소 결과 동일). 밑줄 링크 + 박스 버튼처럼
     서로 다른 종류로 섞지 않는다. */
  /* 액션 행 — 우측 정렬이 기본(§CLAUDE.md 디자인 원칙). 다섯이 공통이고 차이는
     여백·간격뿐이라 그것만 아래에 남긴다. milestone은 세로 스택이라 따로 둔다. */
  .confirm-modal-actions,
  .invite-modal-actions,
  .feedback-modal-actions,
  .share-card-modal-actions,
  .recover-modal-actions{display:flex; align-items:center; justify-content:flex-end; gap:10px;}
  .feedback-modal-actions{margin-top:14px;}
  .share-card-modal-actions{margin-top:16px;}
  .recover-modal-actions{gap:18px;}
  /* 모달 버튼 — confirm · invite · feedback 세 벌이 같은 값이었다(2026-08-31 대조).
     share-card는 아이콘 문법이라, milestone은 세로 스택이라 여기 안 든다. */
  .confirm-modal-actions button,
  .invite-modal-actions button,
  .feedback-modal-actions button{
    font-family:var(--sans); font-size:12px; font-weight:600;
    padding:9px 18px; border-radius:var(--radius); cursor:pointer;
    white-space:nowrap; transition:background var(--dur-1) var(--ease-inout), border-color var(--dur-1) var(--ease-inout), color var(--dur-1) var(--ease-inout), opacity var(--dur-1) var(--ease-inout);
  }
  .confirm-modal-cancel-btn{background:none; border:1px solid var(--line); color:var(--ink-soft);}
  .confirm-modal-confirm-btn{background:var(--moss); border:1px solid var(--moss); color:var(--paper);}
  .confirm-modal-actions button:disabled,
  .invite-modal-actions button:disabled,
  .feedback-modal-actions button:disabled{opacity:0.4; cursor:default;}

  /* 초대 링크 팝업 — confirm 모달과 같은 결. 다른 건 이름 한 칸과 결과 한 줄뿐이라
     오버레이·카드·버튼 문법을 그대로 쓴다(버튼은 confirm의 클래스를 공유). */
  .invite-modal{
    max-width:360px; width:100%;
    background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
    padding:24px; box-shadow:var(--shadow-card);
    transform:translateY(16px); transition:transform var(--dur-2) var(--ease-out);
  }
  .invite-modal-overlay.open .invite-modal{transform:translateY(0);}
  .invite-modal-title{font-family:var(--serif); font-size:17px; font-weight:600; color:var(--ink); margin-bottom:10px;}
  .invite-modal-desc{font-size:13px; color:var(--ink-soft); line-height:1.6; margin:0 0 22px;}
  .invite-modal-nick{margin:0 0 20px;}
  .invite-modal-label{display:block; font-family:var(--sans); font-size:11px; letter-spacing:0.05em; color:var(--ink-faint); margin-bottom:8px;}
  .invite-modal-nick input{
    width:100%; box-sizing:border-box; padding:11px 13px;
    border:1px solid var(--line); border-radius:var(--radius);
    background:var(--paper); color:var(--ink);
    font-size:14px;
  }
  .invite-modal-nick input:focus{outline:none; border-color:var(--moss);}
  .invite-modal-nick input:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  /* 결과 한 줄 — 비어 있을 땐 자리를 차지하지 않는다(로그인 상태줄과 같은 규칙) */
  .invite-modal-status{font-size:12px; color:var(--ink-soft); line-height:1.5; margin-bottom:16px;}
  .invite-modal-status:empty{display:none;}

  /* 의견 보내기 팝업 — confirm 모달과 같은 오버레이 결(백드롭·중앙·올라오는
     카드). 본문 입력이 있어 카드만 조금 넓다. */
  .feedback-modal{
    max-width:420px; width:100%;
    background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
    padding:24px; box-shadow:var(--shadow-card);
    transform:translateY(16px); transition:transform var(--dur-2) var(--ease-out);
  }
  .feedback-modal-overlay.open .feedback-modal{transform:translateY(0);}
  .feedback-modal-title{font-family:var(--serif); font-size:17px; font-weight:600; color:var(--ink); margin-bottom:14px;}
  .feedback-modal-input{
    width:100%; box-sizing:border-box; min-height:120px; padding:14px 15px;
    border:1px solid var(--line); border-radius:var(--radius);
    font-size:14px; line-height:1.7; color:var(--ink);
    background:var(--paper); resize:vertical;
  }
  .feedback-modal-input::placeholder{color:var(--ink-faint); font-family:var(--sans);}
  .feedback-modal-status{display:block; min-height:16px; margin:10px 2px 0;}
  /* 서재 한 장 모달 — feedback 모달과 같은 오버레이/카드 결. */
  .share-card-modal{
    position:relative;
    max-width:420px; width:100%;
    background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
    padding:24px; box-shadow:var(--shadow-card);
    transform:translateY(16px); transition:transform var(--dur-2) var(--ease-out);
  }
  /* 닫기는 우측 상단에 작게 — 하단은 공유·저장만 남겨 깔끔하게. */
  .share-card-modal-x{
    position:absolute; top:14px; right:14px; width:30px; height:30px;
    display:flex; align-items:center; justify-content:center;
    background:none; border:none; border-radius:var(--radius); color:var(--ink-faint); cursor:pointer;
  }
  .share-card-modal-x svg{width:18px; height:18px;}
  .share-card-modal-x:hover{color:var(--ink);}
  .share-card-modal-x:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .share-card-modal-overlay.open .share-card-modal{transform:translateY(0);}
  .share-card-modal-title{font-family:var(--serif); font-size:17px; font-weight:600; color:var(--ink); margin-bottom:8px;}
  .share-card-modal-desc{font-size:13px; line-height:1.7; color:var(--ink-soft); margin:0 0 18px;}
  .share-card-formats{display:flex; gap:10px;}
  .share-card-format-btn{
    flex:1 1 0; display:flex; align-items:center; justify-content:center;
    background:var(--paper); border:1px solid var(--line); border-radius:var(--radius);
    padding:14px; cursor:pointer;
    font-family:var(--sans); font-size:15px; color:var(--ink);
  }
  .share-card-format-btn:hover{border-color:var(--moss);}
  .share-card-format-btn:disabled{opacity:.5; cursor:default;}
  .share-card-format-btn:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .share-card-modal-status{display:block; min-height:16px; margin:14px 2px 0; font-size:12px; color:var(--ink-soft);}
  /* 만든 카드 미리보기 — 완독 카드와 같은 결(폭 꽉·각진 모서리·옅은 그림자). */
  .share-card-preview{display:block; width:100%; height:auto; max-height:52vh; object-fit:contain; border-radius:var(--radius); margin:16px 0 2px; box-shadow:var(--shadow-card);}
  .share-card-preview[hidden]{display:none;}
  .share-card-modal-actions button[hidden]{display:none;}
  /* 동작은 상자 없는 아이콘만 — 부차 기능은 이름도 테두리도 빼 가볍게(서재 공유
     진입 버튼과 같은 결). 위계는 색으로만: 공유=모스(주 동작), 저장=옅은 잉크. */
  .share-act{
    width:40px; height:40px; display:flex; align-items:center; justify-content:center;
    background:none; border:none; border-radius:var(--radius); cursor:pointer;
    transition:color var(--dur-1) var(--ease-inout), transform var(--dur-1) var(--ease-out);
  }
  .share-act svg{width:24px; height:24px;}
  .share-act--primary{color:var(--moss);}
  .share-act--primary:hover{color:var(--moss-deep);}
  .share-act--muted{color:var(--ink-faint);}
  .share-act--muted:hover{color:var(--ink);}
  .share-act:active{transform:translateY(1px);}
  .share-act:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .feedback-modal-cancel-btn{background:none; border:1px solid var(--line); color:var(--ink-soft);}
  .feedback-modal-send-btn{background:var(--moss); border:1px solid var(--moss); color:var(--paper);}

  .milestone-modal{
    position:relative;
    max-width:340px;
    width:100%;
    background:var(--card);
    border:1px solid var(--line);
    border-radius:var(--radius);
    padding:32px 28px;
    box-shadow:var(--shadow-card);
    text-align:center;
    transform:translateY(16px);
    transition:transform var(--dur-2) var(--ease-out);
  }
  .milestone-modal-overlay.open .milestone-modal{transform:translateY(0);}
  /* 우측 상단 닫기 X — 연말 결산 팝업 전용(서재 한 장 모달 .share-card-modal-x와 같은 결). */
  .milestone-modal-x{position:absolute; top:14px; right:14px; width:30px; height:30px; display:flex; align-items:center; justify-content:center; background:none; border:none; border-radius:var(--radius); color:var(--ink-faint); cursor:pointer;}
  .milestone-modal-x svg{width:18px; height:18px;}
  .milestone-modal-x:hover{color:var(--ink);}
  .milestone-modal-x[hidden]{display:none;}
  .milestone-modal-x:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .milestone-modal-num{font-family:var(--serif); font-size:42px; font-weight:700; color:var(--moss); line-height:1; margin-bottom:4px;}
  .milestone-modal-title{font-family:var(--serif); font-size:17px; font-weight:600; color:var(--ink); margin-bottom:14px;}
  .milestone-modal-desc{font-size:13px; color:var(--ink-soft); line-height:1.6; margin:0 0 22px;}
  /* 팝업이 미리 받아 보여주는 축하 카드 — 로딩되면 위 숫자·문구를 대신한다. */
  .milestone-card-img{display:block; width:100%; height:auto; border-radius:var(--radius); margin:2px 0 18px; box-shadow:var(--shadow-card);}
  .milestone-card-img[hidden]{display:none;}
  /* 축하 카드 저장(주 액션)과 확인(조용한 링크)을 세로로. 저장이 자랑의 값진 동작. */
  .milestone-modal-actions{display:flex; flex-direction:column; gap:10px; align-items:center;}
  .milestone-modal-actions .social-primary-btn{width:100%;}

  /* 메모 되살리기 — 이력에서 복원할 후보를 고르는 목록. 카드가 곧 프레임이니
     항목은 hairline 구분선만(박스 안 박스 금지). */
  .recover-modal{
    max-width:420px; width:100%; max-height:80vh; display:flex; flex-direction:column;
    background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
    padding:24px; box-shadow:var(--shadow-card);
    transform:translateY(16px); transition:transform var(--dur-2) var(--ease-out);
  }
  .recover-modal-overlay.open .recover-modal{transform:translateY(0);}
  .recover-modal-title{font-family:var(--serif); font-size:17px; font-weight:600; color:var(--ink); margin-bottom:10px;}
  .recover-modal-desc{font-size:13px; color:var(--ink-soft); line-height:1.6; margin:0 0 18px;}
  .recover-list{overflow-y:auto; flex:1 1 auto; margin:0 0 18px;}
  .recover-item{padding:16px 0; border-top:1px solid var(--line);}
  .recover-item:first-child{border-top:none; padding-top:0;}
  .recover-item-title{font-family:var(--serif); font-size:14px; font-weight:600; color:var(--ink); margin-bottom:2px;}
  .recover-item-when{font-size:11px; color:var(--ink-faint); margin-bottom:8px;}
  .recover-item-memo{font-size:12.5px; color:var(--ink-soft); line-height:1.6; margin-bottom:10px;}
  .recover-item-btn{
    background:none; border:none; padding:0; cursor:pointer;
    font-family:var(--sans); font-size:12px; font-weight:600; color:var(--moss);
  }
  .recover-item.done .recover-item-btn{color:var(--ink-faint); cursor:default;}

  /* ---------- Add bar ---------- */
  /* 책 검색은 서재·담아둔 책의 도구 — 모임 탭에서는 숨겨 탭이 콘텐츠 최상단이 되게 */
  body.tab-social-active .add-bar{display:none;}
  .add-bar{
    position:relative;
    min-height:52px;
    box-sizing:border-box;
    background:var(--card);
    /* 평상시는 잉크 헤어라인 — 모스는 :focus-within(입력 시작)에만 살린다.
       빈 화면의 모스 접점을 줄여 '기록하는 자리'라는 최우선 초점을 살린다(오너 승인 2026-08-17). */
    border:1px solid var(--line);
    border-radius:0;
    --addbar-px: clamp(16px, 11px + 1.4vw, 24px);
    padding:0 var(--addbar-px);
    display:flex;
    align-items:center;
    gap: clamp(12px, 7px + 1.4vw, 20px);
    flex-wrap:wrap;
    margin-bottom:44px;
    transition:border-color var(--dur-1) var(--ease-inout), box-shadow var(--dur-1) var(--ease-inout);
  }
  .add-bar input[type=text]{
    flex:1;
    min-width:200px;
    min-height:50px;
    border:none;
    background:transparent;
    font-size:15px;
    padding:0 2px;
    color:var(--ink);
    outline:none;
  }
  .onboarding-card{
    display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:16px 24px;
    margin:-20px 0 32px; padding:20px 22px;
    border:1px solid var(--line);
    background:var(--card);
  }
  .onboarding-card[hidden], body.demo-mode .onboarding-card{display:none;}
  /* 온보딩 카드는 서재(첫 화면)에서만 — 취향·담아둔 책·모임 탭 위에 얹히면 자리를
     차지하며 어색하다(사용자 지시, 2026-08-01). */
  body:not(.tab-library-active) .onboarding-card{display:none;}
/* 예시 서재 진입 버튼 — 데스크톱에서는 설명 오른쪽 끝에 배치한다. */
#onboarding-demo-btn{
  flex:0 0 auto; max-width:max-content; margin:0 0 0 auto;
    min-height:40px; padding:9px 18px;
    border:1px solid var(--moss); border-radius:var(--radius);
    background:none; color:var(--moss);
    font-family:var(--sans); font-size:11.5px; font-weight:600;
    text-align:center; text-decoration:none; cursor:pointer;
  }
  /* .empty-demo-btn{display:block}가 UA의 [hidden]{display:none}과 특정도가 같아
     author 규칙이 이겨 hidden이 먹지 않는다 — id로 확실히 접는다(add-btn과 같은 관용). */
  #onboarding-demo-btn[hidden]{display:none;}
  /* 설명이 남는 폭을 다 먹는다 — 그래야 두 버튼이 카드 오른쪽에 나란히 붙는다.
     예시 버튼의 margin-left:auto가 여유 공간을 통째로 흡수하는 바람에, 정작 주
     버튼('책 기록하기')은 설명 바로 옆에 붙고 그 오른쪽에 빈 땅이 남았다
     (e2e/journeys.spec.js '…두 버튼은 카드 오른쪽에 나란히 붙는다'가 두 버튼 간격까지 잠근다). */
  .onboarding-copy{flex:1 1 auto; min-width:0;}
  /* 액센트는 위쪽 히어로 눈썹(A READING JOURNAL)에 양보한다 — 폰에서 둘의 계산값이
     색·크기·굵기·자간까지 똑같아, 어느 것이 화면 제목이고 어느 것이 카드 라벨인지
     실눈으로 안 갈렸다(2026-08-13). 낮출 땐 검정이 아니라 웜 그레이로 — 검정이
     모스보다 무겁다(docs/quality-checklist.md §6 무게 사다리). */
  .onboarding-eyebrow{
    margin-bottom:6px; font-family:var(--sans); font-size:9px; font-weight:500;
    letter-spacing:.18em; color:var(--ink-faint); text-transform:uppercase;
  }
  .onboarding-copy h2{
    margin:0; font-family:var(--serif); font-size:16px; font-weight:500;
    line-height:1.5; color:var(--ink); text-wrap:balance;
  }
  .onboarding-copy p{
    margin:5px 0 0; font-family:var(--sans); font-size:11.5px;
    line-height:1.55; color:var(--ink-faint); text-wrap:balance;
  }
  .onboarding-action{display:flex; align-items:center; gap:16px; flex-shrink:0;}
  .onboarding-progress{display:flex; gap:5px;}
  /* display:flex가 UA의 [hidden]{display:none}을 특정도 동률로 이겨 hidden이 안
     먹는다 — 0권에서 진행 막대만 접으려면 명시 규칙이 필요하다(demo-btn과 같은 관용).
     버튼은 0권에서도 항상 보여야 하는 CTA라 이 규칙에서 뺐다. */
  .onboarding-progress[hidden]{display:none;}
  .onboarding-progress span{
    width:18px; height:3px; background:var(--line); transition:background var(--dur-2) var(--ease-inout);
  }
  .onboarding-progress span.filled{background:var(--moss);}
  #onboarding-add-btn{
    min-height:40px; padding:8px 13px; border:1px solid var(--moss);
    border-radius:var(--radius); background:var(--moss); color:var(--paper);
    font-family:var(--sans); font-size:11.5px; font-weight:600; cursor:pointer;
  }
  #onboarding-add-btn[hidden]{display:none;}
  .add-bar input[type=text]::placeholder{color:var(--ink-faint); font-family:var(--sans); font-size:14px;}
  .add-bar:focus-within{
    border-color:var(--moss);
    box-shadow:inset 0 0 0 1px var(--moss);
  }
  .add-bar:has(.add-bar-choices:not([hidden])),
  .add-bar:has(.add-bar-finish:not([hidden])){
    padding-top:14px;
    padding-bottom:14px;
  }
  .add-bar:has(.add-bar-choices:not([hidden])) input[type=text],
  .add-bar:has(.add-bar-finish:not([hidden])) input[type=text]{min-height:36px;}
  .search-results{
    position:absolute;
    z-index:20;
    top:calc(100% + 4px);
    left: var(--addbar-px, 24px);
    right: var(--addbar-px, 24px);
    display:none;
    max-height:360px;
    overflow-y:auto;
    background:var(--card);
    border:1px solid var(--line);
    box-shadow:var(--shadow-card);
  }
  .search-results.open{display:block;}
  .search-result{
    width:100%;
    display:flex;
    gap:12px;
    align-items:center;
    padding:12px;
    border:0;
    border-bottom:1px solid var(--line);
    background:transparent;
    color:var(--ink);
    text-align:left;
    cursor:pointer;
  }
  .search-result:last-child{border-bottom:0;}
  .search-result:focus{background:var(--moss-soft); outline:none;}
  .search-result img{width:42px;height:58px;object-fit:cover;background:var(--paper);}
  /* 제목·메타는 제 줄로 쌓는다 — 둘 다 inline이면 한 줄에 붙어 나오고
     meta의 margin-top(제목→저자 사이 간격)이 죽는다(inline엔 세로 여백이 안 먹는다). */
  .search-result-title{display:block;font-family:var(--serif);font-size:14px;font-weight:600;}
  .search-result-meta{display:block;font-size:11px;color:var(--ink-soft);margin-top:4px;}
  .search-message{padding:14px;font-size:12px;color:var(--ink-soft);}
  /* 결과 목록 끝 '더 보기' — 전폭 조용한 텍스트 액션. 바로 위 결과가 border-bottom을
     가지므로(마지막이 아니게 됨) 헤어라인은 하나만 생긴다. 다음 15개를 이어 붙인다. */
  .search-more{
    width:100%; padding:13px 12px; border:0; background:transparent; cursor:pointer;
    font-family:var(--sans); font-size:12px; letter-spacing:0.02em; color:var(--moss); text-align:center;
  }
  .search-more:hover{color:var(--ink);}
  .search-more:disabled{color:var(--ink-faint); cursor:default;}
  .add-bar-choices{display:flex; gap:8px; flex-wrap:wrap;}
  .add-choice-btn{
    padding:8px 14px;
    border:1px solid var(--line);
    border-radius:var(--radius);
    background:var(--card);
    font-family:var(--sans);
    font-size:13px;
    color:var(--ink-soft);
    cursor:pointer;
  }
  .add-choice-btn:focus-visible{border-color:var(--moss); color:var(--moss); outline:none;}
  /* 읽은 시기·별점·기록하기는 오른쪽 끝에 모인다. flex-end인 이유: 아래 줄로
     내려간 안내가 폭 계산에서는 같은 줄로 세어져 그룹에 gap 한 칸(20px)만큼
     여유가 남는데, 기본값(flex-start)이면 그 여유가 '기록하기' 오른쪽에 붙는다. */
  .add-bar-finish{display:flex; align-items:center; justify-content:flex-end; gap:20px; flex-wrap:wrap;}
  .add-bar-choices[hidden]{display:none;}
  .add-bar-finish[hidden]{display:none;}
  /* 별점 잠금 안내 — 행 아래 제 줄에 온다(min-width:100%). 별점을 고르면 hidden.
     width:0이 핵심이다: 이 안내는 아래 줄로 내려가는데도 .add-bar-finish의
     max-content 폭에는 같은 줄인 것처럼 더해져서, 그룹이 제 내용보다 ~137px
     넓어졌다. 그 폭을 flex:1인 제목 입력창에서 빼앗아 제목이 잘리고 '기록하기'
     오른쪽에 빈 땅이 남았다. width:0으로 폭 계산에서 빠지고, 실제 렌더 폭은
     min-width가 준다(e2e/journeys.spec.js '…기록하기가 오른쪽 끝에 붙는다'가 잠금). */
  .add-finish-hint{flex-basis:100%; width:0; min-width:100%; margin:0; font-family:var(--sans); font-size:12.5px; color:var(--ink-soft);}
  .add-finish-hint[hidden]{display:none;}
  .star-input{display:flex; gap:2px; cursor:pointer; user-select:none;}
  /* 슬라이더로 노출된 별점 입력(lib/stars.js) — 키보드 포커스 때만 링을 보인다. */
  .star-input:focus{outline:none;}
  .star-input:focus-visible{outline:2px solid var(--moss); outline-offset:3px; border-radius:var(--radius);}
  .star-input .star{position:relative; width:22px; height:22px; font-size:22px; line-height:1;}
  .star-input .star .base{color:var(--line); position:absolute; inset:0;}
  .star-input .star .fill{color:var(--gold); position:absolute; inset:0; overflow:hidden; width:0%;}
  .btn-add{
    background:var(--moss);
    color:var(--paper);
    border:1px solid var(--moss);
    padding:10px 22px;
    font-size:13px;
    letter-spacing:0.04em;
    border-radius:var(--radius);
    cursor:pointer;
    font-family:var(--sans);
    font-weight:600;
    transition:opacity var(--dur-1) var(--ease-inout), transform var(--dur-1) var(--ease-out);
  }
  /* 누르면 살짝 들어갔다 나온다 — 눌렀다는 걸 손이 안다(모션 서명). 동작 줄이기면 꺼진다. */
  .btn-add:active:not(:disabled){transform:scale(0.97);}
  .btn-add:disabled{opacity:0.4; cursor:default;}
  .btn-add:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .add-bar input:focus-visible{outline:none;}

  .read-month-input{
    border:none;
    border-bottom:1px solid var(--line);
    background:transparent;
    font-size:clamp(16px, 15px + 0.34vw, 18px);
    color:var(--ink);
    padding:6px 2px;
    outline:none;
  }
  .read-month-input:focus{border-bottom-color:var(--moss);}

  .year-pills{display:flex; gap:8px; flex-wrap:wrap; margin-bottom:12px;}
  /* display:flex가 UA의 [hidden]{display:none}을 이긴다 — 없으면 views/year-shelf.js가
     hidden을 걸어도 빈 알약 줄의 여백만 남는다(scan:render가 잡는다). */
  .year-pills[hidden]{display:none;}
  .year-pill{
    background:transparent;
    border:1px solid var(--line);
    color:var(--ink-soft);
    font-size:12px;
    padding:6px 14px;
    border-radius:var(--radius-pill);
    cursor:pointer;
    font-family:var(--sans);
  }
  .year-pill.active{background:var(--moss); border-color:var(--moss); color:var(--paper);}
  #year-pills{
    gap:14px;
    margin:0 0 0 auto;
  }
  #year-pills .year-pill{
    display:inline-flex;
    align-items:baseline;
    gap:5px;
    min-height:36px;
    padding:4px 2px;
    background:transparent;
    border:0;
    border-radius:0;
    color:var(--ink-soft);
  }
  #year-pills .year-pill.active{
    background:transparent;
    color:var(--moss-deep);
  }
  #year-pills .year-pill-year{
    padding-bottom:2px;
    border-bottom:1px solid transparent;
  }
  #year-pills .year-pill.active .year-pill-year{
    border-bottom-color:var(--moss);
  }
  #year-pills .year-pill-count{
    color:var(--ink-faint);
    font-size:10px;
    font-weight:400;
  }
  .member-space-tabs{display:flex; gap:8px; margin-bottom:18px;}
  .member-space-tab-btn{
    background:transparent;
    border:1px solid var(--line);
    color:var(--ink-soft);
    font-size:12px;
    padding:6px 14px;
    border-radius:var(--radius-pill);
    cursor:pointer;
    font-family:var(--sans);
  }
  /* 3단(멤버 서재)은 가장 깊고 읽기 전용이라 채운 모스가 아니라 모스 아웃라인으로 —
     2단(모임 스페이스, 밑줄)보다 무겁지 않게, 깊어질수록 조용히(오너 승인 2026-08-17). */
  .member-space-tab-btn.active{background:transparent; border-color:var(--moss); color:var(--moss);}


  /* ---------- Tabs ---------- */
  .tabs{
    display:flex;
    gap: clamp(16px, 8px + 2vw, 28px);
    border-bottom:1px solid var(--line);
    margin-bottom: clamp(20px, 8px + 3vw, 32px);
  }
  .tab-btn{
    background:none;
    border:none;
    font-family:var(--sans);
    font-size:14px;
    font-weight:600;
    letter-spacing:0.02em;
    color:var(--ink-faint);
    padding:0 0 14px;
    cursor:pointer;
    border-bottom:2px solid transparent;
    margin-bottom:-1px;
  }
  .tab-btn.active{color:var(--ink); border-bottom-color:var(--moss);}
  .tab-btn:focus-visible{outline:2px solid var(--moss); outline-offset:3px;}
  .tab-icon{display:none;} /* 태블릿+데스크톱 상단 탭에서는 숨김, 폰 하단 탭바에서만 표시 */
  .tab-icon svg{width:20px; height:20px; display:block; stroke:currentColor;}
  .tab-panel{display:none;}
  /* 탭 전환이 뚝 끊기는 컷으로 보이지 않게 — display 전환에도 애니메이션은
     새로 재생되므로 짧은 페이드 하나만 얹는다(슬라이드는 풀스크린 뷰 전용). */
  .tab-panel.active{display:block; animation:tab-fade .18s ease;}
  @keyframes tab-fade{from{opacity:0;} to{opacity:1;}}

  /* ---------- Empty state ---------- */
  .empty-state{
    text-align:center;
    padding:80px 20px;
    color:var(--ink-faint);
    font-family:var(--serif);
    font-size:15px;
    /* 실선 — 2026-08-13 앱 전체 점선→실선 통일에서 빠졌던 잔재를 맞춘다(오너 승인 2026-08-17). */
    border:1px solid var(--line);
    border-radius:var(--radius);
  }
  /* 보조 상태 한 줄(카드 안 빈/로딩/에러) — 탭 대문의 .empty-state 박스가
     아니라, 결과칸 안에 담백하게 얹는 안내. 여기저기 인라인 style로 흩어져 있던
     같은 표현(--ink-faint · 13px)을 한 클래스로 모은다. */
  .state-note{color:var(--ink-faint); font-size:13px; line-height:1.6;}

  /* ---------- Gallery grid / placard cards ---------- */
  /* 검색·정렬 컨트롤 바다. 좌측 정렬이 의도 — 액션 행이 아니다(우아함 헌장 B1/C1). */
  .library-toolbar{
    display:flex;
    align-items:center;
    gap:8px;
    margin-bottom:14px;
  }
  /* display:flex가 [hidden]의 기본 display:none을 이긴다 — 빈 서재에서
     도구를 감추려면(app.js renderLibrary) 여기서 명시해야 한다. */
  .library-toolbar[hidden]{display:none;}
  .library-search-input{
    flex:1 1 auto;
    min-width:0;
    box-sizing:border-box;
    padding:6px 12px;
    border:1px solid var(--line);
    border-radius:var(--radius);
    font-size:12px;
    background:var(--card);
    color:var(--ink);
  }
  .library-search-input::placeholder{color:var(--ink-faint);}
  .library-search-input:focus{outline:none; border-color:var(--moss);}
  .library-search-input:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  /* 툴바 안 정렬 버튼 묶음. 좌측 정렬이 의도다 — 누르는 컨트롤이지 화면의 액션 행이
     아니다(우아함 헌장 B1/C1). */
  .sort-btns{
    display:flex;
    flex:0 0 auto;
    gap:6px;
  }
  .sort-btn{
    background:transparent;
    border:1px solid var(--line);
    color:var(--ink-soft);
    padding:6px 11px;
    border-radius:var(--radius);
    font-family:var(--sans);
    font-size:11px;
    cursor:pointer;
  }
  .sort-btn.active{background:var(--moss);border-color:var(--moss);color:var(--paper);}
  .sort-btn:focus-visible{outline:2px solid var(--moss);outline-offset:2px;}
  /* 서재 한 장(공유 이미지) 진입 — 표지 있는 책이 한 행 이상일 때만 library.js가
     노출한다. 정렬 알약과 같은 결로 두되, 성격이 다른 행동이라 표지 그리드
     아이콘을 하나 달아 무엇을 만드는지 보여준다. */
  .library-share-btn{
    flex:0 0 auto;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    background:transparent;
    border:1px solid var(--line);
    color:var(--ink-soft);
    /* 옆 정렬 알약(.sort-btn)과 같은 세로 높이로 — 실측해 padding 6px 9px에서
       둘 다 29px로 맞았다(아이콘 15px + 위아래 6px + 테두리 2px). */
    padding:6px 9px;
    border-radius:var(--radius);
    cursor:pointer;
  }
  .library-share-btn:hover{color:var(--ink); border-color:var(--moss);}
  /* display를 지정했으므로 [hidden]의 기본 display:none이 덮인다 — 되돌린다
     (모임 버튼들에서 한 번 겪은 자리, 아래 터치 타깃 규칙 옆 메모 참고). */
  .library-share-btn[hidden]{display:none;}
  .library-share-icon{width:15px; height:15px; flex:0 0 auto;}
  /* 아이콘만 — 실제 앱과 똑같이 이름을 뺀다. 예전엔 구경 모드에서만 글자를
     보여 예시 서재가 실제 화면과 달라 보였다(오너 지적, 2026-08-06). 구경 모드는
     '실제 화면 + 시드'라야 하므로 데모 전용 라벨을 두지 않는다. */
  .library-share-label{display:none;}
  .library-share-btn:focus-visible{outline:2px solid var(--moss);outline-offset:2px;}
  /* 정렬 토글·건네기 버튼을 오른쪽에 모은다. 건네기가 숨으면(내 서재) 정렬만
     오른쪽에 남는다 — :has 없이도 안정적. */
  .member-shelf-toolbar{display:flex; justify-content:flex-end; align-items:center; gap:10px; margin-bottom:10px;}
  /* 책 추천하기 — 이 서재 화면의 대표 동작이라 헤더 오른쪽에 앉힌다(내보내기보다
     앞, margin-left:auto로 오른쪽 무리로 민다). 값진 동작이라 모스 아웃라인(commit CTA 문법). */
  .member-give-btn{
    margin-left:auto; align-self:center;
    background:none; border:1px solid var(--moss); color:var(--moss);
    padding:6px 13px; border-radius:var(--radius);
    font-family:var(--sans); font-size:12px; font-weight:600; cursor:pointer;
    white-space:nowrap;
  }
  .member-give-btn:hover{background:var(--moss-bg);}
  .member-give-btn:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}

  /* 건네받은 책 — 내 서재 상단에 얹히는 흔적. 서재 그리드와 구분되도록 여백만
     두고 박스는 씌우지 않는다(카드 안 카드 금지). */
  .handoff-received{margin-bottom:22px;}
  /* '추천받은 책' 제목은 담아둔 책·맞춤 추천과 같은 결 — 앱 표준 .panel-title을 공유해
     세 자락 제목이 어긋나지 않게 한다(2026-08-12, views/handoff-inbox.js). */
  .handoff-received-list{display:flex; flex-direction:column; gap:12px;}
  .handoff-card{
    display:grid; grid-template-columns: clamp(54px, 42px + 3.1vw, 72px) 1fr; gap:16px;
    border:1px solid var(--line); background:var(--card);
    border-radius:var(--radius); padding:16px 18px;
  }
  .handoff-card-body{min-width:0; display:flex; flex-direction:column;}
  .handoff-card-title{font-family:var(--serif); font-size:17px; font-weight:600; line-height:1.3;}
  .handoff-card-author{font-size:12px; color:var(--ink-soft); margin-top:2px;}
  .handoff-card-from{font-size:12px; color:var(--moss); margin-top:8px;}
  .handoff-card-note{
    font-size:13px; line-height:1.7; color:var(--ink); margin-top:6px;
    padding-left:11px; border-left:2px solid var(--moss-soft);
  }
  .handoff-card-actions{display:flex; justify-content:flex-end; gap:8px; margin-top:14px;}
  .handoff-pass{
    background:none; border:1px solid var(--line); color:var(--ink-soft);
    padding:7px 14px; border-radius:var(--radius);
    font-family:var(--sans); font-size:12px; cursor:pointer;
  }
  .handoff-pass:hover{color:var(--ink);}
  .handoff-pass:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}

  /* 모달 닫기는 우측 상단 ✕로 통일한다(서재 한 장 모달과 같은 자리·결). */
  .modal-close-x{
    position:absolute; top:12px; right:12px; width:30px; height:30px;
    display:flex; align-items:center; justify-content:center;
    background:none; border:none; border-radius:var(--radius); color:var(--ink-faint); cursor:pointer;
  }
  .modal-close-x svg{width:18px; height:18px;}
  .modal-close-x:hover{color:var(--ink);}
  .modal-close-x:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}

  /* 추천 팝업 — feedback 모달 결. 앱을 열 때 새 추천을 처음 한 번 알린다. */
  .handoff-gift-modal{max-width:420px; position:relative;}
  /* ✕가 제목을 파고들지 않게 오른쪽 여백을 둔다(두 모달 공통). */
  .handoff-gift-modal .feedback-modal-title,
  .handoff-modal .feedback-modal-title{padding-right:30px;}
  .handoff-gift-list{display:flex; flex-direction:column; gap:12px; margin-top:16px;}
  /* 팝업 안 카드는 카드 안 카드가 되지 않게 테두리를 빼고 여백만 둔다. */
  .handoff-gift-list .handoff-card{border:none; background:none; padding:0;}

  /* 추천하기 피커 모달 — feedback 모달 결을 그대로 쓰되, 목록이 있어 살짝 넓다. */
  .handoff-modal{max-width:440px; position:relative;}
  .handoff-search{
    width:100%; box-sizing:border-box; padding:11px 13px; margin-bottom:12px;
    border:1px solid var(--line); border-radius:var(--radius);
    font-size:14px; color:var(--ink); background:var(--paper);
  }
  .handoff-search::placeholder{color:var(--ink-faint);}
  .handoff-search:focus-visible{outline:2px solid var(--moss); outline-offset:1px;}
  .handoff-pick-list{max-height:46vh; overflow-y:auto; display:flex; flex-direction:column; gap:4px;}
  .handoff-pick-row{
    display:grid; grid-template-columns:38px 1fr; gap:12px; align-items:center;
    width:100%; text-align:left; background:none; border:none; cursor:pointer;
    padding:8px; border-radius:var(--radius);
  }
  .handoff-pick-row:hover{background:var(--moss-bg);}
  .handoff-pick-row:focus-visible{outline:2px solid var(--moss); outline-offset:-2px;}
  .handoff-pick-cover{
    width:38px; height:54px; object-fit:cover;
    border:1px solid var(--line); background:var(--paper); border-radius:var(--radius);
  }
  .handoff-pick-text{min-width:0; display:flex; flex-direction:column;}
  .handoff-pick-title{
    font-family:var(--serif); font-size:15px; color:var(--ink); line-height:1.35;
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
  }
  .handoff-pick-author{font-size:12px; color:var(--ink-soft); margin-top:2px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
  .handoff-pick-empty{padding:24px 8px; text-align:center;}
  .handoff-chosen-card{display:grid; grid-template-columns:38px 1fr; gap:12px; align-items:center; margin-bottom:14px;}
  .handoff-chosen-text{min-width:0; display:flex; flex-direction:column;}
  .handoff-chosen-title{font-family:var(--serif); font-size:16px; color:var(--ink); line-height:1.35;}
  .handoff-chosen-author{font-size:12px; color:var(--ink-soft); margin-top:2px;}
  .handoff-note{min-height:72px;}
  .handoff-modal-status{display:block; min-height:16px; margin:8px 2px 0;}
  /* 멤버 서재 정렬은 내 서재와 같은 .sort-btns/.sort-btn을 그대로 쓴다 —
     따로 스타일을 두지 않아 데스크톱 박스·모바일 보더리스가 저절로 통일된다. */
  /* 카드 높이를 그리드 전체에서 하나로 묶는다. 제목·저자·키워드를 잘라도
     카드가 여전히 들쭉날쭉했던 건(2026-07-23 실측: 270 / 322 / 367px) 글이
     넘쳐서가 아니라 블록이 아예 없어서였다 — 여백의 한 줄이 없는 책엔
     .placard-margins가, 읽는 중인 책엔 키워드·평점 줄이 통째로 빠진다.
     자르기로는 못 메우는 차이라 행 높이를 1fr로 묶어 가장 큰 카드에 맞춘다.
     .placard-foot가 이미 margin-top:auto라, 남는 공간은 제목 아래로 가고
     평점 줄은 카드 바닥에 그대로 붙는다. */
  .gallery-grid{
    display:grid;
    grid-template-columns:repeat(auto-fill, minmax(230px, 1fr));
    grid-auto-rows:1fr;
    gap:16px;
  }
  .gallery-grid > .placard{height:100%;}
  .placard{
    position:relative;
    background:var(--card);
    border:1px solid var(--line);
    border-radius:var(--radius);
    padding:18px 18px 16px;
    /* 표지+서지는 한 줄(.placard-body), 감상·별점·방명록은 그 아래로 촘촘히 쌓인다.
       높이 정책이 반응형이다(오너 2026-08-24): PC(3열 격자)는 카드 높이 통일+별점 바닥
       정렬(한 행의 카드끼리 바닥선이 맞음), 모바일(1열)은 콘텐츠 높이라 슬랙 없이 촘촘
       (§gallery-grid). base에 둔 flex:1·margin-top:auto는 PC에서만 효과가 있고 모바일
       콘텐츠 높이에선 남는 공간이 없어 저절로 무력화된다. 표지를 서지 옆 flex 형제로
       둬서(그리드 행 걸침이 아니라) 짧은 카드에서도 제목·저자·날짜가 안 벌어진다. */
    display:flex;
    flex-direction:column;
    min-height:150px;
    transition:border-color var(--dur-1) var(--ease-inout);
  }
  /* 표지 | 서지(제목·저자·날짜) 한 줄. 표지는 위에 붙고, 서지는 촘촘한 세로 스택. */
  /* No.NNN 줄과 표지 사이에 숨 쉴 여백 — 카드가 촘촘해진 만큼 여기만 살짝 띄워
     우아함을 준다(오너 2026-08-24 "여백 느낌 살게"). */
  .placard-body{display:flex; gap:12px; margin-top:9px;}
  .placard-meta{flex:1; min-width:0; display:flex; flex-direction:column; gap:5px;}
  .placard-cover{
    width:54px;
    height:74px;
    flex:0 0 auto;
    object-fit:cover;
    align-self:flex-start;
    margin:0;
    border:1px solid var(--line);
  }
  .placard.loading{opacity:0.6;}
  .placard.placard-reading{background:var(--moss-bg);}
  /* 종 알림 "한 줄 보기"로 왔을 때 — 어느 카드인지 잠깐 테두리를 밝혀 짚어준다. */
  .placard.placard-cued{animation:placard-cue 1.6s ease-out;}
  @keyframes placard-cue{
    0%,55%{border-color:var(--moss); box-shadow:0 0 0 3px var(--moss-soft);}
    100%{border-color:var(--line); box-shadow:0 0 0 0 transparent;}
  }
  @media (prefers-reduced-motion:reduce){
    .placard.placard-cued{animation:none;}
  }
  .placard-top{
    display:flex;
    justify-content:space-between;
    align-items:flex-start;
    gap:8px;
  }
  .placard-no{
    font-family:var(--sans);
    font-size:10px;
    color:var(--ink-faint);
    letter-spacing:0.05em;
  }
  .placard-del{
    background:none;
    border:none;
    color:var(--ink-faint);
    font-size:13px;
    cursor:pointer;
    transition:color var(--dur-1) var(--ease-inout);
    padding:0;
  }
  /* 장르는 구조 라벨이라 액센트를 쓰지 않는다 — 카드에서 제목보다 위에 있는 유일한
     유채색이라 서재 그리드를 열면 모스 대문자 라벨이 먼저 눈에 들었다. 카드의 최우선
     초점은 세리프 제목이다. 크기·자간·대문자는 그대로 두고 색만 한 단계 아래로
     (2026-08-13). 카드의 모스는 키워드 칩·'다 읽음' 버튼·읽는 중 틴트로 남는다. */
  .placard-tag{
    font-size:10px;
    letter-spacing:0.12em;
    text-transform:uppercase;
    color:var(--ink-faint);
    font-weight:600;
  }
  /* 제목이 길면 줄이 늘어나며 카드가 통째로 커지고, 그리드 특성상 같은 줄에
     선 다른 카드들까지 함께 늘어났다(2026-07-23 피드백). 서재는 책들이 나란히
     선 선반이라 카드 높이가 제목 길이에 따라 들쭉날쭉하면 안 된다 — 두 줄에서
     자르고 말줄임으로 넘긴다. 전체 제목은 눌러서 들어가면 보인다. */
  /* 제목 줄 = [클램프되는 제목 텍스트] + [메모 아이콘]. 아이콘을 클램프 박스 안에 두면
     줄 계산이 틀어져 마지막 줄이 잘렸다(오너 2026-08-24) — 그래서 클램프는 안쪽
     .placard-title-text만 지고, 아이콘은 그 옆 flex 형제로 둔다. */
  .placard-title{
    display:flex; align-items:flex-start; gap:5px;
    /* 잘릴 자리를 미리 잡아둔다 — 한 줄 제목 옆 두 줄 제목과 기준선이 안 어긋나게. */
    min-height:calc(2 * 1.35em);
  }
  .placard-title-text{
    flex:1; min-width:0;
    font-family:var(--serif); font-size:17px; font-weight:600; line-height:1.35; color:var(--ink);
    display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
    max-height:calc(3 * 1.35em);
    text-wrap:balance;
  }
  /* 저자도 같은 이유로 한 줄에서 자른다 — 공저·번역자까지 붙은 긴 저자명이
     제목 다음으로 카드 높이를 흔드는 자리였다. */
  .placard-author{
    font-size:12px;
    color:var(--ink-soft);
    margin-top:2px;
    overflow:hidden;
    text-overflow:ellipsis;
    white-space:nowrap;
    /* flex 안에서 nowrap이 폭을 넓혀 320px에서 넘치던 것 — min-width:0(부모 .placard-meta
       에도)로 폭이 줄고 ellipsis가 대신 자른다. */
    min-width:0;
  }
  .placard-read-at{
    font-size:11px;
    color:var(--ink-faint);
    font-family:var(--sans);
  }
  /* 감상·별점 묶음 — 남는 세로 공간을 먹어(flex:1) 별점 줄을 카드 바닥까지 민다. */
  /* flex:1로 남는 세로 공간을 먹어 별점/상태 줄을 카드 바닥으로 민다(데스크톱 3열 격자
     에서 카드 높이를 통일·정렬). 모바일은 콘텐츠 높이라(§gallery-grid) 남는 공간이 없어
     이 고정이 저절로 무력화된다 — 그래서 모바일은 촘촘, PC는 정렬(오너 2026-08-24). */
  .placard-foot{flex:1; display:flex; flex-direction:column; gap:7px; margin-top:8px;}
  /* 멤버 서재에서 나도 읽은 책 표시 — 카드 결을 해치지 않는 작은 칩 하나 */
  .placard-mine{
    align-self:flex-start;
    font-size:10px;
    color:var(--moss-ink);
    background:var(--moss-soft);
    padding:3px 8px;
    border-radius:var(--radius-pill);
    font-family:var(--sans);
  }
  /* 제목·저자를 자르고 나면 카드 높이를 흔드는 건 칩 줄 수뿐이다. 칩이 세
     줄까지 가는 책은 드물어서(사용자 판단, 2026-07-23) 두 줄에서 자른다.
     칩 한 줄 = 글자(10px × 1.2) + 위아래 여백 3px씩, 줄 사이 간격 4px. */
  /* 칩은 한 줄만 — 넘치면 잘린다. 2줄까지 허용하던 때는 키워드 많은 책 한 권이 그 줄
     카드를 통째로 늘려 서재가 길쭉해 보였다(오너 2026-08-23, grid-auto-rows:1fr). */
  .placard-keywords{
    display:flex; flex-wrap:wrap; gap:4px;
    max-height:calc(10px * 1.2 + 6px);
    overflow:hidden;
  }
  .placard-keyword-chip{
    font-size:10px;
    line-height:1.2; /* 위 max-height 계산의 기준 — 같이 바꿔야 한다 */
    color:var(--moss-ink);
    background:var(--moss-soft);
    padding:3px 8px;
    border-radius:var(--radius-pill);
    font-family:var(--sans);
  }
  .placard-bottom{
    /* 별점 줄을 카드 바닥으로(데스크톱 격자 정렬). 모바일 콘텐츠 높이에선 무력화. */
    margin-top:auto;
    display:flex;
    align-items:center;
    flex-wrap:wrap;
    gap:8px;
    border-top:1px solid var(--line);
    padding-top:10px;
  }
  .placard-rating{display:inline-flex; align-items:center; gap:4px;}
  .stars-display{display:flex; gap:1px; cursor:pointer;}
  .placard-readonly .stars-display{cursor:default;}
  .stars-display .star{position:relative; width:14px; height:14px; font-size:14px; line-height:1;}
  .stars-display .base{position:absolute; inset:0; color:var(--line);}
  .stars-display .fill{position:absolute; inset:0; overflow:hidden; color:var(--gold);}
  .placard-rating-num{font-size:11px; color:var(--ink-faint); font-family:var(--sans);}
  /* 실선 — 저장소의 --line 구분선은 solid가 다수이고, 점선은 이 카드와 rl-detail만
     쓰던 소수 예외였다(2026-08-13). 같은 카드 안에서 문법을 하나로. */
  .placard-reading-row, .placard-finish-form{
    margin-top:auto;
    border-top:1px solid var(--line);
    padding-top:10px;
  }
  .placard-reading-row{display:flex; align-items:center; flex-wrap:wrap; gap:8px;}
  /* 점은 뺐다(2026-08-13) — 카드 전체가 이미 moss 틴트(.placard-reading)이고 그 옆에
     "읽는 중" 글자가 있어, 같은 사실을 세 겹으로 말하고 있었다. 점은 옆 글자를 그림으로
     다시 그린 것이라 지워도 정보·기능이 하나도 안 사라진다. 틴트는 남긴다 — 그리드를
     훑을 때 값이 있다. */
  .reading-badge{
    display:inline-flex; align-items:center;
    font-size:11px; color:var(--ink-soft); font-family:var(--sans);
  }
  /* 덮어둔 책 배지 — 조용한 상태 표시. 바랜 바탕(--paper-2) 위라 --ink-faint는 대비
     미달(a11y)이라 --ink-soft로 한 톤 올린다. 활성(읽는 중)과는 바탕색으로 갈린다. */
  .stopped-badge{
    display:inline-flex; align-items:center;
    font-size:11px; color:var(--ink-soft); font-family:var(--sans);
  }
  /* 덮어두기 — '다 읽음'(채운 모스=주 동작) 옆 보조 동작이라 외곽선으로 갈린다(오너
     2026-08-24: 채움 둘은 둘 다 주 동작처럼 읽힌다). 크기·모양은 같아 높이가 맞는다. */
  .placard-stop-btn{
    background:none; border:1px solid var(--moss); border-radius:var(--radius);
    padding:4px 10px; font-size:11px; font-family:var(--sans); color:var(--moss-ink); cursor:pointer;
  }
  .placard-stop-btn:hover{background:var(--moss-soft);}
  /* 기록하기 — 상태 전환(다 읽음·덮어두기=모스)이 아니라 '기록' 동작이라 중립 라인색으로
     갈린다. 크기·모양은 상태 버튼과 같아 한 줄에서 높이가 맞는다(모바일 터치 padding도
     아래 공통 규칙에 함께 넣는다). 완독 카드에선 별점 줄 오른쪽 끝으로 민다. */
  .placard-memo-btn{
    background:none; border:1px solid var(--line); border-radius:var(--radius);
    padding:4px 10px; font-size:11px; font-family:var(--sans); color:var(--ink-soft); cursor:pointer;
  }
  .placard-memo-btn:hover{border-color:var(--moss); color:var(--moss-ink);}
  .placard-bottom .placard-memo-btn{margin-left:auto;}
  /* 다시 읽기 — 옆 '기록하기'와 통일한다(오너 2026-08-24): 흐린 잉크 중립 박스. 덮어둔
     책은 쉬어가는 상태라 두 동작(다시 읽기·기록하기)을 똑같이 조용하게 둔다. */
  .placard-resume-btn{
    background:none; border:1px solid var(--line); border-radius:var(--radius);
    padding:4px 10px; font-size:11px; font-family:var(--sans); color:var(--ink-soft); cursor:pointer;
  }
  .placard-resume-btn:hover{border-color:var(--moss); color:var(--moss-ink);}
  /* 덮어두기 월 폼 — '다 읽음' 완료 폼(.placard-finish-form)과 같은 골격이라 그 스타일을
     그대로 물려받고, 달 인풋만 폭을 채운다(별점 줄이 없어 한 줄이 비므로). */
  .placard-shelve-form .placard-shelve-month{width:100%; text-align:left; padding:6px 10px;}
  .placard-shelve-confirm{background:var(--moss); color:var(--paper); border:1px solid var(--moss);
    font-size:11px; font-family:var(--sans); padding:4px 10px; border-radius:var(--radius); cursor:pointer;}
  .placard-shelve-cancel{background:none; border:1px solid var(--line); color:var(--ink-soft);
    font-size:11px; font-family:var(--sans); padding:4px 10px; border-radius:var(--radius); cursor:pointer;}
  /* 메모 있으면 제목 아래 첫 줄 미리보기 — 그 줄이 곧 진입점. 세리프 한 줄, 왼쪽 모스 바.
     색은 --ink(내 문장 = 내용이라 또렷하게, 바랜 덮어둔 책 바탕에서도 대비 통과). 선택자를
     .placard-foot로 특정해 뒤에 오는 .placard-review-btn(+모바일 변형)에 안 덮이게 한다. */
  .placard-foot .placard-memo-line{
    display:block; width:100%; text-align:left; margin-top:2px;
    background:none; border:none; border-left:2px solid var(--moss-soft); padding:0 0 0 10px; cursor:pointer;
    font-family:var(--serif); font-size:13px; line-height:1.5; color:var(--ink);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  .placard-memo-line:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  /* 덮어둔 책 — 완독과 날짜로 섞여 꽂히므로 색으로 구분한다(오너 2026-08-23). 바랜 바탕 +
     제목 톤 낮춤으로 "접어둠"이 은은히 드러난다(읽는 중의 옅은 모스와도 갈린다). */
  .placard-shelved{background:var(--paper-2);}
  .placard-shelved .placard-title-text{color:var(--ink-soft);}
  /* 바랜 바탕(--paper-2)에선 --ink-faint 메타가 대비 미달이라(a11y), 한 톤 올린다(--ink-soft).
     읽는 중(--moss-bg)은 통과하지만 이 바탕은 살짝 더 진해 경계에 걸린다. */
  .placard-shelved .placard-no,
  .placard-shelved .placard-tag,
  .placard-shelved .placard-read-at{color:var(--ink-soft);}
  .placard-finish-btn{
    background:var(--moss); border:1px solid var(--moss); border-radius:var(--radius);
    padding:4px 10px; font-size:11px; font-family:var(--sans); color:var(--paper); cursor:pointer;
  }
  .placard-finish-form{display:flex; flex-direction:column; gap:10px;}
  /* 별점과 날짜를 한 줄에 — 별점은 왼쪽, 날짜는 오른쪽 끝. 좁으면 날짜가 아랫줄로. */
  .placard-finish-top{display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;}
  /* 별점 오른쪽 같은 줄 — 값 오른쪽 정렬. 날짜 폭은 브라우저마다 다른 month
     인풋 기본 폭에 맡기지 않고 명시로 못 박는다(iOS·크로뮴 편차 제거). 별점은
     모바일에서 5×32 + gap → ~176px, 여기에 행 gap 8px를 빼면 390 카드 안쪽(~323px)
     에 날짜가 ~139px까지 한 줄로 들어간다 — 그 상한 안에서 136px로 넉넉히 키운다
     (이전 패딩만으로 넓히던 것보다 확연히 크다). 좁은 360·320에선 flex-shrink로
     줄고, 그래도 모자라면 아랫줄로 접힌다. 이 136px는 모바일 기준 — 데스크톱(≥640)은
     서재가 3열이라 카드가 더 좁고 별점도 22px라, 아래 min-width:640 블록에서 폭·글자를
     따로 잡아 별점 옆에 붙인다("웹만 모바일과 다르게 보여" 대응, 2026-08-08). */
  .placard-finish-top .placard-finish-stars{flex:0 0 auto;}
  /* 날짜 글자는 15px — 136px 상자 안에서 16px은 너무 빽빽했다(오너 2026-08-08).
     데스크톱(≥640)은 아래 블록에서 14px로 더 줄인다. */
  .placard-finish-top .placard-finish-month{flex:0 1 auto; width:136px; padding-left:10px; padding-right:6px; text-align:right; font-size:15px;}
  .placard-finish-actions{display:flex; gap:8px; justify-content:flex-end;}
  /* 완료/취소는 '다 읽음'(.placard-finish-btn)과 같은 크기로 둔다 — 한 카드의
     다른 상태에 뜨는 형제 버튼이라 크기가 어긋나면 눈에 띈다(오너 2026-08-09). */
  .placard-finish-confirm, .placard-finish-cancel{
    font-size:11px; font-family:var(--sans); padding:4px 10px; border-radius:var(--radius); cursor:pointer;
  }
  .placard-finish-confirm{background:var(--moss); color:var(--paper); border:1px solid var(--moss);}
  .placard-finish-cancel{background:none; border:1px solid var(--line); color:var(--ink-soft);}
  /* 삭제 ✕를 절대배치해 상단 줄 높이에서 뺀다 — 모바일에서 ✕가 44px 터치 타깃이라
     'No.NNN' 아래에 그만큼 빈 공간이 생겼다(오너 2026-08-24 "104랑 표지 사이 싹 지워").
     절대배치면 44px 히트영역은 유지하되 줄 높이는 No 텍스트만큼으로 준다. */
  .placard-top-actions{position:absolute; top:18px; right:18px; display:flex; align-items:center; gap:6px; z-index:1;}
  /* 메모 아이콘(+ 읽는 중의 '다 읽음' 버튼 / 다 읽음의 평점)은 상태 줄 오른쪽 끝에
     인라인으로 둔다. margin-left:auto로 항상 우측 정렬 — 좁은 2열에서 자리가
     모자라면 통째로 아랫줄로 내려가되(부모의 flex-wrap) 오른쪽 정렬은 유지된다. */
  .placard-foot-actions{display:flex; align-items:center; gap:8px; margin-left:auto;}
  /* 메모 진입점 — 테두리 없는 맨 아이콘으로 둔다. 예전엔 '다 읽음'(채운 pill)
     옆이라 외곽선 pill로 버튼임을 드러냈지만, 흰 카드 위 아이콘에 두른 네모가
     '박스 안 박스'로 지저분해 걷어냈다(오너 결정) — 앱 전체 아이콘 버튼과 결을
     맞추고, 높이도 '다 읽음' pill과 저절로 어긋나지 않는다. */
  .placard-review-btn{
    background:none;
    border:none;
    color:var(--ink-soft);
    font-size:13px;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    cursor:pointer;
    padding:4px 9px;
    transition:color var(--dur-1) var(--ease-inout);
  }
  /* 메모가 있으면 아이콘만 moss로 — 배경·테두리는 없다(박스 안 박스 방지). */
  .placard-review-btn.has-review{color:var(--moss);}

  /* ---------- Review writer screen ---------- */
  /* 전체화면 뷰(리뷰작성/설정/모임스페이스/멤버서재/연말서재)는 메인 화면 위에
     얹히는 고정 오버레이. 밑 화면을 없애지 않고 그대로 둔 채 오른쪽에서 밀려
     들어오고, 닫힐 때는 오른쪽으로 밀려 나가며 밑 화면이 드러난다 — 스크롤
     위치도 건드리지 않으므로 돌아왔을 때 보던 자리가 유지된다.
     좌우 패딩의 max()는 .app처럼 콘텐츠를 920px 컬럼으로 가운데 정렬하는 역할.
     z-index는 탭바(50)·알림 시트(60) 위, 확인/기념 모달(100) 아래. */
  #review-writer-view, #settings-view, #group-space-view, #member-space-view, #year-shelf-view, #chat-view{
    position:fixed;
    inset:0;
    z-index:70;
    overflow-y:auto;
    overscroll-behavior-y:contain;
    background:var(--paper);
    touch-action:pan-y;
    padding:
      calc(clamp(36px, 23px + 3.4vw, 56px) + env(safe-area-inset-top))
      max(calc(clamp(20px, 15px + 1.4vw, 28px) + env(safe-area-inset-right)), calc((100% - 920px)/2 + 28px))
      calc(clamp(40px, 32px + 2vw, 64px) + env(safe-area-inset-bottom))
      max(calc(clamp(20px, 15px + 1.4vw, 28px) + env(safe-area-inset-left)), calc((100% - 920px)/2 + 28px));
    box-sizing:border-box;
  }

  /* 진입·이탈 슬라이드 트랜지션. .tab-panel의 display:none<->block 전환은
     애니메이션이 불가능해서, JS가 .active와 동시에 .entering(오프스크린 상태)을
     걸고 다음 프레임에 .entering만 떼어내는 방식으로 실제 트랜지션을 발생시킨다
     — openFullScreenView/closeFullScreenView 참고. 스와이프 뒤로가기(wireSwipeBack)는
     인라인 transform으로 손가락을 따라가다가 화면 밖까지 밀어내며 닫는다.
     102%는 왼쪽 그림자까지 완전히 화면 밖으로 내보내기 위한 여유. */
  #review-writer-view.active, #settings-view.active,
  #group-space-view.active, #member-space-view.active, #year-shelf-view.active, #chat-view.active{
    transform:translateX(0);
    transition:transform var(--dur-3) cubic-bezier(.32,.72,.33,1);
    box-shadow:var(--shadow-drawer);
  }
  #review-writer-view.entering, #settings-view.entering,
  #group-space-view.entering, #member-space-view.entering, #year-shelf-view.entering, #chat-view.entering{
    transform:translateX(102%);
  }
  @media (prefers-reduced-motion:reduce){
    #review-writer-view.active, #settings-view.active,
    #group-space-view.active, #member-space-view.active, #year-shelf-view.active, #chat-view.active{
      transition:none;
    }
    .tab-panel.active{animation:none;}
    .confirm-modal, .milestone-modal, .notif-panel, #app-toast{transition:none;}
    /* 움직임을 줄인 환경에서도 "대기 중"은 보여야 한다 — 애니메이션만 끄고
       점은 은은하게 남긴다. */
    .chat-pending span{animation:none; opacity:0.55;}
    .group-compare-loading{animation:none; opacity:0.7;}
    .taste-report-refresh.spinning{animation:none; opacity:0.55;}
  }

  /* ---------- 대화 뷰 (딥다이브·독후감 다듬기 공용) ---------- */
  /* 다른 전체화면 뷰는 본문이 문서처럼 흐르지만, 대화는 로그가 남은 높이를
     채우고 컴포저가 바닥에 붙는 앱 결이라 flex 컬럼으로 오버라이드한다. */
  #chat-view.active{display:flex; flex-direction:column;}
  /* flex 컬럼이 자식을 stretch시켜 뒤로가기 버튼 텍스트가 가운데로 몰렸다 —
     다른 뷰들처럼 좌측 상단에 붙인다(2026-07-22 피드백). */
  #chat-view .review-writer-back{align-self:flex-start;}
  /* '새로 시작'은 취향 리포트·추천과 같은 ↻ 아이콘 결로 통일하고, 제목 줄
     오른쪽에 붙인다(패널 헤더와 같은 자리) — 손이 자주 가는 자리가 아니어야 한다. */
  .chat-head{display:flex; align-items:flex-start; justify-content:space-between; gap:12px;}
  .chat-head-titles{min-width:0;}
  .chat-log{
    flex:1 1 auto; overflow-y:auto; overscroll-behavior-y:contain;
    display:flex; flex-direction:column; gap:14px;
    /* 좌우 패딩 0 — 컴포저(입력 상자)가 컬럼 전폭이라, 대화 로그·문서형 버블의
       가장자리를 입력창 너비에 딱 맞춘다(2026-07-22 피드백). */
    padding:20px 0 16px;
  }
  .chat-bubble{
    max-width:85%; box-sizing:border-box;
    padding:12px 16px; border-radius:var(--radius);
    font-family:var(--serif); font-size:14px; line-height:1.85;
    white-space:pre-wrap; overflow-wrap:anywhere;
  }
  /* AI 버블은 전폭 — 긴 관찰 문단은 "말"보다 "글"이고, 85% 말풍선은 오른쪽
     끝선이 컴포저와 어긋나 보였다(2026-07-22 실사용 리포트). 대화의 방향성은
     오른쪽 정렬 유지하는 user 버블(85%)이 담당한다. */
  .chat-bubble--ai{align-self:stretch; max-width:none; background:var(--card); border:1px solid var(--line); color:var(--ink);}
  .chat-bubble--user{align-self:flex-end; background:var(--moss-soft); color:var(--ink);}
  /* 문서형 버블 — AI 버블이 전폭이 되면서 사실상 동의어가 됐지만,
     "글(독후감 전문)" 의미 표시로 남겨둔다. */
  .chat-bubble--doc{max-width:none; align-self:stretch;}
  /* 답변 대기 — 버블 껍데기 없이 점 세 개만. 껍데기가 있으면 빈 말풍선이
     도착한 것처럼 보여서 "쓰는 중"으로 안 읽혔다(2026-07-23 피드백).
     점은 원 대신 각진 사각 — 둥근 모서리를 반려한 에디토리얼 결을 따른다. */
  .chat-pending{
    align-self:flex-start; display:flex; gap:6px; align-items:center;
    padding:6px 2px;
  }
  .chat-pending span{
    width:5px; height:5px; border-radius:var(--radius); background:var(--sage);
    animation:chat-dot 1.3s ease-in-out infinite;
  }
  .chat-pending span:nth-child(2){animation-delay:0.16s;}
  .chat-pending span:nth-child(3){animation-delay:0.32s;}
  @keyframes chat-dot{
    0%,65%,100%{opacity:0.28; transform:translateY(0);}
    30%{opacity:1; transform:translateY(-3px);}
  }
  /* 어시스턴트 버블 안의 동작(다듬기 확정 등) — settings-link-btn 결 재사용,
     본문과 가는 룰로만 구분. 문서를 읽고 내리는 결정이라 문서 끝 오른쪽,
     시선이 끝나는 자리에 둔다 */
  /* 자리는 그대로 문서 끝 오른쪽(c1a2fa6) — 글을 다 읽고 내리는 결정이라
     시선이 끝나는 자리에 둔다. 다만 모양을 고쳤다(2026-07-23):
     · 가는 룰을 뺐다 — AI 버블이 이미 같은 색 테두리를 두르고 있어서
       그 안에 또 선을 그으면 박스 안에 박스가 된다.
     · width:100%를 뺐다 — 오른쪽 끝 작은 링크처럼 보이는데 실제 클릭
       영역은 한 줄 전체였다. 보이는 것과 눌리는 것이 어긋나 있었다.
     · 흐린 밑줄 링크(settings-link-btn)에서 버튼 결로 올렸다(메모·독후감
       액션 줄과 같은 언어). 색 위계: 컴포저의 '보내기'는 대화를 잇는 hero라
       채운 모스, 버전 적용은 편집을 갈무리하는 커밋이라 저장과 같은 모스
       아웃라인(accent) — hero=채움 / commit=아웃라인 문법(app.js에서 부여). */
  .chat-bubble-action{
    display:block; margin:14px 0 0 auto;
  }
  .chat-error{
    display:flex; align-items:center; gap:12px; flex-wrap:wrap;
    padding:10px 0; font-size:12px; color:var(--ink-soft); font-family:var(--sans);
  }
  .chat-error[hidden]{display:none;} /* display:flex가 hidden 속성을 이기지 않게 */
  /* 컴포저는 구분선(border-top) 대신 그 자체가 하나의 입력 상자다 —
     입력창과 보내기 버튼을 한 박스로 묶어 대화창의 결을 만든다
     (2026-07-22 피드백: 구분선 제거, 더 대화창답게). 클로드 채팅창처럼
     보내기 ↑ 버튼을 입력칸 오른쪽 아래에 둔다(2026-07-23 피드백). */
  .chat-composer{
    display:flex; align-items:flex-end; gap:8px;
    border:1px solid var(--line); background:var(--card); border-radius:var(--radius);
    padding:2px 8px 2px 14px; margin-top:12px;
  }
  .chat-composer:focus-within{border-color:var(--moss);}
  #chat-input{
    display:block; flex:1 1 auto; min-width:0; resize:none; box-sizing:border-box;
    min-height:38px; max-height:132px;
    border:none; background:transparent;
    padding:9px 0; font-size:16px; line-height:1.5; color:var(--ink);
  }
  #chat-input:focus{outline:none;}
  /* 채운 모스 동그라미 — 대화의 주 액션임을 색으로 보인다. 입력이 한 줄이든
     여러 줄이든 늘 오른쪽 아래에 붙어 있게 align-self:flex-end. */
  .chat-send-btn{
    flex:0 0 auto; align-self:flex-end; margin-bottom:5px;
    display:inline-flex; align-items:center; justify-content:center;
    width:32px; height:32px; border:none; border-radius:50%; cursor:pointer;
    background:var(--moss); color:var(--paper); font-size:18px; line-height:1;
    transition:background var(--dur-1) var(--ease-inout), opacity var(--dur-1) var(--ease-inout);
  }
  .chat-send-btn:hover{background:var(--moss-deep);}
  .chat-send-btn:disabled{opacity:.4; cursor:default;}
  /* ---------- 토스트 (순간 신호 전용, app.js showToast) ---------- */
  #app-toast{
    position:fixed; left:50%; bottom:calc(24px + env(safe-area-inset-bottom));
    transform:translate(-50%, 8px); z-index:70;
    display:flex; align-items:center; gap:14px;
    /* 모바일에서 폭이 좁아 문구가 어절 중간에서 잘려 어색했다(오너 지적,
       2026-08-06). 좌우 여백을 40→24px로 줄여 폭을 넓히고, keep-all로 낱말이
       쪼개지지 않게, text-wrap:pretty로 마지막 줄에 한 낱말만 남는 걸 막는다. */
    max-width:min(440px, calc(100vw - 24px)); box-sizing:border-box;
    background:var(--ink); color:var(--paper);
    font-family:var(--sans); font-size:12.5px; line-height:1.5;
    text-wrap:pretty;
    padding:11px 16px; border-radius:var(--radius);
    box-shadow:var(--shadow-lg);
    opacity:0; pointer-events:none;
    transition:opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
  }
  #app-toast.show{opacity:1; transform:translate(-50%, 0); pointer-events:auto;}
  #app-toast button{
    flex:0 0 auto; background:none; border:none; padding:0; cursor:pointer;
    color:var(--gold); font-family:var(--sans); font-size:12.5px; font-weight:600;
  }
  .review-writer-back{
    background:none; border:none; color:var(--ink-soft); font-size:13px;
    font-family:var(--sans); cursor:pointer; padding:0; margin-bottom:20px;
  }
  .review-writer-head{margin-bottom:20px;}
  .review-writer-title{font-family:var(--serif); font-size:22px; font-weight:600;}
  .review-writer-author{font-size:13px; color:var(--ink-soft); margin-top:2px;}
  /* font-weight:500 — 형제 섹션 라벨 셋(.passages-label·.review-ai-label·
     .related-books-label)이 전부 500인데 이것만 <label> 기본값 400으로 떨어져,
     하필 이 화면의 1순위 블록이 형제들보다 가볍게 나갔다(2026-08-13). */
  .review-writer-label{display:block; font-size:12px; font-weight:500; color:var(--ink-soft); margin-bottom:6px; font-family:var(--sans);}
  /* 메모는 자동 저장 — 저장 버튼 대신 라벨 줄 오른쪽에 '저장됨'만 조용히 얹는다
     (라벨+우측 상태, review-ai-head와 같은 결). 빈 상태에선 그냥 '메모' 한 줄. */
  .review-writer-memo-head{display:flex; align-items:baseline; justify-content:space-between; gap:12px; margin-bottom:10px;}
  .review-writer-memo-head .review-writer-label{margin-bottom:0;}
  /* (독후감 화면의 키워드란은 사라졌지만 설정 화면 입력들이 이 클래스를
     재사용 중) radius 8px는 각진 2px 브랜딩 확정 전의 유산이라 통일. */
  .review-keyword-input{
    width:100%; box-sizing:border-box; padding:10px 12px;
    border:1px solid var(--line); border-radius:var(--radius);
    font-size:13px; margin-bottom:14px;
    background:var(--card); color:var(--ink);
    resize:none; overflow:hidden; line-height:1.5;
  }
  .review-keyword-input::placeholder{color:var(--ink-faint);}
  .review-writer-textarea{
    width:100%; box-sizing:border-box; min-height:280px; padding:16px;
    border:1px solid var(--line); border-radius:var(--radius);
    font-size:14px; line-height:1.8; color:var(--ink);
    background:var(--card); resize:vertical;
  }
  .review-writer-textarea::placeholder{color:var(--ink-faint); font-family:var(--sans);}
  /* 이 화면의 hero — 메모 아래 전폭 채운 모스로 '메모 → 생성'을 직결한다
     (btn-add 채움 위에 전폭·세로 여유만 얹음, login-btn과 같은 hero 결). */
  .review-generate-hero{display:block; width:100%; padding:12px; margin-top:12px;}
  /* #갈무리 마커 도구 줄 — 칩(제 줄 홀로 서는 표식 삽입) + 정의/배지. 칩은 본문에
     들어갈 마커와 같은 결(아웃라인 모스 알약, 11.5px)이라 어디서든 같은 크기로 읽힌다.
     정의는 조용한 세리프(초대 한 줄과 같은 결). */
  .review-galmuri{display:flex; align-items:flex-start; gap:10px; margin-top:12px;}
  .review-galmuri[hidden]{display:none;} /* display:flex가 UA [hidden]을 덮어 명시 */
  .review-galmuri-chip{
    display:inline-flex; align-items:center; flex:none; cursor:pointer;
    padding:2px 9px; border:1px solid var(--moss); color:var(--moss-ink);
    background:none; border-radius:var(--radius-pill);
    font-family:var(--sans); font-size:11.5px; font-weight:600; line-height:1.5;
  }
  .review-galmuri-chip:hover{background:var(--moss-bg);}
  .review-galmuri-chip:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .review-galmuri-hint{flex:1; min-width:0; font-family:var(--serif); font-size:12px; color:var(--ink-faint); line-height:1.65;}
  /* 인식된 갈무리 미리보기 — 완성본으로 잡힌 부분을 가는 모스 괘선으로 조용히 되비춘다
     (앱의 인용 blockquote 어휘). 편집칸의 되풀이가 아니라 '경계가 맞나' 확인용이라
     본문은 한 단계 죽여(ink-soft) 원본보다 뒤로 물린다. */
  .review-galmuri-preview{margin-top:16px;}
  .review-galmuri-preview[hidden]{display:none;}
  /* 접기 라벨(summary) — 기본 마커 대신 도는 셰브런, 조용한 모스 산세리프 */
  summary.review-galmuri-preview-label{cursor:pointer; list-style:none; display:inline-flex; align-items:center; gap:5px; font-family:var(--sans); font-size:11px; font-weight:600; color:var(--moss-ink); letter-spacing:0.03em;}
  summary.review-galmuri-preview-label::-webkit-details-marker{display:none;}
  summary.review-galmuri-preview-label::before{content:"▸"; font-size:9px;}
  .review-galmuri-preview[open] summary.review-galmuri-preview-label::before{content:"▾";}
  summary.review-galmuri-preview-label:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .review-galmuri-preview-body{border-left:2px solid var(--moss-soft); padding-left:14px; margin-top:8px; font-family:var(--serif); font-size:13px; line-height:1.85; color:var(--ink-soft); white-space:pre-wrap;}
  /* 키 없는 계정의 발견 티저 — 취향 탭 초대(.ai-invite)와 같은 조용한 세리프 결.
     hero가 숨은 자리에 큰 버튼 대신 한 줄로. JS가 '의도 시점'에만 hidden을 뗀다. */
  .review-ai-invite{
    display:flex; align-items:baseline; gap:12px; flex-wrap:wrap;
    font-family:var(--serif); font-size:13px; color:var(--ink-faint); line-height:1.8;
    margin-top:14px;
  }
  .review-ai-invite[hidden]{display:none;}
  .review-ai-invite-txt{flex:1; text-wrap:balance;}
  .review-ai-invite-x{
    flex:none; background:none; border:none; padding:2px 4px;
    color:var(--ink-faint); font-size:15px; line-height:1; cursor:pointer; opacity:0.6;
  }
  .review-ai-invite-x:hover{opacity:1;}
  .review-ai-invite-x:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  /* hero 버튼 곁 무료 체험 신호 — status와 같은 조용한 결(12px·ink-faint), 가운데로. */
  .review-trial-note{margin:8px 0 0; text-align:center; font-size:12px; color:var(--ink-faint);}
  .review-writer-status{font-size:12px; color:var(--ink-faint);}
  .passages-section{border-top:1px solid var(--line); margin-top:28px; padding-top:18px;}
  /* 라벨 아래 간격은 이 화면 네 블록이 10px로 같다 — 전엔 6/12/10/10으로 갈려 있었고
     그룹 경계라는 이유가 코드에도 문서에도 없었다(2026-08-13). */
  .passages-head{display:flex; align-items:baseline; justify-content:space-between; gap:12px; margin-bottom:10px;}
  .passages-label{font-family:var(--sans); font-size:12px; font-weight:500; color:var(--ink-soft);}
  .passages-status{font-family:var(--sans); font-size:12px; color:var(--ink-faint); text-align:right;}
  .passage-row{display:flex; align-items:flex-start; gap:10px; padding-bottom:22px;}
  .passage-body{flex:1; min-width:0;}
  /* 공유 토글 — 문장 옆 작은 링크 아이콘 하나. 텍스트·박스 없이 색만: 꺼짐=옅은 회색,
     켜짐(공유 중)=모스(아이콘은 빗금 링크). 기존 '메모로 옮기기'와 같은 조용한 결. */
  .passage-share{flex:0 0 auto; width:26px; height:26px; display:inline-flex; align-items:center; justify-content:center; margin-top:3px; padding:0; background:none; border:none; border-radius:50%; color:var(--ink-faint); cursor:pointer;}
  .passage-share svg{width:16px; height:16px;}
  .passage-share:hover,.passage-share:focus-visible{color:var(--moss);}
  .passage-share.on{color:var(--moss);}
  .passage-share:disabled{opacity:.5; cursor:default;}
  /* 행 사이는 헤어라인이 아니라 여백으로 나눈다 — 행마다 가로선을 그으면 각 행의
     좌측 인용 괘선과 겹쳐 한 행이 두 방향으로 구획돼 표처럼 보인다('우리 이야기'
     탭이 같은 이유로 헤어라인을 걷어낸 것과 같은 결, style.css의 member 목록). 목록
     끝은 padding을 비워 바로 밑 '메모로 옮기기'와 붙지 않게 닫는다. */
  .passage-row:last-child{padding-bottom:2px;}
  .passage-row blockquote{margin:0; padding-left:14px; border-left:2px solid var(--moss-soft); font-family:var(--serif); font-size:15px; line-height:1.8; color:var(--ink); white-space:pre-wrap;}
  .passage-row p{margin:10px 0 0 16px; font-family:var(--serif); font-size:13px; line-height:1.75; color:var(--ink-soft); white-space:pre-wrap;}
  /* 새로 담는 길은 없다(메모 한 칸으로 합침, 2026-08-04) — 남은 건 옮기기 하나뿐이라
     조용한 텍스트 동작으로 둔다. 목록·블록이 통째로 사라질 자리라 장식하지 않는다. */
  .passage-merge-btn{display:block; margin-top:13px; background:none; border:none; padding:3px 0; color:var(--moss); font-family:var(--sans); font-size:12px; cursor:pointer;}
  .passage-merge-btn:hover,.passage-merge-btn:active{color:var(--ink);}
  .passage-merge-btn[hidden],.passages-section[hidden]{display:none;}
  .passage-merge-btn:disabled{color:var(--ink-faint); cursor:default;}
  .btn-add--ghost{background:none; color:var(--ink); border:1px solid var(--line);}
  /* 이 화면의 주 액션(저장)은 강조하되, 채운 잉크 블록은 종이·세리프 결을
     해쳐서 쓰지 않는다 — 완독 표식·모임 선택처럼 "채움 말고 모스 테두리·
     글자"로 든다(add-choice-btn[finished]·inapp-notice-btn과 같은 결). */
  .btn-add--accent{background:none; color:var(--moss); border:1px solid var(--moss); font-weight:700;}
  /* 생성된 독후감 — 취향 리포트와 같은 결(조용한 모스색 산세리프 라벨 +
     세리프 본문)로 메모 아래에 얹는다. 평소엔 완성본으로 읽히고,
     "직접 고치기"로만 편집칸이 열린다(블로그 발행 전 마지막 손질). */
  .review-ai-section{border-top:1px solid var(--line); margin-top:28px; padding-top:18px;}
  /* 편집 취소/저장 쌍은 우측 정렬 — 메모의 [생성·저장] 액션 줄과 같은 결. */
  /* 메모 액션 줄과 같은 간격·같은 리듬 — 버튼이 서로 붙어
     한 덩어리로 보이던 걸 떼어낸다(2026-07-23 피드백). */
  .review-ai-actions{margin-top:16px; display:flex; justify-content:flex-end; gap:8px;}
  /* 다듬기는 블록 하단 풀 버튼 — 폭·높이를 바로 위 '독후감 생성'(hero, padding:12px)과
     맞춰 세로로 이어지는 두 버튼의 크기를 통일한다. 위계는 색으로만(생성=채움,
     다듬기=아웃라인 액센트). */
  #review-refine-btn{flex:1; padding:12px;}
  .review-ai-section[hidden]{display:none;}
  /* 라벨 왼쪽 · 직접 고치기 연필 오른쪽 */
  .review-ai-head{display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:10px;}
  .review-ai-head .review-ai-label{margin-bottom:0;}
  /* 직접 고치기 — 서재 카드의 메모 연필(placard-review-btn)과 같은 결:
     동그라미 없이 흐린 잉크색 맨 아이콘, 같은 연필 SVG를 쓴다. */
  .review-ai-edit-btn{
    background:none; border:none; color:var(--ink-faint);
    display:inline-flex; align-items:center; cursor:pointer;
    padding:4px; border-radius:var(--radius); font-size:15px;
    transition:color var(--dur-1) var(--ease-inout);
  }
  .review-ai-edit-btn[hidden]{display:none;}
  .review-ai-edit-btn:hover, .review-ai-edit-btn:active{color:var(--moss);}
  .review-ai-label{font-size:12px; font-weight:500; color:var(--ink-soft); margin-bottom:10px; font-family:var(--sans);}
  .review-ai-text{font-family:var(--serif); font-size:14px; line-height:1.85; color:var(--ink); white-space:pre-wrap;}
  .review-ai-meta{font-size:11px; color:var(--ink-faint); margin-top:10px; font-family:var(--sans);}
  /* 직접 고치기 — 본문과 같은 세리프 결에 메모란의 편집 관례(카드 배경·
     수직 리사이즈)만 얹는다. 높이는 열 때 본문 높이에 맞춰 JS가 잡는다. */
  .review-ai-edit{
    width:100%; box-sizing:border-box; min-height:200px; padding:16px;
    border:1px solid var(--line); border-radius:var(--radius);
    font-size:14px; line-height:1.85; color:var(--ink);
    background:var(--card); resize:vertical;
  }
  /* .review-ai-actions는 display:flex라 hidden 속성이 안 먹는다 */
  .review-ai-actions[hidden]{display:none;}

  /* 내 서재 안에서 이 책과 결(무드키워드·세부 장르·작가)이 겹치는 책 —
     독후감과 같은 결(모스색 산세리프 라벨 + 세리프 제목)로 조용히 잇는다. */
  /* '이 책과 내 서재' — 연결 사실 두 줄. '결이 닿는 책' 목록과 같은 섹션 리듬
     (border-top·28/18)으로 얹되, 라벨 없이 조용한 명조 문장으로만 둔다(중복 헤더 회피). */
  .book-connections-facts{border-top:1px solid var(--line); margin-top:28px; padding-top:18px; display:flex; flex-direction:column; gap:12px;}
  .book-connections-facts[hidden]{display:none;}
  .book-connection-line{font-family:var(--serif); font-size:14px; color:var(--ink); line-height:1.75; text-wrap:pretty;}
  .book-connection-line b{color:var(--moss-ink); font-weight:600;}
  .related-books-section{border-top:1px solid var(--line); margin-top:28px; padding-top:18px;}
  .related-books-section[hidden]{display:none;}
  .related-books-label{font-size:12px; font-weight:500; color:var(--ink-soft); margin-bottom:10px; font-family:var(--sans);}
  .related-book-row{display:flex; align-items:baseline; gap:8px; padding:5px 0; width:100%; background:none; border:none; text-align:left; cursor:pointer;}
  .related-book-title{font-family:var(--serif); font-size:14px; line-height:1.6; color:var(--ink);}
  .related-book-note{font-size:11px; color:var(--ink-faint); font-family:var(--sans); flex:none;}

  /* ---------- 올해의 책장 (연간 결산) ----------
     연말에만 열리는 결산 페이지 — 모션이나 장식 없이, 가운데 정렬 마스트헤드
     (앱에서 유일하게 중앙 정렬을 쓰는 화면)와 표지가 깔리는 책장으로
     "이벤트"의 무게를 낸다. 표지는 앱 공통 관례(1px 라인 보더, 플랫). */
  /* 진입점은 메인 화면 히어로 바로 아래, 12월에만 보이는 배너 행.
     맨 텍스트 한 줄은 안 보인다는 피드백(2026-07-17)으로, 이 앱에서
     이미 통용되는 강조 문법(모스 소프트 배경 면 — 캘린더 오늘 칸,
     taste-chip)으로 한 단계 올렸다. display를 클래스에 직접 주므로
     [hidden] 오버라이드 필수. */
  .year-shelf-entry{display:flex; align-items:baseline; justify-content:space-between; gap:12px; width:100%; box-sizing:border-box; background:var(--moss-soft); border:none; border-radius:var(--radius); cursor:pointer; padding:13px 18px; margin:-4px 0 18px; text-align:left;}
  .year-shelf-entry[hidden]{display:none;}
  .year-shelf-entry-text{font-family:var(--serif); font-size:14px; color:var(--ink);}
  .year-shelf-entry-cta{font-family:var(--sans); font-size:12px; font-weight:500; color:var(--moss-ink); flex:none;}
  .year-shelf-masthead{text-align:center; margin:6px 0 20px;}
  .year-shelf-year{font-family:var(--serif); font-size:46px; font-weight:600; color:var(--ink); line-height:1.1;}
  .year-shelf-hero-line{font-family:var(--serif); font-size:15px; color:var(--ink-soft); margin-top:12px;}
  .year-pills--center{justify-content:center;}
  .year-shelf-shelf{display:flex; flex-wrap:wrap; gap:8px; justify-content:center; margin:26px auto 0; max-width:540px;}
  .year-shelf-cover{width:52px; height:74px; object-fit:cover; border:1px solid var(--line); background:var(--paper);}
  .year-shelf-more{width:52px; height:74px; box-sizing:border-box; background:var(--moss-soft); color:var(--moss-ink); font-size:12px; font-family:var(--sans); display:flex; align-items:center; justify-content:center;}
  .year-shelf-section{border-top:1px solid var(--line); margin-top:30px; padding-top:20px; text-align:center;}
  .year-shelf-label{font-size:12px; font-weight:400; color:var(--ink-soft); margin-bottom:14px; font-family:var(--sans); letter-spacing:0.08em;}
  /* 3장이 모바일(콘텐츠 폭 ~320px)에서도 한 줄에 앉는 치수 —
     96×3 + 14×2 = 316px. 그보다 좁으면 가운데 정렬로 자연 줄바꿈. */
  .year-shelf-loved{display:flex; justify-content:center; align-items:flex-start; gap:14px; flex-wrap:wrap;}
  .year-shelf-loved-item{width:96px;}
  .year-shelf-loved-cover{width:72px; height:104px; object-fit:cover; border:1px solid var(--line); background:var(--paper); display:block; margin:0 auto 9px;}
  .year-shelf-loved-title{font-family:var(--serif); font-size:13px; color:var(--ink); line-height:1.5;}
  .year-shelf-loved-note{font-size:11px; color:var(--ink-faint); font-family:var(--sans); margin-top:3px;}
  .year-shelf-quote{font-family:var(--serif); font-size:15px; line-height:1.9; color:var(--ink); max-width:480px; margin:0 auto;}
  .year-shelf-quote-meta{font-size:11px; color:var(--ink-faint); margin-top:10px; font-family:var(--sans);}
  .year-shelf-line{font-size:13px; color:var(--ink-soft); font-family:var(--sans); line-height:1.8;}
  .year-shelf-closing{font-family:var(--serif); font-size:13px; color:var(--ink-faint); text-align:center; margin-top:38px;}
  .year-shelf-empty{font-size:13px; color:var(--ink-faint); margin-top:16px; text-align:center;}

  /* ---------- Dashboard ---------- */
  .panel-title{
    font-family:var(--serif);
    font-size:16px;
    font-weight:600;
    margin:0 0 10px; /* 제목→콘텐츠는 한 세트(리듬 체계 참고) */
    color:var(--ink);
  }
  .chart-block{
    border-top:1px solid var(--line);
    padding:24px 0 26px;
    margin:0;
  }
  .chart-block:first-child{border-top:none; padding-top:0;}
  /* 취향 탭 3묶음(지금의 나/기록의 결/다음 책) — 여섯 섹션이 같은 무게로
     나열되던 걸 세 이야기로 묶는다. 머리글에 가로선을 이어 붙여(에디토리얼
     섹션 룰) 묶음의 닻으로 쓰고, 별도 구분선은 두지 않는다. 여백만으로
     나누던 첫 판은 모든 게 붕 떠 보인다고 반려됨 — 묶음 안 간격(28px)을
     묶음 사이(46px)보다 뚜렷이 좁혀 소속감을 만든다. */
  /* 취향 탭 세로 리듬 체계(2026-07-22 — "한 세트가 떨어져 보이고 섹션 구분은
     애매하다" 피드백): 세트 안(제목→콘텐츠) 10px / 섹션 사이 40px / 묶음
     사이 56px. 근접한 것이 한 덩어리로, 떨어진 것이 다른 얘기로 읽히게
     간격 대비를 4배로 벌린다. */
  /* 자락 사이 구분선(오너 2026-08-12) — 세 자락(읽은 나날·취향 리포트·대화)을 온전한
     가로 실선으로 가른다. 제목 위계는 셋 다 .panel-title로 통일(눈썹 라벨 dash-group-label
     제거 — '지금의 나' 묶음 라벨도 뺐다, 오너 2026-08-12). 섹션 간격은 세 구분선
     (.ai-invite + .dash-group · 여기 · .deep-dive-block)이 38/34로 똑같다.
     실제 렌더에선 .ai-invite가 늘 사이에 껴 이 규칙은 안 잡히나, 값은 맞춰 둔다. */
  .dash-group + .dash-group{margin-top:38px; border-top:1px solid var(--line); padding-top:34px;}
  .dash-group .chart-block{border-top:none; padding:0; margin:0;}
  .dash-group .chart-block + .chart-block{margin-top:40px;}
  .dash-group .taste-recommend-section{border-top:none; padding:0; margin:0;}

  /* ── 취향 탭 '읽은 나날' 지도(2026-08-12) ── 여러 해 × 12달 격자. 칸 농도 = 그 달
     권수(--rl-alpha), 안 읽은 달은 빈 칸(paper-2). 색은 --moss를 color-mix로 섞어 색
     토큰을 단일 소스로 둔다(하드코딩 없음). 드릴다운 없음 — 지도다. 칸은 폭에 맞춰
     늘어나(1fr) 자락이 화면을 꽉 채운다(좁게 왼쪽에 쏠리지 않게). */
  .rl-grid{display:flex; flex-direction:column; gap:6px;}
  /* 2열: 연도(권수) | 12칸. 첫 열을 auto로 두면 축 행(빈 연도=0px)과 데이터 행
     ("2026 (5권)"≈72px)의 1fr 폭이 달라져 달 라벨이 칸과 안 맞는다(폭 좁을수록 오른쪽
     달이 밀림 — 오너 2026-08-12). 고정폭이라야 두 행의 칸 트랙이 정확히 겹친다. */
  .rl-row{display:grid; grid-template-columns:78px minmax(0,1fr); align-items:center; gap:14px;}
  .rl-year{font-size:11px; color:var(--ink-faint); font-variant-numeric:tabular-nums; white-space:nowrap;}
  .rl-cells{display:grid; grid-template-columns:repeat(12, 1fr); gap:5px;}
  .rl-cell{height:24px; border-radius:var(--radius); background:var(--paper-2);}
  .rl-cell.has{background:color-mix(in srgb, var(--moss) calc(var(--rl-alpha, 1) * 100%), var(--paper-2));}
  /* 책 있는 칸은 버튼 — 눌러서 그 달 목록을 편다. 기본 버튼 꼴을 벗겨 span 칸과 같게. */
  button.rl-cell{appearance:none; -webkit-appearance:none; border:none; padding:0; margin:0; width:100%; font:inherit; cursor:pointer; position:relative;}
  /* 12칸 격자라 칸 자체는 폰에서 11~17px로 좁다(가로는 늘릴 수 없다). 보이는 칸은
     그대로 두고 ::after로 손끝 히트 영역만 세로로 넓힌다(24→40px, scan:layout이
     권장하는 방식). ±8px는 행 간격(6px)을 살짝만 넘어 이웃 행과 거의 안 겹친다. */
  button.rl-cell::after{content:''; position:absolute; top:-8px; bottom:-8px; left:0; right:0;}
  button.rl-cell:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  button.rl-cell.active{outline:2px solid var(--ink); outline-offset:1px;} /* 지금 펼친 달 */
  /* 권수는 연도 뒤 조용한 곁말 — 색은 연도(--ink-faint)를 물려받고 살짝만 띄운다. */
  .rl-total{margin-left:5px;}
  .rl-axis{margin-bottom:6px;}
  .rl-axis .rl-mhead{text-align:center; font-size:10px; color:var(--ink-faint); line-height:1; white-space:nowrap;}
  .rl-axis .rl-mo{margin-left:1px;}
  /* 제목(읽은 나날)과 히트맵 사이를 한 박자 띄운다(오너 2026-08-12) — 기본
     .panel-title 10px보다 넓게. 이 dash-group의 직속 제목은 읽은 나날뿐이다. */
  #tab-dashboard .dash-group > .panel-title{margin-bottom:18px;}
  /* 좁은 폭에선 12칸 위에 "N월"이 안 들어간다 — '월'만 숨겨 숫자로(scan:layout
     320/360/390 통과). 넓은 폭은 "1월…12월" 그대로. */
  @media (max-width:440px){ .rl-axis .rl-mo{display:none;} }
  /* 칸을 누르면 지도 아래로 그 달 목록이 인라인으로 펼쳐진다(옛 막대 차트 드릴다운을
     지도에 되살림 — 오너 2026-08-12). 점선 실선 위로 제목 + 책 행(제목·저자·별점). */
  .rl-detail{margin-top:16px;}
  .rl-detail:empty{margin-top:0;}
  .rl-detail-title{font-size:12px; color:var(--ink-soft); font-family:var(--sans); margin-bottom:10px; padding-top:14px; border-top:1px solid var(--line);}
  .rl-detail-empty{font-size:12px; color:var(--ink-faint); padding-top:14px; border-top:1px solid var(--line);}
  .rl-detail-list{list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:8px;}
  .rl-detail-list li{display:flex; align-items:baseline; justify-content:space-between; gap:10px;}
  .rl-detail-book{display:flex; align-items:baseline; gap:8px; min-width:0; flex:1;}
  .rl-detail-name{flex:1; min-width:0; font-family:var(--serif); font-size:13px; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
  .rl-detail-author{flex-shrink:0; max-width:35%; font-family:var(--sans); font-size:11px; color:var(--ink-faint); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
  .rl-detail-rating{font-family:var(--sans); font-size:11px; color:var(--ink-faint); flex-shrink:0;}
  /* 회상 카드(작년 이맘때) — 서재 맨 위 모스 카드(2026-08-01 취향 탭→서재 이사).
     정렬은 취향 리포트의 인용 문법: ❝ 왼 거터, 헤드 들여쓰기, 본문(책 행)은
     따옴표 선(왼끝)에서 전폭. 빽빽함은 헤드↔본문 세로 여백(14px)이 담당.
     하단 22px = 아래 검색·정렬 툴바와 사이, 패딩 22/24 = 대화 카드와 동일. */
  /* 배경은 옅은 웜 톤(--gold-bg)으로 깐다 — 예전엔 모스 그린 채움이었는데, 주
     액션색(버튼·활성 탭)을 서재 맨 위에서 통째로 채워 배너처럼 튀었다(오너 지적,
     2026-08-11). 채도를 낮춘 웜 바탕이라 모스 계열(책 카드 --card, 읽는 중
     --moss-bg)과도 갈리면서 회상다운 온기가 난다. 바탕이 밝아 글씨는 --ink,
     별점·인용부호만 --gold로 잡는다(경계 판정은 branding §2). */
  .memory-lane{position:relative; margin:0 0 22px; padding:22px 24px; background:var(--gold-bg); border-radius:var(--radius);}
  .memory-lane-close{
    position:absolute; top:10px; right:10px; width:28px; height:28px;
    display:flex; align-items:center; justify-content:center;
    background:none; border:none; border-radius:var(--radius);
    color:var(--ink-faint); cursor:pointer;
  }
  .memory-lane-close svg{width:16px; height:16px;}
  .memory-lane-close:hover{color:var(--ink);}
  .memory-lane-close:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  /* 라벨은 앱의 날짜 문구("지난 가을, 이맘때")다 — 따옴표는 '누구 말인지'를 가르는 표식이라
     사용자의 발췌(.memory-lane-quote)에만 남긴다(branding §3.1). 라벨 앞에는 따옴표가 아니라
     **지금 계절 오너먼트**(.memory-lane-orn — 봄 새싹·여름 해·가을 잎·겨울 눈꽃)를 골드로 얹는다.
     padding-right는 ✕ 버튼(절대배치) 회피용이라 유지. */
  .memory-lane-label{
    display:flex; align-items:center; gap:9px;
    font-family:var(--serif); font-size:15px; font-weight:600; color:var(--ink);
    padding-right:30px; margin-bottom:18px; line-height:1.5;
  }
  .memory-lane-orn{flex:none;}
  .panel-title-row{
    display:flex;
    align-items:center;
    justify-content:space-between;
    margin-bottom:10px; /* 제목→콘텐츠는 한 세트(리듬 체계 참고) */
  }
  .panel-title-row .panel-title{margin:0;}
  /* 제목 옆 날짜·권수 메타 — 좁은 화면에선 제목 아래 줄로 자연스럽게 감긴다 */
  .panel-title-group{display:flex; align-items:baseline; gap:10px; min-width:0; flex-wrap:wrap;}
  .panel-title-group .panel-title{margin:0;}
  .panel-title-group .taste-report-meta{margin-bottom:0;}
  .taste-report-refresh{
    background:none;
    border:none;
    width:28px;
    height:28px;
    flex-shrink:0;
    font-size:14px;
    color:var(--ink-soft);
    cursor:pointer;
    transition:color var(--dur-1) var(--ease-inout), transform var(--dur-2) var(--ease-out);
  }
  .taste-report-refresh:disabled{opacity:.5; cursor:default;}
  .taste-report-refresh.spinning{animation:spin 1s linear infinite;}
  /* 첫 리포트 CTA — 제목 행 오른쪽에 담백한 작은 채움 버튼. 리포트가 생기면 숨고
     ↻(재분석)가 같은 자리를 잇는다(오너 지시 2026-08-07). */
  .taste-report-start-inline{
    flex-shrink:0;
    padding:6px 13px;
    border:1px solid var(--moss); border-radius:var(--radius);
    background:var(--moss); color:var(--paper);
    font-family:var(--sans); font-size:12px; font-weight:600; cursor:pointer;
  }
  .taste-report-start-inline[hidden]{display:none;}
  @keyframes spin{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}
  .taste-report-status{display:block; margin-bottom:12px; font-size:12px; color:var(--ink-soft);}
  .taste-report-meta{font-size:11px; color:var(--ink-faint); margin-bottom:12px; font-family:var(--sans);}
  .taste-report-stale{color:var(--ink-faint);}
  .taste-report-text{font-family:var(--serif); font-size:14px; line-height:1.85; color:var(--ink); white-space:pre-wrap;}
  /* 저장된 리포트는 접어서 — 하단은 배경색과 무관하게 마스크로 흐려진다 */
  .taste-report-text.collapsed{
    max-height:190px; overflow:hidden;
    -webkit-mask-image:linear-gradient(180deg, #000 60%, transparent);
    mask-image:linear-gradient(180deg, #000 60%, transparent);
  }
  /* 리포트 꼬리는 사고의 흐름 순서로 세로 배치 — 본문 여닫기(펼치기)는
     본문 바로 아래, 작년 이맘때 회상을 지나, 프롬프트 복사(다음 행동)가
     묶음의 맺음. 펼치기·복사를 한 줄 양 끝에 두던 문법(taste-report-foot)은
     그 아래 회상 카드가 매달려 보여서 반려됨. */
  /* 가운데 밑줄 낱말 "펼치기 / 접기"(오너 2026-08-12) — 오른쪽 끝 단독 화살표는
     지도·본문에서 떨어져 어색했다. 밑줄·색·서체(--sans 12px)는 .settings-link-btn에서
     물려받고, 여기선 가운데 배치와 옅은 밑줄만 얹는다. 모임 '우리 이야기'의
     함께 읽은 책 펼치기 토글(#group-overlap-toggle)도 이 클래스를 달아 같은
     문법을 공유한다 — 고칠 땐 두 자리(대시보드·모임)에 같이 반영할 것. */
  .taste-report-toggle{
    display:block;
    width:max-content;
    max-width:100%;
    margin:14px auto 0;
    padding:5px 10px;
    text-underline-offset:3px;
    text-decoration-color:var(--line);
  }
  .taste-report-toggle[hidden]{display:none;}
  /* 나를 더 읽는 대화 — 모스 카드(배경 박스)는 배너처럼 튀어 반려됨(오너 2026-08-12).
     종이 위에 그냥 흐르게 둔다(박스 안 박스 금지). 제목+부제(좌)와 CTA(우)가 마주 보는 한 행. */
  .deep-dive-block{margin-top:38px; border-top:1px solid var(--line); padding-top:34px;}
  /* 제목과 부제(설명)는 한 덩어리로 — 12px는 남남처럼 벌어져 보였다(2026-07-22). */
  .deep-dive-block .panel-title{margin-bottom:6px;}
  .deep-dive-desc{font-size:13px; color:var(--ink-soft); font-family:var(--sans); line-height:1.9; margin:0;}
  /* 지난 대화 티저 — 기억이 쌓이는 기능임을 보이게 하는 한 줄. 모스 배경
     위라 왼선은 moss-soft 대신 sage(한 단계 진한 톤)로. 본문(main) 뒤 전폭. */
  /* 제목+부제 묶음(좌)과 CTA(우 세로 중앙)가 마주 보는 배너형 한 행 —
     모임 "함께 읽을 책" 헤더와 같은 문법. 폰에서는 flex-wrap. */
  .deep-dive-main{display:flex; align-items:center; justify-content:space-between; gap:14px 24px; flex-wrap:wrap;}
  .deep-dive-text{flex:1 1 320px; min-width:0;}
  /* 상태 문구는 좌, CTA는 우 — 액션 줄들의 공통 결. */
  .deep-dive-cta-row{display:flex; align-items:center; justify-content:flex-end; gap:12px; flex-wrap:wrap; margin-left:auto;}
  /* '대화 시작하기'는 채운 히어로가 아니라 덜 시끄러운 아웃라인 — 종이 위에서
     조용히 앉는다(오너 지시 2026-08-07: 히어로로 만들지 말고 덜 시끄럽게). */
  .deep-dive-cta-row .social-primary-btn{background:none; color:var(--moss); border-color:var(--moss); text-align:center;}
  /* 모임 '책 추천받기' — 채운 모스가 아니라 조용한 아웃라인(투명 배경·회색
     테두리·모스 글자). 전폭 배치는 그대로 두되 배경만 벗긴다(오너 2026-08-08). */
  #group-recommend-btn{
    background:transparent;
    border-color:var(--line);
    color:var(--moss);
  }
  /* 담아둔 책을 모임 멤버가 먼저 읽었을 때 — 이름과 평점만 조용히 */
  .wish-peer-note{display:flex; align-items:center; flex-wrap:wrap; gap:6px; margin:2px 0 8px;}
  .wish-peer-label{font-size:11px; font-weight:600; color:var(--moss);}
  .wish-peer-chip{font-size:11px; color:var(--ink-soft); background:var(--moss-soft); padding:4px 9px; border-radius:var(--radius-pill);}
  /* 작년 이맘때 본문 행 — 라벨(세리프+인용부호)은 위 회상 카드 블록 참고 */
  .memory-lane-card{display:flex; gap:14px; align-items:flex-start;}
  .memory-lane-cover{width:44px; border-radius:var(--radius); box-shadow:var(--shadow-sm); flex-shrink:0;}
  .memory-lane-body{min-width:0; flex:1;}
  .memory-lane-line{font-family:var(--serif); font-size:14px; color:var(--ink); line-height:1.6;}
  .memory-lane-sub{font-size:12px; color:var(--ink-soft); font-family:var(--sans); margin-top:4px;}
  .memory-lane-quote{
    font-family:var(--serif); font-size:13px; color:var(--ink-soft);
    line-height:1.75; margin-top:6px; text-wrap:balance;
  }
  .memory-lane-quote::before{content:"“ "; color:var(--gold);}
  .memory-lane-quote::after{content:" ”"; color:var(--gold);}

  /* ---------- Recommend ---------- */
  .recommend-intro{
    color:var(--ink-soft);
    font-size:13px;
    margin-bottom:20px;
    line-height:1.6;
  }
  .taste-recommend-section{
    border-top:1px solid var(--line);
    padding:24px 0 0;
    margin-top:0;
  }
  .taste-summary{display:flex;gap:8px 10px;flex-wrap:wrap;margin:-6px 0 20px;}
  /* display:flex가 UA의 [hidden]{display:none}을 이긴다 — 지금은 칩이 없어 0×0이라
     화면에 안 보이지만, 내용이 생기는 순간 숨긴 채로 그려진다(scan:render가 잡는다). */
  .taste-summary[hidden]{display:none;}
  .taste-chip{font-size:11px;color:var(--moss-ink);background:var(--moss-soft);padding:6px 10px;border-radius:var(--radius-pill);}
  /* 담아둔 책 탭 세 자락의 사이 — '추천받은 책' 인박스가 있으면 그 아래 자락('담아둔
     책')을 얇은 눈썹 실선으로 가른다(앱 기존 문법). 인박스가 없으면(흔한 경우) '담아둔
     책'이 첫 자락이라 실선 없이 제목만 선다(유령 실선 방지). */
  #handoff-inbox:not([hidden]) + .wishlist-saved{
    border-top:1px solid var(--line); padding-top:24px; margin-top:24px;
  }
  /* 맞춤 추천은 취향 탭에서 담아둔 책 탭으로 이사했다(2026-08-12) — 늘 '담아둔 책' 아래라
     .taste-recommend-section 기본형의 눈썹 실선(border-top)을 그대로 써서 가른다.
     AI 꺼진 계정에선 .ai-surface가 통째로 접힌다. */
  .wishlist-recommend{margin-top:36px;}
  /* 키 없을 때 위 .ai-surface 대신 뜨는 연결 초대 — 같은 자리·같은 눈썹 실선 리듬. */
  .wishlist-recommend-off{margin-top:36px;}
  .wishlist-recommend-off[hidden]{display:none;}
  /* 취향의 흐름 칩(taste-shift-*)은 뺐다(2026-07-22) — 리포트·추천과 삼중
     중복. 변화 데이터는 리포트 프롬프트로 계속 흐른다. */
  /* 추천 출처 한 줄 — "이 추천이 당신 서재의 어느 결에서 나왔나"를 조용히 되읽어
     준다(되읽힘을 보이게, 플라이휠 1번 페달). 카드 위 한 줄이라 인용부호 없이
     담백하게 — 결 단어만 moss로 든다. */
  .rec-origin{
    font-family:var(--serif); font-size:15px; color:var(--ink-soft);
    line-height:1.6; margin:2px 0 16px; text-wrap:pretty;
  }
  .rec-origin b{color:var(--moss-ink); font-weight:600;}
  .rec-card{
    border:1px solid var(--line);
    background:var(--card);
    border-radius:var(--radius);
    padding:20px 22px;
    margin-bottom:14px;
    display:grid;
    grid-template-columns: clamp(54px, 42px + 3.1vw, 72px) 1fr;
    gap:18px;
    height:100%;
  }
  .rec-cover{
    width: clamp(54px, 42px + 3.1vw, 72px);
    height: clamp(76px, 61px + 4.1vw, 100px);
    object-fit:cover;border:1px solid var(--line);background:var(--paper);
  }
  .rec-body{min-width:0; display:flex; flex-direction:column;}
  .rec-title{
    font-family:var(--serif);
    font-size:18px;
    font-weight:600;
    line-height:1.3;
    min-height:2.6em;
    margin-bottom:2px;
    display:-webkit-box;
    -webkit-line-clamp:2;
    -webkit-box-orient:vertical;
    overflow:hidden;
  }
  .rec-author{
    font-size:12px; color:var(--ink-soft); margin-bottom:12px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  /* 이유 줄은 내용만큼만 차지한다(최대 4줄). 예전엔 height:6.8em 고정 +
     is-empty를 visibility:hidden으로 자리만 남겼는데 — 추천 그리드의 키 맞춤
     용도였지만, 목록형(담아둔 책)에선 이유 없는 카드 한가운데가 빈 구멍으로
     보였다(2026-07-22 점검). 그리드 키 맞춤은 .rec-card{height:100%}와
     .rec-actions{margin-top:auto}가 이미 담당하므로 고정 높이는 필요 없다. */
  .rec-reason{
    font-size:13px; line-height:1.7; color:var(--ink);
    /* 추천 이유는 AI가 쓴 자유 문장이라 길이를 우리가 못 정한다 — 문구를 손봐서
       고아 줄을 없애는 방법은 다음 문장에서 다시 깨진다. 마지막 줄이 혼자 짧게
       남지 않도록 브라우저에 맡긴다(2026-08-03, npm run scan:layout 390px). */
    text-wrap:pretty;
    display:-webkit-box;
    -webkit-line-clamp:4;
    -webkit-box-orient:vertical;
    overflow:hidden;
  }
  .rec-reason::before{content:"— "; color:var(--clay);}
  .rec-reason.is-empty{display:none;}
  .rec-meta{
    font-size:11px;color:var(--ink-faint);margin-top:10px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  /* 담아둔 책 표지를 알라딘 상품 페이지 링크로 — 약관의 '상품 페이지 링크' 요건.
     표지 그리드 열에 맞춰 표지 크기만 갖고(align-self:start) 밑줄 없는 조용한 링크. */
  .rec-cover-link{display:block; line-height:0; align-self:start;}
  /* 액션 행은 우측 정렬이 기본이다(§CLAUDE.md 디자인 원칙). 예전엔 base에 이게 없고
     #wishlist-list·#recommend-results 두 ID가 뒤에서 기웠는데, 그 둘이 이미 전수를
     덮고 있어(쓰는 곳은 views/wishlist.js·views/recommendations.js 둘뿐) 결과는 같으면서
     세 번째 자리가 생기면 좌측으로 떨어질 자리였다. */
  .rec-actions{margin-top:auto; padding-top:10px; display:flex; justify-content:flex-end; gap:8px;}
  .btn-wishlist-add, .wishlist-remove-btn, .wishlist-read-btn, .btn-wishlist-skip{
    background:none;
    border:1px solid var(--line);
    border-radius:var(--radius);
    padding:5px 10px;
    font-size:11px;
    font-family:var(--sans);
    color:var(--ink-soft);
    cursor:pointer;
    transition:border-color var(--dur-1) var(--ease-inout), color var(--dur-1) var(--ease-inout);
  }
  .btn-wishlist-add:disabled, .wishlist-remove-btn:disabled, .wishlist-read-btn:disabled, .btn-wishlist-skip:disabled{opacity:.5; cursor:default;}

  /* ---------- Social groups ---------- */
  .social-shell{display:flex; flex-direction:column; gap:18px;}
  .social-nickname-nudge{
    display:flex;
    flex-direction:column;
    justify-content:center;
    gap:8px;
    /* 테두리 없음 — 채운 moss 면이 이미 구분한다. 바로 아래 나란히 놓이는
       .group-card도 border:none이라, 한쪽만 선을 덧입던 자리였다(2026-08-13). */
    border-radius:var(--radius);
    background:var(--moss-soft);
    padding:14px 16px;
    /* 모임 카드·고스트 카드(126px)와 같은 키 — 폰에서 위아래로 나란히
       보일 때 박스 크기가 통일돼 보이도록 */
    min-height:126px;
    box-sizing:border-box;
  }
  .social-nickname-nudge[hidden]{display:none;}
  .social-actions{
    display:grid;
    grid-template-columns:repeat(2, minmax(0, 1fr));
    gap:18px;
    align-items:end;
  }
  .social-actions[hidden]{display:none;}
  .social-form{
    min-width:0;
  }
  /* 폼 제목(모임 만들기·코드로 가입)은 명조 15/600 — 콘텐츠 눈썹(.exchange-subsection-title
     12/400)보다 일부러 무겁다. 역할이 다르다: 이건 '지금 채우는 폼의 제목', 저건 '목록 위의
     구획 이름'이라 위계가 뒤집히지 않게 스케일을 나눈다(오너 판정 2026-08-17, 통일 대신 유지). */
  .social-form-title{
    font-family:var(--serif);
    font-size:15px;
    font-weight:600;
    margin:0 0 8px;
    color:var(--ink);
  }
  .social-inline-form{display:flex; gap:8px; align-items:center;}
  .social-inline-form input{
    width:100%;
    min-width:0;
    box-sizing:border-box;
    border:1px solid var(--line);
    border-radius:var(--radius);
    background:var(--paper);
    color:var(--ink);
    font-size:12px;
    padding:9px 10px;
  }
  .social-inline-form input:focus{outline:none; border-color:var(--moss);}
  .social-inline-form input:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .social-primary-btn{
    flex:0 0 auto;
    border:1px solid var(--moss);
    background:var(--moss);
    color:var(--paper);
    border-radius:var(--radius);
    padding:9px 14px;
    font-family:var(--sans);
    font-size:12px;
    font-weight:600;
    cursor:pointer;
  }
  .social-primary-btn:disabled{opacity:.45; cursor:default;}
  /* 폼의 '저장'은 새로 만드는 hero가 아니라 편집을 갈무리하는 커밋 액션 — 리뷰 화면
     저장(btn-add--accent)과 같은 결로 채움을 벗겨 모스 아웃라인으로 둔다(크기·정렬은 유지). */
  .social-primary-btn.is-commit{background:none; color:var(--moss); font-weight:700;}
  .social-status-row{display:flex; align-items:center; gap:8px; min-height:24px; flex-wrap:wrap;}
  .social-status{font-size:12px; color:var(--ink-faint);}
  .group-pills{display:grid; grid-template-columns:repeat(auto-fill, minmax(240px, 1fr)); gap:14px; margin-bottom:4px;}
  .group-pills:empty{margin-bottom:0;}
  .group-card{
    display:flex;
    flex-direction:column;
    justify-content:space-between;
    min-height:126px;
    border:none;
    background:var(--moss-soft);
    border-radius:var(--radius);
    padding:18px 18px 12px;
    box-shadow:none;
    cursor:pointer;
  }
  .group-card:not(.dragging){transition:transform var(--dur-2) var(--ease-out);}
  .group-card.dragging{box-shadow:var(--shadow-card); position:relative; z-index:2;}
  .group-card-handle{
    align-self:flex-start;
    color:var(--ink-faint); /* 토큰화 — 팔레트 밖 리터럴 제거(오너 승인 목업 2026-08-27) */
    font-size:13px;
    line-height:1;
    cursor:grab;
    touch-action:none;
    padding:6px;
    margin:-6px 0 8px -6px;
  }
  .group-card-open{
    flex:1;
    min-width:0;
    display:flex;
    align-items:flex-start;
    border:none;
    background:transparent;
    color:var(--ink);
    padding:0;
    font-family:var(--sans);
    text-align:left;
    cursor:pointer;
  }
  .group-card-name{
    font-family:var(--serif);
    font-size:18px;
    font-weight:600;
    line-height:1.35;
    letter-spacing:-0.01em;
    overflow:hidden;
    text-overflow:ellipsis;
    display:-webkit-box;
    -webkit-line-clamp:2;
    -webkit-box-orient:vertical;
  }
  .group-card-open{flex-direction:column; gap:7px;}
  .group-card-meta{font-size:11.5px; color:var(--ink-soft); letter-spacing:0.01em;}
  /* 드래그 핸들: 호버 가능한 기기에서만 숨겼다 보여준다 — 터치 기기는 상시 노출 유지 */
  @media (hover:hover){
    .group-card-handle{opacity:0; transition:opacity var(--dur-1) var(--ease-out);}
    .group-card:hover .group-card-handle,
    .group-card.dragging .group-card-handle{opacity:1;}
  }
  /* 고스트 카드: 만들기/가입 진입점. 모임이 없으면 이 카드가 곧 빈 상태 화면 */
  .group-card-ghost{
    min-height:126px;
    border:1px dashed var(--line); /* 토큰화 — 팔레트 밖 리터럴 제거(오너 승인 목업 2026-08-27) */
    border-radius:var(--radius);
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    gap:6px;
    padding:16px;
  }
  /* 모임이 없으면(그리드에 고스트뿐) 그리드 한 칸이 아니라 전체 폭으로 —
     위 닉네임 안내 박스와 같은 크기의 박스로 나란히 읽히도록(아이패드처럼
     넓은 화면에서 반 칸짜리로 쪼그라드는 것 방지) */
  .group-card-ghost:only-child{grid-column:1 / -1;}
  .ghost-create-btn{
    display:flex; flex-direction:column; align-items:center; gap:5px;
    border:none; background:transparent; cursor:pointer;
    font-family:var(--sans); font-size:13px; font-weight:600; color:var(--ink);
  }
  .ghost-plus{font-family:var(--sans); font-size:24px; line-height:1; font-weight:500; color:var(--moss);}
  .social-empty-hint{font-size:12px; color:var(--ink-faint); line-height:1.6; margin:14px 2px 0; text-align:center;}
  .social-empty-hint[hidden]{display:none;}
  /* 모임 스페이스 상단: 초대 코드는 알약 테두리를 두르면 멤버 칩과 같은 부류로
     읽혀서(실사용 피드백) 맨 글자로 둔다.
     컨트롤은 모임 이름과 같은 줄에 흘려두지 않고 이름 아래 제 줄에 고정한다 —
     한 줄에 같이 두면 이름 길이·화면 폭·'가입 잠그기/가입 열기'의 글자 수에 따라
     버튼이 줄을 넘나들어 자리가 매번 달라졌다(폰에서 특히, 실사용 피드백). */
  .group-space-head{display:block;}
  .group-space-actions{display:flex; align-items:center; gap:16px; margin-top:8px; justify-content:flex-end;}
  .group-space-invite{
    display:inline-flex; align-items:center; gap:5px;
    border:none; background:transparent; cursor:pointer;
    padding:0;
    font-family:var(--sans); font-size:11px; letter-spacing:0.05em;
    color:var(--ink-faint);
  }
  .group-space-invite svg{width:12px; height:12px; stroke:currentColor;}
  /* 모임 나가기 — 스페이스 맨 아래 푸터에 가운데로(.group-space-footer가
     justify-content:center), 초대 코드와 같은 조용한 텍스트 문법으로 한 번만.
     탭 아래 푸터에 두면 모든 탭 하단에 따라붙어 상시 위협처럼 읽힌다(실사용 피드백).
     파괴적 동작이라 hover도 moss 대신 잉크로만 진해진다. 확인 모달이 오터치를 받친다. */
  .group-space-leave{
    margin-left:auto;
    border:none; background:transparent; cursor:pointer;
    padding:0;
    font-family:var(--sans); font-size:11px; letter-spacing:0.05em;
    color:var(--ink-faint);
  }
  .group-space-leave:hover{color:var(--ink);}
  .group-space-leave:active{color:var(--ink);}
  /* 모임 삭제 — 소유자에게만, 나가기와 같은 조용한 텍스트 문법으로. 되돌릴 수
     없는 동작이지만 크게 만들어 겁줄 일이 아니다(확인 모달이 사람 수까지 적어
     받친다). 떠나는 동작 둘(삭제·나가기)은 오른쪽에 붙어 한 무리로 읽히되,
     빈칸을 잡는 건 앞선 하나뿐이어야 자리가 안 흔들린다(margin:auto가 둘이면
     빈칸을 반씩 나눠 가진다). 소유자가 아니면 삭제가 없으니 나가기가 그 자리를
     그대로 이어받는다 — 멤버에겐 예전과 똑같이 보인다. */
  .group-space-delete{
    margin-left:auto;
    border:none; background:transparent; cursor:pointer;
    padding:0;
    font-family:var(--sans); font-size:11px; letter-spacing:0.05em;
    color:var(--ink-faint);
  }
  .group-space-delete:hover{color:var(--ink);}
  .group-space-delete:active{color:var(--ink);}
  /* 파괴 동작(나가기·삭제)은 스페이스 맨 아래 조용한 자리로 — 초대·잠금(주 동작)과
     떼어 스크롤 끝에서만 만난다(상시 위협처럼 읽히지 않게). 버튼은 헤드와 같은
     조용한 텍스트 문법 그대로 쓰되, 여기선 가운데로 모은다(margin-left:auto 무효화). */
  .group-space-footer{
    display:flex; justify-content:center; gap:8px 24px; flex-wrap:wrap;
    margin-top:44px; padding-top:20px; border-top:1px solid var(--line);
  }
  .group-space-footer .group-space-leave,
  .group-space-footer .group-space-delete{margin-left:0;}
  /* 가입 잠금 — 초대 코드 바로 옆, 같은 조용한 텍스트 문법. 코드가 새면
     되돌릴 길이 이것뿐이라(코드가 곧 문서 ID여서 재발급은 구조 변경) 소유자에게만
     보인다. 잠긴 동안엔 초대 코드를 흐리게 해 "지금은 안 열린다"가 눈에 보이게. */
  /* 문구가 '가입 잠그기'(긴 쪽)↔'가입 열기'(짧은 쪽)로 오갈 때 폭이 ~11px 달라진다.
     행이 우측 정렬(flex-end)이라 폭이 줄면 버튼 왼쪽 모서리가 그만큼 오른쪽으로
     밀려 초대 코드까지 흔들렸다. 긴 문구가 들어갈 만큼 min-width를 잡아 폭을 고정하고,
     글자는 오른쪽(정렬 앵커)에 붙여 짧은 문구일 때 여백이 왼쪽에서만 열리게 한다.
     서체가 기기마다 달라(--sans는 Pretendard 미로드 시 -apple-system로 폴백) 폴백
     실측(가입 잠그기 ~62px)에 여유를 둔다. 자리 고정은 e2e groups.spec.js가 잠근다. */
  .group-space-lock{
    border:none; background:transparent; cursor:pointer;
    padding:0;
    min-width:68px; text-align:right; justify-content:flex-end;
    font-family:var(--sans); font-size:11px; letter-spacing:0.05em;
    color:var(--ink-faint);
  }
  .group-space-lock:hover{color:var(--moss);}
  /* 이름 옆 연필 — 평소엔 조용한 아이콘, 누르면 제목이 입력창이 된다 */
  /* 소유자는 제목을 눌러 바로 편집한다 — 데스크톱은 hover 때 커서·엷은 밑줄로
     "누르면 고칠 수 있다"를 살짝 귀띔한다(모바일은 눌러서 발견). */
  .group-space-title-row.editable #group-space-title{cursor:text;}
  .group-space-title-row.editable:hover #group-space-title{
    text-decoration:underline; text-decoration-thickness:1px;
    text-decoration-color:var(--ink-faint); text-underline-offset:5px;
  }
  /* 편집 중 제목 자리에 뜨는 입력 — 제목과 같은 크기, 밑줄만 살짝. 글꼴은 전역
     입력 규칙(--serif)에 맡긴다(재선언 금지 — test/typography.test.js가 잠근다). */
  .group-space-title-input{
    width:100%;
    font-size:22px; font-weight:600; color:var(--ink);
    border:none; border-bottom:1.5px solid var(--moss); background:transparent;
    padding:0 0 2px; margin:0;
  }
  .group-space-title-input:focus{outline:none;}
  .group-space-head.locked .group-space-invite{opacity:0.45;}
  /* 멤버 서재 머리 — 이름·권수 왼쪽, 책 추천하기 오른쪽 끝(margin-left:auto).
     파괴적 동작인 내보내기는 여기서 빼 맨 아래 푸터로 내렸다(모임 나가기·삭제와
     같은 자리감) — 대표 동작인 추천하기만 오른쪽에 남아 자리가 깔끔해진다. */
  .member-space-head{display:flex; align-items:baseline; gap:14px; flex-wrap:wrap;}
  /* 내보내기 푸터 — 모임 나가기·삭제 푸터와 같은 조용한 자리감. 소유자에게만 보인다
     (canRemove=false면 views/member-space.js가 hidden을 건다).
     **[hidden] 규칙이 반드시 함께 있어야 한다** — display:flex는 작성자 선언이라
     UA의 [hidden]{display:none}을 특이도로 이긴다. 없던 동안 소유자가 아닌 모든
     경우에 빈 구분선 + 64px 빈칸이 그려졌다(2026-08-13). 이제 scan:render가
     "hidden인데 그려지는 요소"를 통째로 잡으므로 주석이 아니라 검사가 지킨다. */
  .member-space-footer{
    display:flex; justify-content:center;
    margin-top:44px; padding-top:20px; border-top:1px solid var(--line);
  }
  .member-space-footer[hidden]{display:none;}
  .member-space-footer .group-space-leave{margin-left:0;}
  .group-space-invite:hover{color:var(--moss);}
  .group-space-invite:active{color:var(--moss);}
  /* 문패 무리와 탭 사이 숨 — 붙어 있으면 문패와 탭이 한 덩어리로 읽힌다 */
  .member-strip{display:flex; flex-wrap:wrap; gap:8px; margin-bottom:30px;}
  /* 멤버 서재 문패 — 남의 서재로 드는 문이 모임의 핵심이라, 부속 태그(알약)가
     아니라 장서표(ex libris) 문법으로: 각진 2px 카드, 아이콘의 마진 마크 한 획,
     세리프 이름. 이름(세리프 15px)과 "의 서재"(산세리프 11px)는 인라인 흐름에
     둬서 베이스라인이 저절로 맞는다 — flex 가운데 정렬로 섞으면 어긋난다. */
  .member-plate{
    display:inline-flex; align-items:center;
    background:transparent;
    border:1px solid var(--line);
    border-radius:var(--radius);
    padding:10px 14px 10px 12px;
    cursor:pointer;
    /* 긴 닉네임(최대 20자)이 스트립을 넘겨 형제 문패를 화면 밖으로 밀지 않게 —
       이름만 말줄임하고 획·"의 서재"는 유지한다(2026-08-22 오너 승인). */
    max-width:100%; min-width:0;
  }
  .member-plate svg{width:7px; height:20px; stroke:var(--moss); flex:0 0 auto; margin-right:9px;}
  .member-plate-text{font-family:var(--sans); font-size:11px; letter-spacing:0.02em; color:var(--ink-soft); white-space:nowrap; min-width:0; overflow:hidden;}
  .member-plate-name{font-family:var(--serif); font-size:15px; font-weight:500; color:var(--ink); letter-spacing:0; margin-right:2px; display:inline-block; max-width:9.5em; overflow:hidden; text-overflow:ellipsis; vertical-align:bottom;}
  .member-plate:focus-visible{outline:2px solid var(--moss); outline-offset:3px;}
  .social-section{margin-top:20px;}
  .social-section:first-child{margin-top:0;}
  .exchange-subsection{border-top:1px solid var(--line); margin-top:16px; padding-top:16px;}
  .exchange-books-subsection{border-top:none; margin-top:22px; padding-top:0;}
  .exchange-subsection-first{border-top:none; margin-top:0; padding-top:0;}
  .exchange-subsection-title{font-size:12px; font-weight:400; color:var(--ink-soft); margin-bottom:4px;}
  .exchange-subsection-header{display:flex; align-items:center; justify-content:space-between; gap:12px;}
  /* "함께 읽을 책" — 데스크톱은 버튼을 제목 옆에 담백하게(담아둔 책 "책 고르기"와
     같은 무게). 모바일은 아래 전폭으로 스택(<640px 블록·폰 pointer:coarse 블록).
     추천 기능이 지금의 성숙도에 맞는 무게를 갖게 — 정교해지면 다시 키운다. */
  .exchange-subsection-header-text{flex:1; min-width:0;}
  .exchange-subsection-header-text .exchange-subsection-desc{margin-bottom:0;}
  .exchange-subsection-desc{font-size:11px; color:var(--ink-faint); line-height:1.5; margin-bottom:10px;}
  .exchange-member-book-list{display:flex; flex-direction:column; gap:10px; list-style:none; margin:10px 0 0; padding:0;}
  .exchange-member-label{display:flex; align-items:center; gap:7px; min-width:0;}
  .exchange-order-list{display:flex; flex-direction:column; gap:6px; list-style:none; margin:0; padding:0;}
  /* 우리 이야기 탭의 닻 — "함께 쌓은 권수"라는 온기 있는 숫자 하나를 맨 위에
     세운다. 볼드 풀쿼트 문장 시안은 소개문인지 제목인지 자리가 애매하고,
     아래 로우들의 글자량과 겹쳐 여백의 결이 죽는다고 반려됨(2026-07-18).
     인용이 아니라 통계이므로 조용한 캡션 한 줄 + 히어로 숫자(서재 헤더
     hero-count-inline과 같은 문법)로 세우고, 아래 여백이 제목을 대신한다. */
  .group-compare-lead{margin:4px 0 30px;}
  .group-compare-lead-caption{font-size:12px; color:var(--ink-soft); margin-bottom:5px;}
  .group-compare-lead-num{font-family:var(--serif); font-size:32px; line-height:1.15; color:var(--moss);}
  .group-compare-lead-unit{font-size:14px; color:var(--ink-soft); margin-left:3px;}
  /* 멤버당 두 줄 로우 — 윗줄은 이름(왼쪽)·닮은꼴(오른쪽), 아랫줄은 권수·평점과
     장르 칩. 모든 로우가 같은 골격이라 세로로 훑을 때 정돈돼 보인다. */
  /* 아래 12px — "함께 읽은 책" 구분선 위 공기를, 펼치기 버튼(margin 12px)이
     끼어 있는 "함께 읽을 책" 구분선 위와 같게 맞춘다(28px). */
  /* 멤버 줄은 가로선 대신 여백으로 가른다(2026-08-21) — 헤어라인이 멤버마다,
     또 아래 책 목록까지 겹겹이 쌓여 '우리 이야기'가 줄이 그어진 표처럼 보였다.
     섹션 구분선(.exchange-subsection)만 남기고 목록 안은 숨으로 나눈다. */
  .group-compare-members{display:flex; flex-direction:column; gap:16px; padding-bottom:6px;}
  .group-compare-member-row{display:flex; flex-direction:column; gap:7px;}
  /* 닮은꼴은 이름 바로 옆에 붙인다 — space-between이면 넓은 화면에서 이름과 화면
     양 끝으로 떨어져 누구의 닮은꼴인지 안 읽힌다(폰은 아래 미디어쿼리가 이미 이름
     아래로 내려 같은 문제를 고쳐 뒀다). 화면이 넓을수록 나빠지던 자리. */
  .group-compare-member-top{display:flex; align-items:baseline; justify-content:flex-start; gap:10px;}
  .group-compare-member-sub{display:flex; align-items:center; flex-wrap:wrap; gap:6px;}
  .group-compare-member-name{font-family:var(--serif); font-size:14px; color:var(--ink);}
  .group-compare-member-meta{font-size:11px; color:var(--ink-faint); margin-right:2px;}
  .group-compare-member-match{font-size:11px; color:var(--moss); flex:none;}
  .group-compare-actions{margin-bottom:14px;}
  /* 추천 상태(social-status)가 비어 있는 흔한 상태(추천 전)에서는 이 행이 내용 없이
     14px 여백만 남긴다 — 상태 문구가 실릴 때만 아래 결과와 사이를 벌린다. */
  .group-compare-actions:has(.social-status:empty){margin-bottom:0;}
  /* 추천 결과(카드)·로딩 문구가 '책 추천받기' 버튼에 붙지 않게 숨을 준다 — 위
     :has(:empty)로 actions 행이 접히는 흔한 상태에서 카드가 버튼에 딱 붙던 것을 벌린다.
     비어 있을 때는(추천 전) 여백을 만들지 않는다. */
  #group-recommend-results:not(:empty){margin-top:14px;}
  .group-compare-books{display:flex; flex-direction:column; gap:20px;}
  /* 펼치기/접기 토글(#group-overlap-toggle)은 공용 .taste-report-toggle(가운데 밑줄
     낱말)을 그대로 따른다 — 취향 리포트와 같은 문법(오너 2026-08-12). 별도 정렬 규칙 없음. */
  /* 멤버 목록과 마찬가지로 가로선 없이 여백으로 가른다(2026-08-21) — 08-13에
     박스를 헤어라인으로 바꿨는데, 5권이면 가로선이 겹겹이 쌓여 목록이 표처럼
     보였다. 표지·별점·키워드는 그대로. */
  .group-compare-book{display:flex; gap:12px;}
  .group-compare-book-cover{flex:none; width:42px; height:60px; object-fit:cover; border:1px solid var(--line); background:var(--paper);}
  .group-compare-book-body{min-width:0;}
  .group-compare-book-title{font-family:var(--serif); font-size:14px; color:var(--ink); margin-bottom:6px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
  /* 별점은 알약 칩 대신 차분한 한 줄 — 6명이 겹치면 칩 테두리가 소음이 된다 */
  .group-compare-book-ratings{display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 7px; font-size:11px; color:var(--ink-soft); line-height:1.6;}
  .group-compare-rating{white-space:nowrap;}
  .group-compare-rating-sep{color:var(--ink-faint);}
  /* 같은 책, 다른 결 — 메모 남긴 멤버의 무드키워드를 이름과 나란히 */
  .group-compare-kws{display:flex; flex-direction:column; gap:4px; margin-top:8px;}
  .group-compare-kw-row{display:flex; align-items:center; flex-wrap:wrap; gap:4px;}
  .group-compare-kw-name{font-size:11px; color:var(--ink-faint); margin-right:3px;}
  .group-compare-kw-chip{font-size:10px; color:var(--moss-ink); background:var(--moss-soft); padding:3px 8px; border-radius:var(--radius-pill);}
  .group-compare-empty{font-size:12px; color:var(--ink-faint);}
  /* 이 탭의 추천 카드는 박스를 벗는다(2026-08-22) — 위 멤버·책 목록이 전부 여백으로
     갈리는 borderless인데 추천만 박스라 탭 끝에서 결이 튀었다. 담아둔 책 탭에선
     화면 전체가 카드라 .rec-card 박스가 맞지만, 여기선 여백에 얹는 게 맞다.
     공유 .rec-card는 그대로 두고 이 컨테이너 안에서만 벗긴다. */
  #group-recommend-results .rec-card{border:none; background:transparent; padding:0; margin-bottom:24px;}
  /* 추천을 찾는 중 — 빈 상태(정적)와 달리 "일하는 중"은 은은히 숨 쉰다
     (대화 대기 점과 같은 어휘). 빈 상태와 시각언어를 나눈다. */
  .group-compare-loading{font-size:12px; color:var(--ink-faint); text-align:center; animation:group-loading-breathe 1.4s ease-in-out infinite;}
  @keyframes group-loading-breathe{0%,100%{opacity:0.5;} 50%{opacity:0.9;}}
  /* 아직 나 혼자인 모임 — 탭 대신 초대 유도 한 자락. 상자 없이 여백으로만. */
  .group-solo{padding:40px 8px; text-align:center;}
  .group-solo-line{font-size:15px; color:var(--ink); margin:0 0 10px;}
  .group-solo-sub{font-size:13px; line-height:1.7; color:var(--ink-faint); margin:0;}
  .exchange-order-item{
    border:1px solid var(--line);
    border-radius:var(--radius);
    padding:12px 14px;
  }
  .exchange-order-item:not(.dragging){transition:transform var(--dur-2) var(--ease-out);}
  .exchange-order-item.has-book{background:var(--moss-soft); border-color:transparent;}
  .exchange-order-item.dragging{
    position:relative;
    z-index:5;
    background:var(--card);
    box-shadow:var(--shadow-card);
  }
  .exchange-order-handle{
    cursor:grab; touch-action:none; color:var(--ink-faint); font-size:16px; user-select:none; line-height:1;
    display:inline-flex; align-items:center; justify-content:center;
    /* 라벨 줄 오른쪽 끝(= 행 우측 상단)으로 — margin-left:auto가 앞을 밀어낸다.
       나머지 세 변은 음수로 두어 44px 터치 영역은 유지한다. */
    padding:12px; margin:-12px -12px -12px auto;
  }
  /* 참여 체크 — 켜면 책 선택·순서가 열리는 참여자 행, 끄면 원 밖.
     브라우저 기본 accent(하늘색)를 벗기고 흰 바탕에 모스 체크로 결을 맞춘다.
     체크 표식은 --moss로 그려 색 토큰의 단일 소스를 지킨다(하드코딩 금지). */
  .exchange-join-input{
    width:16px; height:16px; flex:none; margin:0; cursor:pointer;
    appearance:none; -webkit-appearance:none;
    background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
    display:inline-grid; place-content:center;
  }
  .exchange-join-input::before{
    content:""; width:10px; height:10px; background:var(--moss);
    /* 체크 표식 — 켜졌을 때만 scale로 드러난다 */
    clip-path:polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
    transform:scale(0); transition:transform var(--dur-1) var(--ease-out);
  }
  .exchange-join-input:checked{border-color:var(--moss);}
  .exchange-join-input:checked::before{transform:scale(1);}
  .exchange-join-input:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  /* 참여 안 하는 멤버 — 이름만 담백하게, 원 밖임을 톤 다운으로 보인다. */
  .exchange-member-out{border:1px solid transparent;}
  .exchange-member-out .exchange-round-name{color:var(--ink-faint);}
  .group-space-tabs{display:flex; gap:20px; border-bottom:1px solid var(--line); margin-bottom:16px;}
  .group-space-tab-btn{
    background:none; border:none; font-family:var(--sans); font-size:13px; font-weight:600;
    letter-spacing:0.02em; color:var(--ink-faint); padding:0 0 10px; cursor:pointer;
    border-bottom:2px solid transparent; margin-bottom:-1px;
  }
  .group-space-tab-btn.active{color:var(--ink); border-bottom-color:var(--moss);}
  .group-space-tab-btn:focus-visible{outline:2px solid var(--moss); outline-offset:3px;}

  /* 프레임을 뺐다(2026-08-13) — 배경이 #group-space-view와 같은 값이라 실제로 일하던
     건 선 하나뿐이었다. 게다가 안의 덩어리 셋(라운드 요약·월 격자·일정 폼)은 이미
     각자 헤어라인으로 갈려 있어 박스 경계가 내용 경계와 맞지 않았다.
     폰에서 좌우 패딩까지 빠져 본문 폭이 그만큼 돌아온다. */
  .exchange-calendar{
    margin-bottom:14px;
  }
  .exchange-cal-head{display:flex; align-items:center; justify-content:space-between; margin-bottom:10px;}
  .exchange-cal-month{font-family:var(--serif); font-size:14px; font-weight:600; color:var(--ink);}
  .exchange-cal-nav{
    width:26px; height:26px;
    border:0;
    background:transparent;
    color:var(--ink-soft);
    cursor:pointer;
    font-size:14px;
    line-height:1;
    padding:0;
  }
  .exchange-cal-weekdays{
    display:grid;
    grid-template-columns:repeat(7, 1fr);
    text-align:center;
    font-size:10px;
    color:var(--ink-faint);
    margin-bottom:4px;
  }
  .exchange-cal-grid{display:grid; grid-template-columns:repeat(7, 1fr); gap:4px;}
  .exchange-cal-cell{
    position:relative;
    aspect-ratio:1;
    border:1px solid transparent;
    border-radius:var(--radius);
    background:transparent;
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    cursor:pointer;
    font-family:var(--sans);
    padding:0;
  }
  .exchange-cal-cell.empty{cursor:default;}
  .exchange-cal-day{font-size:12px; color:var(--ink);}
  /* 오늘은 숫자 색만으로는 훑을 때 안 잡혀서 조용한 배경으로 — 셀 강조는
     이 앱에서 selected(모스 채움)가 이미 쓰는 언어라 그 연한 버전을 쓴다. */
  .exchange-cal-cell.today{background:var(--moss-soft);}
  /* 글씨는 --moss가 아니라 --moss-ink다. --moss는 종이 위에서만 충분하고
     --moss-soft 위에서는 4.31:1로 미달한다(tokens.css가 이 경우를 위해 둔 토큰이
     --moss-ink, 여기선 4.62:1). 다른 칩·배지는 전부 지키고 있었는데 이 한 곳만
     빠져 있었다. */
  .exchange-cal-cell.today .exchange-cal-day{color:var(--moss-ink); font-weight:700;}
  .exchange-cal-cell.selected{background:var(--moss); border-color:var(--moss);}
  .exchange-cal-cell.selected .exchange-cal-day{color:var(--paper);}
  /* 일정은 점 대신 그 자리에서 읽히는 시간 라벨(모스) — 무슨 날인지
     눌러봐야 알면 캘린더가 죽어 보인다는 피드백. 교환일 점 표식은
     통일성 문제로 제거(2026-07-18) — 라운드 안내가 그 역할을 한다. */
  .exchange-cal-hints{display:flex; align-items:center; gap:3px; margin-top:1px; max-width:100%; overflow:hidden;}
  .exchange-cal-hint{font-size:9px; line-height:1.2; font-weight:500; white-space:nowrap;}
  .exchange-cal-hint.event{color:var(--moss);}
  .exchange-cal-cell.selected .exchange-cal-hint{color:var(--paper);}
  .exchange-cal-empty{font-size:11px; color:var(--ink-faint); margin-top:10px; text-align:center;}

  /* 좌우 0 — 부모(.exchange-calendar) 패딩이 사라져 형제(라운드 요약·월 격자)와
     좌측 기준선이 하나로 맞는다. 전엔 이 폼만 20px 더 안으로 들어가 있었다. */
  .group-event-panel{padding:28px 0 0;}
  .group-event-head{display:flex; align-items:baseline; justify-content:space-between; gap:12px; margin-bottom:12px;}
  .group-event-date{font-family:var(--serif); font-size:14px; font-weight:600; color:var(--ink);}
  .group-event-time-group{display:flex; align-items:center; gap:6px;}
  .group-event-time-select{flex:1; min-width:0; cursor:pointer;}
  /* 삭제/저장은 같은 필 형식·같은 크기, 채움만 구분(저장=모스, 삭제=아웃라인).
     화면 절반씩 채우던 flex:1은 제거 — 내용 크기로 오른쪽 정렬 */
  .group-event-actions{
    display:flex; align-items:center; justify-content:flex-end; gap:10px;
    margin-top:16px;
  }
  .group-event-actions .social-primary-btn,
  .group-event-actions .group-event-delete{padding:9px 26px; text-align:center;}
  /* 캘린더 담기는 날짜의 짝 — 실행 줄이 아니라 머리행 오른쪽에 두고,
     각주 잉크보다 한 단계 올려 값진 동작임이 보이게 */
  .group-event-gcal{color:var(--ink-soft); text-underline-offset:3px; text-decoration-color:var(--line); flex-shrink:0;}
  /* 저장(social-primary-btn.is-commit)과 한 줄에 놓이는 파괴적/보조 동작 —
     같은 2px 박스로 맞추고, 채움·모스 대신 회색 라인으로 역할만 구분한다
     (btn-add--accent ↔ btn-add--ghost와 같은 쌍 문법). */
  .group-event-delete{
    border:1px solid var(--line);
    background:transparent;
    color:var(--ink-soft);
    border-radius:var(--radius);
    font-family:var(--sans);
    font-size:12px;
    font-weight:600;
    cursor:pointer;
  }

  .exchange-setup-actions{display:flex; justify-content:flex-end; margin-top:14px;}
  /* 캘린더 저장과 같은 크기 — 형제 탭의 동일 라벨 버튼은 같은 무게 */
  #exchange-setup-save-btn{padding:9px 26px;}
  .exchange-control{
    display:block; width:100%; min-width:0; max-width:100%; height:40px; box-sizing:border-box;
    border:none; border-bottom:1px solid var(--line); border-radius:0; background:transparent; color:var(--ink);
    font-size:12px; line-height:1.2; padding:9px 2px;
  }
  .exchange-control:focus{outline:none; border-bottom-color:var(--moss);}
  .exchange-control:focus-visible{outline:2px solid var(--moss); outline-offset:2px;}
  .exchange-control:-webkit-autofill,
  .exchange-control:-webkit-autofill:hover,
  .exchange-control:-webkit-autofill:focus{
    -webkit-text-fill-color:var(--ink);
    caret-color:var(--ink);
    transition:background-color 9999s ease-in-out 0s;
    box-shadow:0 0 0 1000px transparent inset;
  }
  textarea.exchange-control{height:auto; min-height:64px; max-height:none; resize:vertical; line-height:1.5;}
  .exchange-date-control{position:relative; overflow:hidden; padding:0;}
  .exchange-date-control:focus-within{border-bottom-color:var(--moss);}
  .exchange-date-control input[type="date"]::-webkit-date-and-time-value{text-align:left;}
  .exchange-date-control input[type="date"]{
    position:absolute; inset:0; display:block; width:100%; min-width:0; max-width:100%; height:100%;
    box-sizing:border-box; border:0; border-radius:inherit; background:transparent; color:var(--ink);
    font:inherit; line-height:inherit; padding:9px 2px; cursor:pointer;
  }
  .exchange-date-control input[type="date"]:focus{outline:none;}
  /* 시작일·교환주기 값도 세리프 — 같은 설정 박스의 책 목록 입력(세리프 14px)과
     글씨체가 갈린다는 피드백(2026-07-18). 책 제목(600)만큼 무겁진 않게 500.
     날짜 입력은 font:inherit라 래퍼에 걸면 안쪽 값까지 따라온다. */
  .exchange-config-row .exchange-control{font-size:14px; font-weight:500;}

  .exchange-round-rows{display:flex; flex-direction:column; gap:8px; margin-bottom:12px;}
  .exchange-round-row{display:grid; grid-template-columns:minmax(130px, .55fr) minmax(0, 1.45fr); gap:14px; align-items:center;}
  .exchange-round-row.exchange-config-row{grid-template-columns:90px minmax(0, 1fr); gap:14px;}
  .exchange-round-name{font-size:12px; color:var(--ink-soft);}
  /* 책 행만 여러 줄(입력·저자·진행 딱지)이라, 라벨을 열 가운데가 아니라
     제목 첫 줄 옆에 붙인다 — 다른 행은 한 줄이라 가운데=위쪽으로 동일 */
  .exchange-round-row.exchange-book-row{align-items:start;}
  .exchange-book-row .exchange-round-name{margin-top:11px;}
  .exchange-book-picker{display:flex; align-items:flex-start; gap:8px; min-width:0;}
  .exchange-book-search-icon{flex:0 0 auto; width:14px; height:14px; margin-top:11px; color:var(--ink-faint);}
  .exchange-book-field{position:relative; min-width:0; flex:1;}
  .exchange-member-row .exchange-round-book-input{
    height:auto; min-height:auto; max-height:none;
    padding:8px 0; border-bottom:none;
    font-size:14px; font-weight:600; line-height:1.35; color:var(--ink);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  .exchange-book-results{
    display:none; position:absolute; z-index:20; top:calc(100% + 5px); left:0; right:0;
    max-height:260px; overflow:auto; background:var(--card); border:1px solid var(--line);
    border-radius:var(--radius); box-shadow:var(--shadow-card);
  }
  /* 라운드 안내는 표준 시각 언어(모스 산세리프 라벨 + 세리프 본문) —
     취향 흐름 한 줄과 같은 문법 */
  .round-summary{margin-bottom:14px; padding-bottom:14px; border-bottom:1px solid var(--line);}
  /* 돌려주며 한 줄 — 라운드 안내 아래, 모스 라벨 + 세리프 인용 표준 문법 */
  .exchange-note-block{margin-top:14px;}
  .exchange-note-block .round-summary-label{margin-bottom:6px;} /* "내 책에 담긴 한 줄"만 라벨 유지 */
  .exchange-note-row{display:flex; align-items:baseline; gap:8px; margin-bottom:4px;}
  /* 방명록 골격 — 모든 상태가 "이름 + 문장" 로우. 내 이름 옆 빈 자리(한 줄
     남기기…)는 문장이 놓일 자리의 유령 글씨처럼, 고치기는 반 발 물러난 크기로 */
  .exchange-note-invite{
    background:none; border:none; padding:0; cursor:pointer;
    font-family:var(--serif); font-size:13px; color:var(--ink-faint);
    text-decoration:underline; text-underline-offset:3px; text-decoration-color:var(--line);
  }
  .exchange-note-edit{font-size:11px; flex-shrink:0;}
  .exchange-note-row-form{align-items:center;}
  .exchange-note-row-form .exchange-note-form{flex:1; min-width:0; margin-top:0;}
  .exchange-note-name{flex-shrink:0; font-family:var(--sans); font-size:11px; color:var(--ink-faint);}
  .exchange-note-text{font-family:var(--serif); font-size:13px; color:var(--ink-soft); line-height:1.6;}
  .exchange-note-text::before{content:"“ "; color:var(--gold);}
  .exchange-note-text::after{content:" ”"; color:var(--gold);}
  .exchange-note-form{display:flex; gap:8px; align-items:center; margin-top:8px;}
  .exchange-note-form input{
    flex:1; min-width:0; box-sizing:border-box; height:38px;
    border:none; border-bottom:1px solid var(--line); border-radius:0; background:transparent;
    color:var(--ink); font-size:12px; padding:9px 2px;
  }
  .exchange-note-form input:focus{outline:none; border-bottom-color:var(--moss);}
  /* 문패 아래 여백의 연필 자국 — 교환독서 방명록 골격을 그대로 빌리되,
     구분선 없이 간격으로만 띄우고 본문보다 반 발 작은 세리프로 흐린 흑연 톤 */
  .placard-margins{display:flex; flex-direction:column; gap:3px; margin-top:2px;}
  .placard-margins .exchange-note-row{margin-bottom:0;}
  /* 방명록도 카드에선 한 줄만 — 넘치면 말줄임(오너 2026-08-24 "댓글도 한 줄"). 여러 줄
     전체는 '여백 N ›' 시트에서 본다. min-width:0로 flex 트랙이 줄고 ellipsis가 자른다. */
  .placard-margins .exchange-note-text{
    font-size:12px; color:var(--ink-faint);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0;
  }
  /* 카드에 다 안 보이는 여백을 여는 조용한 카운트 — 누르면 여백 시트가 겹쳐 뜬다. */
  .placard-margins-open{
    align-self:flex-start; background:none; border:none; padding:3px 0 0; margin:0; cursor:pointer;
    font-family:var(--sans); font-size:11px; color:var(--moss-ink);
  }
  .placard-margins-open:hover{color:var(--ink);}
  /* 여백 시트 — 남이 내 책에 남긴 한 줄을 넉넉한 여백·세리프 인용으로 읽는다. */
  .margins-sheet-book{font-size:12px; color:var(--ink-faint); margin:-6px 0 16px;}
  .margins-sheet-list{display:flex; flex-direction:column; max-height:56vh; overflow-y:auto;}
  .margins-sheet-entry{padding:13px 0; border-top:1px solid var(--line);}
  .margins-sheet-entry:first-child{border-top:none;}
  .margins-sheet-voice{font-family:var(--sans); font-size:11.5px; color:var(--ink-soft); letter-spacing:.02em; margin-bottom:5px;}
  .margins-sheet-quote{font-family:var(--serif); font-size:15px; color:var(--ink); line-height:1.7;}
  .margins-sheet-quote::before{content:"“ "; color:var(--gold);}
  .margins-sheet-quote::after{content:" ”"; color:var(--gold);}
  .placard-margins .exchange-note-name{font-size:10px;}
  .placard-margins .exchange-note-invite{font-size:12px;}
  /* 좁은 문패 폭 — 입력칸이 한 줄을 다 쓰고 남기기 버튼은 아랫줄로 */
  .placard-margins .exchange-note-form{margin-top:0; flex-wrap:wrap; justify-content:flex-end;}
  .placard-margins .exchange-note-form input{flex:1 1 100%; height:34px;}
  /* 주인의 ✕ — placard-del보다 옅고 작게, 확인 모달이 오터치를 막는다 */
  .placard-margin-del{
    background:none; border:none; padding:0 2px; cursor:pointer;
    color:var(--ink-faint); opacity:.55; flex-shrink:0; margin-left:auto;
    display:inline-flex; align-items:center; line-height:1;
  }
  .placard-margin-del svg{ width:12px; height:12px; }
  .round-summary-label{font-family:var(--sans); font-size:11px; font-weight:600; letter-spacing:0.1em; color:var(--ink-faint); margin-bottom:4px;}
  .round-summary-line{font-family:var(--serif); font-size:14px; color:var(--ink); line-height:1.5;}
  .round-summary-line strong{font-weight:600;}
  /* 일정에 붙은 책의 멤버별 진행 상태 — 완독만 모스, 나머지는 조용한 테두리 칩 */
  .group-event-book-progress{display:flex; flex-wrap:wrap; gap:6px; margin-top:12px;}
  .group-event-book-chip{font-size:11px; color:var(--ink-faint); background:var(--paper); border:1px solid var(--line); border-radius:var(--radius-pill); padding:4px 9px;}
  .group-event-book-chip.done{color:var(--moss-ink); background:var(--moss-soft); border-color:transparent;}
  .exchange-book-results.open{display:block;}
  .exchange-book-results .search-result{padding:9px 10px;}
  .exchange-book-selected{font-size:11px; color:var(--ink-soft); margin-top:4px; line-height:1.4; min-height:1.4em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}

  /* 모임 상세는 메인 탭 바깥의 독립 화면이므로, 입력 방식과 무관하게 화면 너비로 반응한다. */
  @media (max-width:639.98px){
    #group-space-view{
      width:100%;
      padding:
        calc(24px + env(safe-area-inset-top))
        calc(16px + env(safe-area-inset-right))
        calc(36px + env(safe-area-inset-bottom))
        calc(16px + env(safe-area-inset-left));
      overflow-x:hidden;
    }
    /* 숨 고르기(2026-07-18): 폰은 여백이 세로 리듬에서만 나오는데 그동안
       모바일 규칙이 전부 "줄이기"라 데스크톱보다 오히려 촘촘했다 — 섹션
       사이를 데스크톱(20px)보다 넓게 벌려 한 박자씩 쉬게 한다. */
    /* 머리 영역 숨(2026-07-18): 돌아가기→모임 이름→서재 칩→탭이 붙어
       보여서 페이지 전체가 빽빽하게 읽힌다는 피드백 — 머리 간격을
       데스크톱(20/20/30/16)과 같거나 넓게. */
    #group-space-view .review-writer-back{margin-bottom:24px;}
    #group-space-view .review-writer-head{margin-bottom:24px;}
    .group-space-tabs{margin-bottom:22px;}
    #group-space-view .review-writer-title{font-size:20px; line-height:1.35; overflow-wrap:anywhere;}
    #group-space-view .group-space-title-input{font-size:20px;}
    #group-space-view .social-section{margin-top:28px;}
    /* :not 둘은 캐스케이드 방어 — 이 규칙이 뒤에 선언돼서, 여백 0인
       first/books 변형까지 덮어쓰지 않게 제외한다 */
    .exchange-subsection:not(.exchange-subsection-first):not(.exchange-books-subsection){margin-top:22px; padding-top:22px;}
    /* 멤버 사이는 컨테이너 gap(16px)만으로 가른다 — 행에 padding:14px 0까지 있어
       사람 사이가 44px로 벌어졌다(오너 2026-08-24 "사람 사이 여백 너무 넓어"). */
    .group-compare-member-row{padding:0;}
    /* 장르 칩은 폰에서 감지 않는다 — "4개는 윗줄, 1개는 아랫줄"처럼 낙오
       칩이 덜렁 남는 모양이 지저분하다는 피드백(2026-07-18). member-strip과
       같은 문법으로 한 줄 스와이프(스크롤바 숨김). */
    .group-compare-member-sub{flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch;}
    .group-compare-member-sub::-webkit-scrollbar{display:none;}
    .group-compare-member-sub .taste-chip{flex:0 0 auto;}
    .group-compare-member-meta{flex:0 0 auto;}
    #group-space-view .member-strip{
      flex-wrap:nowrap; overflow-x:auto; margin:0 -16px 30px; padding:0 16px 4px;
      scrollbar-width:none; -webkit-overflow-scrolling:touch;
    }
    #group-space-view .member-strip::-webkit-scrollbar{display:none;}
    #group-space-view .member-plate{flex:0 0 auto; min-height:var(--tap);}
    .exchange-order-list{gap:5px;}
    .exchange-order-item{padding:10px 12px;}
    .exchange-round-rows{gap:14px;}
    .exchange-round-row{grid-template-columns:minmax(0, 1fr); gap:6px; align-items:stretch;}
    .exchange-round-row.exchange-config-row{grid-template-columns:76px minmax(0, 1fr); gap:10px; align-items:center;}
    /* 책 행은 딱지 때문에 세로로 길어 라벨을 제목 첫 줄에 붙인다(모바일) */
    .exchange-round-row.exchange-book-row{align-items:start;}
    .exchange-book-row .exchange-round-name{margin-top:13px;}
    .exchange-round-row.exchange-member-row{grid-template-columns:76px minmax(0, 1fr); gap:10px; align-items:start;}
    .exchange-member-row .exchange-member-label{padding-top:8px;}
    .exchange-round-name{font-size:11px; font-weight:600; color:var(--ink-soft);}
    .exchange-member-row .exchange-round-name{white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
    .exchange-control,
    .exchange-round-book-input{width:100%; height:var(--tap); min-height:var(--tap); max-height:var(--tap); margin:0; font-size:16px; line-height:20px; padding:10px 2px;}
    /* 위 16px(iOS 확대 방지)가 세리프 규칙(.exchange-config-row .exchange-control,
       특이도가 더 높음)에 지지 않게 같은 특이도로 한 번 더 */
    .exchange-config-row .exchange-control{font-size:16px;}
    /* 멤버 행 책 입력도 마찬가지 — base override(.exchange-member-row …, 특이도가
       더 높음)의 데스크톱용 height:auto·14px가 위 44px·16px 통일을 이겨서, 이
       입력만 형제보다 낮고 14px라 iOS에서 포커스 시 확대됐다. 높이·글자·패딩만
       같은 특이도로 되돌리고 세리프 책제목 느낌(font-family·굵기·말줄임)은 남긴다. */
    .exchange-member-row .exchange-round-book-input{
      height:var(--tap); min-height:var(--tap); max-height:var(--tap);
      font-size:16px; padding:10px 2px;
    }
    .exchange-date-control input[type="date"]{padding:10px 2px;}
    /* 한 줄 로우: 폰에선 이름(+고치기)이 윗줄, 문장이 아랫줄 전체 폭 —
       이름과 고치기 사이에 문장이 끼어 좁은 기둥으로 감기는 모양이
       지저분하다는 피드백(2026-07-18). 고치기는 이름 옆에 그대로. */
    .exchange-note-row{flex-wrap:wrap; row-gap:2px;}
    .exchange-note-row .exchange-note-text{flex-basis:100%; order:2;}
    .exchange-note-row-form .exchange-note-form{flex:1 1 100%;}
    .exchange-book-results{position:relative; top:auto; margin-top:5px; max-height:240px; box-shadow:none;}
    .exchange-book-results .search-result{min-height:60px; padding:10px;}
    .exchange-setup-actions{margin-top:16px;}
    #exchange-setup-save-btn{width:auto; min-width:0; min-height:0; padding:9px 20px; font-size:12px;}
    /* 카드 좌우 8px는 달력 칸 공간용이라 좁다 — 라운드 안내만 제 여백을 갖고,
       세리프 본문도 폰에선 한 단계 줄인다("너무 크고 왼쪽에 붙어 보인다" 피드백) */
    .round-summary{padding:2px 8px 14px; margin-bottom:16px;}
    .round-summary-line{font-size:13px;}
    .exchange-cal-grid{gap:1px;}
    .exchange-cal-cell{min-width:0;}
    .exchange-cal-day{font-size:11px;}
    .group-event-panel{padding:20px 0 0;}
    /* 장르 칩이 여러 줄로 감기는 폰 폭에선 오른쪽 위에 핀된 닮은꼴이
       아무것에도 안 붙은 채 허공에 떠 보인다 — 이름 아래 제 줄로 내려
       왼쪽 정렬로 잇는다. */
    .group-compare-member-top{flex-wrap:wrap;}
    .group-compare-member-match{flex-basis:100%;}
  }

  /* ---------- 설명 문장의 줄바꿈 (폭과 무관) ----------
     설명문이 "기준이 바뀌면 전부 / 새로 붙일 수 있어요"처럼 뜻이 끊기는
     자리에서 감겼다. text-wrap:balance가 줄 길이를 고르게 맞추면서 끊는
     자리를 문장·절 경계로 옮긴다(실측: 위 예문이 "…근거로 써요. /
     기준이 바뀌면…"로). 지원 안 하는 브라우저는 무시 — 지금 동작 그대로다.
     좁은 화면에서 두드러지지만 원인은 폭이 아니라 한국어 줄바꿈이라
     데스크톱에도 같이 건다(오너 지시 2026-07-27). 한 줄로 끝나는 곳에선
     balance가 하는 일이 없어 손해도 없다.
     낱말 안에서 끊기는 것(복원 / 할)은 body의 전역 keep-all이 이미 막고
     있다 — 여기에 다시 걸지 않는다(전역 규칙 주석 참고).
     여러 줄로 긴 본문(taste-report-text)과 줄 수를 자르는 rec-reason은
     balance가 듣지 않거나 line-clamp와 얽혀서 뺐다. */
  .settings-row-desc,
  .recommend-intro,
  .deep-dive-desc,
  .confirm-modal-desc,
  .recover-modal-desc,
  .recover-item-memo,
  .milestone-modal-desc,
  .exchange-cal-empty,
  .empty-state{text-wrap:balance;}
  #settings-view .settings-row-desc{text-wrap:pretty;}

  /* ---------- 반응형 브레이크포인트 구성 ----------
     아직 데스크톱 우선 구조라 모바일 전용 규칙들이 아래 블록들에 모여있다(순서는
     원래 캐스케이드를 안 건드리기 위해 유지, 물리적 위치만 모바일 관련끼리 인접시킴):
       1) @media (max-width:639.98px) — 모임 스페이스 레이아웃
       2) @media (max-width:639.98px) and (pointer:coarse) — 폰 전용(하단 탭바/터치 타깃)
       3) @media (min-width:640px) / (min-width:1024px) — 태블릿+ 그리드 확장
       4) @media (hover:hover) / (hover:none) — 포인터 종류별 상호작용(폭과 무관) */

  /* ---------- 모바일 콘텐츠 편집(<640px) ----------
     데스크톱 화면은 그대로 두고, 좁은 화면에서만 정보 밀도와 읽기 흐름을
     다시 편집한다. pointer 종류와 무관하게 적용해 모바일 브라우저와
     반응형 미리보기에서 같은 결과를 보장한다. */
  @media (max-width:639.98px){
    .app{
      padding-left:calc(16px + env(safe-area-inset-left));
      padding-right:calc(16px + env(safe-area-inset-right));
      overflow-x:hidden;
    }

    /* 홈: 브랜드보다 바로 기록하는 행동이 먼저 눈에 들어오도록 머리 영역을
       압축하되, 데스크톱의 구성과 문구는 건드리지 않는다. */
    .hero{margin-bottom:24px;}
    .hero-eyebrow{font-size:9px; letter-spacing:.18em;}
    .hero h1{font-size:clamp(30px, 10vw, 38px); line-height:1.05;}
    .add-bar{gap:10px;}
    /* 칸의 치수는 아래 '검색은 선 한 겹' 블록이 잡는다(칸 44px · 입력창 42px).
       여기 있던 48px·12px 패딩이 id 우선순위로 그걸 덮어써서, 입력창이 칸보다
       4px 높아지고 글자가 칸 한가운데보다 3px 내려앉아 있었다 — 치수는 넘기고
       iOS가 탭할 때 확대하지 않게 하는 16px만 남긴다.
       안내 문구는 아직 아무것도 안 한 상태의 속삭임이라 한 단계 더 물린다. */
    #title-input{font-size:16px;}
    #title-input::placeholder{opacity:.7;}
    .search-results{max-height:min(52vh, 360px);}

    /* 서재가 아닌 탭은 매번 큰 브랜드 헤더를 다시 보여주지 않는다.
       기능은 그대로 두고 워드마크·권수만 한 줄로 축약해 실제 콘텐츠가
       첫 화면 안에 더 빨리 들어오게 한다. 데스크톱에는 영향 없음. */
    body.tab-dashboard-active .hero,
    body.tab-wishlist-active .hero,
    body.tab-social-active .hero{
      padding-bottom:8px;
      margin-bottom:18px;
    }
    body.tab-dashboard-active .hero-eyebrow,
    body.tab-wishlist-active .hero-eyebrow,
    body.tab-social-active .hero-eyebrow{display:none;}
    body.tab-dashboard-active .hero h1,
    body.tab-wishlist-active .hero h1,
    body.tab-social-active .hero h1{font-size:26px; line-height:1;}
    .hero-count-inline .num{font-size:18px;}
    .hero-count-inline .label{font-size:11px;}
    /* 취향 화면에서 책 추가 검색은 분석 콘텐츠와 무관하다. 서재와 담아둔
       책에서는 계속 보여 기존 검색/추가 흐름을 보존한다. */
    body.tab-dashboard-active .add-bar{display:none;}

    /* 서재 도구는 두 줄로 분리한다. 검색창과 정렬 버튼이 한 줄 폭을
       서로 빼앗지 않아 320px에서도 글자가 눌리지 않는다. */
    .library-toolbar{align-items:stretch; flex-direction:column; gap:10px; margin-bottom:22px;}
    /* 폰에선 탭 바가 하단으로 내려가서 기록 카드 바로 아래에 이 검색창이
       붙는데, 둘 다 같은 테두리·같은 배경의 전폭 상자라 "기록하는 자리"와
       "찾는 자리"의 위계가 흐려졌다(오히려 알약 윤곽이 더 또렷했다).
       상자를 벗겨 가는 밑줄만 남기면 떠 있는 면은 기록 카드 하나뿐이 되고,
       검색은 아래 서재에 딸린 도구로 물러난다. 데스크톱은 탭 바가 둘 사이에
       끼어 있고 검색이 작은 알약이라 그대로 둔다. */
    .library-search-input{
      width:100%; min-height:var(--tap); font-size:16px;
      background:transparent;
      border:none; border-bottom:1px solid var(--line); border-radius:0;
      padding:10px 2px;
    }
    /* 정렬은 서재를 보는 방식일 뿐인데, 폰에선 전폭 2등분에 선택된 쪽이
       채운 모스라 화면에서 가장 무거운 덩어리가 됐다 — 기록 카드보다 먼저
       눈에 들어와 위계가 뒤집혔다. 상자를 벗겨 글자만 남기고 선택은 색과
       굵기로만 말한다. 데스크톱은 11px 작은 알약이라 그대로 둔다. */
    /* 오른쪽 정렬 — 데스크톱은 검색창이 flex:1로 늘어나며 정렬을 이미 오른쪽
       끝으로 밀어낸다. 폰에선 세로로 쌓이면서 그 관계가 끊겨 왼쪽에 남아
       있었다(2026-07-27 피드백). 좌우 6px는 "평점순"(글자 폭 33px)의 히트
       영역을 44px로 채우는 몫이라, 그만큼 오른쪽으로 밀어 마지막 글자가
       검색창 오른쪽 끝과 같은 선에 서게 한다. gap 8px는 그 패딩과 합쳐
       눈에 보이는 사이를 20px로 만든다. */
    .sort-btns{display:flex; gap:8px; align-self:flex-end; margin-right:-6px;}
    .sort-btn{
      min-height:var(--tap); padding:0 6px; font-size:12px;
      background:transparent; border:none; border-radius:0;
      color:var(--ink-faint);
    }
    /* 채움을 거는 기본 .sort-btn.active(특이도 0,2,0)를 같은 특이도로 되돌린다 */
    .sort-btn.active{background:transparent; color:var(--moss); font-weight:600;}
    /* 폰에선 테두리를 빼 도구 줄에서 안 튀게(아이콘만이라 더 조용하다).
       히트 영역만 44px로 채운다. */
    .library-share-btn{
      min-height:var(--tap); padding:0 6px;
      background:transparent; border:none; border-radius:0;
      color:var(--ink-faint);
      margin-left:auto; /* 줄이 바뀌면 오른쪽 끝에 — 검색·정렬 줄의 오른쪽 선과 맞는다 */
    }
    .library-share-btn[hidden]{display:none;}
    /* 이 줄은 이미 검색+정렬로 꽉 차 있었다 — 320px에서 넷째 칸을 같은 줄에 두면
       flex가 검색창을 57px까지 줄여버린다(2026-07-30 실측). 검색창의 최소 폭을
       못박아 대신 공유 아이콘이 다음 줄로 내려가게 한다. 넓은 폰에선 한 줄에 다 선다. */
    .library-toolbar{flex-wrap:wrap;}
    .library-search-input{min-width:120px;}
    /* 1열이라 카드끼리 높이를 맞출 이웃이 없다 — auto로 두면 각 카드가 제 콘텐츠
       높이가 된다(1fr은 모든 카드를 가장 큰 카드 높이로 부풀린다, 오너 2026-08-24). */
    /* 1열에선 카드를 콘텐츠 높이로 둔다(오너 2026-08-24). 높이를 통일(1fr)하면 가장
       바쁜 카드(방명록 많은 책 등)가 모든 카드의 높이를 끌어올려 단순한 책 카드까지
       길어지는 논리적 모순이 생긴다 — 카드마다 높이는 달라지되 여백 0으로 촘촘히 둔다.
       (방명록은 카드에 2개까지만 보이고 나머지는 '여백 N ›' 시트라 무한정 길어지진 않는다.) */
    /* 모바일 서재 카드 여백은 아래 §max-width:639.98px 블록(표지 72×100·패딩을 쥔
       곳)에서 함께 손본다 — 여기서 gap만 넉넉히 둔다(오너 2026-08-24). 1열이라 높이가
       콘텐츠에 맞춰져(auto) PC처럼 조일 이유가 없다. */
    .gallery-grid{grid-template-columns:minmax(0, 1fr); gap:16px; grid-auto-rows:auto;}
    .gallery-grid > .placard{height:auto;}
    .placard{padding:16px; gap:12px;}
    .placard-title{min-height:0;}
    .placard-title-text{font-size:16px;}
    .placard-author{font-size:12px; margin-top:-3px;}
    .placard-foot{gap:12px;}

    /* 취향/추천: 설명문은 넉넉한 행간으로 읽히게 하고, 칩은 한 줄
       가로 스크롤로 두어 마지막 칩 하나만 다음 줄에 남는 현상을 없앤다.
       (대시보드 세로 리듬 토큰은 아래 'Mobile dashboard rhythm' 한 곳에 단일화 —
        여기 있던 dash-group/chart-block/label 간격은 그 블록에 덮여 사문이라 제거) */
    .recommend-intro{font-size:14px; line-height:1.75; margin-bottom:16px;}
    .taste-summary{
      flex-wrap:nowrap;
      overflow-x:auto;
      margin:0 -16px 18px;
      padding:0 16px 4px;
      scrollbar-width:none;
      -webkit-overflow-scrolling:touch;
    }
    .taste-summary::-webkit-scrollbar{display:none;}
    .taste-summary > *{flex:0 0 auto;}

    /* 추천 결과는 표지와 핵심 문장을 한 덩어리로 읽고, 행동은 카드 아래
       전폭으로 분리한다. 이유는 모바일에서 세 줄까지만 보여 밀도를 낮춘다. */
    .rec-card{
      grid-template-columns:60px minmax(0, 1fr);
      gap:14px;
      padding:16px;
      margin-bottom:12px;
      height:auto;
    }
    .rec-cover{width:60px; height:84px;}
    .rec-title{font-size:16px; line-height:1.4; min-height:0;}
    .rec-author{margin-bottom:8px;}
    .rec-reason{-webkit-line-clamp:3; font-size:13px; line-height:1.65;}
    .rec-meta{white-space:normal; line-height:1.45; text-wrap:pretty;} /* 알라딘 평점이 홀로 다음 줄에 떨어지지 않게 */
    .rec-actions{grid-column:1 / -1; width:100%; padding-top:12px;}
    .rec-actions > button{
      flex:1 1 0;
      min-height:42px;
      padding:9px 12px;
      font-size:12px;
    }

    /* 상세 성격의 카드와 AI 대화 진입부는 한 화면에서 문장이 빽빽하게
       붙지 않도록 안쪽 여백과 버튼 흐름을 모바일에 맞춘다. */
    .deep-dive-block{margin-top:32px;}
    /* 회상 카드는 서재 맨 위 — 위 여백 대신 아래(툴바와 사이)로 준다. */
    .memory-lane{padding:18px 16px; margin:0 0 20px;}
    .deep-dive-desc{font-size:14px; line-height:1.7;}
    .deep-dive-cta-row{width:100%; margin-left:0;}
    .deep-dive-cta-row .social-primary-btn{width:100%; min-height:var(--tap);}
    /* 예시 배너의 두 버튼(둘러보기·내 서재로 돌아가기)은 같은 폭으로 가운데 —
       텍스트 길이가 달라 크기가 들쭉날쭉하던 것 통일(오너 지시 2026-08-07). */
    .demo-banner-actions{width:100%; justify-content:center;}
    .demo-banner-actions button{flex:1 1 0; max-width:200px;}
    /* 서재 빈 화면 두 버튼은 풀폭·동일폭·가운데 — 주(책 기록하기)는 채움, 보조
       (예시 서재 구경)는 덜 시끄러운 아웃라인으로(오너 지시 2026-08-07). */
    .onboarding-action{width:100%;}
    #onboarding-add-btn{width:100%; text-align:center;}
    /* 히트영역 규칙(하단 pointer:coarse)이 이 버튼을 inline-flex로 만들어 text-align이
       안 먹는다 — 풀폭에서 글씨를 가운데로 두려면 justify-content가 필요하다. */
    #onboarding-demo-btn{width:100%; max-width:none; margin:0; justify-content:center;}
    .taste-report-text{font-size:14px; line-height:1.8;}
    .taste-report-text.collapsed{max-height:12em;}
    /* 가운데 밑줄 낱말 — 손끝 타깃을 위해 세로 여백만 넉넉히(가로 배치는 기본 규칙). */
    #tab-dashboard .taste-report-toggle{
      min-height:40px;
      margin-top:14px;
      padding:8px 12px;
    }

    /* 모임 탭은 카드만 바로 나오면 기능을 처음 보는 사용자가 맥락을 알기
       어렵다. 설명은 모바일에만 한 줄 보이고, 기존 데스크톱 구성은 유지한다. */
    #tab-social .group-card{position:relative; padding-right:42px;}
    #tab-social .group-card-open::after{
      content:"›";
      position:absolute;
      right:16px;
      top:50%;
      transform:translateY(-50%);
      color:var(--ink-faint);
      font-family:var(--sans);
      font-size:22px;
      line-height:1;
    }

    /* 모임 추천은 입력 흐름을 시작하는 주 액션이라 아래 전폭 배치를 유지한다. */
    .exchange-subsection--recommend .exchange-subsection-header{flex-direction:column; align-items:stretch; gap:10px;}
    #group-recommend-btn{width:100%; min-height:var(--tap);}

    /* 리뷰 작성 화면 — 폰에선 긴 책 제목이 22px 그대로면 좁은 폭에서 몇
       글자씩 어색하게 감긴다. 모임 상세의 제목 처리와 같은 결로 한 단계
       줄이고 어디서든 줄바꿈되게 한다. 머리 영역도 다른 화면들처럼 한 박자
       넓혀 돌아가기→제목→메모가 붙어 읽히지 않게 한다. */
    #review-writer-view .review-writer-back{margin-bottom:24px;}
    #review-writer-view .review-writer-head{margin-bottom:24px;}
    #review-writer-view .review-writer-title{font-size:20px; line-height:1.35; overflow-wrap:anywhere;}
    /* '독후감 생성'은 이 화면의 hero — 폰에서도 엄지로 누르기 좋게 44px 보장 */
    .review-generate-hero{min-height:var(--tap);}
  }

  /* ---------- 폰(<640px, 터치 기기): 하단 탭바, 폼 세로 스택, 터치 타깃 확대 ----------
     pointer:coarse를 같이 걸어서, 데스크톱 브라우저 창을 좁게 줄인 것만으로는
     모바일 레이아웃(하단 탭바)로 안 바뀌게 한다. 실제 터치 기기에서만 적용. */
  @media (max-width:639.98px) and (pointer: coarse){
    .app{padding-bottom: calc(var(--bottomnav-h) + 28px + env(safe-area-inset-bottom));}

    /* 목록 맨 위에서 실수로 당겨도 전체 리로드(당겨서 새로고침)가 일어나지
       않게 막는다 — 설치 앱에서 갑자기 웹사이트처럼 보이던 순간. */
    html, body{overscroll-behavior-y:contain;}

    /* 확인 모달: 데스크톱 중앙 팝업 대신 네이티브 앱처럼 하단에서 올라오는 바텀시트로 */
    .confirm-modal-overlay{align-items:flex-end; padding:0;}
    .confirm-modal{
      max-width:none;
      width:100%;
      border-radius:var(--radius-sheet) var(--radius-sheet) 0 0;
      border-bottom:none;
      padding:20px 20px calc(20px + env(safe-area-inset-bottom));
      transform:translateY(100%);
    }
    .confirm-modal-overlay.open .confirm-modal{transform:translateY(0);}

    /* 완독 마일스톤 팝업도 확인 모달과 같은 바텀시트 처리 */
    .milestone-modal-overlay{align-items:flex-end; padding:0;}
    .milestone-modal{
      max-width:none;
      width:100%;
      border-radius:var(--radius-sheet) var(--radius-sheet) 0 0;
      border-bottom:none;
      padding:28px 20px calc(28px + env(safe-area-inset-bottom));
      transform:translateY(100%);
    }
    .milestone-modal-overlay.open .milestone-modal{transform:translateY(0);}

    /* 의견 보내기도 같은 바텀시트 — 폰에선 긴 글을 쓰는 동안 키보드가 올라오는데,
       중앙 팝업은 그 키보드에 가려 입력칸이 밀려 올라간다. 하단에 붙여두면
       시트 위쪽이 그대로 남아 쓰던 글이 보인다. */
    .feedback-modal-overlay{align-items:flex-end; padding:0;}
    .feedback-modal{
      max-width:none;
      width:100%;
      border-radius:var(--radius-sheet) var(--radius-sheet) 0 0;
      border-bottom:none;
      padding:24px 20px calc(24px + env(safe-area-inset-bottom));
      transform:translateY(100%);
    }
    .feedback-modal-overlay.open .feedback-modal{transform:translateY(0);}

    /* 되살릴 메모도 같은 바텀시트. 목록이 스크롤되는 시트라 높이는 화면
       기준으로 잡는다(기본 max-height:80vh는 세로로 긴 폰에서 너무 낮게 뜬다). */
    .recover-modal-overlay{align-items:flex-end; padding:0;}
    .recover-modal{
      max-width:none;
      width:100%;
      max-height:85vh;
      border-radius:var(--radius-sheet) var(--radius-sheet) 0 0;
      border-bottom:none;
      padding:24px 20px calc(24px + env(safe-area-inset-bottom));
      transform:translateY(100%);
    }
    .recover-modal-overlay.open .recover-modal{transform:translateY(0);}

    /* 알림 패널: 상단 드롭다운 대신 하단 시트로, 벨 아이콘 위치와 무관하게 화면 하단 고정 */
    .notif-panel{
      position:fixed;
      top:auto; left:0; right:0; bottom:0;
      width:100%;
      max-width:none;
      border-radius:var(--radius-sheet) var(--radius-sheet) 0 0;
      border-left:none; border-right:none; border-bottom:none;
      padding-bottom:env(safe-area-inset-bottom);
      transform:translateY(100%);
      z-index:60;
    }
    .notif-panel.open{transform:translateY(0);}
    /* 시트가 열려 있는 동안 하단 탭바는 화면 밖으로 내린다 — 시트(z-index:60)가
       탭바(z-index:50) 위에 얹히면서 아이콘·라벨이 시트 밑으로 비쳐 보이던 문제.
       키보드 대응(app.js)이 인라인 transform을 쓰는데, 인라인이 ''로 리셋돼도
       이 클래스 규칙은 살아 있어서 시트가 열린 동안엔 탭바가 다시 올라오지 않는다. */
    body.notif-open .tabs{transform:translateY(100%);}

    /* 머리 영역 숨 — 워드마크 배너 아래도 폰에선 데스크톱(22px)보다 넓게.
       아이콘 배치(아이브로 줄로 올림)는 아래 grid 블록의 .hero-actions가 맡는다. */
    .hero{position:relative; padding-bottom:14px; margin-bottom:28px;}
    .hero-count-inline .num{font-size:22px;}
    .hero-count-inline .label{font-size:13px;}

    /* 하단 고정 탭바: 네이티브 앱처럼 아이콘 위·라벨 아래, 화면 최하단 고정 */
    .tabs{
      position:fixed; left:0; right:0; bottom:0; z-index:50;
      display:grid; grid-template-columns:repeat(4, 1fr); gap:0;
      margin-bottom:0; border-bottom:none; border-top:1px solid var(--line);
      background:var(--card);
      /* 실측 결과 탭바 자체는 항상 화면 맨 아래(bottom:0)에 정확히 붙어 있었다 --
         "떠 보인다"는 인상의 실제 원인은 위치가 아니라, 홈 인디케이터를 피하려고
         넣은 세이프에어리어 하단 패딩(약 40px) 구간이었다. 그 구간 배경(--card)이
         페이지 배경(--paper)이랑 색이 거의 같아서, 아이콘·라벨 줄과 화면 끝 사이에
         빈 여백이 떠 있는 것처럼 보였다. 그림자를 뚜렷하게 줘서 세이프에어리어
         구간까지 포함한 탭바 전체가 하나의 판으로 화면에 들러붙어 보이게 한다. */
      box-shadow:var(--shadow-topbar);
      padding:6px calc(4px + env(safe-area-inset-right)) calc(6px + env(safe-area-inset-bottom)) calc(4px + env(safe-area-inset-left));
      transition:transform var(--dur-2) var(--ease-out);
    }
    .tab-btn{
      display:flex; flex-direction:column; align-items:center; justify-content:center;
      gap:3px; padding:6px 2px; margin-bottom:0; border-bottom:none; font-size:10.5px;
    }
    .tab-btn.active{color:var(--moss); border-bottom-color:transparent;}
    .tab-icon{display:block;}

    .settings-row{padding:20px 0;}
    .settings-row-actions{flex-wrap:wrap; row-gap:10px;}
    .settings-key-field{flex-direction:column; align-items:stretch; flex-basis:100%;}
    .settings-key-field input{width:100%; flex:1 1 auto;}
    .settings-provider-select{width:100%;}
    .settings-data-row .settings-row-actions{width:100%; flex:1 1 100%; justify-content:flex-end;}
    #tab-dashboard .chart-block{border-top:none; padding:0; margin:0;}
    /* 세로 리듬 토큰(묶음/블록 간격)은 아래 'Mobile dashboard rhythm' 한 곳에
       단일화 — 여기 있던 coarse 전용 재정의는 같은 값으로 덮여 no-op이라 제거. */
    #tab-dashboard .panel-title,
    #tab-dashboard .panel-title-row .panel-title{
      font-size:16px;
      margin-left:0;
    }
    #tab-dashboard .panel-title-row{margin-bottom:16px;}
    /* 폰에선 캡션이 제목 옆에 못 들어가고 감기는데, 감긴 줄이 오른쪽
       정렬이면 붕 떠 보인다 — 제목 아래 부제처럼 왼쪽 정렬로 */
    #tab-dashboard .taste-recommend-section{border-top:none; padding:0; margin:0;}
    .social-actions{grid-template-columns:1fr;}
    .social-inline-form{flex-direction:column; align-items:stretch;}
    .social-primary-btn{width:100%;}
    /* 위 width:100%가 "함께 읽을 책" 헤더(텍스트+버튼 한 줄 플렉스)의
       버튼에도 걸리면 제목·설명이 좌측 좁은 기둥으로 찌그러진다 —
       모바일에선 텍스트 아래 꽉 찬 버튼으로 세로 스택. */
    .exchange-subsection-header{flex-direction:column; align-items:stretch; gap:10px;}
    /* 일정 폼의 삭제/저장도 width:100% 예외 — 둘이 같은 크기로 오른쪽 정렬 유지.
       터치 타깃을 44px로 고정하면 짧은 라벨이 뭉툭한 정사각형처럼 보여서
       (비율 지적) 콘텐츠 크기 그대로 두고 여백만 여유 있게 준다. */
    .group-event-actions .social-primary-btn,
    .group-event-actions .group-event-delete{width:auto; min-height:0; padding:9px 20px;}
    /* 돌려주며 한 줄 폼도 width:100% 예외 — 입력칸 옆 내용 크기 버튼 유지 */
    .exchange-note-form .social-primary-btn{width:auto; min-height:var(--tap);}
    /* 입력칸(base 38px)을 옆 버튼과 같은 44px로 — 나란히 놓일 때 높이가 어긋나
       보이던 것과 손끝 타깃(<44px)을 함께 해소. placard-margins의 옅은 연필
       자국(34px·전폭)은 의도된 처리라 그대로 둔다. */
    .exchange-note-form input{height:var(--tap);}
    /* 카드 안쪽 여백은 폰에서 깎지 않는다 — 글이 벽에 붙으면 급해 보인다 */
    .group-card{padding:18px 16px 12px;}
    .group-card-name{font-size:16px;}

    /* 책 기록하기 박스: 데스크톱은 검색창과 다 읽음 후 평점·날짜·버튼이 한 줄에
       다 들어가지만 모바일은 줄바꿈되면서 순서가 뒤죽박죽 보였음 — 책 선택 후
       나오는 액션 3개는 꽉 찬 3등분 세그먼트로, 평점+날짜는 한 줄로 묶고
       기록하기는 모바일 앱 흔한 패턴대로 꽉 찬 버튼으로 분리 */
    /* 아래 44px는 데스크톱 기준 — 폰은 탭바가 하단에 고정이라 입력창 아래가 휑해 보임 */
    .add-bar{flex-direction:column; align-items:stretch; margin-bottom:32px;}

    .add-bar-choices{display:grid; grid-template-columns:repeat(3, 1fr); gap:6px;}
    .add-choice-btn{padding:10px 4px; font-size:11.5px; text-align:center; white-space:normal; line-height:1.25;}

    .add-bar-finish{gap:14px;}
    #read-month-input{flex:0 0 auto;}
    #star-input{flex:0 0 auto; margin-left:auto;}
    #add-btn{flex:1 1 100%; width:100%; margin-top:2px;}

    .stars-display{gap:5px;}
    .stars-display .star{width:26px; height:26px; font-size:26px;}
    .placard-rating-num{font-size:13px;}

    .placard-top-actions{top:15px; right:14px; gap:14px;}
    .placard-del, .placard-review-btn, .review-ai-edit-btn{
      font-size:20px; padding:4px;
      min-width:var(--tap); min-height:var(--tap);
      display:inline-flex; align-items:center; justify-content:center;
    }
    /* 메모 아이콘은 하단 상태/별점 줄에 인라인이라, 히트영역 폭까지 44px로 넓히면
       좁은 2열(가로 폰)에서 줄이 넘친다 — 세로 44px 타깃은 유지하되 폭만 푼다. */
    /* 하단 pill은 '다 읽음' 버튼과 같은 크기여야 나란히 균형이 맞는다 —
       상단 아이콘용 44px 터치 규칙(위)을 이 자리에선 되돌린다. */
    .placard-foot-actions .placard-review-btn{min-width:0; min-height:0; padding:4px 9px; font-size:13px;}
    /* 대신 히트 영역만 따로 넓힌다(25px → 44px). 보이는 알약 크기는 그대로라
       옆 '다 읽음'과의 균형이 유지된다. 예시 서재는 읽기 전용이라 이 버튼이
       아예 없어서, 기록이 있는 실계정 상태를 스캔에 넣고서야 드러났다. */
    .placard-foot-actions .placard-review-btn{position:relative;}
    .placard-foot-actions .placard-review-btn::after{
      content:''; position:absolute; left:0; right:0; top:50%;
      height:var(--tap); transform:translateY(-50%);
    }
    /* 기록하기·다시 읽기도 같은 방식 — 보이는 박스(31px)는 '다 읽음'과 나란한 높이로
       두고, ::after로 손끝 히트 영역만 44px로 넓힌다(scan:layout 터치 타깃). */
    .placard-memo-btn, .placard-resume-btn{position:relative;}
    .placard-memo-btn::after, .placard-resume-btn::after{
      content:''; position:absolute; left:0; right:0; top:50%;
      height:var(--tap); transform:translateY(-50%);
    }
    /* 인라인 'AI 연결' 링크 — 보이는 밑줄 모양은 그대로, 손끝 히트만 44px로 넓힌다. */
    .ai-connect-link{position:relative;}
    .ai-connect-link::after{
      content:''; position:absolute; left:0; right:0; top:50%;
      height:var(--tap); transform:translateY(-50%);
    }
    /* 헤더에선 오른쪽 끝에 붙어야 하니 중앙 정렬 대신 우측 정렬 */
    .review-ai-edit-btn{justify-content:flex-end;}

    /* 터치 타깃이 44px 미만인 아이콘 버튼들 — 아이콘 크기는 그대로 두고
       히트 영역만 확대(레이아웃 안 깨지게 min-width/height로 중앙 정렬 유지) */
    /* .notif-bell을 44px로 키우면서 안의 20px 아이콘이 중앙 정렬되어, 원래
       아이콘 모서리에 딱 붙어있던 .notif-dot(top/right:2px, 좁은 28px 박스 기준)이
       아이콘에서 멀리 떨어져 보이게 됐다. 커진 박스 기준으로 아이콘이 위치한
       지점(12px)에 맞춰 오프셋을 다시 계산해 아이콘 모서리에 붙인다. */
    .notif-dot{top:10px; right:10px;}
    .notif-bell{min-width:var(--tap); min-height:var(--tap);}
    .header-settings-btn{min-width:var(--tap); min-height:var(--tap);}
    /* 폰에선 화살표를 감싼 흰 배경 원(카드색+테두리)이 지저분해서 걷어낸다 —
       터치 타깃(44px)만 유지하고 배경·테두리는 없앤다. */
    .exchange-cal-nav{min-width:var(--tap); min-height:var(--tap); background:transparent; border:0;}
    .group-event-delete{min-height:var(--tap);}

    /* 맞춤 추천 위 취향 신호 칩 — 폰에서 네 줄로 낙오되던 것을 칩 줄 원칙
       (한 줄 스와이프)대로. .group-compare-member-sub와 같은 문법 */
    .taste-summary{flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch;}
    .taste-summary::-webkit-scrollbar{display:none;}
    .taste-summary > *{flex:0 0 auto;}

    /* iOS Safari는 폼 필드 font-size가 16px보다 작으면 탭할 때 화면을 확대해버려서
       ("갑자기 확대되는" 원인) 여기서만 16px로 올려서 막는다(웹 디자인은 그대로).
       title-input은 위에서 clamp(16px,...)로 이미 16px 아래로 안 내려가서 제외. */
    .read-month-input,
    .library-search-input,
    .review-keyword-input,
    .review-writer-textarea,
    .review-ai-edit,
    .feedback-modal-input,
    .settings-provider-select,
    .social-inline-form input,
    .exchange-note-form input{
      font-size:16px;
    }

    /* 터치 타깃 — 텍스트 링크·작은 컨트롤은 시각 크기는 두고 히트 영역만
       44px 급으로. 설정 화면 링크들이 특히 손끝으로 못 맞히던 문제. */
    .settings-link-btn,
    .settings-footer-link,
    .exchange-note-invite,
    .exchange-note-edit,
    .group-space-invite,
    .group-space-leave,
    .group-space-lock,
    .group-space-delete,
    /* 아래 넷은 scripts/layout-scan.mjs가 잡아낸 것(2026-07-28) — 전부 맨 텍스트
       버튼이라 min-height만으로 시각 크기 변화 없이 히트 영역이 넓어진다. */
    #demo-exit-btn,
    .empty-demo-btn,
    .review-writer-back,
    .settings-back{min-height:var(--tap); display:inline-flex; align-items:center;}
    /* 위 히트 영역 규칙이 display를 지정하는 바람에 [hidden]의 기본 display:none이
       덮여 무력화된다 — 소유자에게만 보여야 할 버튼(내보내기·가입 잠금)이 모바일
       에서만 드러났다(e2e가 잡음). 숨김은 숨김대로 지켜지게 되돌린다. */
    .group-space-leave[hidden],
    .group-space-lock[hidden],
    .group-space-delete[hidden]{display:none;}

    /* 테두리가 보이는 알약(정렬·연도)은 min-height를 주면 상자가 그대로 커져
       디자인이 바뀐다 — 눈에 보이는 상자는 두고, 위아래로 뻗은 투명한 층을
       버튼 안에 깔아 히트 영역만 44px로 만든다. 가상 요소도 히트 테스트에
       포함되므로 탭은 이 층에서 받는다. */
    /* 취향 리포트 접기 화살표도 같은 처리 — 이 아이콘 버튼은 대시보드 쪽
       규칙(#tab-dashboard .taste-report-toggle)이 더 구체적이라 위 min-height가
       닿지 않는다. 크기 싸움을 하지 말고 히트 영역만 따로 깐다. */
    .sort-btn,
    .year-pill,
    .library-share-btn,
    .taste-report-toggle{position:relative;}
    .sort-btn::after,
    .year-pill::after,
    .library-share-btn::after,
    .taste-report-toggle::after{
      content:''; position:absolute; left:0; right:0; top:50%;
      height:var(--tap); transform:translateY(-50%);
    }

    /* 로그인 버튼은 첫 화면의 유일한 동작이라 실제로 커지는 편이 낫다. */
    .login-btn{min-height:var(--tap);}
    /* '이 책과 결이 닿는 책' 행(5px 패딩)은 탭해 이동하는 버튼인데 손끝으로
       맞히기엔 얕다 — 시각 크기는 두고 히트 영역만 44px로 넓힌다. ('직접
       고치기' 연필은 placard 편집 아이콘과 묶여 이미 44px.) */
    .related-book-row{min-height:var(--tap); align-items:center;}
    /* 되살릴 메모 목록의 '되살리기'도 맨 텍스트 버튼이라 같은 처리 */
    .recover-item-btn{min-height:var(--tap); display:inline-flex; align-items:center;}
    /* 가운데 밑줄 낱말 — 폰에서도 가운데(기본 규칙과 같은 결). 터치 타깃은
       위 ::after 히트 영역과 min-height가 담당한다. */
    .taste-report-toggle{display:block; margin-inline:auto;}
    .group-space-tab-btn{padding:10px 0 12px;}
    .year-pill, .member-space-tab-btn, .sort-btn, .library-share-btn{min-height:36px;}
    /* 별점 입력(반쪽 판정이 절반 폭이라 실질 타깃은 더 작다) — 표시용(26px)보다 크게 */
    .star-input .star{width:32px; height:32px; font-size:32px;}
    .star-input{gap:4px;}

    /* 가로 스와이프 줄의 "더 있음" 힌트 — 스크롤바를 숨겨서 잘림이 안 보이던
       문제. 오른쪽 가장자리를 살짝 흐려 이어짐을 암시한다(끝까지 밀면 마지막
       칩이 여백 안쪽에 있어 실사용에서 가려지지 않는다). */
    #group-space-view .member-strip,
    .group-compare-member-sub{
      -webkit-mask-image:linear-gradient(to right, #000 calc(100% - 28px), transparent);
      mask-image:linear-gradient(to right, #000 calc(100% - 28px), transparent);
    }

    /* 토스트는 하단 탭바 위로 */
    #app-toast{bottom:calc(var(--bottomnav-h) + 14px + env(safe-area-inset-bottom));}
  }

  /* ---------- 태블릿+데스크톱(≥640px): 추천/담아둔 책 다열 그리드 ---------- */
  @media (min-width:640px){
    /* 완독 폼 날짜를 별점 바로 옆에 — 데스크톱은 서재가 3열이라 카드 안쪽이 ≈246px로
       폰보다 좁다. 기본 clamp(18px) 날짜로는 별점(22px×5) 옆에 안 들어가 아랫줄로 밀렸다.
       한 줄로 고정(nowrap)하고 별점 바로 뒤에 붙인 뒤(flex-start), 날짜만 14px로 줄여
       "2026년 08월"이 온전히 보이게 한다. 폰(터치)은 위 기본 규칙 그대로 둔다. */
    .placard-finish-top{flex-wrap:nowrap; justify-content:flex-start;}
    .placard-finish-top .placard-finish-month{width:auto; font-size:14px; min-width:0; text-align:left; padding-left:6px;}

    /* 서재 카드 PC 여백 — 넓은 격자(3열)에선 여백을 넉넉히 주고, foot 내용(키워드·메모·
       별점)을 통째로 아래로 몰아붙인다(오너 2026-08-24). flex 재구성 뒤 짧은 카드는
       키워드·메모가 위로 뭉치고 가운데가 비어 별점만 바닥에 남았다 — foot를
       justify-content:flex-end로 아래 정렬하면 빈 공간이 서지와 하단 묶음 사이 한 곳으로
       모이고 메모·키워드가 구분선 바로 위에 붙는다. 모바일(1열)은 콘텐츠 높이라 남는
       공간이 없어 이 규칙이 저절로 무력화되므로 촘촘함을 그대로 유지한다(§gallery-grid).
       base의 margin-top:auto(.placard-bottom·-reading-row·-finish-form)와 memo-line의
       margin-top:2px를 여기서 0으로 눌러야 flex-end 정렬이 이긴다. */
    .gallery-grid{gap:22px;}
    .placard{padding:22px 22px 20px;}
    .placard-body{margin-top:14px;}
    .placard-meta{gap:7px;}
    .placard-foot{gap:12px; margin-top:14px; justify-content:flex-end;}
    .placard-foot .placard-memo-line{margin-top:0;}
    .placard-bottom, .placard-reading-row, .placard-finish-form{margin-top:0; padding-top:14px;}

    #recommend-results .rec-grid, #wishlist-list, #member-space-wishlist-grid{
      display:grid;
      grid-template-columns:repeat(auto-fill, minmax(340px, 1fr));
      gap:16px;
      align-items:stretch;
      /* 행끼리도 키를 맞춘다 — align-items:stretch는 같은 행 안에서만 카드를
         늘려서, 이유·먼저 읽은 사람 유무로 행마다 높이가 어긋났다. auto-rows:1fr이
         모든 행을 가장 큰 카드에 맞춰 균일하게 만든다(남는 공간은 .rec-actions의
         margin-top:auto가 하단으로 밀어 여백으로 고인다 — 가운데 빈 구멍 아님). */
      grid-auto-rows:1fr;
    }
    #recommend-results .rec-card, #wishlist-list .rec-card, #member-space-wishlist-grid .rec-card{margin-bottom:0;}

    /* 모임 달력: 셀이 정사각형(aspect-ratio:1)이라 넓은 컨테이너에서 한 줄이 너무 커져
       첫 화면이 달력만으로 꽉 참. 데스크톱에서는 고정 높이로 눌러 컴팩트하게 유지. */
    #group-space-view .exchange-cal-cell{aspect-ratio:auto; height:38px;}
  }

  /* ---------- 넓은 화면(≥1024px): 대시보드 차트 2열 ---------- */
  @media (min-width:1024px){
    #tab-dashboard.active{
      display:block;
    }
  }

  /* ---------- 마우스 hover 전용 상호작용 ----------
     터치 기기는 (hover:hover)를 지원하지 않으므로 이 블록 밖으로 뺀 :hover 규칙들은
     여기서만 적용된다 — 탭 이후에도 hover 스타일이 "눌린 채로 고정"되는 문제 방지.
     터치 기기 피드백은 아래 (hover:none) 블록의 :active 규칙으로 대신한다. */
  @media (hover:hover) and (pointer:fine){
  .login-btn:hover{opacity:0.88;}
  .login-demo-btn:hover{color:var(--moss);}
  .empty-demo-btn:hover{color:var(--moss);}
  .notif-bell:hover{color:var(--moss);}
  .notif-item-btn:hover{opacity:0.8;}
  .notif-item-dismiss:hover{color:var(--ink-soft);}
  .settings-link-btn:hover{color:var(--moss);}
  .exchange-note-invite:hover{color:var(--moss);}
  .confirm-modal-cancel-btn:hover{border-color:var(--ink-faint);}
  .confirm-modal-confirm-btn:hover{opacity:0.88;}
  .btn-add:hover{opacity:0.85;}
  /* 채운 모스 버튼만은 opacity로 흐리지 않는다 — 배경이 종이 쪽으로 밝아지면서
     종이색 글씨와의 대비가 4.98:1 → 3.71:1로 AA(4.5:1) 아래로 떨어졌다(2026-07-29,
     axe가 독후감 화면에서 잡음). --moss-deep은 애초에 "채운 모스를 누르는 순간
     한 단계 어둡게"를 위해 둔 토큰이고, 그걸 쓰면 대비가 6.2:1로 오히려 올라간다 —
     누르려는 순간에 글씨가 더 또렷해지는 쪽이 맞다.
     테두리 변형(--ghost/--accent)은 채운 배경이 없어 이 규칙이 안 맞으므로 뺀다. */
  .btn-add:not(.btn-add--ghost):not(.btn-add--accent):hover,
  .placard-finish-btn:hover{opacity:1; background:var(--moss-deep); border-color:var(--moss-deep);}
  .year-pill:hover:not(.active){border-color:var(--ink-faint);}
  .member-space-tab-btn:hover:not(.active){border-color:var(--ink-faint);}
  .sort-btn:hover{border-color:var(--ink-faint);}
  .placard:hover{border-color:var(--ink-faint);}
  .placard-del:hover{color:var(--moss);}
  .placard-finish-btn:hover{opacity:0.85;}
  .placard-review-btn:hover{color:var(--moss);}
  .review-writer-back:hover{color:var(--moss);}
  .taste-report-refresh:hover:not(:disabled){color:var(--moss);}
  .social-primary-btn:hover:not(:disabled){opacity:.88;}
  .member-plate:hover{border-color:var(--moss);}
  .exchange-cal-nav:hover{border-color:var(--moss); color:var(--moss);}
  .exchange-cal-cell:hover:not(.empty){border-color:var(--moss);}
  .group-event-delete:hover{background:var(--moss-soft);}
  .btn-wishlist-add:hover:not(:disabled), .wishlist-remove-btn:hover, .wishlist-read-btn:hover:not(:disabled), .btn-wishlist-skip:hover{border-color:var(--moss); color:var(--moss);}
  .search-result:hover{background:var(--moss-soft); outline:none;}
  .add-choice-btn:hover{border-color:var(--moss); color:var(--moss); outline:none;}
  }
  @media (hover:none){
  /* 터치 기기에서 웹 티 나는 기본 동작 정리 — OS 탭 하이라이트(회색 번쩍임)는
     이 블록의 :active 피드백이 대신하고, 표지 이미지 길게 누르기 메뉴와
     버튼·탭바 라벨의 텍스트 선택을 막는다. 리뷰·메모 등 읽는 텍스트 선택은 그대로. */
  html{-webkit-tap-highlight-color:transparent;}
  button, .tabs{-webkit-user-select:none; user-select:none;}
  img{-webkit-touch-callout:none; -webkit-user-select:none; user-select:none;}
  .login-btn:active{opacity:0.88;}
  .login-demo-btn:active{color:var(--moss);}
  .empty-demo-btn:active{color:var(--moss);}
  .notif-bell:active{color:var(--moss);}
  .notif-item-btn:active{opacity:0.8;}
  .settings-link-btn:active{color:var(--moss);}
  .confirm-modal-cancel-btn:active, .confirm-modal-confirm-btn:active{opacity:0.8;}
  .btn-add:active{opacity:0.85;}
  /* hover와 같은 이유 — 누르는 순간에도 대비가 떨어지면 안 된다. */
  .btn-add:not(.btn-add--ghost):not(.btn-add--accent):active,
  .placard-finish-btn:active{opacity:1; background:var(--moss-deep); border-color:var(--moss-deep);}
  .year-pill:active:not(.active){border-color:var(--ink-faint);}
  /* 카드·탭 눌림 피드백 — 카드 전체가 클릭 대상인데 반응이 없어 죽은 판처럼 느껴지던 것 */
  .group-card:active{box-shadow:var(--shadow-sm); transform:translateY(1px);}
  .ghost-create-btn:active{opacity:0.7;}
  .tab-btn:active{opacity:0.7;}
  .group-space-tab-btn:active{color:var(--moss);}
  .exchange-note-invite:active{color:var(--ink-soft);}
  .placard-finish-confirm:active, .placard-finish-cancel:active{opacity:0.8;}
  .settings-footer-link:active{color:var(--moss);}
  .member-space-tab-btn:active:not(.active){border-color:var(--ink-faint);}
  .sort-btn:active{border-color:var(--ink-faint);}
  .placard:active{border-color:var(--ink-faint);}
  .placard-del:active{color:var(--moss);}
  .placard-finish-btn:active{opacity:0.85;}
  .placard-review-btn:active{color:var(--moss);}
  .review-writer-back:active{color:var(--moss);}
  .taste-report-refresh:active:not(:disabled){color:var(--moss);}
  .social-primary-btn:active:not(:disabled){opacity:.88;}
  .member-plate:active{border-color:var(--moss);}
  .exchange-cal-nav:active{border-color:var(--moss); color:var(--moss);}
  .exchange-cal-cell:active:not(.empty){border-color:var(--moss);}
  .group-event-delete:active{background:var(--moss-soft);}
  .btn-wishlist-add:active:not(:disabled), .wishlist-remove-btn:active, .wishlist-read-btn:active:not(:disabled), .btn-wishlist-skip:active{border-color:var(--moss); color:var(--moss);}
  .search-result:active{background:var(--moss-soft); outline:none;}
  .add-choice-btn:active{border-color:var(--moss); color:var(--moss); outline:none;}
  }

  /* 키보드 포커스 표준(접근성) — 마우스·터치엔 안 보이고 Tab 이동 시에만 모스 아웃라인이
     뜬다. 개별 정의가 빠져 있던 버튼들을 한 곳에서 묶어 채운다(기존 관습: 2px 모스 아웃라인,
     style.css의 btn-add·tab-btn·sort-btn 등과 동일). hover 능력과 무관하므로 미디어 블록 밖. */
  .login-btn:focus-visible, .login-demo-btn:focus-visible, .social-primary-btn:focus-visible, .inapp-notice-btn:focus-visible,
  .notif-item-btn:focus-visible, .notif-item-dismiss:focus-visible, .settings-link-btn:focus-visible,
  .confirm-modal-cancel-btn:focus-visible, .confirm-modal-confirm-btn:focus-visible,
  .settings-footer-link:focus-visible,
  .member-space-tab-btn:focus-visible, .year-pill:focus-visible,
  .placard-finish-btn:focus-visible, .placard-finish-confirm:focus-visible, .placard-finish-cancel:focus-visible,
  .placard-review-btn:focus-visible, .placard-del:focus-visible, .review-ai-edit-btn:focus-visible,
  .taste-report-refresh:focus-visible, .btn-wishlist-add:focus-visible, .wishlist-remove-btn:focus-visible,
  .wishlist-read-btn:focus-visible,
  .btn-wishlist-skip:focus-visible, .exchange-cal-nav:focus-visible,
  .group-event-delete:focus-visible, .header-settings-btn:focus-visible, .ghost-create-btn:focus-visible,
  .review-writer-back:focus-visible, .empty-demo-btn:focus-visible, .group-space-invite:focus-visible,
  .group-space-leave:focus-visible, .exchange-note-invite:focus-visible, .member-plate:focus-visible{
    outline:2px solid var(--moss); outline-offset:2px;
  }

  /* ---------- Mobile editorial refinement ----------
     모바일의 여백은 화면 바깥과 문단 사이에 남기고, 카드 안의 목적 없는
     빈 공간과 겹친 프레임은 덜어낸다. 웹의 조형과 레이아웃은 유지한다. */
@media (max-width:639.98px){
  .onboarding-card{
    align-items:flex-start; flex-direction:column; gap:16px;
    margin:-10px 0 28px; padding:18px;
  }
  /* 0권에선 진행 막대가 접혀 .onboarding-action에 '책 기록하기'만 남고,
     그 아래 예시 서재 버튼이 온다. 카드가 왼쪽 정렬(align-items:flex-start)이라
     두 버튼도 같은 왼쪽 모서리에 세로로 쌓아야 어긋나 보이지 않는다 — 예전엔
     예시 버튼만 가운데(margin:0 auto)라 '책 기록하기'와 계단처럼 엇갈렸다. */
  #onboarding-demo-btn{align-self:flex-start; margin:0;}
  .onboarding-action{width:100%; justify-content:space-between;}
    /* .add-bar 모바일 정의는 아래 'Mobile regression fix' 블록(박스+44px)에
       단일화돼 있다 — 여기 있던 '투명/선 한 겹' 편집은 그 블록에 통째로
       덮여 사문이었어서 제거했다. */

    /* 책 한 권을 작은 지면처럼: 표지는 왼쪽, 서지는 오른쪽(.placard-body), 감상·별점은
       그 아래. 데스크톱과 같은 flex 뼈대이고 모바일은 표지 크기·간격만 키운다.
       여백은 넉넉히 — 1열이라 높이가 콘텐츠에 맞춰져(auto) 조일 이유가 없다(오너
       2026-08-24). 이 블록이 위 §max-width:639.98px의 padding·meta gap을 눌러 이긴다. */
    .placard{padding:19px 18px 17px;}
    .placard-body{gap:14px; margin-top:11px;}
    .placard-meta{gap:6px;}
    .placard-bottom, .placard-reading-row{padding-top:13px;}
    .placard-cover{
      width:72px;
      height:100px;
      margin:0;
      float:none;
    }
    .placard-author,
    .placard-read-at{margin-top:0;}
    .placard-title-text{
      font-size:17px;
      line-height:1.4;
      -webkit-line-clamp:2;
      max-height:calc(2 * 1.4em);
    }
    .placard-del,
    .placard-review-btn{
      border:0;
      border-radius:0;
      color:var(--ink-faint);
    }
    .placard-del svg,
    .placard-review-btn svg{display:block;}
    /* '다 읽음'·'덮어두기'·완료/취소는 한 카드의 형제 버튼이라 모바일에서도 같은 크기로
       둔다(터치 padding). '덮어두기'(.placard-stop-btn)가 빠져 있어 다 읽음만 높았다
       (오너 2026-08-24 실사용 제보). */
    .placard-finish-btn,
    .placard-stop-btn,
    .placard-memo-btn,
    .placard-resume-btn,
    .placard-finish-confirm,
    .placard-finish-cancel{
      border-radius:var(--radius);
      padding:7px 10px;
    }

    /* 담아둔 책은 표지와 서지가 중심이고, 행동은 조용한 하단 링크가 된다. */
    /* 한 칸짜리 세로 목록도 그리드로 세운다 — block으로 쌓으면 이유·먼저 읽은
       사람 유무로 카드마다 키가 제각각이었다. auto-rows:1fr이 전부 같은 키로
       통일한다(간격은 gap이 맡으므로 카드 margin-bottom은 뺀다). */
    #wishlist-list,
    #member-space-wishlist-grid{
      display:grid;
      grid-template-columns:1fr;
      grid-auto-rows:1fr;
      gap:16px;
    }
    #wishlist-list .rec-card,
    #member-space-wishlist-grid .rec-card{
      grid-template-columns:64px minmax(0, 1fr);
      gap:16px;
      padding:18px;
      margin-bottom:0;
    }
    #wishlist-list .rec-cover,
    #member-space-wishlist-grid .rec-cover{
      width:64px;
      height:90px;
    }
    #wishlist-list .rec-title,
    #member-space-wishlist-grid .rec-title{font-size:16px;}
    #wishlist-list .rec-actions,
    #member-space-wishlist-grid .rec-actions{
      border-top:1px solid var(--line);
      padding-top:4px;
      gap:18px;
    }
    #wishlist-list .rec-actions > button,
    #member-space-wishlist-grid .rec-actions > button{
      min-height:38px;
      padding:7px 0;
      border:0;
      border-radius:0;
      text-align:left;
    }

    /* 모임 카드는 큰 빈 면보다 제목과 사람의 관계가 먼저 보이게 한다. */
    .group-pills{gap:10px;}
    #tab-social .group-card{
      min-height:104px;
      padding:15px 40px 14px 14px;
      box-shadow:none;
      border:0;
    }
    .group-card-handle{
      margin:-3px 0 6px -3px;
      padding:3px;
    }
    .group-card-ghost{min-height:104px;}

    /* 리포트는 흐리게 사라지지 않고 인쇄 면처럼 또렷하게 끝난다. */
    .taste-report-text.collapsed{
      max-height:11.1em;
      -webkit-mask-image:linear-gradient(180deg, #000 58%, transparent);
      mask-image:linear-gradient(180deg, #000 58%, transparent);
    }
    #tab-dashboard .taste-report-toggle{
      min-height:40px;
      padding:8px 12px;
    }
  }

  @media (max-width:639.98px) and (pointer:coarse){
    /* 앱 크롬도 본문과 같은 종이 위에 놓는다. */
    .tabs{
      background:var(--paper);
      box-shadow:none;
      padding-top:5px;
    }
    .tab-btn{gap:2px; color:var(--ink-faint);}
    .tab-icon svg{width:19px; height:19px; stroke-width:1.5;}
  }

  /* Mobile regression fix: keep the primary book search visible and give every
     header control its own layout cell instead of positioning icons over copy. */
  @media (max-width:639.98px){
    .hero{
      border-bottom:none;
      margin-bottom:28px;
    }
    .sync-status:empty{display:none;}

    /* 폰: 워드마크 옆에 권수·아이콘 셋까지 한 줄에 넣으면 좁은 폭(360px)에서
       물리적으로 안 들어가 권수가 종을 파고든다(실측: 360px에서 74px 초과).
       그래서 두 줄로 나눈다 — 아이콘 셋은 위 아이브로("A READING JOURNAL") 줄
       오른쪽 빈자리에 올리고, 워드마크 줄에는 권수만 남긴다. */
    /* 폰: 아이콘 셋(종·설정·콘솔)을 히어로 맨 위 유틸리티 줄로 올리고, 그만큼
       본문(아이브로·워드마크·권수)을 상단 여백으로 내린다. 아이콘이 제목과 겹치지
       않고 "앱 컨트롤"로 읽힌다. 권수는 예전처럼 워드마크 옆에 그대로 둔다. */
    .hero{position:relative; padding-top:40px;}
    .hero-title-row{
      display:grid;
      grid-template-columns:auto minmax(0, 1fr);
      align-items:baseline;
      column-gap:12px;
    }
    .hero h1{
      grid-column:1;
      min-width:0;
      font-size:clamp(30px, 9.5vw, 38px);
      white-space:nowrap;
    }
    .hero-count-inline{
      grid-column:2;
      grid-row:1;
      justify-self:start;
      gap:4px;
      white-space:nowrap;
    }
    /* 아이콘 묶음 — 히어로 최상단 오른쪽(위 padding-top이 만든 유틸리티 줄).
       44px 터치 타깃이 40px 줄에 눈높이로 앉도록 top:-2px. 관리자 아이콘이
       숨어도(display:none) flex라 나머지가 자연히 우측 정렬된다.
       주의: .notif-wrap 안 하단 시트(position:fixed)의 기준이 바뀌지 않도록
       transform 대신 top/right로만 정렬한다. */
    .hero-actions{
      display:flex;
      align-items:center;
      position:absolute;
      top:-2px;
      right:0;
    }
    .notif-wrap{position:relative; top:auto; right:auto; margin-left:0;}
    .header-settings-btn{position:relative; top:auto; right:auto;}

    @media (max-width:359.98px){
      .hero h1{font-size:29px;}
      .hero-count-inline .num,
      body.tab-dashboard-active .hero-count-inline .num,
      body.tab-wishlist-active .hero-count-inline .num,
      body.tab-social-active .hero-count-inline .num{font-size:18px;}
      .hero-count-inline .label,
      body.tab-dashboard-active .hero-count-inline .label,
      body.tab-wishlist-active .hero-count-inline .label,
      body.tab-social-active .hero-count-inline .label{font-size:11px;}
    }

    .add-bar{
      display:flex;
      height:44px;
      min-height:var(--tap); /* 줄이 손끝보다 작아지지 않게 — flex가 눌러도 여기서 멈춘다 */
      box-sizing:border-box;
      background:var(--card);
      /* 기본 규칙과 같은 잉크 헤어라인 — 모스는 :focus-within(입력 시작)에만.
         여기 모스 28%가 재선언돼 있어 **폰에서만** 그 판정이 뒤집혀 있었다
         (오너 승인 2026-08-17의 근거는 base 규칙 주석에). 모바일이 주 사용
         환경이라, 승인된 결정이 정작 가장 많이 보는 화면에서 안 지켜졌다.
         목업으로 확인하고 되돌린다(오너 승인 2026-08-31). */
      border:1px solid var(--line);
      border-radius:0;
      padding:0 14px;
      margin-bottom:32px;
    }
    .add-bar:focus-within{
      border-color:var(--moss);
      box-shadow:inset 0 0 0 1px var(--moss);
    }
    .add-bar input[type=text]{
      width:100%;
      min-width:0;
      min-height:42px;
      padding:0 2px;
      /* 16px 미만이면 iOS가 포커스 시 화면을 확대한다 — #title-input(16px)이
         이미 이겨 실렌더엔 영향 없지만, 특이도가 흔들려도 안전하게 16px. */
      font-size:16px;
    }
    .add-bar:has(.add-bar-choices:not([hidden])),
    .add-bar:has(.add-bar-finish:not([hidden])){
      height:auto;
      padding-top:12px;
      padding-bottom:12px;
    }
    .search-results{
      left:14px;
      right:14px;
      top:calc(100% + 4px);
    }

    /* The hero rule and the editorial rule beside “기록의 결” should not read
       as a cramped double divider. */
    body.tab-dashboard-active .hero{margin-bottom:28px;}

    /* Keep the brand header identical across every primary tab. Tab changes
       should affect the content, not the size or voice of the wordmark. */
    body.tab-dashboard-active .hero,
    body.tab-wishlist-active .hero,
    body.tab-social-active .hero{
      padding-bottom:14px;
      margin-bottom:28px;
    }
    body.tab-dashboard-active .hero-eyebrow,
    body.tab-wishlist-active .hero-eyebrow,
    body.tab-social-active .hero-eyebrow{
      display:block;
      font-size:9px;
      letter-spacing:.18em;
    }
    body.tab-dashboard-active .hero h1,
    body.tab-wishlist-active .hero h1,
    body.tab-social-active .hero h1{
      font-size:clamp(30px, 9.5vw, 38px);
      line-height:1.05;
    }
    .hero-count-inline .num{font-size:18px;}
    .hero-count-inline .label{font-size:11px;}
    body.tab-dashboard-active .hero{margin-bottom:28px;}

    /* Mobile dashboard rhythm: three distances only.
       폰은 여백이 세로 리듬에서만 나오므로 데스크톱보다 넓게 숨쉬게 한다
       (묶음 사이는 데스크톱 56px보다 한 박자 넓은 60px).
       16px = directly related, 36px = blocks, 60px = editorial groups. */
    #tab-dashboard .dash-group .chart-block + .chart-block{margin-top:36px;}
    /* 섹션 간격은 셋 다 같은 값으로(구분선 padding-top:34px는 데스크톱에서 이어받음). */
    #tab-dashboard .dash-group + .dash-group,
    #tab-dashboard .ai-invite + .dash-group,
    #tab-dashboard .deep-dive-block{margin-top:44px;}
    #tab-dashboard .panel-title-row{margin-bottom:12px;}
    #tab-dashboard .chart-block > .panel-title{margin-bottom:12px;}
    #tab-dashboard .taste-report-toggle{
      min-height:32px;
      margin-top:6px;
    }
    #tab-dashboard .recommend-intro{margin-bottom:16px;}

    /* Search and sort are one library toolset. Keep them in a single row while
       preserving enough width for a useful search field on narrow phones. */
    .library-toolbar{
      flex-direction:row;
      align-items:center;
      gap:8px;
      margin-bottom:22px;
    }
    .library-search-input{
      flex:1 1 140px;
      width:auto;
      min-width:0;
      min-height:36px;
      height:36px;
      box-sizing:border-box;
      padding:6px 2px;
      background:transparent;
      border:0;
      border-bottom:1px solid var(--line);
      border-radius:0;
    }
    .library-search-input::placeholder{font-size:14px;}
    .library-search-input:focus,
    .library-search-input:focus-visible{
      outline:none;
      border-bottom-color:var(--moss);
    }
    .sort-btns{
      flex:0 0 auto;
      align-self:center;
      margin-right:0;
      gap:4px;
    }
    .sort-btn{
      min-height:36px;
      padding:0 7px;
      border:0;
      border-radius:0;
      white-space:nowrap;
    }
    /* 밑줄은 뺐다(2026-08-13) — 앞 블록이 이미 색과 굵기로 선택을 말하고 있어서
       (그 블록 주석의 "선택은 색과 굵기로만 말한다"), 여기서 밑줄까지 얹으면 신호가
       세 겹이 되고 그 주석이 거짓이 된다. 형제 컨트롤(.year-pill·.member-space-tab-btn)도
       채움으로 말하지 밑줄을 쓰지 않는다. */
    .sort-btn.active{
      background:transparent;
    }

    /* Wishlist actions change book state, so they must read as controls rather
       than loose text beneath an editorial divider. */
    #wishlist-list .rec-actions{
      border-top:0;
      padding-top:16px;
      gap:8px;
      justify-content:flex-end;
    }
    #wishlist-list .rec-meta{margin-top:14px;}
    /* 서재 카드의 '다 읽음' 버튼과 같은 크기·정렬로 — 콘텐츠 크기 pill을
       오른쪽에 붙인다(넓은 112px 고정 + 가운데 정렬은 뭉툭해 보인다). */
    #wishlist-list .rec-actions > button,
    #recommend-results .rec-actions > button{
      flex:0 0 auto;
      min-height:0;
      padding:7px 10px;
      font-size:11px;
      border:1px solid var(--line);
      border-radius:var(--radius);
      text-align:center;
    }
    #recommend-results .rec-actions{justify-content:flex-end;}
    #wishlist-list .wishlist-read-btn{
      background:transparent;
      border-color:var(--line);
      color:var(--ink-soft);
    }
    #wishlist-list .wishlist-remove-btn{
      background:transparent;
      color:var(--ink-soft);
    }
  }

/* 예시 서재 스포트라이트 투어 (lib/tour.js) — 딤은 검은 막이 아니라 spot의 큰
   box-shadow가 만든다: 타깃 사각형만 빛으로 남고 둘레가 따뜻한 잉크로 어두워진다.
   z-index는 전체화면 뷰(70)·모달(100)보다 위. */
.tour-root{position:fixed;inset:0;z-index:120;touch-action:none}
.tour-scrim{position:absolute;inset:0;background:transparent} /* 조작 차단만; 딤은 spot이 만든다 */
.tour-spot{position:fixed;border-radius:var(--radius-sheet);pointer-events:none;opacity:0;
  box-shadow:0 0 0 9999px rgba(42,37,32,0.6), 0 0 0 1.5px rgba(255,255,255,0.55);
  transition:top var(--dur-3) var(--ease-inout),left var(--dur-3) var(--ease-inout),width var(--dur-3) var(--ease-inout),height var(--dur-3) var(--ease-inout),opacity var(--dur-2) var(--ease-out)}
.tour-card{position:fixed;z-index:1;width:min(320px,calc(100vw - 32px));
  background:var(--card);border:1px solid var(--line);border-radius:var(--radius-sheet);
  box-shadow:var(--shadow-lg);padding:18px 18px 14px}
.tour-card-title{font-family:var(--sans);font-size:15.5px;font-weight:650;color:var(--ink);letter-spacing:-.01em}
.tour-card-text{font-family:var(--sans);font-size:13.5px;color:var(--ink-soft);line-height:1.55;margin-top:7px}
.tour-card-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px}
.tour-skip{background:none;border:0;font-family:var(--sans);font-size:12.5px;color:var(--ink-faint);cursor:pointer;padding:4px}
.tour-skip:hover{color:var(--ink-soft)}
.tour-dots{display:flex;gap:6px}
.tour-dot{width:6px;height:6px;border-radius:50%;background:var(--line)}
.tour-dot.on{background:var(--moss)}
.tour-next{font-family:var(--sans);font-size:13px;font-weight:600;color:var(--paper);background:var(--moss);
  border:0;border-radius:var(--radius);padding:8px 16px;cursor:pointer}
.tour-next:hover{background:var(--moss-deep)}
@media (max-width:700px){
  /* 모바일: 카드는 하단 시트로 도킹(모달 시트와 같은 결) */
  .tour-card.sheet{left:0!important;right:0;top:auto!important;bottom:0;
    width:100%;border-radius:var(--radius-sheet) var(--radius-sheet) 0 0;padding:20px 20px calc(18px + env(safe-area-inset-bottom))}
}

/* 전역 동작 줄이기 가드 — 기기가 'prefers-reduced-motion:reduce'면 앱의 모든 전환·
   애니메이션을 사실상 즉시로 만든다(접근성). 위쪽의 스코프 가드(.placard-cued 등)는
   특정 애니메이션만 껐는데, 이건 모션 토큰이 새로 켜는 것까지 통째로 덮는다.
   0으로 두지 않고 0.01ms로 두는 건 transitionend/animationend에 의존하는 코드가
   멈추지 않게 하는 표준 패턴이다. */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
