/* ============================================================
   main.css — 사용자 앱 페이지 전용 (로그인/회원가입/문진/홈/측정이력)
   (global.css 다음에 로드. 색·라운드·그림자는 global.css :root 토큰 var() 참조)

   구역 지도
   01. 페이지 공통 (.page-title / .section-label / .state-block)
   02. 히어로 (.hero-card / .hero-banner)
   03. 로그인 (.login-page)
   04. 회원가입 (.signup-page / .pass-card)
   05. 지역 선택 (.region-picker)
   06. 문진 인트로 (.survey-intro)
   07. 문진 단계 (.survey-page)
   08. 홈 (.home-page / .menu-grid)
   09. 측정이력 셸 (.history-page / .tab-switch / .period-bar)
   10. 대시보드 탭 (.metric-grid / .metric-head — 변화 탭 카드 헤더와 공용)
       ※ 2026-08-27 개편 후 대시보드 탭의 정본은 파일 맨 끝 [dash-v2] 블록이다.
         이 구역의 .metric-grid 계열은 .metric-head 만 변화 탭과 공용으로 살아 있다.
   11. AI 분석 요약 (.ai-summary / .advice-grid) — 2026-08-27 개편으로 화면에서 내려갔다([dash-v2] 참조)
   12. 변화 탭 (.change-grid — 2026-08-27 부터 2열)
   13. 상세리포트 탭 툴바 (.report-toolbar)
   14. 모달 / 최근값 행 (.modal-backdrop / .latest-row)
   15. 인쇄 (리포트만 남기기)
   ── 이하 에이전트 추가 블록 ──
   [design-polish] 은은한 모션 (모달 · 팝오버 · 선택지 전환) — 파일 맨 끝
   [period-picker] 측정이력 조회기간 — 측정일 선택 (.period-picker) — 파일 맨 끝
   [dash-v2] 측정이력 대시보드 탭 2열 6카드 (.dash-grid) — 파일 맨 끝
   [frontend-dev] login-id-field 회원가입 아이디+중복확인 행 (.field-check-row) — 파일 맨 끝

   폰트 규칙(R4): 모든 font-size 는 clamp() + `/* 390px→Npx, 1280px→Mpx *​/` 기준 주석.
   ※ 페이지 에이전트(frontend-dev)는 이 파일 하단에
     `/* ===== [frontend-dev] 블록명 ===== *​/` 로만 추가한다(기존 블록 수정 금지).
   ============================================================ */


/* ===== 01. 페이지 공통 ===== */
.page-title {
  font-size: clamp(30px, calc(23.87px + 1.573vw), 44px);
  /* 390px→30px, 1280px→44px */
  font-weight: 900;
  letter-spacing: -.045em;
  line-height: 1.15;
  color: var(--text);
}

.page-desc {
  margin: var(--sp-2) 0;
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 600;
  color: var(--text-sub);
}

/* 폼 섹션 제목 — "1. 본인인증" 같은 단계 라벨 */
.section-label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-6) 0 var(--sp-3);
  font-size: clamp(17px, calc(14.8px + 0.562vw), 22px);
  /* 390px→17px, 1280px→22px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--text);
}

.section-label::before {
  content: "";
  width: 4px;
  height: 20px;
  border-radius: var(--r-pill);
  background: var(--blue);
}

/* API 미기동·빈 데이터 공용 폴백 (로딩/에러/빈상태 3종) */
.state-block {
  display: grid;
  place-items: center;
  gap: var(--sp-3);
  padding: var(--sp-8) var(--sp-4);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--surface-soft);
  text-align: center;
  font-size: clamp(14px, calc(12.25px + 0.449vw), 18px);
  /* 390px→14px, 1280px→18px */
  font-weight: 600;
  color: var(--text-sub);
}

.state-block .state-icon {
  font-size: clamp(32px, calc(27.25px + 1.348vw), 44px);
  /* 390px→32px, 1280px→44px */
  line-height: 1;
}

.state-block .state-title {
  font-size: clamp(16px, calc(13.8px + 0.562vw), 21px);
  /* 390px→16px, 1280px→21px */
  font-weight: 700;
  color: var(--text);
}

.state-block.state-error {
  border-color: var(--grade-consult-line);
  background: var(--grade-consult-bg);
  color: var(--grade-consult);
}

.state-block.state-error .state-title {
  color: var(--grade-consult);
}

.state-block.state-loading {
  border-style: solid;
  border-color: var(--line);
}


/* ===== 02. 히어로 ===== */
/* 로그인·회원가입 상단 큰 카드 (시안 01) */
.hero-card {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  overflow: hidden;
  padding: var(--sp-6);
  border-radius: var(--r-xl);
  background: linear-gradient(135deg, var(--surface-soft) 0%, var(--purple-soft) 100%);
}

.hero-card .hero-title {
  font-size: clamp(34px, calc(26.5px + 1.91vw), 51px);
  /* 390px→34px, 1280px→51px */
  font-weight: 900;
  letter-spacing: -.05em;
  line-height: 1.1;
  color: var(--text);
}

.hero-card .hero-desc {
  margin-top: var(--sp-3);
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 600;
  line-height: 1.5;
  color: var(--text-sub);
}

/* [design-css] 일러스트 폭 +28% (clamp(120px,34vw,260px) → 154/43.5vw/333px).
   아래로 남던 카드 여백은 "밀착" 기법으로 제거한다 — 이미지만 카드 바닥에 붙이고
   (align-self:end + 카드 아래 패딩만큼 음수 마진), 텍스트 쪽 패딩은 그대로 둔다.
   .hero-card 는 overflow:hidden 이라 라운드 밖으로 비어져 나오지 않는다. */
.hero-card .hero-art {
  flex: 0 0 auto;
  align-self: flex-end;
  width: clamp(154px, 43.5vw, 333px);
  margin-bottom: calc(var(--sp-6) * -1);
}

/* 측정이력·서브페이지 상단 배너 (시안 12~14) */
/* [design-css] 일러스트를 흐름에서 빼고(absolute) 배너 높이는 min-height 로 고정한다.
   이전에는 이미지가 배너 높이를 결정해서(200×141) 이미지를 키우면 제목·탭·본문이 통째로 밀렸다.
   min-height 값은 "확대 전 이미지 높이"와 같아 아래 레이아웃은 1px 도 움직이지 않는다.
   (이미지 22vw × 세로비 0.7028 = 15.46vw, 상한 200px×0.7028 = 141px) */
.hero-banner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  /* 사용자 피드백(2026-08-13): 배너 영역 높이 20% 축소 (158→126px, 일러스트도 비례 축소) */
  min-height: clamp(59px, 13.9vw, 125px);
  padding-right: clamp(122px, calc(24.3vw + 16px), 237px);
  margin-bottom: var(--sp-4);
}

.hero-banner .hero-banner-title {
  font-size: clamp(30px, calc(23.87px + 1.573vw), 46px);
  /* 390px→30px, 1280px→44px */
  font-weight: 900;
  letter-spacing: -.045em;
  color: var(--text);
}

.hero-banner .hero-banner-desc {
  margin-top: var(--sp-2);
  font-size: clamp(14px, calc(12.25px + 0.449vw), 18px);
  /* 390px→14px, 1280px→18px */
  font-weight: 600;
  color: var(--text-sub);
}

/* [design-css] 확대 이력 clamp(96px,22vw,200px) → +15% → 여기서 +20% (230px → 276px).
   absolute 라 아래 레이아웃은 그대로고, 넘치는 만큼만 min-height 로 흡수한다
   (이미지 세로비 0.7028 → 276px×0.7028 = 194px, min-height 158px 로 위아래 18px 씩 여백에 얹힌다). */
.hero-banner .hero-banner-art {
  position: absolute;
  /* 사용자 피드백(2026-08-13): 이미지가 너무 위에 떠 보여 중앙(50%)보다 아래로 내림 */
  top: 62%;
  right: 0;
  transform: translateY(-50%);
  /* 배너 높이 20% 축소에 맞춰 비례 축소 (276→221px) */
  width: clamp(106px, 24.3vw, 221px);
  pointer-events: none;
}

/* [design-css] 모바일에서도 일러스트를 보여준다(기존 display:none 제거).
   좁은 폭에서는 이미지만 줄이고 가로 2단 배치는 유지 — 390px 기준 텍스트 최소폭 확보. */
@media (max-width: 560px) {

  .hero-card,
  .hero-banner {
    align-items: center;
    gap: var(--sp-3);
  }

  .hero-card {
    padding: var(--sp-4);
  }

  /* [design-css] 모바일도 같은 비율로 +28% (100/26vw/150px → 128/33.3vw/192px) */
  .hero-card .hero-art {
    width: clamp(128px, 33.3vw, 192px);
    margin-bottom: calc(var(--sp-4) * -1);
  }

  .hero-card .hero-desc {
    margin-top: var(--sp-2);
    font-size: clamp(14px, calc(11.81px + 0.562vw), 19px);
    /* 390px→14px, 1280px→19px */
  }

  /* [design-css] 모바일도 데스크톱과 같은 비율로 +20% (기존 clamp(97px, 27.6vw, 150px)).
     여백은 6px 만 둔다 — 390px 에서 설명문 한 줄이 두 줄로 접히면
     배너가 21px 높아져 아래 탭·본문이 밀리기 때문. */
  .hero-banner {
    padding-right: clamp(99px, calc(26.5vw + 6px), 150px);
  }

  .hero-banner .hero-banner-art {
    /* 배너 20% 축소 비례 (180→144px) */
    width: clamp(93px, 26.5vw, 144px);
  }
}


/* ===== 03. 로그인 ===== */
/* 노트북에서 세로 스크롤 없이 한 화면: .app-main 안에서 세로 중앙 정렬 */
/* [design-css] 콘텐츠 영역은 1500px 정책을 따르고(래퍼), 읽기 폭은 카드에서 잡는다 */
.login-page {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: var(--sp-5);
  min-height: 100%;
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
}

.login-page>.card {
  width: 100%;
  max-width: 820px;
}

.login-page .login-actions {
  display: grid;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}

.login-page .login-guide {
  margin-top: var(--sp-2);
}


/* ===== 04. 회원가입 ===== */
.signup-page {
  width: 100%;
  /* 사용자 피드백(2026-08-13): 가로 확대 (720 → 1080px) */
  max-width: 1080px;
  margin: 0 auto;
  display: grid;
  gap: var(--sp-5);
}

/* 회원정보 입력 필드 — PC(760px+)에서 한 줄에 2개씩 */
.signup-field-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-4) var(--sp-5);
}

@media (min-width: 760px) {
  .signup-field-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* [design-css] 카드 안 요소들이 서로 margin 없이 붙어 있었다(지역 필드↔가입 버튼↔안내↔링크).
   리듬을 3단계로 통일한다 — 큰 덩어리 sp-6 / 섹션 제목 위 sp-6 / 입력 사이 sp-5(.field 기본). */
.signup-page>.card {
  display: grid;
  gap: var(--sp-6);
}

/* 카드 gap 과 섹션 제목의 자기 여백이 겹치지 않게 폼 첫 제목만 0 */
.signup-page form>.section-label:first-child {
  margin-top: 0;
}

/* 제출 버튼은 입력 영역과 확실히 떼어 놓는다 */
.signup-page form>.btn {
  margin-top: var(--sp-6);
}

/* PASS 본인인증 카드 — NICE 팝업 활성 (2026-09-08, 종전 "추후 제공" 비활성 안내에서 전환)
   구성: .pass-icon(로고) | .pass-text(.pass-title / .pass-desc) | .pass-btn(공용 .btn.btn-primary)
   가져온 shadcn/Tailwind 버튼(색·그림자·라운드)은 R3 위반이라 쓰지 않고 이 블록에서 값을 잡는다. */
.pass-card {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border: 1px solid var(--purple-line);
  border-radius: var(--r-lg);
  background: var(--purple-soft);
}

.pass-card .pass-icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line);
  font-size: clamp(24px, calc(20.49px + 0.899vw), 32px);
  /* 390px→24px, 1280px→32px */
}

.pass-card .pass-text {
  flex: 1 1 auto;
  min-width: 0;
}

.pass-card .pass-title {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: clamp(16px, calc(13.8px + 0.562vw), 21px);
  /* 390px→16px, 1280px→21px */
  font-weight: 700;
  color: var(--text);
}

.pass-card .pass-desc {
  margin-top: var(--sp-1);
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 600;
  color: var(--text-mute);
}

/* 인증 버튼 — 아이콘 상자(52px)와 같은 높이로 맞추고, 문구가 "시작하기"↔"진행 중…" 으로 바뀌어도
   카드 폭이 흔들리지 않게 최소 폭을 준다. 줄바꿈 금지(원래 하단 별도 블록에 있던 규칙을 여기로 모음) */
.pass-card .pass-btn {
  flex: 0 0 auto;
  min-height: 52px;
  min-width: 11em;
  padding: 0 var(--sp-5);
  border-radius: var(--r-md);
  white-space: nowrap;
  font-size: clamp(16px, calc(13.8px + 0.562vw), 21px);
  /* 390px→16px, 1280px→21px — 제목과 같은 크기 */
}

/* 팝업이 열려 있는 동안(진행 중) — 눌린 상태가 아니라 대기 중임이 보이게 옅게만 */
.pass-card .pass-btn:disabled {
  opacity: .6;
  cursor: default;
}

/* 640px 이하: 아이콘·텍스트·버튼이 좁게 눌리지 않게 세로 적층 + 버튼은 한 줄 전체
   (종전 480px — 버튼이 커지면서 500px 안팎에서 설명이 5줄로 눌려 640px 로 올림) */
@media (max-width: 640px) {
  .pass-card {
    flex-wrap: wrap;
  }

  /* 기준폭 0 — 아이콘 옆 남은 폭만큼만 차지해 로고와 같은 줄에 남는다(auto 면 로고 혼자 한 줄) */
  .pass-card .pass-text {
    flex: 1 1 0;
  }

  .pass-card .pass-title {
    flex-wrap: wrap;
  }

  .pass-card .pass-btn {
    flex: 1 1 100%;
    min-width: 0;
  }
}


/* ===== 05. 지역 선택 (트리거 + 모달 3단계) ===== */
/* [design-css] 시/도·시군구·동 그리드를 페이지에 세로로 늘어놓던 방식 → 트리거 한 줄 + 모달.
   모달은 한 번에 한 단계만 보여주고 내용이 교체된다(RegionPicker 의 step 상태). */
.region-picker {
  display: grid;
  gap: var(--sp-3);
}

/* 페이지에 남는 유일한 요소 — 선택 전/후 문구가 바뀐다 */
.region-picker .region-trigger {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 58px;
  padding: 0 var(--sp-3) 0 var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  text-align: left;
  transition: border-color .15s ease, background .15s ease;
}

.region-picker .region-trigger:hover {
  border-color: var(--blue-line);
  background: var(--blue-soft);
}

.region-picker .region-trigger-icon {
  flex: 0 0 auto;
  font-size: clamp(18px, calc(15.37px + 0.674vw), 24px);
  /* 390px→18px, 1280px→24px */
  line-height: 1;
}

.region-picker .region-trigger-text {
  flex: 1 1 auto;
  min-width: 0;
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--text-mute);
}

.region-picker .region-trigger.is-filled .region-trigger-text {
  font-weight: 700;
  color: var(--text);
}

.region-picker .region-trigger-action {
  flex: 0 0 auto;
  padding: 6px var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--blue-soft);
  color: var(--blue-dark);
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
}

/* --- 모달 카드 --- */
/* [frontend-dev 2026-08-12] 카드 높이를 뷰포트에 묶어(height: min(86dvh, 640px)) flex 컬럼으로 쓴다.
   → 헤더는 flex 고정칸이라 언제나 보이고, 스크롤은 본문 안(패널 목록 · 동 그리드)에서만 일어난다.
   종전에는 카드 자체가 늘어나며 헤더(제목·뒤로가기)가 화면 위로 잘려 나갔다. */
.region-modal .region-modal-card {
  display: flex;
  flex-direction: column;
  width: min(820px, 100%);
  height: min(86dvh, 640px);
  max-height: 86dvh;
  overflow: hidden;
}

/* 동이 6개뿐인 군(郡)도 있어 풀그리드 화면은 내용만큼만 쓴다.
   상한(86dvh)은 그대로라 목록이 길면 그리드 안에서 스크롤된다 — 헤더는 항상 보인다. */
.region-modal .region-modal-card.is-dong {
  height: auto;
  min-height: min(58dvh, 400px);
  max-height: min(86dvh, 640px);
}

.region-modal .region-modal-head {
  position: sticky;
  top: 0;
  z-index: 2;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
}

.region-modal .region-head-text {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
}

.region-modal .region-trail {
  margin-top: 2px;
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
  color: var(--blue-dark);
}

/* 제목은 폭에 따라 둘 중 하나만 — 데스크톱은 2패널이라 "지역 선택" 한 줄 */
.region-modal .region-title-narrow {
  display: none;
}

.region-modal .region-trail-narrow {
  display: none;
}

.region-modal .region-back {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--r-sm);
  color: var(--text-sub);
  font-size: clamp(26px, calc(22.5px + 0.899vw), 34px);
  /* 390px→26px, 1280px→34px */
  line-height: 1;
}

.region-modal .region-back:hover {
  background: var(--surface-sunken);
}

.region-modal .region-back.is-hidden {
  visibility: hidden;
}

/* 데스크톱 2패널에서는 시/군/구 단계도 같은 화면이라 뒤로가기가 필요 없다 */
.region-modal .region-modal-card.step-sigungu .region-back {
  visibility: hidden;
  pointer-events: none;
}

/* 본문은 남는 높이를 전부 쓰고, 스크롤은 그 안쪽에서 처리한다 */
.region-modal .region-modal-body {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  padding: var(--sp-4);
  overflow: hidden;
}

/* --- 2패널 (① 시/도 · ② 시/군/구) --- */
/* [frontend-dev 2026-08-12] 참고 이미지(연도 선택) 방식 — 좌측에서 시/도를 고르면
   우측이 즉시 갱신된다. 시/도 선택 전 우측은 점선 플레이스홀더. */
.region-modal .region-panes {
  display: grid;
  grid-template-columns: minmax(160px, 0.85fr) minmax(0, 1.6fr);
  gap: var(--sp-4);
  min-height: 0;
}

.region-modal .region-pane {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
}

/* ② 패널은 왼쪽에 구분선을 두어 두 단계가 나뉘어 보이게 한다 */
.region-modal .region-pane.is-sigungu {
  padding-left: var(--sp-4);
  border-left: 1px solid var(--line);
}

.region-modal .region-pane-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--line);
}

.region-modal .region-pane-no {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--blue);
  color: var(--text-invert);
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 700;
  line-height: 1;
}

.region-modal .region-pane-name {
  font-size: clamp(14px, calc(12.25px + 0.449vw), 18px);
  /* 390px→14px, 1280px→18px */
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--text);
}

.region-modal .region-pane-pick {
  margin-left: auto;
  overflow: hidden;
  max-width: 55%;
  padding: 3px var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--blue-soft);
  color: var(--blue-dark);
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 700;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* 목록만 스크롤한다 — 서울(구 25개) · 경기(시군구 31개) 도 헤더를 밀지 않는다 */
.region-modal .region-pane-list {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  align-content: start;
  gap: 4px;
  overflow-y: auto;
  padding: 2px;
}

.region-modal .region-pane-list.is-grid {
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--sp-2);
}

/* 패널 안의 로딩·빈·에러 폴백도 남는 높이를 채운다 */
.region-modal .region-pane>.state-block {
  flex: 1 1 auto;
  min-height: 0;
}

.region-modal .region-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap-min);
  padding: 0 var(--sp-3);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text);
  text-align: left;
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
  letter-spacing: -.02em;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}

.region-modal .region-item-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.region-modal .region-item-arrow {
  flex: 0 0 auto;
  color: var(--text-mute);
  font-size: clamp(16px, calc(14.25px + 0.449vw), 20px);
  /* 390px→16px, 1280px→20px */
  line-height: 1;
}

.region-modal .region-item:hover {
  border-color: var(--blue-line);
  background: var(--blue-soft);
}

.region-modal .region-item.is-selected {
  border-color: var(--blue);
  background: var(--blue);
  color: var(--text-invert);
}

.region-modal .region-item.is-selected .region-item-arrow {
  color: var(--text-invert);
}

/* 시/도 선택 전의 ② 패널 — 점선 박스 안내 */
.region-modal .region-pane-empty {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--sp-2);
  padding: var(--sp-4);
  border: 2px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface-soft);
  text-align: center;
}

.region-modal .region-pane-empty-icon {
  font-size: clamp(28px, calc(24.5px + 0.899vw), 36px);
  /* 390px→28px, 1280px→36px */
  line-height: 1;
}

.region-modal .region-pane-empty-text {
  font-size: clamp(14px, calc(12.25px + 0.449vw), 18px);
  /* 390px→14px, 1280px→18px */
  font-weight: 700;
  color: var(--text-mute);
}

/* --- ③ 읍/면/동 풀그리드 (참고 이미지의 "월 선택" 화면) --- */
.region-picker .region-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  align-content: start;
  gap: var(--sp-2);
  min-height: 0;
  overflow-y: auto;
  padding: 2px;
}

.region-picker .region-btn {
  min-height: 54px;
  padding: 0 var(--sp-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-soft);
  color: var(--text);
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  letter-spacing: -.02em;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}

.region-picker .region-btn:hover {
  border-color: var(--blue-line);
  background: var(--blue-soft);
}

.region-picker .region-btn.is-selected {
  border-color: var(--blue);
  background: var(--blue);
  color: var(--text-invert);
}


/* ===== 06. 문진 인트로 ===== */
.survey-intro {
  display: grid;
  gap: var(--sp-5);
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
}

.survey-intro .intro-steps {
  display: grid;
  gap: var(--sp-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.survey-intro .intro-step {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  color: var(--text);
}

.survey-intro .intro-step .step-no {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--blue-soft);
  color: var(--blue);
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 900;
}


/* ===== 07. 문진 단계 ===== */
.survey-page {
  display: grid;
  gap: var(--sp-5);
  width: 100%;
  max-width: 820px;
  margin: 0 auto;
}

.survey-page .survey-progress {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  color: var(--blue);
}

.survey-page .progress-track {
  flex: 1 1 auto;
  height: 10px;
  border-radius: var(--r-pill);
  background: var(--surface-sunken);
  overflow: hidden;
}

.survey-page .progress-fill {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--blue);
  transition: width .25s ease;
}

.survey-page .survey-question {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.survey-page .question-index {
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--blue);
}

.survey-page .question-text {
  font-size: clamp(18px, calc(15.37px + 0.674vw), 24px);
  /* 390px→18px, 1280px→24px */
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1.35;
  color: var(--text);
}

/* 문진 작성(스텝)·만족도 조사 화면도 같은 상단 여백 (2026-08-24) */
.survey-page {
  margin-top: var(--sp-4);
}

.survey-page .choice-list {
  display: grid;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.survey-page .choice-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 58px;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font-size: clamp(16px, calc(13.8px + 0.562vw), 21px);
  /* 390px→16px, 1280px→21px */
  font-weight: 700;
  text-align: left;
  letter-spacing: -.02em;
  /* 선택지는 클릭 대상 — 포인터 커서 (2026-08-24 사용자 지시. label 이라 기본 커서였다.
     만족도 조사도 .survey-page 스킨을 그대로 쓰므로 함께 적용된다) */
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}

.survey-page .choice-item:hover {
  border-color: var(--blue-line);
  background: var(--blue-soft);
}

.survey-page .choice-item.is-checked {
  border-color: var(--blue);
  background: var(--blue-soft);
  color: var(--blue-dark);
}

.survey-page .choice-mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border: 2px solid var(--line-strong);
  border-radius: 50%;
  background: var(--surface);
  color: transparent;
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
}

.survey-page .choice-item.is-checked .choice-mark {
  border-color: var(--blue);
  background: var(--blue);
  color: var(--text-invert);
}

.survey-page .choice-mark.is-square {
  border-radius: 7px;
}

.survey-page .survey-nav {
  display: flex;
  gap: var(--sp-3);
}

.survey-page .survey-nav .btn {
  flex: 1 1 0;
}


/* ===== 08. 홈 (시안 07) ===== */
/* [design-css] 콘텐츠 최대 폭 1500px 중앙 정렬 (AppShell wide 모드에서 폭을 직접 잡는다) */
/* [design-css] 시안 07 — 인사 일러스트 아래끝이 카드 그리드 위끝에 밀착(간격 0)해야 하므로
   행 간격은 0으로 두고, 안내박스만 자기 위 여백(margin-top)을 직접 갖는다. */
.home-page {
  display: grid;
  gap: 0;
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
}

/* [frontend-dev 2026-08-12] 안내박스가 배경(#EEF0F7)에 묻혀 보이지 않던 문제 —
   흰 카드 + 블루 좌측 액센트 바 + 테두리로 바탕과 확실히 분리한다.
   (.info-box.is-blue 의 연블루 배경보다 대비가 크다. 두 규칙은 명시도가 같고
    main.css 가 global.css 뒤에 로드되므로 여기 값이 이긴다.) */
.home-page>.info-box {
  align-items: center;
  margin-top: var(--sp-5);
  border: 1px solid var(--blue-line);
  border-left: 5px solid var(--blue);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.home-page>.info-box .info-text {
  min-width: 0;
}

.home-page>.info-box .info-body {
  min-width: 0;
}

.home-page .greet-block {
  display: grid;
  gap: var(--sp-1);
}

/* [design-css] 모바일에서 우측 일러스트와 나란히 놓기 위해 하한만 낮춘다(상한 동일) */
.home-page .greet-title {
  font-size: clamp(22px, calc(13.24px + 2.247vw), 42px);
  /* 390px→22px, 1280px→42px */
  font-weight: 900;
  letter-spacing: -.05em;
  line-height: 1.15;
  color: var(--text);
}

.home-page .greet-sub {
  font-size: clamp(15px, calc(11.06px + 1.011vw), 24px);
  /* 390px→15px, 1280px→24px */
  font-weight: 700;
  color: var(--text);
}

.home-page .greet-memo {
  font-size: clamp(12px, calc(9.81px + 0.562vw), 17px);
  /* 390px→12px, 1280px→17px */
  font-weight: 600;
  color: var(--text-mute);
}

.menu-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-3);
}

.menu-grid .menu-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  text-align: center;
  transition: transform .2s ease, box-shadow .2s ease;
}

/* [design-css] 마우스 오버 시 카드가 살짝 떠오른다 (터치 기기는 제외 — sticky hover 방지).
   위로 5px 이동 + 아래로 크게 깔리는 그림자 = 공중에 떠 있는 느낌.
   홈 .menu-card 전용 — 측정이력 .metric-card 등에는 적용하지 않는다. */
@media (hover: hover) {
  .menu-grid .menu-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 18px 40px rgba(37, 99, 235, .16), 0 6px 12px rgba(15, 23, 42, .08);
  }

  .menu-grid .menu-card:active {
    transform: translateY(-1px);
    box-shadow: 0 8px 18px rgba(37, 99, 235, .14), 0 2px 6px rgba(15, 23, 42, .07);
  }

  /* [frontend-dev 2026-08-12] 카드 어디에 올려도 안쪽 버튼이 같이 반응한다
     (버튼 위에 정확히 올렸을 때만 색이 변해 "눌리는 곳"이 헷갈리던 문제).
     시작하기·확인하기 = 블루 → 진한 블루, 준비 중 = outline 배경만 옅게. */
  .menu-grid .menu-card:hover .menu-action.btn-blue {
    background: var(--blue-dark);
    box-shadow: 0 6px 16px rgba(37, 99, 235, .28);
  }

  .menu-grid .menu-card:hover .menu-action.btn-outline {
    background: var(--purple-soft);
    border-color: var(--purple-deep);
  }
}

/* 모션 민감 사용자는 이동 없이 그림자만 */
@media (prefers-reduced-motion: reduce) {

  .menu-grid .menu-card,
  .menu-grid .menu-card:hover,
  .menu-grid .menu-card:active {
    transform: none;
    transition: box-shadow .2s ease;
  }
}

/* [design-css] 번호 배지(.menu-no)는 시안 07 재확인 후 삭제.
   썸네일은 카드 폭 전체를 쓰는 고정 높이 박스 + contain 이라
   가로/세로 비율이 다른 자산들도 카드 간 크기가 일정하게 보인다. */
/* [design-css] 썸네일 아래가 제목에 붙어 보여 아래 여백만 sp-4 로 키운다
   (카드 gap sp-2 + margin-bottom sp-4 = 24px). 위 여백은 종전 sp-2 유지. */
.menu-grid .menu-thumb {
  width: 100%;
  height: clamp(96px, 11.7vw, 95px);
  object-fit: contain;
  margin: var(--sp-1) var(--sp-2) var(--sp-2) var(--sp-2);
}

.menu-grid .menu-title {
  font-size: clamp(18px, calc(15.37px + 0.674vw), 24px);
  /* 390px→18px, 1280px→24px */
  font-weight: 700;
  letter-spacing: -.04em;
  color: var(--text);
}

.menu-grid .menu-desc {
  font-size: clamp(13px, calc(11.25px + 0.449vw), 16px);
  /* 390px→13px, 1280px→17px */
  font-weight: 600;
  line-height: 1.35;
  color: var(--text-sub);
}

/* [frontend-dev 2026-08-12] 카드 리프트(.2s)와 같은 호흡으로 색이 바뀌도록 전환을 맞춘다 */
.menu-grid .menu-action {
  width: 100%;
  margin-top: var(--sp-3);
  transition: background .2s ease, border-color .2s ease, color .2s ease, box-shadow .2s ease;
}

.menu-grid .menu-badge {
  position: absolute;
  top: var(--sp-4);
  right: var(--sp-4);
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 0 var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--surface-sunken);
  color: var(--text-mute);
  font-size: clamp(11px, calc(9.7px + 0.337vw), 14px);
  /* 390px→11px, 1280px→14px */
  font-weight: 700;
}

/* 측정 완료 뱃지 — 준비 중 뱃지와 같은 자리에 초록 톤으로 (사용자 지시 2026-08-16) */
.menu-grid .menu-badge.is-done {
  gap: 3px;
  border: 1px solid color-mix(in srgb, var(--success) 35%, transparent);
  background: var(--success-bg);
  color: var(--success);
}

.menu-grid .menu-card.is-disabled {
  background: var(--surface-soft);
  box-shadow: none;
}

.menu-grid .menu-card.is-disabled .menu-thumb {
  opacity: .5;
}

.menu-grid .menu-card.is-disabled .menu-title,
.menu-grid .menu-card.is-disabled .menu-desc {
  color: var(--text-mute);
}

/* [design-css] 데스크톱 3열(시안 07) / 중간 폭 2열 / 모바일 1열 */
@media (max-width: 1099px) {
  .menu-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 560px) {
  .menu-grid {
    grid-template-columns: 1fr;
  }

  /* [2026-08-27 사용자 지정 — 개발자도구로 이 규칙을 직접 짚어 지시] sp-2 → 0.
     인사 배너의 시니어 일러스트 아래끝이 메뉴 카드 그리드에 **완전히 밀착**해야 한다.
     이 폭에서만 8px 을 주던 종전 판단(아래 묘비)을 사용자 화면 확인으로 뒤집는다.
     [묘비] "모바일에서는 일러스트가 작아 밀착 효과가 살지 않고 인사 문구가 카드에 그대로
     닿아 보인다 — 이 폭에서만 최소 간격을 준다(데스크톱 시안 07 은 그대로 0)."
     ※ 부작용 확인(390px 실측). .home-page 직계 자식은 셋이라 gap 이 두 경계에 닿는다:
         .greet-block → .menu-grid   8 → 0px   ← 지시한 밀착
         .menu-grid  → .info-box    28 → 20px  ← 곁딸린 변화
       뒤쪽은 .info-box 가 자기 margin-top 20px 을 따로 들고 있어 gap 이 빠져도 20px 이 남는다.
       하단 안내 상자가 카드 그리드에 붙지 않으므로 이 폭에서 손볼 것은 없다. */
  .home-page {
    gap: 0;
  }
}


/* ===== 09. 측정이력 셸 (시안 12~14) ===== */
.history-page {
  display: grid;
  gap: var(--sp-3);
}

/* [design-css] 탭 본문 래퍼(.tab-panel-enter)가 블록이라 .history-page 의 gap 이 안쪽까지
   내려가지 않았다 — 지표 그리드와 AI 분석 카드가 간격 0 으로 맞붙던 원인.
   래퍼에도 같은 간격을 주고, 자기 margin 을 갖는 섹션 제목은 gap 과 겹치지 않게 0 으로 맞춘다. */
.history-page .tab-panel-enter {
  display: grid;
  gap: var(--sp-5);
}

.history-page .tab-panel-enter>.section-label {
  margin: 0;
}

/* 탭 고도화(사용자 피드백 2026-08-13, 참고 이미지): 전체 폭 기준선(2px) 위에
   버튼 폭을 꽉 채우는 두꺼운 활성 밑줄이 얹히는 스타일 + 라벨 확대 */
.tab-switch {
  display: flex;
  gap: var(--sp-6);
  border-bottom: 2px solid var(--line);
}

.tab-switch .tab-switch-btn {
  position: relative;
  min-height: 54px;
  padding: 0 var(--sp-4);
  color: var(--text-mute);
  font-size: clamp(16px, calc(13.8px + 0.562vw), 24px);
  /* 390px→16px, 1280px→21px */
  font-weight: 700;
  letter-spacing: -.02em;
  transition: color .15s ease;
}

.tab-switch .tab-switch-btn:hover {
  color: var(--text-sub);
}

.tab-switch .tab-switch-btn.is-active {
  color: var(--blue);
  font-weight: 700;
}

.tab-switch .tab-switch-btn.is-active::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 4px;
  border-radius: var(--r-pill) var(--r-pill) 0 0;
  background: var(--blue);
  animation: tab-underline-in .18s ease-out both;
}

@keyframes tab-underline-in {
  from {
    transform: scaleX(.4);
    opacity: .4;
  }

  to {
    transform: scaleX(1);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tab-switch .tab-switch-btn.is-active::after {
    animation: none;
  }
}

.period-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
}

/* [2026-08-27 사용자 지시] 변화 탭 인쇄 버튼 — 필터 바 **우측 끝**에 세운다.
   margin-left:auto 는 앞 요소들이 쓰고 남은 자리를 전부 왼쪽 여백으로 삼는다. 이 바는
   조회 기간·장소 필터가 폭을 다 쓰지 않는 경우가 많아(장소 필터가 없는 회원 등) auto 마진이
   있어야 어느 조합에서도 오른쪽에 붙는다.
   flex-shrink 0 — 좁은 폭에서 "🖨 인쇄하기" 가 두 줄로 접히거나 눌리지 않게 한다.
   ※ 버튼 자체 시각 규칙은 공용 .btn.btn-blue 가 갖는다. `.report-toolbar .print-btn` 는
      상세리포트 툴바로 스코프돼 있어 이 바에는 걸리지 않으므로 크기 보정도 여기서 하지 않는다.
   ※ .period-bar 는 JSX 에서 no-print 라 지면에는 이 버튼이 나오지 않는다. */
.period-bar .change-print-btn {
  flex: 0 0 auto;
  margin-left: auto;
}

.period-bar .period-field {
  flex: 1 1 0;
  min-width: 0;
}

.period-bar .period-input {
  width: 100%;
  min-height: 52px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  color: var(--text);
}

.period-bar .period-input:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 4px rgba(37, 99, 235, .14);
}

.period-bar .period-tilde {
  flex: 0 0 auto;
  color: var(--text-mute);
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
}

.period-bar .period-submit {
  flex: 0 0 auto;
  min-width: 120px;
  min-height: 52px;
}

@media (max-width: 560px) {
  .period-bar {
    flex-wrap: wrap;
  }

  .period-bar .period-field {
    flex: 1 1 40%;
  }

  .period-bar .period-submit {
    flex: 1 1 100%;
  }
}


/* ===== 10. 대시보드 탭 — 지표 카드 ===== */
.metric-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-4);
}

.metric-grid .metric-card {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

/* [design-css] 지표 카드 헤더 — 대시보드 탭(.metric-card)과 변화 탭(.change-card) 공용.
   이전에는 `.metric-grid .metric-head` 로 묶여 있어서 같은 마크업을 쓰는 변화 탭에서는
   규칙이 하나도 걸리지 않았고, 아이콘·지표명·단위가 좌상단에 세로로 붙어 보였다.
   블록 클래스(.metric-head) 기준으로 바꿔 두 탭이 같은 헤더를 쓴다. */
.metric-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.metric-head .metric-icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  /* 사용자 지시(2026-08-14): 기본 크기 확대 + 디바이스 폭에 따라 가변 (고정 40px → 44~56px) */
  width: clamp(44px, calc(38.74px + 1.348vw), 56px);
  height: clamp(44px, calc(38.74px + 1.348vw), 56px);
  border-radius: var(--r-md);
  background: var(--blue-soft);
  font-size: clamp(22px, calc(18.5px + 0.899vw), 30px);
  /* 390px→22px, 1280px→30px */
}

/* 지표명 + 단위 묶음. 아이콘 오른쪽에 붙고 남는 폭을 먹어 수치를 오른쪽 끝으로 민다 */
.metric-head .metric-title {
  flex: 1 1 auto;
  min-width: 0;
}

.metric-head .metric-name {
  font-size: clamp(17px, calc(14.8px + 0.562vw), 26px);
  /* 390px→17px, 1280px→22px */
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1.25;
  color: var(--text);
}

.metric-head .metric-unit {
  margin-top: 2px;
  font-size: clamp(12px, calc(11.7px + 0.337vw), 18px);
  /* 390px→12px, 1280px→15px */
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-mute);
}

.metric-head .metric-value {
  flex: 0 0 auto;
  font-size: clamp(24px, calc(20.49px + 0.899vw), 32px);
  /* 390px→24px, 1280px→32px */
  font-weight: 700;
  letter-spacing: -.04em;
  color: var(--text);
  text-align: right;
}

/* 추이 카드 범례 (2026-08-17) — 차트 밖 한 줄. 차트 안 범례는 값 라벨과 겹쳐 폐기했다.
   글자를 키우라는 지시라 축 눈금(12px)보다 한 단계 위로 잡았다. */
.metric-legend,
.change-card .metric-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-4);
  margin: calc(var(--sp-1) * -1) 0 var(--sp-1);
  color: var(--text-sub);
  font-size: clamp(13px, calc(12.12px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 700;
}

.metric-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.metric-legend-line {
  display: inline-block;
  width: 16px;
  height: 3px;
  border-radius: 2px;
}

.metric-grid .metric-chart {
  width: 100%;
  height: clamp(150px, 16vw, 190px);
}

.metric-grid .metric-foot {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-soft);
  overflow: hidden;
}

.metric-grid .foot-cell {
  padding: var(--sp-2);
  border-right: 1px solid var(--line);
  text-align: center;
}

.metric-grid .foot-cell:last-child {
  border-right: 0;
}

.metric-grid .foot-key {
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 700;
  color: var(--text-mute);
}

.metric-grid .foot-val {
  margin-top: 2px;
  font-size: clamp(14px, calc(12.25px + 0.449vw), 18px);
  /* 390px→14px, 1280px→18px */
  font-weight: 700;
  color: var(--blue);
}

.metric-grid .foot-val.is-up {
  color: var(--danger);
}

.metric-grid .foot-val.is-down {
  color: var(--blue);
}

@media (max-width: 1000px) {
  .metric-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 640px) {
  .metric-grid {
    grid-template-columns: 1fr;
  }
}


/* ===== 11. AI 분석 요약 ===== */
.ai-summary {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.ai-summary .ai-summary-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}

/* 제목 옆 안내 — 분석 기준(최근 6개월 종합)을 밝힌다 (사용자 지시 2026-08-19) */
.ai-summary .ai-summary-note {
  color: var(--text-mute);
  font-size: clamp(12px, calc(10.9px + 0.281vw), 14.5px);
  /* 390px→12px, 1280px→14.5px */
  font-weight: 600;
  text-align: right;
}

.ai-summary .ai-summary-title {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: clamp(18px, calc(15.37px + 0.674vw), 24px);
  /* 390px→18px, 1280px→24px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--text);
}

/* [design-css] 게이지 · 코멘트 · 태그를 하나의 연회색 패널로 묶는다(시안 12).
   간격은 패널 padding(sp-4) / 열 gap(sp-5) / 세로 gap(sp-3) 3단계로만 쓴다. */
.ai-summary .ai-summary-body {
  display: grid;
  grid-template-columns: minmax(0, 210px) minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: center;
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface-soft);
}

/* 게이지 상세(트랙·호·애니메이션)는 파일 하단 [design-ai-card] 블록 */
.ai-summary .ai-score {
  display: grid;
  justify-items: center;
  gap: var(--sp-2);
  padding-right: var(--sp-5);
  border-right: 1px solid var(--line);
}

/* 코멘트 + 태그 열 — 두 덩어리 사이 간격을 gap 하나로 관리한다 */
.ai-summary .ai-summary-text {
  display: grid;
  gap: var(--sp-3);
  min-width: 0;
}

.ai-summary .ai-comment-list {
  display: grid;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 600;
  line-height: 1.6;
  color: var(--text);
}

.ai-summary .ai-tag-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-2);
}

/* [design-css] 라벨-상태가 한 줄에 붙어 있어 좁아지면 줄바꿈이 지저분했다 —
   2행 스택 + 왼쪽 상태색 바 (상태색은 tone 클래스로 온다) */
.ai-summary .ai-tag {
  display: grid;
  align-content: center;
  gap: 2px;
  min-height: 54px;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--line);
  border-left: 4px solid var(--text-mute);
  border-radius: var(--r-md);
  background: var(--surface);
}

.ai-summary .ai-tag.is-ok {
  border-left-color: var(--grade-normal);
}

.ai-summary .ai-tag.is-warn {
  border-left-color: var(--grade-attention);
}

.ai-summary .ai-tag.is-danger {
  border-left-color: var(--grade-consult);
}

.ai-summary .ai-tag-key {
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 700;
  color: var(--text-mute);
}

.ai-summary .ai-tag-val {
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
  line-height: 1.3;
  color: var(--text);
}

.ai-summary .ai-tag.is-ok .ai-tag-val {
  color: var(--grade-normal);
}

.ai-summary .ai-tag.is-warn .ai-tag-val {
  color: var(--grade-attention);
}

.ai-summary .ai-tag.is-danger .ai-tag-val {
  color: var(--grade-consult);
}

@media (max-width: 1180px) {
  .ai-summary .ai-tag-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {
  .ai-summary .ai-summary-body {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-4);
  }

  /* 1열로 접히면 세로 구분선으로 바꾼다 */
  .ai-summary .ai-score {
    padding-right: 0;
    padding-bottom: var(--sp-4);
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }
}

@media (max-width: 520px) {
  .ai-summary .ai-tag-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* 맞춤 건강 관리 권장사항 3열 (운동·식단·모니터링) */
.advice-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
}

/* [design-css] 연한 단색 배경이라 페이지 바탕(#EEF0F7)과 구분이 안 됐다 —
   ① 위에서 아래로 틴트→흰색 그라데이션(아래쪽이 바탕보다 확실히 밝다)
   ② 계열색 테두리 + 카드 그림자
   ③ 좌측 6px 액센트 바 + 흰 아이콘 칩
   카드별 계열색은 아래 is-* 에서 --advice-ink/tint/line 3개만 갈아끼운다. */
.advice-grid .advice-card {
  --advice-ink: var(--blue);
  --advice-tint: var(--blue-soft);
  --advice-line: var(--blue-line);
  position: relative;
  display: grid;
  gap: var(--sp-3);
  align-content: start;
  overflow: hidden;
  padding: var(--sp-4) var(--sp-4) var(--sp-4) calc(var(--sp-4) + 6px);
  border: 1px solid var(--advice-line);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--advice-tint) 0%, var(--surface) 72%);
  box-shadow: var(--shadow-card);
}

.advice-grid .advice-card::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 6px;
  background: var(--advice-ink);
}

.advice-grid .advice-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: clamp(16px, calc(13.8px + 0.562vw), 21px);
  /* 390px→16px, 1280px→21px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--text);
}

.advice-grid .advice-icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border: 1px solid var(--advice-line);
  border-radius: var(--r-pill);
  background: var(--surface);
  font-size: clamp(16px, calc(14.24px + 0.45vw), 20px);
  /* 390px→16px, 1280px→20px */
  line-height: 1;
}

.advice-grid .advice-list {
  display: grid;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 600;
  line-height: 1.55;
  color: var(--text-sub);
}

.advice-grid .advice-list li {
  position: relative;
  padding-left: 16px;
}

/* 기본 disc 대신 계열색 점 — 여러 줄로 접혀도 들여쓰기가 유지된다 */
.advice-grid .advice-list li::before {
  content: "";
  position: absolute;
  top: .6em;
  left: 2px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--advice-ink);
}

.advice-grid .advice-card.is-exercise {
  --advice-ink: var(--blue);
  --advice-tint: var(--blue-soft);
  --advice-line: var(--blue-line);
}

.advice-grid .advice-card.is-diet {
  --advice-ink: var(--grade-normal);
  --advice-tint: var(--grade-normal-bg);
  --advice-line: var(--grade-normal-line);
}

.advice-grid .advice-card.is-monitor {
  --advice-ink: var(--purple-deep);
  --advice-tint: var(--purple-soft);
  --advice-line: var(--purple-line);
}

@media (max-width: 900px) {
  .advice-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-3);
  }
}


/* ===== 12. 변화 탭 (지표별 추이 카드) ===== */
/* [dash-v2 2026-08-27] 항목이 5 → 8개(산소포화도·골격근량·체수분 추가)로 늘면서 1열 세로
   나열은 세로 길이가 두 배가 됐다 — 대시보드 탭과 같은 2열 그리드로 바꾼다.
   1열 → 2열 전환점은 780px(대시보드 .dash-grid 와 같은 값)이고, 카드가 절반 폭이 되면
   .change-body 의 `1fr 220px` 이 눌리므로 아래 1180px 분기에서 side 를 차트 밑으로 내린다. */
.change-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}

/* [design-css] 헤더와 본문이 간격 0 으로 맞붙어 있어 grid + gap 으로 띄운다
   (대시보드 탭 .metric-card 와 같은 sp-3 간격) */
.change-grid .change-card {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.change-grid .change-body {
  display: grid;
  grid-template-columns: 1fr 220px;
  gap: var(--sp-4);
  align-items: center;
}

.change-grid .change-chart {
  width: 100%;
  height: clamp(150px, 15vw, 200px);
  min-width: 0;
}

.change-grid .change-side {
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border-radius: var(--r-md);
  background: var(--surface-soft);
}

.change-grid .change-side-label {
  font-size: clamp(12px, calc(10.7px + 0.337vw), 16px);
  /* 390px→12px, 1280px→15px */
  font-weight: 700;
  color: var(--text-mute);
}

.change-grid .change-side-value {
  font-size: clamp(22px, calc(18.49px + 0.899vw), 30px);
  /* 390px→22px, 1280px→30px */
  font-weight: 700;
  letter-spacing: -.04em;
  color: var(--text);
}

.change-grid .change-side-delta {
  font-size: clamp(14px, calc(12.25px + 0.449vw), 20px);
  /* 390px→14px, 1280px→18px */
  font-weight: 700;
  color: var(--text-sub);
}

.change-grid .change-side-delta.is-up {
  color: var(--danger);
}

.change-grid .change-side-delta.is-down {
  color: var(--blue);
}

/* [dash-v2 2026-08-27] 종전 분기는 780px 이었다(1열 그리드 전제). 2열이 되면서 카드 안쪽 폭이
   절반이 됐으므로 side 를 내리는 시점을 1180px 로 올린다 — 1180px 에서 카드 안쪽은
   (1180-40-16)/2-40 ≈ 522px 이고, 여기서 side 220px 을 빼면 차트가 286px 밖에 안 남는다.
   ≤780px 은 그리드가 1열로 돌아가지만 모바일에서는 어차피 세로 스택이 맞으므로 이 분기가 겸한다.

   [2026-08-27 사용자 지시 "사이드 정보가 모바일에서 세로 4줄로 나온다 — 1줄에 배치"]
   side 가 차트 **아래 가로 띠**로 내려오는 순간(=이 분기) 세로 1열은 폭을 다 버리고 높이만
   먹는다. [최근값 쌍] | [변화량 쌍] 두 칸으로 나눠 나란히 세운다 — 라벨·값 쌍은 그대로 유지.
   마크업은 그대로다(.change-side 의 자식 4~5개가 순서대로 라벨·값·라벨·변화량[·변화량2]).
   클래스가 라벨 두 개에 같아서(.change-side-label 이 1·3번째) 클래스 선택자로는 좌우를 가를
   수 없다 — 자식 순서가 곧 계약이라 nth-child 로 짚는다.
   혈압만 변화량이 두 줄(수축기·이완기)인데, 5번째 자식을 오른쪽 칸 3행에 두면 그 칸 안에서
   자연스럽게 아래로 쌓인다(왼쪽 칸은 2행까지라 빈 3행은 높이 0). 항목별 예외 처리가 필요 없다.
   행 간격을 row-gap 이 아니라 padding-top 으로 주는 이유: 오른쪽 칸의 세로 구분선을
   border-left 로 그리는데, row-gap 이면 행 사이에서 선이 끊긴다(padding 안쪽은 테두리가 이어진다).
   390px 실측: side 안쪽 308px → 칸당 144px. 혈압 값 "111/74 mmHg" 113px · 변화량
   "수축기 +5 mmHg" 98px 로 둘 다 들어간다. */
@media (max-width: 1180px) {
  .change-grid .change-body {
    grid-template-columns: minmax(0, 1fr);
  }

  .change-grid .change-side {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-content: start;
    row-gap: 0;
    column-gap: var(--sp-5);
  }

  .change-grid .change-side>:nth-child(1) {
    grid-area: 1 / 1;
  }

  .change-grid .change-side>:nth-child(2) {
    grid-area: 2 / 1;
    padding-top: 2px;
  }

  .change-grid .change-side>:nth-child(3) {
    grid-area: 1 / 2;
  }

  .change-grid .change-side>:nth-child(4) {
    grid-area: 2 / 2;
    padding-top: 2px;
  }

  /* 혈압 전용 — 이완기 줄. 있을 때만 3행이 생긴다 */
  .change-grid .change-side>:nth-child(5) {
    grid-area: 3 / 2;
    padding-top: 2px;
  }

  /* 두 쌍 사이 구분선. 오른쪽 칸 자식 전부에 걸어 위→아래로 이어지게 한다
     (row-gap 0 + padding-top 조합이라 3행까지 한 줄로 보인다) */
  .change-grid .change-side>:nth-child(3),
  .change-grid .change-side>:nth-child(4),
  .change-grid .change-side>:nth-child(5) {
    padding-left: var(--sp-4);
    border-left: 1px solid var(--line);
  }
}

@media (max-width: 780px) {
  .change-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}


/* ===== 13. 상세리포트 탭 툴바 ===== */
.report-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  /* 리포트 본문(AI 건강리포트 대제목)과의 간격 (2026-08-24 사용자 지시) */
  margin-bottom: var(--sp-5);
}

.report-toolbar .report-select {
  min-height: 48px;
  min-width: 200px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  color: var(--text);
}

.report-toolbar .print-btn {
  flex: 0 0 auto;
}

@media (max-width: 560px) {
  .report-toolbar {
    flex-wrap: wrap;
  }

  .report-toolbar .report-select {
    flex: 1 1 100%;
  }
}


/* ===== 14. 모달 / 최근값 행 ===== */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: var(--sp-4);
  background: rgba(14, 22, 38, .45);
}

.modal-card {
  width: min(560px, 100%);
  max-height: 86dvh;
  overflow-y: auto;
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
}

.modal-card .modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--line);
}

.modal-card .modal-title {
  font-size: clamp(18px, calc(15.37px + 0.674vw), 24px);
  /* 390px→18px, 1280px→24px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--text);
}

.modal-card .modal-close {
  display: grid;
  place-items: center;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--r-sm);
  color: var(--text-mute);
  font-size: clamp(20px, calc(17.37px + 0.674vw), 26px);
  /* 390px→20px, 1280px→26px */
}

.modal-card .modal-close:hover {
  background: var(--surface-sunken);
}

.modal-card .modal-body {
  padding: var(--sp-5);
}

.modal-card .modal-foot {
  display: flex;
  gap: var(--sp-2);
  padding: 0 var(--sp-5) var(--sp-5);
}

.modal-card .modal-foot .btn {
  flex: 1 1 0;
}

.latest-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--line);
}

.latest-row:last-child {
  border-bottom: 0;
}

.latest-row .latest-key {
  font-size: clamp(14px, calc(12.25px + 0.449vw), 18px);
  /* 390px→14px, 1280px→18px */
  font-weight: 700;
  color: var(--text-sub);
}

.latest-row .latest-val {
  font-size: clamp(18px, calc(15.37px + 0.674vw), 24px);
  /* 390px→18px, 1280px→24px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--text);
}

.latest-row .latest-date {
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 600;
  color: var(--text-mute);
}


/* ===== 15. 인쇄 — 앱 크롬을 걷어내고 그때 열려 있는 탭만 남긴다 ===== */
/* [스코프 확인 2026-08-27] 탭 본문은 React 가 **활성 탭 하나만** 렌더한다
   (`{tab === "change" ? renderChangeTab() : null}` 꼴). 상세리포트 탭에서 인쇄하면 .change-grid
   가 DOM 에 아예 없고, 변화 탭에서 인쇄하면 .report 가 없다. 그래서 "리포트만 남긴다" 식의
   전역 body 숨김 규칙은 원래부터 없고(위 목록은 크롬 요소만 지운다), 아래 변화 탭 지면 규칙도
   .change-* 로만 스코프돼 두 탭이 서로를 건드리지 않는다. */
@media print {

  .tab-switch,
  .report-toolbar,
  .period-bar,
  .hero-banner,
  .modal-backdrop {
    display: none !important;
  }

  .history-page {
    gap: 0;
  }
}


/* ===== 15-1. 인쇄 — 변화 탭 지면 (2026-08-27 사용자 지시) ===== */
/* 앱 크롬(헤더·탭 스위치·필터 바·하단 탭바)을 지우는 일은 위 15 블록과 global.css 14 블록이
   이미 한다 — 여기서는 **카드 조판만** 더한다(중복 제거 규칙을 만들지 않는다).
   스코프는 프론트가 신설한 탭 정체 훅 `.tab-panel-enter.is-change` 다. .change-grid 하나로도
   실제로는 충돌하지 않지만(활성 탭만 렌더된다), 훅으로 묶어 두면 나중에 두 탭이 동시에
   렌더되는 구조로 바뀌어도 이 규칙이 상세리포트 지면으로 새지 않는다.

   지면 규격(@page size A4 / margin 9mm)은 report.css 인쇄 블록이 문서 전역으로 이미 잡는다 —
   두 CSS 가 같은 페이지에 함께 로드되고 @page 는 스코프가 없다. 여기서 다시 선언하면 값이
   갈라질 위험만 생기므로 두지 않는다(report.css 는 수정 금지, 값만 참조).

   [지면 폭 실측] A4 세로 210mm − 여백 18mm = 192mm ≈ 726px(96dpi). 화면 규칙으로는 780px
   이하라 .change-grid 가 1열로 접히는데, 그러면 카드 8장이 3쪽을 넘긴다. 2열로 되돌리면
   카드 폭 약 350px · 높이 약 320px 이라 4행이 2쪽에 담긴다 — 2열을 유지한다.
   같은 폭에서 위 [1180px] 분기는 그대로 살아 있어 .change-body 는 1열(차트 위 · 사이드 아래),
   사이드는 [최근값 | 변화량] 두 칸 가로 배치가 된다 — 지면에서 가장 조밀한 조합이라 그대로 쓴다.

   [색] report.css 는 .report 에 print-color-adjust: exact 를 걸어 배경·테두리를 지면에 남긴다.
   그 선언은 .report 스코프라 여기 걸리지 않으므로 같은 값을 카드에 건다 — 상속되는 속성이라
   카드 하나에 걸면 안쪽 사이드 틴트·구분선·차트까지 함께 받는다. 없으면 브라우저 기본값
   (economy)이 배경색을 통째로 버려 사이드 박스와 카드 경계가 지면에서 사라진다.
   차트 선은 SVG stroke 라 이 설정과 무관하게 찍히지만, 축 눈금선(--chart-grid)은 배경 계열이라
   같이 살려야 그림이 읽힌다. */
@media print {

  /* [2026-09-02 사용자 지시 "2쪽 → A4 한 장"] 8카드 만재 기준 1쪽이 조건이다.
     종전 2열 4행은 행당 약 356px 이라 4행 1424px — 지면 높이(297 − 18 = 279mm ≈ 1054px)를
     370px 넘겼고 마지막 두 행이 2쪽으로 밀렸다.
     [압축 방식] 두 가지를 겹쳤다. 어느 한쪽만으로는 글자가 읽기 하한(7px) 밑으로 가거나
     행이 여전히 넘친다.
       ① 카드 자체를 지면용으로 조인다 — 차트 높이 clamp(150~200px) → 고정 120px,
          카드 padding sp-4 → sp-3, 카드 안 gap sp-3 → sp-2, 그리드 gap sp-3 → sp-2.
          행 높이가 약 356 → 290px 로 내려간다(4행 1160px).
       ② 남은 분량은 zoom 으로 줄인다 — .86 으로는 여전히 2쪽이었고(행 실측이 어림보다 두꺼웠다)
          .72 에서 1쪽에 들어갔다(실측 PDF 1쪽).
     zoom 을 쓰는 이유: 리포트 지면(report.css S12)이 이미 같은 방식으로 쪽을 맞추고 있어
     이 앱의 인쇄 조판 어휘와 어긋나지 않는다. transform: scale 은 레이아웃 크기를 바꾸지
     않아 뒤에 빈 지면이 남는 반면, zoom 은 레이아웃 자체가 줄어 쪽 계산에 그대로 반영된다.
     [가독 하한] 가장 작은 글자는 사이드 라벨 12px → 12 × .72 ≈ 8.6px, 변화량 14px → 10.1px,
     지표명 18px → 13px. 모두 7px 하한 위다.
     둘 다 7px 하한을 크게 넘는다. */
  .tab-panel-enter.is-change .change-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-2);
    zoom: .72;
  }

  .tab-panel-enter.is-change .change-card {
    padding: var(--sp-3);
    gap: var(--sp-2);
  }

  .tab-panel-enter.is-change .change-chart {
    height: 110px;
  }

  .tab-panel-enter.is-change .change-card {
    /* 카드가 쪽 경계에 걸쳐 반 토막 나는 것을 막는다. 카드 한 장(약 320px)이 지면 높이
       (297 − 18 = 279mm ≈ 1054px)보다 훨씬 작아 통짜로 밀어도 빈 공간이 크게 남지 않는다.
       두 줄로 적는 이유는 report.css 인쇄 블록과 같다 — 구형 엔진의 page-break-inside 병기. */
    break-inside: avoid;
    page-break-inside: avoid;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
    /* 그림자는 지면에서 회색 얼룩으로 찍힌다(리포트가 그림자를 전부 없앤 것과 같은 이유) */
    box-shadow: none;
  }
}


/* ===== [frontend-dev] 홈 인사 영역 — 시안 07 우측 일러스트 ===== */
/* .greet-block 은 09 구역의 1열 그리드. 여기서 텍스트/그림 2열로 확장만 한다. */
.home-page .greet-block {
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp-4);
}

.home-page .greet-text {
  display: grid;
  gap: var(--sp-1);
  min-width: 0;
  /* [design-css] 좁은 폭에서 한글이 음절 단위로 끊기지 않도록 어절 단위 줄바꿈 */
  word-break: keep-all;
}

/* [design-css] 시안 07 기준 확대 — 우측 상단 여백을 크게 차지한다.
   align-self:end 로 그림 아래끝을 블록 아래끝(= 카드 그리드 위끝)에 맞춰 간격 0 을 만든다.
   블록 흐름 안에 있으므로 위로는 콘텐츠 영역까지만 자라고 상단 헤더(흰 바)를 침범하지 않는다. */
.home-page .greet-art {
  flex: 0 0 auto;
  display: block;
  align-self: end;
  /* 높이 −10% (사용자 지시 2026-08-16). 비율 고정 이미지라 폭을 0.9배로 줄인다 */
  width: clamp(135px, 25.2vw, 275px);
}

/* [design-css] 모바일에서도 인사말 일러스트를 보여준다(기존 display:none 제거).
   텍스트 폰트 하한을 낮춰(08 구역) 좁은 폭에서도 2단 배치를 유지한다. */
@media (max-width: 700px) {
  .home-page .greet-block {
    gap: var(--sp-3);
  }

  /* [2026-08-26 사용자 지시] 모바일에서 그림이 카드 위에 작게 떠 보였다 — 390px 에서 폭 112px·
     높이 68px 뿐이라 인사 문구 높이(92px)에 못 미쳐, align-self:end 로 아래를 맞춰도 그림
     위·오른쪽으로 배경이 비쳤다. 기울기를 세워 **좁은 폭은 종전 값 그대로**(320px→104px —
     제목 "안녕하세요, ○○○ 님!"이 한 줄로 남는 한계폭) 두고 390px 부터 키운다.
     390px→153px 는 그림 높이(92px)가 문구 높이와 같아지는 값이라 배너 높이가 그대로고
     아래 카드 그리드도 1px 움직이지 않는다. 상한 180px 는 종전과 같아 700px 경계에서
     데스크톱 규칙(25.2vw)으로 매끄럽게 이어진다. (기존 clamp(104px, 28.8vw, 180px)) */
  .home-page .greet-art {
    width: clamp(104px, calc(-120px + 70vw), 180px);
    /* 320px→104px, 390px→153px, 428px 이상→180px */
  }
}


/* ===== [frontend-dev] 문진 인트로 일러스트 ===== */
/* 1.35배 확대 (사용자 지시 2026-08-17) — 문진 첫 화면이라 시선을 먼저 끌어야 한다 */
.survey-intro .intro-art {
  display: block;
  width: clamp(216px, 43.2vw, 405px);
  margin: 0 auto var(--sp-4);
}


/* ===== [frontend-dev] 문진 단계 보강 (머리말 / 3-1 중첩 / 마무리 배너) ===== */
.survey-page .survey-header {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.survey-page .survey-header-art {
  flex: 0 0 auto;
  /* [2026-08-24 사용자 지시 "더 크게"] 86 → 112px */
  width: clamp(68px, 8.8vw, 112px);
}

/* 문항 번호 + 질문을 한 줄로 (시안 04~06) */
.survey-page .question-head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}

.survey-page .question-head .question-index {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  min-width: 32px;
  height: 32px;
  padding: 0 var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--blue);
  color: var(--text-invert);
}

/* 3-1 조건부 문항 — 문3 카드 안에 들여쓴 블록 */
.survey-page .sub-question {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--blue-line);
  border-radius: var(--r-lg);
  background: var(--blue-soft);
}

.survey-page .sub-condition {
  display: block;
  margin-bottom: var(--sp-1);
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
  color: var(--blue);
}

/* 미응답 안내 */
.survey-page .survey-hint {
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
  color: var(--text-mute);
  text-align: center;
}

/* 마지막 단계 하단 배너 (시안 06) */
.survey-page .survey-outro {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4);
  border-radius: var(--r-lg);
  background: var(--blue-soft);
}

.survey-page .survey-outro-art {
  flex: 0 0 auto;
  width: clamp(96px, 16vw, 190px);
}

.survey-page .survey-outro-title {
  display: block;
  font-size: clamp(17px, calc(14.8px + 0.562vw), 22px);
  /* 390px→17px, 1280px→22px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--blue-dark);
}

.survey-page .survey-outro-desc {
  margin-top: var(--sp-1);
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 600;
  color: var(--text-sub);
}

@media (max-width: 560px) {

  .survey-page .survey-header-art,
  .survey-page .survey-outro-art {
    display: none;
  }
}


/* ===== [frontend-dev] 측정이력 콘텐츠 폭 (리포트 1500px 대응) ===== */
/* AppShell 이 wide 모드에서 .app-inner(1100px) 래퍼를 벗기므로 여기서 폭을 잡는다 */
.history-page {
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
}


/* ===== [frontend-dev] AI 종합 점수 도넛 (시안 12) — 폐기 ===== */
/* 2026-08-27 대시보드 개편으로 "종합 건강 점수(100−위험점수)" 지표 자체가 없어졌다.
   경위와 대체물은 파일 맨 끝 [dash-v2] 블록 머리 주석 참조(위험점수 바 .dash-risk 로 대체). */


/* ===== [frontend-dev] 문진 선택지 배열 — 시안 04·06 은 가로 4열 ===== */
/* 07 구역의 .choice-list 는 1열 그리드. 단일선택만 가로로 펼치고,
   복수선택(q5)은 문장이 길어 .is-stacked 로 1열을 유지한다. */
/* 문진 작성(스텝)·만족도 조사 화면도 같은 상단 여백 (2026-08-24) */
.survey-page {
  margin-top: var(--sp-4);
}

.survey-page .choice-list {
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
}

.survey-page .choice-list.is-stacked {
  grid-template-columns: 1fr;
}

@media (max-width: 560px) {

  /* 문진 작성(스텝)·만족도 조사 화면도 같은 상단 여백 (2026-08-24) */
  .survey-page {
    margin-top: var(--sp-4);
  }

  .survey-page .choice-list {
    grid-template-columns: 1fr;
  }
}


/* [2026-08-20 사용자 지시] 홈 화면의 높이(max-height) 기반 압축 분기 3종을 전부 제거했다 —
   높이에 따라 문구(.greet-memo, .info-lead)가 사라지고 카드 크기가 변하던 동작을 없앤다.
   이제 홈은 화면 높이와 무관하게 같은 레이아웃이고, 좁은 화면에서는 자연 스크롤된다. */






/* [2026-08-20 사용자 지시] 로그인 화면의 높이(max-height) 기반 압축 분기 2종 제거 —
   높이와 무관하게 같은 레이아웃, 좁은 화면에서는 자연 스크롤. (홈과 같은 정리) */



/* ===== [frontend-dev] 사용자 측정 입력 (시안 08~11) — .measure-user =====
   홈 "시작하기" → /measure/[type]. 현장 키오스크 동선이라 시안의 "관리자 입력 화면"
   안내박스를 그대로 둔다. 블록: .measure-user, 하위 역할 클래스만 사용.
   구성: .topbar-row(뒤로+제목) / .hero-row(.device-art | .hero-side) / .form-card / .action-row
   ※ admin.css 의 .measure-page 와는 별개 블록(사용자 앱은 admin.css 를 로드하지 않는다) */
.measure-user {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  width: 100%;
  /* 사용자 피드백(2026-08-13): 측정 입력도 콘텐츠 최대 폭 1500px 정책 */
  max-width: var(--content-max);
  margin: 0 auto;
}

/* --- 상단 뒤로가기 + 제목 --- */
.measure-user .topbar-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.measure-user .back-btn {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--text-sub);
  font-size: clamp(24px, calc(20.49px + 0.899vw), 32px);
  /* 390px→24px, 1280px→32px */
  font-weight: 700;
  line-height: 1;
}

.measure-user .back-btn:hover {
  background: var(--blue-soft);
  border-color: var(--blue-line);
  color: var(--blue);
}

.measure-user .page-heading {
  min-width: 0;
  font-size: clamp(24px, calc(19.17px + 1.236vw), 35px);
  /* 390px→24px, 1280px→35px */
  font-weight: 700;
  letter-spacing: -.045em;
  line-height: 1.2;
  color: var(--text);
}

/* --- 기기 일러스트 + 안내/최근기록 --- */
/* [2026-08-17 사용자 지시] 일러스트가 우측 안내 상자보다 훨씬 높아 상단이 비대했다.
   두 열을 stretch 로 붙이고 그림 높이를 우측 열에 맞춘다 — 안내 상자가 짧으면 그림도
   같이 낮아져 상단 높이가 우측 기준으로 정해진다(contain 이라 비율은 그대로).
   max-height 는 안전 상한 — 우측에 최근 기록 상자까지 붙어 열이 길어질 때 그림만
   과하게 커지지 않게 잡아 둔다. */
.measure-user .hero-row {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  align-items: stretch;
  gap: var(--sp-4);
}

.measure-user .device-art {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 0;
  max-height: clamp(120px, calc(96.2px + 6.292vw), 176px);
  /* 390px→120px, 1280px→176px */
  object-fit: contain;
}

.measure-user .hero-side {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
}

.measure-user .notice-box {
  align-items: center;
  border: 1px solid var(--blue-line);
}

/* 최근 측정 기록 — 모달로 보던 정보를 입력 화면 안으로 옮긴 것 */
.measure-user .recent-box {
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
}

.measure-user .recent-heading {
  margin-bottom: var(--sp-1);
  font-size: clamp(14px, calc(12.25px + 0.449vw), 18px);
  /* 390px→14px, 1280px→18px */
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--text-sub);
}

.measure-user .recent-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.measure-user .recent-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
}

.measure-user .recent-item:last-child {
  border-bottom: 0;
}

.measure-user .item-date {
  flex: 0 0 auto;
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 600;
  color: var(--text-mute);
}

.measure-user .item-value {
  text-align: right;
  font-size: clamp(14px, calc(12.25px + 0.449vw), 18px);
  /* 390px→14px, 1280px→18px */
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--text);
}

.measure-user .recent-box .state-block {
  padding: var(--sp-3);
  gap: var(--sp-1);
}

/* --- 입력 폼 카드 --- */
.measure-user .form-card {
  padding: var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.measure-user .form-heading {
  margin-bottom: var(--sp-4);
  font-size: clamp(19px, calc(16.37px + 0.674vw), 25px);
  /* 390px→19px, 1280px→25px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--text);
}

.measure-user .field-grid {
  display: grid;
  gap: var(--sp-3) var(--sp-5);
  grid-template-columns: 1fr;
}

.measure-user .field-row {
  min-width: 0;
}

.measure-user .field-row.is-wide {
  grid-column: 1 / -1;
}

/* 측정일시는 열 수와 무관하게 한 줄 전체를 쓰되 입력칸만 적당한 폭으로 */
.measure-user .field-row.is-wide .input-unit {
  max-width: 420px;
}

.measure-user .row-label {
  display: block;
  margin-bottom: 6px;
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--text);
}

.measure-user .label-sub {
  margin-left: 4px;
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 600;
  color: var(--text-sub);
}

/* 필수 뱃지 — 선택(회색)과 한 쌍. 필수는 블루 강조로 한눈에 갈리게 (2026-08-21 사용자 지시) */
.measure-user .label-req {
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  background: var(--blue-soft);
  border: 1px solid var(--blue-line);
  font-size: clamp(11px, calc(10.12px + 0.225vw), 13px);
  /* 390px→11px, 1280px→13px */
  font-weight: 700;
  color: var(--blue);
}

.measure-user .label-opt {
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  font-size: clamp(11px, calc(10.12px + 0.225vw), 13px);
  /* 390px→11px, 1280px→13px */
  font-weight: 700;
  color: var(--text-mute);
}

/* 입력칸 + 단위 태그 (시안 08~11 의 회색 단위 블록) */
.measure-user .input-unit {
  display: flex;
  align-items: stretch;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  transition: border-color .15s ease, box-shadow .15s ease;
}

.measure-user .input-unit:focus-within {
  border-color: var(--blue);
  box-shadow: 0 0 0 4px rgba(37, 99, 235, .14);
}

.measure-user .input-unit .field-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 54px;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.measure-user .input-unit .field-input:focus {
  box-shadow: none;
}

/* 숫자 입력 스피너 제거 — 단위 태그와 겹쳐 보이는 것을 막는다 */
.measure-user .input-unit .field-input::-webkit-outer-spin-button,
.measure-user .input-unit .field-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.measure-user .input-unit .field-input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.measure-user .unit-tag {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  min-width: 62px;
  padding: 0 var(--sp-3);
  border-left: 1px solid var(--line);
  background: var(--surface-sunken);
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
  color: var(--text-sub);
}

.measure-user .row-hint {
  margin-top: var(--sp-1);
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 500;
  color: var(--text-mute);
}

.measure-user .row-error {
  margin-top: var(--sp-1);
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 700;
  color: var(--danger);
}

/* 측정 구분 라디오 (시안 11) */
.measure-user .radio-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-5);
  min-height: 54px;
}

.measure-user .radio-item {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap-min);
  cursor: pointer;
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  color: var(--text);
}

.measure-user .radio-item input {
  width: 24px;
  height: 24px;
  accent-color: var(--blue);
  cursor: pointer;
}

/* --- 하단 버튼 --- */
.measure-user .action-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}

/* 시안 08 은 2열 폼. 좁은 폭에서는 1열로 접는다 */
@media (min-width: 760px) {
  .measure-user.is-body .field-grid.is-two {
    grid-template-columns: 1fr 1fr;
  }

  /* 시안 09·11 처럼 라벨 왼쪽 / 입력칸 오른쪽 */
  .measure-user .field-grid.is-one .field-row {
    display: grid;
    grid-template-columns: minmax(0, 190px) minmax(0, 1fr);
    align-items: center;
    column-gap: var(--sp-4);
  }

  .measure-user .field-grid.is-one .row-label {
    margin-bottom: 0;
  }

  .measure-user .field-grid.is-one .row-hint,
  .measure-user .field-grid.is-one .row-error {
    grid-column: 2;
  }

  .measure-user .action-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  }
}

/* 노트북 무스크롤 압축 (1366×768 / 1536×864) — 100dvh 안에 들어가게
   안내박스와 최근기록을 가로로 눕혀 히어로 높이를 절반으로 줄인다 */
@media (min-width: 1000px) and (max-height: 900px) {
  .measure-user {
    gap: var(--sp-3);
  }

  .measure-user .hero-row {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.7fr);
  }

  .measure-user .hero-side {
    flex-direction: row;
    align-items: stretch;
  }

  .measure-user .hero-side>* {
    flex: 1 1 0;
    min-width: 0;
  }

  .measure-user .notice-box {
    padding: var(--sp-3);
  }

  .measure-user .recent-item:nth-child(n+3) {
    display: none;
  }

  /* 가로로 눕은 최근기록 칸은 폭이 좁다 — 요약값을 한 줄로 잘라 높이를 고정한다 */
  .measure-user .item-value {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .measure-user .device-art {
    /* 상단 영역 20% 축소 비례 (154→123px) */
    max-height: 123px;
  }

  .measure-user .form-card {
    padding: var(--sp-4) var(--sp-5);
  }

  .measure-user .form-heading {
    margin-bottom: var(--sp-3);
  }

  .measure-user .field-grid {
    gap: var(--sp-2) var(--sp-5);
  }

  .measure-user .input-unit .field-input,
  .measure-user .radio-row {
    min-height: 48px;
  }

  .measure-user .action-row {
    margin-top: var(--sp-4);
  }

  .measure-user .action-row .btn-lg {
    min-height: 52px;
  }

  /* 항목이 8개인 신체계측만 3열로 더 압축 */
  .measure-user.is-body .field-grid.is-two {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* 2단계 압축 (1366×768 처럼 세로가 짧은 노트북) */
@media (min-width: 1000px) and (max-height: 820px) {
  .measure-user {
    gap: var(--sp-2);
  }

  .measure-user .back-btn {
    width: 40px;
    height: 40px;
  }

  .measure-user .device-art {
    max-height: 112px;
  }

  .measure-user .notice-box,
  .measure-user .recent-box {
    padding: var(--sp-2) var(--sp-3);
  }

  .measure-user .recent-box .state-block {
    padding: var(--sp-2);
    gap: 2px;
  }

  .measure-user .recent-box .state-icon {
    display: none;
  }

  .measure-user .form-card {
    padding: var(--sp-3) var(--sp-4);
  }

  .measure-user .form-heading {
    margin-bottom: var(--sp-2);
  }

  .measure-user .field-grid {
    gap: 6px var(--sp-4);
  }

  .measure-user .row-label {
    margin-bottom: 2px;
  }

  .measure-user .input-unit .field-input,
  .measure-user .radio-row {
    min-height: 40px;
  }

  .measure-user .action-row {
    margin-top: var(--sp-3);
  }

  .measure-user .action-row .btn-lg {
    min-height: 46px;
  }
}

/* 항목 8개짜리 신체계측만 4열로 — 세로가 짧은 노트북에서 행 하나를 줄인다 */
@media (min-width: 1280px) and (max-height: 820px) {
  .measure-user.is-body .field-grid.is-two {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* 모바일 390px — 기기 그림 위, 안내/최근기록 아래로 쌓는다 */
@media (max-width: 759px) {
  .measure-user .hero-row {
    grid-template-columns: 1fr;
    gap: var(--sp-3);
  }

  .measure-user .form-card {
    padding: var(--sp-4);
    border-radius: var(--r-lg);
  }

  .measure-user .action-row {
    grid-template-columns: 1fr;
  }
}


/* ============================================================
   [front-speed] 라우트 전환 스켈레톤 + 갱신 표시
   ------------------------------------------------------------
   목적: 라우트를 클릭한 즉시(데이터 도착 전에) 화면 뼈대를 그려
         "빈 화면에서 기다리는" 구간을 없앤다.
   사용처: app/**\/loading.js → components/RouteSkeleton.jsx
           + components/Skeleton.jsx (.sk 계열)
   색·라운드는 전부 :root 토큰(global.css). 하드코딩 없음.
   ============================================================ */

/* 스켈레톤 본문 래퍼 — 실제 페이지 본문과 같은 세로 리듬 */
.sk-page {
  display: grid;
  gap: var(--sp-4);
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
}

/* 기본 블록 — admin.css 의 .adm-skel 과 같은 shimmer 톤 */
.sk {
  display: block;
  position: relative;
  overflow: hidden;
  width: 100%;
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
}

.sk::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .72), transparent);
  background-size: 200% 100%;
  animation: skShimmer 1.5s ease-in-out infinite;
}

@keyframes skShimmer {
  0% {
    background-position: -200% 0;
  }

  100% {
    background-position: 200% 0;
  }
}

/* 카드 자리 — 흰 카드와 같은 라운드 */
.sk-card {
  border-radius: var(--r-lg);
}

/* 배너 자리 */
.sk-hero {
  border-radius: var(--r-xl);
}

/* 지표 카드 그리드 — history 의 .metric-grid 와 같은 흐름 */
.sk-grid {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

/* 2열 (측정 입력의 기기그림 + 안내/최근기록) */
.sk-two {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

/* 탭 3종 자리 */
.sk-tabs {
  display: grid;
  gap: var(--sp-2);
  grid-template-columns: repeat(3, minmax(0, 1fr));
  max-width: 520px;
}

/* 문장 여러 줄 자리 */
.sk-lines {
  display: grid;
  gap: var(--sp-2);
}

@media (max-width: 759px) {

  .sk-two,
  .sk-grid {
    grid-template-columns: 1fr;
  }
}

/* 움직임을 줄이는 설정에서는 shimmer 를 멈춘다 */
@media (prefers-reduced-motion: reduce) {
  .sk::after {
    animation: none;
  }
}

/* ── 저장본 표시 중 + 백그라운드 갱신 중 알림 ────────────────
   position: fixed 라 어떤 페이지에 넣어도 레이아웃을 밀지 않는다.
   하단 탭바 위로 띄운다. */
.arc-refresh {
  position: fixed;
  right: var(--sp-4);
  bottom: calc(var(--tabbar-h) + var(--sp-3));
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 6px var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  color: var(--text-sub);
  font-size: clamp(12px, calc(11.1px + 0.225vw), 14px);
  /* 390px→12px, 1280px→14px */
  pointer-events: none;
}

.arc-refresh-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--blue);
  animation: arcPulse 1.1s ease-in-out infinite;
}

@keyframes arcPulse {

  0%,
  100% {
    opacity: .35;
  }

  50% {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .arc-refresh-dot {
    animation: none;
  }
}


/* ============================================================
   [user-ui-polish] 사용자 앱 입력 UI 고도화 (2026-08-12)
   ------------------------------------------------------------
   브라우저 기본 팝업(datetime-local · date · select)을 관리자와 같은 수준의
   커스텀 팝오버로 바꾼 공용 컴포넌트 스킨.
     components/DateTimeField.jsx  → .ui-dtf  (측정 입력 "측정 일시")
     components/DateRangeField.jsx → .ui-drf  (측정이력 대시보드 "조회 기간")
     components/SelectField.jsx    → .ui-sel  (측정이력 상세리포트 "날짜 선택")
   admin.css 도 같은 ui-* 이름을 관리자 톤으로 따로 정의한다
   (사용자 앱과 관리자는 서로의 스타일시트를 로드하지 않는다 — 충돌 없음).

   시니어 친화: 트리거 54px · 시간/옵션 버튼 44px 이상 · 큰 글자.
   좁은 화면(<=560px)에서는 JS 가 .is-sheet 를 붙여 화면 하단 시트로 띄운다.

   구성
     P1. 트리거 (ui-dtf-trigger / ui-sel-trigger)
     P2. 팝오버 · 하단 시트 · 머리말 · 푸터 (공용)
     P3. 시간 선택 (오전·오후 / 시 / 분 버튼 그리드)
     P4. 달력 rdp-ui (react-day-picker v10 오버라이드)
     P5. 기간 선택 (프리셋 · 달력 2개)
     P6. Select 패널
     P7. 페이지 적용 (측정입력 · 기간바 · 리포트 툴바)
     P8. 반응형
   ============================================================ */


/* ===== [user-ui-polish] P1. 트리거 ===== */
.ui-dtf,
.ui-drf {
  display: block;
  min-width: 0;
}

.ui-sel {
  display: inline-grid;
  min-width: 0;
}

.ui-sel.is-block {
  display: grid;
  width: 100%;
}

.ui-dtf-trigger,
.ui-sel-trigger {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 54px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  /* 상한 22px → 20px (2026-08-18 **사용자 직접 수정** — 에이전트가 무관 변경으로 오인해
     원복했다가 사용자 재지시로 확정. 다시 되돌리지 말 것) */
  font-size: clamp(17px, calc(15.68px + 0.337vw), 20px);
  /* 390px→17px, 1280px→20px */
  font-weight: 700;
  letter-spacing: -.02em;
  text-align: left;
  transition: border-color .15s ease, box-shadow .15s ease;
}

.ui-dtf-trigger:hover:not(:disabled),
.ui-sel-trigger:hover:not(:disabled) {
  border-color: var(--blue-line);
}

.ui-dtf-trigger.is-open,
.ui-sel-trigger.is-open {
  border-color: var(--blue);
  box-shadow: 0 0 0 4px var(--blue-soft);
}

.ui-dtf-trigger.is-invalid {
  border-color: var(--danger);
}

.ui-dtf-trigger:disabled,
.ui-sel-trigger:disabled {
  background: var(--surface-sunken);
  color: var(--text-mute);
}

.ui-dtf-icon {
  flex: 0 0 auto;
  color: var(--blue);
}

/* flex-basis 0 — 트리거의 최소 폭이 글자 길이에 끌려가지 않게 한다(좁은 화면 가로 스크롤 방지) */
.ui-dtf-text,
.ui-sel-text {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}

.ui-dtf-placeholder,
.ui-sel-placeholder {
  color: var(--text-mute);
  font-weight: 600;
}

.ui-dtf-chevron {
  flex: 0 0 auto;
  color: var(--text-mute);
  transition: transform .2s ease;
}

.ui-dtf-chevron.is-open {
  transform: rotate(-180deg);
}


/* ===== [user-ui-polish] P2. 팝오버 · 하단 시트 ===== */
/* body 포털 + fixed — 카드 overflow 에 잘리지 않는다 */
.ui-dtf-pop,
.ui-drf-pop {
  position: fixed;
  z-index: 70;
  width: max-content;
  max-width: calc(100vw - 24px);
  /* 창 높이가 낮은 노트북에서 패널이 화면 밖으로 나가지 않게 — 내부 스크롤 */
  max-height: calc(100dvh - 16px);
  overflow-y: auto;
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
}

/* 배경 딤 — 하단 시트 뒤를 어둡게 (전용 토큰이 없어 그림자와 같은 남색 계열로 맞춘다) */
.ui-dtf-backdrop {
  position: fixed;
  inset: 0;
  z-index: 69;
  background: rgba(23, 43, 77, .45);
}

.ui-dtf-pop.is-sheet,
.ui-drf-pop.is-sheet,
.ui-sel-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  top: auto;
  z-index: 70;
  width: 100%;
  max-width: 100%;
  max-height: 88dvh;
  overflow-y: auto;
  padding: var(--sp-4) var(--sp-3) calc(var(--sp-5) + env(safe-area-inset-bottom));
  border: 0;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  background: var(--surface);
  box-shadow: var(--shadow-pop);
  animation: uiSheetUp .18s ease-out;
}

@keyframes uiSheetUp {
  from {
    transform: translateY(14px);
    opacity: .6;
  }

  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.ui-dtf-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding-bottom: var(--sp-3);
  margin-bottom: var(--sp-3);
  border-bottom: 1px solid var(--line);
}

.ui-dtf-head-title {
  font-size: clamp(16px, calc(14.25px + 0.449vw), 20px);
  /* 390px→16px, 1280px→20px */
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--text);
}

.ui-dtf-close {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-mute);
  font-size: clamp(15px, calc(13.69px + 0.337vw), 18px);
  /* 390px→15px, 1280px→18px */
  font-weight: 700;
}

.ui-dtf-close:hover {
  background: var(--surface-sunken);
  color: var(--text);
}

.ui-dtf-body {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
}

.ui-dtf-cal {
  flex: 0 0 auto;
}

.ui-dtf-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding-top: var(--sp-3);
  margin-top: var(--sp-3);
  border-top: 1px solid var(--line);
}

.ui-dtf-today {
  flex: 0 0 auto;
  min-height: var(--tap-min);
  padding: 0 var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text-sub);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
}

.ui-dtf-today:hover:not(:disabled) {
  border-color: var(--blue-line);
  background: var(--blue-soft);
  color: var(--blue-dark);
}

.ui-dtf-today:disabled {
  opacity: .45;
}

.ui-dtf-summary {
  flex: 1 1 auto;
  text-align: center;
  color: var(--text-sub);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.ui-dtf-summary.has-value {
  color: var(--blue-dark);
  font-weight: 700;
}

.ui-dtf-apply {
  flex: 0 0 auto;
  min-width: 104px;
  min-height: 48px;
  padding: 0 var(--sp-5);
}


/* ===== [user-ui-polish] P3. 시간 선택 (오전·오후 / 시 / 분) ===== */
.ui-dtf-time {
  flex: 0 0 auto;
  display: grid;
  align-content: start;
  gap: var(--sp-2);
  width: 214px;
}

.ui-dtf-seg {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  padding: 4px;
  border-radius: var(--r-md);
  background: var(--surface-sunken);
}

.ui-dtf-seg-btn {
  min-height: var(--tap-min);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-sub);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
}

.ui-dtf-seg-btn.is-on {
  background: var(--surface);
  color: var(--blue-dark);
  box-shadow: var(--shadow-card);
}

.ui-dtf-time-label {
  margin-top: 2px;
  font-size: clamp(13px, calc(12.12px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 700;
  color: var(--text-sub);
}

.ui-dtf-grid {
  display: grid;
  gap: 5px;
}

.ui-dtf-grid.is-hour {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.ui-dtf-grid.is-min {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.ui-dtf-cell {
  min-height: var(--tap-min);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text-sub);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  transition: border-color .12s ease, background .12s ease, color .12s ease;
}

.ui-dtf-cell:hover {
  border-color: var(--blue-line);
  background: var(--blue-soft);
  color: var(--blue-dark);
}

.ui-dtf-cell.is-on {
  border-color: var(--blue);
  background: var(--blue);
  color: var(--text-invert);
  font-weight: 700;
}


/* ===== [user-ui-polish] P4. 달력 rdp-ui ===== */
.rdp-ui.rdp-root {
  --rdp-accent-color: var(--blue);
  --rdp-accent-background-color: var(--blue-soft);
  --rdp-range_middle-background-color: var(--blue-soft);
  --rdp-range_middle-color: var(--text);
  --rdp-range_start-color: var(--text-invert);
  --rdp-range_end-color: var(--text-invert);
  --rdp-range_start-date-background-color: var(--blue);
  --rdp-range_end-date-background-color: var(--blue);
  --rdp-selected-border: 2px solid var(--blue);
  --rdp-today-color: var(--blue-dark);
  --rdp-outside-opacity: .38;
  --rdp-day-width: 44px;
  --rdp-day-height: 44px;
  --rdp-nav_button-width: 36px;
  --rdp-nav_button-height: 36px;
  color: var(--text);
  font-size: clamp(14px, calc(13.12px + 0.225vw), 16px);
  /* 390px→14px, 1280px→16px */
  font-weight: 600;
}

.rdp-ui .rdp-day_button {
  border-radius: var(--r-sm);
  font-variant-numeric: tabular-nums;
}

.rdp-ui .rdp-weekday {
  color: var(--text-mute);
  font-size: clamp(12px, calc(11.12px + 0.225vw), 14px);
  /* 390px→12px, 1280px→14px */
  font-weight: 700;
}

.rdp-ui .rdp-selected .rdp-day_button,
.rdp-ui .rdp-today:not(.rdp-selected) .rdp-day_button {
  font-weight: 700;
}

.rdp-ui .rdp-chevron {
  fill: var(--blue);
}

.rdp-ui .rdp-month_caption {
  padding: 2px 0 var(--sp-2);
}

.rdp-ui .rdp-dropdowns {
  display: flex;
  align-items: center;
  gap: 6px;
}

.rdp-ui .rdp-dropdown_root {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.rdp-ui .rdp-dropdown_root .rdp-caption_label {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  min-height: 38px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
  color: var(--text);
  font-size: clamp(14px, calc(13.12px + 0.225vw), 16px);
  /* 390px→14px, 1280px→16px */
  font-weight: 700;
}

.rdp-ui .rdp-dropdown_root:hover .rdp-caption_label {
  border-color: var(--blue);
}

.rdp-ui .rdp-dropdown_root .rdp-caption_label .rdp-chevron {
  width: 14px;
  height: 14px;
  fill: var(--text-mute);
}

.rdp-ui select.rdp-dropdown {
  cursor: pointer;
}

.rdp-ui .rdp-button_previous:hover,
.rdp-ui .rdp-button_next:hover {
  background: var(--surface-sunken);
  border-radius: var(--r-sm);
}


/* ===== [user-ui-polish] P5. 기간 선택 (프리셋 · 달력 2개) ===== */
.ui-drf-presets {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding-bottom: var(--sp-3);
  margin-bottom: var(--sp-3);
  border-bottom: 1px solid var(--line);
}

.ui-drf-preset {
  min-height: var(--tap-min);
  padding: 0 var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-sunken);
  color: var(--text-sub);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}

.ui-drf-preset:hover {
  border-color: var(--blue-line);
  background: var(--blue-soft);
  color: var(--blue-dark);
}

.ui-drf-cals {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
}

.ui-drf-divider {
  width: 1px;
  align-self: stretch;
  background: var(--line);
}

/* 적용된 기간의 일수 배지 (트리거 안) */
.ui-drf-days {
  flex: 0 0 auto;
  padding: 2px 10px;
  border-radius: var(--r-pill);
  background: var(--blue-soft);
  color: var(--blue-dark);
  font-size: clamp(13px, calc(12.12px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 700;
}


/* ===== [user-ui-polish] P6. Select 패널 ===== */
.ui-sel-trigger {
  justify-content: space-between;
  min-width: 220px;
}

.ui-sel-panel {
  position: fixed;
  z-index: 95;
  max-width: min(420px, calc(100vw - 24px));
  max-height: 320px;
  margin: 0;
  padding: var(--sp-1);
  overflow-y: auto;
  list-style: none;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
}

/* 하단 시트 안에서는 패널이 시트의 본문이 된다 */
.ui-sel-panel.is-sheet {
  position: static;
  max-width: none;
  max-height: 56dvh;
  padding: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.ui-sel-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 52px;
  padding: 0 var(--sp-4);
  border-radius: var(--r-md);
  color: var(--text-sub);
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}

.ui-sel-option.is-active {
  background: var(--blue-soft);
  color: var(--blue-dark);
}

.ui-sel-option.is-selected {
  color: var(--blue-dark);
  font-weight: 700;
}

.ui-sel-option-text {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.ui-sel-check {
  flex: 0 0 auto;
  color: var(--blue);
}


/* ===== [user-ui-polish] P7. 페이지 적용 ===== */
/* 측정 입력(시안 08~11) — 측정 일시 트리거는 숫자 입력칸과 같은 폭으로 */
.measure-user .field-row.is-wide .ui-dtf {
  max-width: 420px;
}

/* 측정이력 대시보드 기간 바 — 네이티브 date 2개 → 기간 선택기 한 개 + 안내 */
.period-bar .period-label {
  flex: 0 0 auto;
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--text);
}

.period-bar .ui-drf {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 560px;
}

.period-bar .period-note {
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--text-sub);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
}

/* 상세리포트 툴바 — 라벨 + 커스텀 Select (라벨과 선택창을 한 줄로, 사용자 지시 2026-08-16) */
.report-toolbar .report-pick {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
}

.report-toolbar .report-pick .field-label {
  flex: 0 0 auto;
  margin-bottom: 0;
  white-space: nowrap;
}

.report-toolbar .ui-sel {
  min-width: 260px;
}


/* ===== [user-ui-polish] P8. 반응형 ===== */
@media (max-width: 760px) {
  .period-bar .period-note {
    width: 100%;
    margin-left: 0;
  }
}

@media (max-width: 560px) {
  .period-bar {
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-3);
  }

  .period-bar .ui-drf {
    flex: 1 1 100%;
    max-width: none;
  }

  /* 좁은 화면에서는 일수 배지를 접는다 — 날짜 두 개를 다 보여주는 쪽이 낫고,
     바로 아래 안내문("최근 N일 기록을 보고 있습니다.")이 같은 정보를 준다 */
  .ui-drf-days {
    display: none;
  }

  .report-toolbar .report-pick,
  .report-toolbar .ui-sel {
    flex: 1 1 100%;
    min-width: 0;
  }

  /* [2026-08-21] 휴대폰 가로 넘침 수정 — 상세리포트 탭이 좌우로 밀리던 원인.
     .history-page 는 display:grid 이고 .report-toolbar 는 그 그리드 아이템이라
     min-width:auto(=자동 최소 크기)가 걸린다. 그래서 툴바의 min-content 폭이
     그대로 그리드 열의 하한이 되고, 열이 컨테이너보다 넓어지면 .app-main
     (overflow-x:auto)이 그 넘침을 가로 스크롤로 만든다. 이때 문서 루트의
     scrollWidth 는 clientWidth 와 같아서 흔한 넘침 점검에는 잡히지 않는다.

     툴바의 min-content 를 만든 것은 .report-pick 한 줄이었다(실측 390px 뷰포트):
       라벨 "최근 측정 리포트 선택" 123.47px(nowrap, flex 0 0 auto)
       + gap 12px + .ui-sel 220px = 355.47px, + 툴바 패딩 32 + 테두리 2 = 389.47px.
     쓸 수 있는 폭은 366px(390 - .app-main 좌우 패딩 24) 뿐이라 23.47px 이 넘쳤다.
     이 하한은 뷰포트와 무관한 고정값이라 413px 미만 화면이 전부 같은 증상이었다.

     라벨을 줄이거나 지우지 않고 배치로 푼다 — .report-pick 을 접을 수 있게 하면
     라벨과 선택창이 각자 한 줄을 쓰고, 툴바의 min-content 는 가장 넓은 아이템
     하나(.ui-sel 220px)로 내려간다. min-width:0 은 그리드 아이템의 자동 최소
     크기를 아예 끊어 두는 이중 안전장치다(라벨 문구가 길어져도 다시 넘치지 않는다).
     .report-toolbar 는 .no-print 라 인쇄 지면에는 영향이 없고,
     561px 이상(데스크톱·태블릿)은 이 블록 밖이라 그대로다. */
  .report-toolbar {
    min-width: 0;
  }

  .report-toolbar .report-pick {
    flex-wrap: wrap;
  }

  /* 하단 시트 — 달력·시간을 세로로 쌓고 버튼을 화면 폭에 맞춰 넓힌다 */
  .ui-dtf-pop.is-sheet .ui-dtf-body {
    flex-direction: column;
    align-items: stretch;
  }

  .ui-dtf-pop.is-sheet .ui-dtf-cal,
  .ui-drf-pop.is-sheet .ui-drf-cals {
    display: grid;
    justify-items: center;
  }

  .ui-dtf-pop.is-sheet .ui-dtf-time {
    width: 100%;
  }

  .ui-dtf-pop.is-sheet .ui-dtf-grid.is-hour,
  .ui-dtf-pop.is-sheet .ui-dtf-grid.is-min {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }

  .ui-drf-pop.is-sheet .ui-drf-preset {
    flex: 1 1 44%;
  }

  /* 시트 안에서 내용이 길면 내부 스크롤 — 제목과 [확인] 은 항상 보이게 고정한다 */
  .ui-dtf-pop.is-sheet .ui-dtf-head,
  .ui-drf-pop.is-sheet .ui-dtf-head {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface);
  }

  .ui-dtf-pop.is-sheet .ui-dtf-foot,
  .ui-drf-pop.is-sheet .ui-dtf-foot {
    position: sticky;
    bottom: 0;
    z-index: 1;
    padding-bottom: var(--sp-2);
    background: var(--surface);
  }

  .ui-dtf-foot {
    flex-wrap: wrap;
  }

  .ui-dtf-summary {
    order: -1;
    width: 100%;
    text-align: left;
  }

  .ui-dtf-today,
  .ui-dtf-apply {
    flex: 1 1 auto;
  }

  /* 390px 에서도 달력이 가로로 넘치지 않게 한 칸을 줄인다 */
  .rdp-ui.rdp-root {
    --rdp-day-width: 40px;
    --rdp-day-height: 40px;
  }
}

@media (prefers-reduced-motion: reduce) {

  .ui-dtf-pop.is-sheet,
  .ui-drf-pop.is-sheet,
  .ui-sel-sheet {
    animation: none;
  }
}


/* ============================================================
   [design-polish] 은은한 모션 — 사용자 앱
   원칙: 0.15~0.25s, 등장(enter)에만 건다. 차트·리포트 본문 등
   콘텐츠 자체에는 걸지 않는다. 블록 끝에서 prefers-reduced-motion 처리.
   ============================================================ */

/* ===== [design-polish] 모달 (.modal-backdrop / .modal-card) ===== */
/* 딤은 페이드만, 카드는 페이드 + 살짝 상승 + 살짝 확대 */
.modal-backdrop {
  animation: dim-in .18s ease-out both;
}

.modal-card {
  animation: pop-rise .2s cubic-bezier(.2, .7, .3, 1) both;
}

@keyframes dim-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes pop-rise {
  from {
    opacity: 0;
    transform: translateY(10px) scale(.98);
  }

  to {
    opacity: 1;
    transform: none;
  }
}


/* ===== [design-polish] 팝오버 (달력 · 기간 · Select) ===== */
/* 데스크톱 팝오버는 페이드만 건다.
   popoverPos 의 useFitInViewport 가 렌더 직후 getBoundingClientRect() 로 패널을
   재서 화면 밖 삐져나감을 보정하는데, transform(이동·스케일)은 그 측정값을
   흔들어 보정이 건너뛰어진다(실제로 1px 넘침 발생). 위치를 건드리지 않는
   opacity 만 쓴다. 이동감은 아래 하단 시트(uiSheetUp)에서 살린다. */
.ui-dtf-pop:not(.is-sheet),
.ui-drf-pop:not(.is-sheet),
.ui-sel-panel:not(.is-sheet) {
  animation: pop-open .16s ease-out both;
}

@keyframes pop-open {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

/* 하단 시트 뒤 딤 — 시트(uiSheetUp)와 같은 호흡으로 페이드 */
.ui-dtf-backdrop {
  animation: dim-in .18s ease-out both;
}

/* 하단 시트는 useFitInViewport 대상이 아니라(sheet 면 즉시 return) 이동을 써도 안전하다.
   기존 uiSheetUp(.6 → 1)을 완전 페이드로 다듬는다. */
.ui-dtf-pop.is-sheet,
.ui-drf-pop.is-sheet,
.ui-sel-sheet {
  animation: sheet-up .22s cubic-bezier(.2, .7, .3, 1) both;
}

@keyframes sheet-up {
  from {
    opacity: 0;
    transform: translateY(18px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}


/* ===== [design-polish] 선택지 색 전환 (문진 · 측정 폼) ===== */
/* 문진 선택지: 카드 배경/테두리는 07 블록에서 이미 전환된다. 원형 마크도 같이 전환. */
.survey-page .choice-item {
  transition: background .18s ease, border-color .18s ease, color .18s ease;
}

.survey-page .choice-mark {
  transition: background .18s ease, border-color .18s ease, color .18s ease;
}

/* 측정 폼 라디오: 네이티브 accent-color 는 전환되지 않으므로
   선택된 항목의 글자색만 부드럽게 바꿔 "선택됨"을 거들어 준다. */
.measure-user .radio-item {
  transition: color .18s ease;
}

.measure-user .radio-item:has(input:checked) {
  color: var(--blue-dark);
}


/* ===== [design-polish] 모션 민감 사용자 ===== */
@media (prefers-reduced-motion: reduce) {

  .modal-backdrop,
  .modal-card,
  .ui-dtf-pop:not(.is-sheet),
  .ui-drf-pop:not(.is-sheet),
  .ui-sel-panel:not(.is-sheet),
  .ui-dtf-backdrop,
  .ui-dtf-pop.is-sheet,
  .ui-drf-pop.is-sheet,
  .ui-sel-sheet {
    animation: none;
  }

  /* 스켈레톤 shimmer 도 함께 멈춘다 */
  .sk::after {
    animation: none;
  }
}


/* ===== [design-region-modal] 지역 선택 모달 모션 ===== */
/* 등장은 [design-polish] 의 dim-in / pop-rise 를 그대로 쓴다.
   여기서는 ① 퇴장(.is-closing) ② 390px 하단 시트 두 가지만 더한다.
   JSX 는 .is-closing 을 180ms 유지한 뒤 언마운트한다(RegionPicker.close). */
.modal-backdrop.is-closing {
  animation: dim-out .18s ease-in both;
}

.modal-backdrop.is-closing .modal-card {
  animation: pop-fall .18s ease-in both;
}

@keyframes dim-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

@keyframes pop-fall {
  from {
    opacity: 1;
    transform: none;
  }

  to {
    opacity: 0;
    transform: translateY(8px) scale(.98);
  }
}

/* 모바일 — 아래에서 올라오는 시트 (Select·달력 하단 시트와 같은 결)
   [frontend-dev 2026-08-12] 390px 에서는 2패널이 너무 좁다 — 마크업은 그대로 두고
   CSS 로 활성 패널 하나만 보여 종전의 단계 교체(시/도 → 시/군/구 → 동) 동선을 유지한다. */
@media (max-width: 560px) {
  .region-modal {
    place-items: end stretch;
    padding: 0;
  }

  .region-modal .region-modal-card {
    width: 100%;
    height: min(86dvh, 620px);
    max-height: 86dvh;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    animation: sheet-up .22s cubic-bezier(.2, .7, .3, 1) both;
  }

  .region-modal.is-closing .region-modal-card {
    animation: sheet-down .2s ease-in both;
  }

  /* 제목·뒤로가기는 단계별 표기로 되돌린다 */
  .region-modal .region-title-wide {
    display: none;
  }

  .region-modal .region-title-narrow,
  .region-modal .region-trail-narrow {
    display: block;
  }

  .region-modal .region-modal-card.step-sigungu .region-back {
    visibility: visible;
    pointer-events: auto;
  }

  /* 한 번에 한 패널 — 구분선·번호 머리말은 헤더 제목과 중복이라 접는다 */
  .region-modal .region-panes {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .region-modal .region-modal-card.step-sido .region-pane.is-sigungu,
  .region-modal .region-modal-card.step-sigungu .region-pane.is-sido {
    display: none;
  }

  .region-modal .region-pane-head {
    display: none;
  }

  .region-modal .region-pane.is-sigungu {
    padding-left: 0;
    border-left: 0;
  }

  .region-modal .region-pane-list.is-grid {
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  }

  .region-picker .region-grid {
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  }
}

@keyframes sheet-down {
  from {
    opacity: 1;
    transform: none;
  }

  to {
    opacity: 0;
    transform: translateY(18px);
  }
}

@media (prefers-reduced-motion: reduce) {

  .modal-backdrop.is-closing,
  .modal-backdrop.is-closing .modal-card,
  .region-modal .region-modal-card,
  .region-modal.is-closing .region-modal-card {
    animation: none;
  }
}


/* ===== [design-css] 데스크톱에는 하단 탭바가 없다 (global.css 분기와 짝) ===== */
/* .arc-refresh 는 main.css 정의라 global.css 의 같은 분기가 순서상 밀린다 — 여기서 한 번 더 */
@media (min-width: 701px) {
  .arc-refresh {
    bottom: var(--sp-4);
  }
}


/* ===== [design-ai-card] · [ai-grade-badge] — 삭제 (2026-08-27) =====
   여기에 있던 두 블록(.ai-summary .ai-score* SVG 스트로크 게이지 + .badge-grade-lg 확대 변형)은
   '종합 건강 점수(100−위험점수)' 게이지 전용 규칙이었다. 2026-08-27 대시보드 개편에서 그 게이지가
   폐지되고(plan_dashboard_v2 §8) 등급 카드(.dash-grade) + 위험점수 바(.dash-risk) 로 나뉘어 대체돼,
   어느 화면에서도 걸리지 않는 죽은 규칙이 됐다 — 라이브편집(개발자도구 왕복) 때 실제 화면에 없는
   130여 줄을 계속 스크롤하게 되므로 남겨 두지 않는다. 복구가 필요하면 이 커밋 직전 리비전에 있다.
   후속 정리 대상(이번 범위 밖): 같은 이유로 화면에서 내려간 10·11 구역의 .metric-grid · .ai-summary ·
   .advice-grid 계열. plan_dashboard_v2 §3-3 이 "이번 커밋에서는 손대지 않는다" 로 못박아 남겨 뒀다. */


/* ===== [measure-3col] 측정 입력 PC 3열 (사용자 피드백 2026-08-13) ===== */
/* PC(≥1100px)에서는 입력 필드를 한 줄에 3개씩. 라디오(측정 구분)는 한 줄 전체를 쓴다.
   is-one(혈압·혈당·산소포화도)의 "라벨 왼쪽" 행 배치는 3열에서는 라벨 위 배치로 되돌린다. */
@media (min-width: 1100px) {

  .measure-user .field-grid,
  .measure-user.is-body .field-grid.is-two {
    grid-template-columns: repeat(3, 1fr);
  }

  .measure-user .field-grid.is-one .field-row {
    display: block;
  }

  .measure-user .field-grid.is-one .row-label {
    margin-bottom: var(--sp-2);
  }

  .measure-user .field-grid .field-row.is-radio {
    grid-column: 1 / -1;
  }

  .measure-user .field-grid.is-one .row-hint,
  .measure-user .field-grid.is-one .row-error {
    grid-column: auto;
  }
}


/* ===== [period-picker] 측정이력 조회기간 — 측정일 범위 지정 (사용자 지시 2026-08-13) ===== */
/* 일반 달력을 걷어내고 "본인이 측정받은 날짜"만 고르게 한다.
   트리거 버튼 1개 → 팝오버에서 측정일 두 번 클릭으로 범위 확정(적용 버튼 없음).
   팝오버 껍데기·딤·등장 애니메이션은 기존 .ui-drf-pop / .ui-dtf-backdrop 규약을 그대로 쓰고
   여기서는 날짜 목록(.ui-mrf-*)과 기간 바 배치만 정의한다. */
.period-bar.period-picker {
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-4);
}

.period-picker .ui-mrf {
  display: grid;
  flex: 1 1 auto;
  min-width: 0;
  max-width: 480px;
}

/* 측정 기록이 아직 없거나 목록을 불러오는 중일 때의 안내 */
.period-picker .period-empty {
  flex: 1 1 auto;
  color: var(--text-sub);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
}

/* 트리거 — 현재 범위를 그대로 읽어주는 버튼 하나 */
.ui-mrf-trigger {
  width: 100%;
  justify-content: flex-start;
}

/* 팝오버 — 날짜 목록이 길어지면 목록만 스크롤된다 */
.ui-mrf-pop {
  width: min(420px, calc(100vw - 24px));
}

.ui-mrf-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}

.ui-mrf-all {
  flex: 0 0 auto;
  min-height: var(--tap-min);
  padding: 0 var(--sp-4);
  border: 1px solid var(--blue-line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--blue);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
  letter-spacing: -.02em;
  transition: background .15s ease, opacity .15s ease;
}

.ui-mrf-all:hover:not(:disabled) {
  background: var(--blue-soft);
}

.ui-mrf-all:disabled {
  opacity: .45;
}

/* 지금 무엇을 고를 차례인지 알려준다 — 시니어 대상이라 문장으로 */
.ui-mrf-guide {
  flex: 1 1 auto;
  text-align: right;
  color: var(--blue);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
  letter-spacing: -.02em;
}

.ui-mrf-list {
  display: grid;
  gap: var(--sp-2);
  max-height: 46dvh;
  padding: 2px;
  overflow-y: auto;
  list-style: none;
}

.ui-mrf-day {
  width: 100%;
  min-height: 52px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font-size: clamp(16px, calc(14.25px + 0.449vw), 20px);
  /* 390px→16px, 1280px→20px */
  font-weight: 700;
  letter-spacing: -.02em;
  text-align: left;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.ui-mrf-day:hover {
  border-color: var(--blue-line);
  background: var(--blue-soft);
}

/* 고르는 중인 구간(첫 클릭 ~ 커서) · 이미 적용된 구간 — 연한 강조 */
.ui-mrf-day.is-in-range {
  border-color: var(--blue-line);
  background: var(--blue-soft);
  color: var(--blue-dark);
}

/* 구간의 양 끝 — 진하게 */
.ui-mrf-day.is-edge {
  border-color: var(--blue);
  background: var(--blue);
  color: var(--text-invert);
}

.ui-mrf-day.is-edge:hover {
  background: var(--blue-dark);
}

/* 첫 클릭 지점 — 종료일을 고르는 동안 기준점을 잃지 않게 테두리를 두껍게 */
.ui-mrf-day.is-anchor {
  box-shadow: 0 0 0 3px rgba(37, 99, 235, .22);
}

/* 좁은 화면(390px 포함) — 하단 시트. 목록이 화면을 넘지 않게 높이를 줄인다 */
@media (max-width: 560px) {
  .period-picker .ui-mrf {
    flex: 1 1 100%;
    max-width: none;
  }

  .ui-mrf-pop.is-sheet {
    width: 100%;
  }

  .ui-mrf-pop.is-sheet .ui-mrf-list {
    max-height: 42dvh;
  }

  .ui-mrf-guide {
    font-size: clamp(13px, calc(12.13px + 0.225vw), 15px);
    /* 390px→13px, 1280px→15px */
  }
}

/* ===== [login-gate] 로그인 안내 모달 (측정이력·설정 — 로그인 전 클릭 시) ===== */
.modal-card.login-gate {
  width: min(420px, calc(100% - 32px));
  text-align: center;
}

.login-gate .gate-icon {
  font-size: clamp(34px, calc(30.5px + 0.899vw), 42px);
  /* 390px→34px, 1280px→42px */
  line-height: 1;
  margin-bottom: var(--sp-3);
}

.login-gate .gate-title {
  font-size: clamp(18px, calc(16.25px + 0.449vw), 22px);
  /* 390px→18px, 1280px→22px */
  font-weight: 700;
  color: var(--text);
}

.login-gate .gate-desc {
  margin-top: var(--sp-2);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  color: var(--text-sub);
}

.login-gate .modal-foot {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: var(--sp-3);
}

/* ============================================================
   [places] 측정 장소 (2026-08-14 사용자 확정 · 계약 §3-1)
   구성 ① 내비/탭바 "장소선택" 표기  ② 장소 선택 모달  ③ 측정입력 장소 바
        ④ 측정이력 장소 필터
   원칙: 색·크기는 전부 토큰 + clamp(390px→1280px). 인라인 style 은 쓰지 않는다.
   ============================================================ */

/* ===== [places] ① 상단 헤더 우측 — 장소 칩 (관리자 상단바와 같은 배치, 2026-08-14) ===== */
/* 우측 묶음: [장소 칩][회원명][로그아웃]. user-area 의 margin-left:auto 는 묶음이 대신 맡는다 */
.app-header .hdr-right {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-left: auto;
  min-width: 0;
}

.app-header .hdr-right .user-area {
  margin-left: 0;
}

/* 선택된 장소 칩 — 관리자 상단 칩(dvp-chip)과 같은 차콜 라운드 박스 + 옐로우 글자
   (사용자 지시 2026-08-14 — 파란 알약 칩에서 통일). 핀·이름·남은 시간·변경으로 구성. */
.app-header .nav-place-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 40px;
  max-width: 280px;
  padding: 0 var(--sp-3);
  border: 1px solid #3D4450;
  border-radius: var(--r-md);
  background: linear-gradient(180deg, #333945 0%, #272C35 100%);
  color: #FFD666;
  font-family: inherit;
  cursor: pointer;
}

/* ===== [places] 헤더 겹침 방지 — 1024~1440px (2026-08-27 사용자 지시) ===== */
/* [증상] 1280×1020 키오스크(실증 장비 폭)에서 우측 묶음[장소 칩 + 회원명 + 로그아웃]이
   좌측 내비의 "만족도 조사" 글자 **위를 덮었다**.
   [근본 원인] global.css 의 `.app-header .app-nav` 가 `position: absolute; left: 50%` 로
   **흐름 밖**에 있다. 화면 정중앙 고정이라 폭이 줄어도 자리를 비켜 주지 않고, 헤더의 flex 는
   존재하지 않는 요소로 취급해 우측 묶음을 그 위까지 밀어 넣는다. 1280px 실측:
     내비 오른쪽 끝 963px  ↔  우측 묶음 왼쪽 끝 790px  →  173px 겹침
   폭 조절(칩 max-width·글자 축약)만으로는 190px 을 걷어내야 해 장소명이 남지 않는다 —
   현장 운영 기능(장소명·변경 버튼)을 지우게 되므로 택하지 않았다.
   [해법] 겹침이 생기는 폭에서만 내비를 **흐름 안으로** 되돌린다. `position: static` +
   `margin-inline: auto` 면 브랜드와 우측 묶음 사이 남는 공간의 가운데에 서므로, flex 가
   세 덩어리를 나란히 배치해 겹침이 구조적으로 불가능해진다. 1440px 초과에서는 종전
   화면-정중앙 고정을 그대로 둔다(그 폭에서는 원래 겹치지 않는다). */
@media (max-width: 1440px) {
  .app-header .app-nav {
    position: static;
    transform: none;
    margin-inline: auto;
    min-width: 0;
    /* 항목 간 간격을 sp-6 → sp-4 로 좁혀 다섯 항목이 좁은 폭에서도 한 줄에 남게 한다 */
    gap: var(--sp-4);
  }

  /* 우측 묶음은 남는 폭 안에서만 자란다 — 내비를 밀어내지 않는다 */
  .app-header .hdr-right {
    min-width: 0;
    flex: 0 1 auto;
  }

  /* 칩은 가장 먼저 양보하는 조각이다. 장소명은 ellipsis 로 줄이되 지우지 않고,
     핀 아이콘·"변경" 버튼은 그대로 남긴다(현장 운영 기능). */
  .app-header .nav-place-chip {
    flex: 0 1 auto;
    max-width: clamp(150px, 17vw, 240px);
    /* 1024px→174px, 1440px→240px */
    min-width: 0;
  }

  .app-header .npc-name {
    text-overflow: ellipsis;
  }
}

.app-header .nav-place-chip:hover {
  border-color: rgba(255, 214, 102, .55);
}

.app-header .nav-place-chip:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.app-header .npc-ico {
  display: inline-flex;
  flex: 0 0 auto;
}

.app-header .npc-ico svg {
  width: 16px;
  height: 16px;
}

.app-header .npc-body {
  display: grid;
  gap: 1px;
  min-width: 0;
  text-align: left;
}

.app-header .npc-name {
  overflow: hidden;
  font-size: clamp(13px, calc(12.12px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.app-header .npc-time {
  /* 남은 시간은 보조 정보 — 옐로우 본문과 위계를 가르는 밝은 회색 */
  color: #B8BFCC;
  font-size: 11px;
  font-weight: 400;
  line-height: 1.2;
}

.app-header .npc-act {
  flex: 0 0 auto;
  padding-left: var(--sp-2);
  border-left: 1px solid currentColor;
  font-size: 12px;
  font-weight: 700;
  opacity: .8;
}

/* 미선택 상태 — 같은 차콜에 밝은 경고 옐로우로 "골라 달라"가 도드라지게 (관리자 칩과 동일 규칙) */
.app-header .nav-place-chip.is-empty {
  border-color: rgba(255, 201, 77, .6);
  color: #FFC94D;
}

/* ----- 모바일 (내비는 숨지만 장소 칩은 남긴다, 사용자 지시 2026-08-14) ----- */
@media (max-width: 700px) {
  .app-header .nav-place-chip {
    min-height: 36px;
    max-width: 180px;
    gap: var(--sp-1);
    padding: 0 var(--sp-2);
  }

  .app-header .npc-time {
    font-size: 10px;
  }

  /* 로그아웃은 아이콘만 — 칩 자리 확보 */
  .app-header .logout-text {
    display: none;
  }

  .app-header .logout-btn {
    min-width: 38px;
    justify-content: center;
    padding: 0 var(--sp-2);
  }
}

/* ===== [places] ① 하단 탭바 — 고른 장소가 있으면 이름을 그대로 보여 준다 ===== */
.tabbar .tab-item.is-place {
  min-width: 0;
  padding: 0 var(--sp-2);
}

.tabbar .tab-item.is-place .tab-name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* ===== [places] ② 장소 선택 모달 =====
   .modal-backdrop / .modal-card 규약을 그대로 쓰고(등장 dim-in·pop-rise,
   퇴장 .is-closing) 목록 영역만 따로 스크롤시킨다 — 머리말·버튼은 늘 보인다. */
.place-modal .place-modal-card {
  display: flex;
  flex-direction: column;
  width: min(520px, 100%);
  max-height: 86dvh;
  overflow: hidden;
}

.place-modal .place-modal-card:focus {
  outline: none;
}

.place-modal .place-modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.place-modal .place-desc {
  margin-bottom: var(--sp-4);
  color: var(--text-sub);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  line-height: 1.5;
}

.place-modal .place-list {
  display: grid;
  gap: var(--sp-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* 시니어 친화 — 68px 이상 큰 버튼, 선택은 색 + 테두리 + "✓ 선택됨" 글자 3중 표기 */
.place-modal .place-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 68px;
  padding: var(--sp-3) var(--sp-4);
  border: 2px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  text-align: left;
  transition: border-color .15s ease, background .15s ease;
}

@media (hover: hover) {
  .place-modal .place-item:hover {
    border-color: var(--blue-line);
    background: var(--surface-soft);
  }
}

.place-modal .place-item.is-selected {
  border-color: var(--blue);
  background: var(--blue-soft);
}

.place-modal .place-item-pin {
  flex: 0 0 auto;
  font-size: clamp(20px, calc(17.37px + 0.674vw), 26px);
  /* 390px→20px, 1280px→26px */
  line-height: 1;
}

.place-modal .place-item-text {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: 2px;
}

.place-modal .place-item-name {
  overflow: hidden;
  color: var(--text);
  font-size: clamp(17px, calc(15.25px + 0.449vw), 21px);
  /* 390px→17px, 1280px→21px */
  font-weight: 700;
  letter-spacing: -.02em;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.place-modal .place-item.is-selected .place-item-name {
  color: var(--blue-dark);
}

.place-modal .place-item-addr {
  overflow: hidden;
  color: var(--text-mute);
  font-size: clamp(13px, calc(12.13px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.place-modal .place-item-mark {
  flex: 0 0 auto;
  color: var(--blue);
  font-size: clamp(13px, calc(12.13px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 700;
  white-space: nowrap;
}

.place-modal .place-modal-foot {
  flex: 0 0 auto;
  border-top: 1px solid var(--line);
  padding-top: var(--sp-4);
}

/* 모바일 — 아래에서 올라오는 시트(지역 선택 모달과 같은 결) */
@media (max-width: 560px) {
  .place-modal {
    place-items: end stretch;
    padding: 0;
  }

  .place-modal .place-modal-card {
    width: 100%;
    max-height: 88dvh;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    animation: sheet-up .22s cubic-bezier(.2, .7, .3, 1) both;
  }

  .place-modal.is-closing .place-modal-card {
    animation: sheet-down .2s ease-in both;
  }

  .place-modal .place-modal-foot {
    flex-direction: column-reverse;
  }
}

@media (prefers-reduced-motion: reduce) {

  .place-modal .place-modal-card,
  .place-modal.is-closing .place-modal-card,
  .place-modal .place-item {
    animation: none;
    transition: none;
  }
}

/* ===== [places] ③ 측정입력 화면 상단 장소 바 =====
   차콜 배경 + 옐로우 글자 (사용자 지시 2026-08-14 — 파란 톤에서 고도화).
   페이지 전체가 밝은 톤이라 이 바만 진하게 눌러 "지금 어디서 재는지"가 한눈에 온다. */
.measure-user .place-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid #3D4450;
  border-radius: var(--r-lg);
  /* 위가 살짝 밝은 차콜 — 단색보다 눌린 느낌이 덜하다 */
  background: linear-gradient(180deg, #333945 0%, #272C35 100%);
}

.measure-user .place-bar-pin {
  flex: 0 0 auto;
  display: inline-flex;
  color: #FFD666;
}

.measure-user .place-bar-pin svg {
  width: clamp(18px, calc(16.25px + 0.449vw), 22px);
  /* 390px→18px, 1280px→22px */
  height: auto;
}

.measure-user .place-bar-label {
  flex: 0 0 auto;
  color: #B8BFCC;
  font-size: clamp(13px, calc(12.12px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 700;
}

.measure-user .place-bar-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  color: #FFD666;
  font-size: clamp(16px, calc(14.69px + 0.337vw), 19px);
  /* 390px→16px, 1280px→19px */
  font-weight: 700;
  letter-spacing: -.02em;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.measure-user .place-bar-name.is-empty {
  color: #98A0AE;
  font-weight: 700;
}

/* 차콜 배경 위 버튼 — 옐로우 라인 고스트 버튼으로 맞춘다 */
.measure-user .place-bar-change {
  flex: 0 0 auto;
  min-height: 38px;
  padding: 0 var(--sp-4);
  border-color: rgba(255, 214, 102, .55);
  background: transparent;
  color: #FFD666;
}

.measure-user .place-bar-change:hover {
  border-color: #FFD666;
  background: rgba(255, 214, 102, .1);
  color: #FFD666;
}

/* 모바일 — 줄바꿈 없이 한 줄 유지, 라벨을 숨겨 높이를 줄인다 (사용자 지시 2026-08-14) */
@media (max-width: 560px) {
  .measure-user .place-bar {
    gap: var(--sp-2);
    margin-bottom: var(--sp-3);
    padding: var(--sp-1) var(--sp-3);
  }

  .measure-user .place-bar-label {
    display: none;
  }

  .measure-user .place-bar-change {
    min-height: 34px;
    padding: 0 var(--sp-3);
  }
}

/* ===== [places] ④ 측정이력 장소 필터 (조회 기간 옆) ===== */
.period-bar .period-place-label {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}

.period-bar .period-place {
  flex: 0 1 240px;
  min-width: 0;
}

/* 좁은 화면에서는 [조회 기간] 줄과 [측정 장소] 줄을 나눈다
   (기존 .period-bar 의 flex-wrap 분기는 560px 이라 여기서 900px 까지 앞당긴다) */
@media (max-width: 900px) {
  .period-bar {
    flex-wrap: wrap;
  }

  .period-bar .period-place {
    flex: 1 1 100%;
  }
}


/* ============================================================
   [recent-prefill] 홈 카드 "최근 완료" + 측정입력 프리필 안내 (2026-08-14 사용자 지시)
   ============================================================ */

/* ===== [recent-prefill] 홈 카드 — ✅ 최근 측정 8.14 09:30 · 미라클 크루즈 ===== */
.menu-grid .menu-recent {
  display: block;
  width: 100%;
  min-height: 1.35em;
  overflow: hidden;
  /* 한 단계 확대 (사용자 지시 2026-08-16 — 시니어 가독성) */
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
  line-height: 1.35;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.menu-grid .menu-recent.is-done {
  color: var(--success);
}

.menu-grid .menu-recent.is-none {
  color: var(--text-mute);
  font-weight: 600;
}

/* 측정 이력 카드 — AI리포트 최근 생성 일시 (2026-08-20 사용자 지시, 붉은 텍스트) */
.menu-grid .menu-recent.is-report {
  color: #d13e30;
}

.menu-grid .menu-recent-mark {
  margin-right: 4px;
  font-size: .9em;
}

.menu-grid .menu-card.is-disabled .menu-recent {
  color: var(--text-mute);
}

/* [2026-08-20 정리] 이 자리에 있던 동일 미디어쿼리(노트북 압축 — 썸네일 44px·행 간격 등)는
   08 구역의 같은 쿼리 블록으로 병합했다 — 같은 셀렉터가 두 블록에서 서로 덮던 중복 제거. */

/* ===== [recent-prefill] 측정입력 — 지난 값 프리필 안내 문구 ===== */
.measure-user .form-prefill-note {
  margin: calc(var(--sp-2) * -1) 0 var(--sp-3);
  color: var(--text-sub);
  font-size: clamp(13px, calc(12.13px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 600;
}


/* ============================================================
   [ai-report] 홈 "측정 이력" 카드 — 버튼 2개 (2026-08-16 사용자 지시)
   [확인하기](외곽선) + [AI리포트 생성](파랑) 을 한 줄에 나란히 둔다.
   ------------------------------------------------------------
   높이 규칙: 행(.menu-action-row)에는 여백을 주지 않고 버튼(.menu-action)이
   기존 margin-top 을 그대로 쓴다 → 08 구역·컴팩트 분기의 여백 조정이 그대로
   먹혀 버튼 1개 카드와 카드 높이가 항상 같다(노트북 무스크롤 유지).
   ============================================================ */

.menu-grid .menu-card.is-multi .menu-action-row {
  display: flex;
  gap: var(--sp-2);
  width: 100%;
}

/* flex-basis 0 으로 정확히 반씩 나눈다. 글자는 한 줄 고정 —
   줄바꿈이 생기면 이 카드만 높이가 늘어 그리드 정렬이 깨진다. */
.menu-grid .menu-card.is-multi .menu-action {
  flex: 1 1 0;
  min-width: 0;
  width: auto;
  padding: 0 var(--sp-2);
  white-space: nowrap;
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px — 버튼이 둘이라 기본(15~19px)보다 한 단계 낮춘다 */
}

/* 버튼이 둘인 카드에서는 "카드에 올리면 안쪽 버튼도 같이 반응"을 끈다 —
   두 버튼의 동작이 서로 달라 어느 쪽이 눌리는지 헷갈리면 안 된다.
   대신 각 버튼이 자기 위에 올렸을 때만 global.css 의 hover 색을 쓴다. */
@media (hover: hover) {
  .menu-grid .menu-card.is-multi:hover .menu-action.btn-blue {
    background: var(--blue);
    box-shadow: none;
  }

  .menu-grid .menu-card.is-multi:hover .menu-action.btn-blue:hover:not(:disabled) {
    background: var(--blue-dark);
    box-shadow: 0 6px 16px rgba(37, 99, 235, .28);
  }
}

/* 생성 중 — 눌린 상태가 글자("생성 중…")와 흐림 두 가지로 드러난다 */
.menu-grid .menu-card.is-multi .menu-action:disabled {
  cursor: default;
}

/* 모바일 1열(≤560px)에서도 두 버튼을 한 줄로 유지한다.
   390px 카드 안쪽 폭 ≈ 318px → 버튼 각 155px 로 "AI리포트 생성"(13px)이 넉넉히 들어간다. */
@media (max-width: 560px) {
  .menu-grid .menu-card.is-multi .menu-action-row {
    gap: var(--sp-1);
  }
}

/* ===== [ai-report] 상세리포트 — GPT 해설 작성 중 안내 (인쇄 제외) ===== */
.history-page .report-ai-pending {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: calc(var(--sp-2) * -1) 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--blue-line);
  border-radius: var(--r-md);
  background: var(--blue-soft);
  color: var(--text-sub);
  font-size: clamp(13px, calc(12.13px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 600;
  line-height: 1.4;
}

.history-page .report-ai-pending-ico {
  flex: none;
  font-size: 1.15em;
}

/* [ai-retry] 안내문과 버튼을 한 줄에 두되, 좁아지면 버튼이 아래로 내려간다 */
.history-page .report-ai-pending-text {
  flex: 1 1 260px;
}

/* 생성 실패 — 노랑(주의). 리포트 자체가 잘못된 것이 아니므로 등급 빨강은 쓰지 않는다 */
.history-page .report-ai-pending.is-failed {
  flex-wrap: wrap;
  border-color: var(--grade-attention-line);
  background: var(--grade-attention-bg);
}

.history-page .report-ai-pending .report-ai-retry {
  flex: none;
  min-height: var(--tap-min);
  padding: 0 var(--sp-4);
  font-size: clamp(14px, calc(13.13px + 0.225vw), 16px);
  /* 390px→14px, 1280px→16px */
}

@media (max-width: 560px) {

  /* 390px — 버튼을 한 줄 아래에 폭 전체로 둔다(시니어 터치 타깃 확보) */
  .history-page .report-ai-pending .report-ai-retry {
    flex: 1 0 100%;
  }
}


/* ============================================================
   [ai-report] AI 건강리포트 생성 진행 화면 (2026-08-16 사용자 지시)
   components/ReportProgressModal.jsx — 4단계 진행 목록.
   14 구역 .modal-backdrop / .modal-card 규약을 그대로 쓰고 목록만 덧입힌다.
   상태 3중 표기: 색(테두리·배경) + 표식(✓/숫자/스피너/⏱/!) + 오른쪽 글자.
   폰트는 전부 clamp(390px→1280px) 기준.
   ============================================================ */

.rpg-modal .rpg-card {
  width: min(560px, 100%);
}

.rpg-card .rpg-body {
  display: grid;
  gap: var(--sp-4);
}

/* ----- 머리말 ----- */
.rpg-card .rpg-head {
  display: grid;
  justify-items: center;
  gap: var(--sp-1);
  text-align: center;
}

/* ----- AI 분석 장면 (2026-08-20 사용자 지시 — 로봇 + 스캔 링 + 데이터 궤도 + 심전도) -----
   진행 중(.is-run)에만 움직인다. 완료·오류 화면에서는 정지 상태의 차분한 그림이 된다. */
.rpg-card .rpg-hero {
  position: relative;
  width: min(240px, 70vw);
  height: 128px;
  margin-bottom: var(--sp-1);
}

.rpg-card .rpg-hero-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.rpg-card .rpg-hero-bot {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: clamp(46px, 12vw, 56px);
  height: auto;
}

.rpg-hero.is-run .rpg-hero-bot {
  animation: rpg-bot-float 2.6s ease-in-out infinite;
}

/* 스캔 링 — 퍼지는 파동. 정지 상태에서는 가장 안쪽 링 하나만 옅게 */
.rpg-hero .rpg-ring {
  fill: none;
  stroke: var(--blue-line);
  stroke-width: 1.5;
  opacity: 0;
  transform-origin: 120px 64px;
}

.rpg-hero .rpg-ring.r1 {
  opacity: .55;
}

.rpg-hero.is-run .rpg-ring {
  animation: rpg-ring-pulse 2.4s ease-out infinite;
}

.rpg-hero.is-run .rpg-ring.r2 {
  animation-delay: .8s;
}

.rpg-hero.is-run .rpg-ring.r3 {
  animation-delay: 1.6s;
}

/* 데이터 점 궤도 — 진행 중에만 회전 */
.rpg-hero .rpg-orbit {
  transform-origin: 120px 64px;
}

.rpg-hero.is-run .rpg-orbit {
  animation: rpg-orbit-spin 9s linear infinite;
}

.rpg-hero .rpg-orbit-path {
  fill: none;
  stroke: var(--blue-line);
  stroke-width: 1;
  stroke-dasharray: 3 5;
  opacity: .7;
}

.rpg-hero .rpg-dot {
  stroke: #fff;
  stroke-width: 2;
}

.rpg-hero .rpg-dot.d1 {
  fill: #d13e30;
}

.rpg-hero .rpg-dot.d2 {
  fill: #6B21A8;
}

.rpg-hero .rpg-dot.d3 {
  fill: #16a34a;
}

.rpg-hero .rpg-dot-glyph {
  fill: #fff;
  font-size: 9px;
  font-weight: 700;
}

/* 심전도 라인 — 대시가 흘러가며 "신호를 읽는 중" 느낌 */
.rpg-hero .rpg-ecg {
  stroke: var(--blue);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .5;
  stroke-dasharray: 260 640;
  stroke-dashoffset: 0;
}

.rpg-hero.is-run .rpg-ecg {
  opacity: .8;
  animation: rpg-ecg-flow 2.8s linear infinite;
}

@keyframes rpg-bot-float {

  0%,
  100% {
    transform: translate(-50%, -50%);
  }

  50% {
    transform: translate(-50%, calc(-50% - 5px));
  }
}

@keyframes rpg-ring-pulse {
  0% {
    transform: scale(.62);
    opacity: .75;
  }

  100% {
    transform: scale(1.22);
    opacity: 0;
  }
}

@keyframes rpg-orbit-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes rpg-ecg-flow {
  to {
    stroke-dashoffset: -900;
  }
}

/* 궤도 점들이 뒤집혀 보이지 않게 — 점(글리프 포함)만 역방향으로 되돌린다.
   바깥 g 의 translate(위치)를 CSS transform 이 덮어쓰면 안 되므로 안쪽 래퍼에만 건다. */
.rpg-hero.is-run .rpg-dot-wrap {
  transform-box: fill-box;
  transform-origin: center;
  animation: rpg-orbit-counter 9s linear infinite;
}

@keyframes rpg-orbit-counter {
  to {
    transform: rotate(-360deg);
  }
}

/* 모션 민감 사용자 — 전부 정지 */
@media (prefers-reduced-motion: reduce) {

  .rpg-hero.is-run .rpg-hero-bot,
  .rpg-hero.is-run .rpg-ring,
  .rpg-hero.is-run .rpg-orbit,
  .rpg-hero.is-run .rpg-dot-wrap,
  .rpg-hero.is-run .rpg-ecg {
    animation: none;
  }
}

.rpg-card .rpg-title {
  font-size: clamp(20px, calc(17.37px + 0.674vw), 26px);
  /* 390px→20px, 1280px→26px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--text);
  word-break: keep-all;
}

.rpg-card .rpg-sub {
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  color: var(--text-sub);
  word-break: keep-all;
}

/* ----- 단계 목록 ----- */
.rpg-card .rpg-steps {
  display: grid;
  gap: var(--sp-2);
  list-style: none;
  margin: 0;
  padding: 0;
}

.rpg-card .rpg-step {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--tap-min);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  transition: background .18s ease-out, border-color .18s ease-out;
}

.rpg-card .rpg-step-mark {
  flex: none;
  display: grid;
  place-items: center;
  width: clamp(34px, calc(31.37px + 0.674vw), 40px);
  height: clamp(34px, calc(31.37px + 0.674vw), 40px);
  /* 390px→34px, 1280px→40px */
  border-radius: var(--r-pill);
  background: var(--line);
  color: var(--text-mute);
  font-size: clamp(15px, calc(13.69px + 0.337vw), 18px);
  /* 390px→15px, 1280px→18px */
  font-weight: 700;
  line-height: 1;
}

.rpg-card .rpg-step-text {
  flex: 1 1 auto;
  display: grid;
  gap: 2px;
  min-width: 0;
}

.rpg-card .rpg-step-label {
  font-size: clamp(16px, calc(14.25px + 0.449vw), 20px);
  /* 390px→16px, 1280px→20px */
  font-weight: 700;
  color: var(--text);
  word-break: keep-all;
}

.rpg-card .rpg-step-desc {
  font-size: clamp(13px, calc(12.13px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  color: var(--text-mute);
  word-break: keep-all;
}

.rpg-card .rpg-step-state {
  flex: none;
  font-size: clamp(12px, calc(10.7px + 0.337vw), 15px);
  /* 390px→12px, 1280px→15px */
  font-weight: 700;
  color: var(--text-mute);
  white-space: nowrap;
}

/* 진행 중 — 파랑으로 강조 */
.rpg-card .rpg-step.is-run {
  border-color: var(--blue-line);
  background: var(--blue-soft);
}

.rpg-card .rpg-step.is-run .rpg-step-mark {
  background: var(--blue);
}

.rpg-card .rpg-step.is-run .rpg-step-label,
.rpg-card .rpg-step.is-run .rpg-step-state {
  color: var(--blue);
}

/* [ai-retry] 자동 재시도 중 — 노랑. 진행 중이긴 하지만 "한 번 더" 라는 것이 드러나야 한다 */
.rpg-card .rpg-step.is-retry {
  border-color: var(--grade-attention-line);
  background: var(--grade-attention-bg);
}

.rpg-card .rpg-step.is-retry .rpg-step-mark {
  background: var(--grade-attention);
}

.rpg-card .rpg-step.is-retry .rpg-step-label,
.rpg-card .rpg-step.is-retry .rpg-step-state {
  color: var(--grade-attention);
}

/* 완료 — 초록 */
.rpg-card .rpg-step.is-done {
  border-color: var(--grade-normal-line);
  background: var(--grade-normal-bg);
}

.rpg-card .rpg-step.is-done .rpg-step-mark {
  background: var(--grade-normal);
  color: var(--text-invert);
}

.rpg-card .rpg-step.is-done .rpg-step-state {
  color: var(--grade-normal);
}

/* 계속 진행(제한 시간 초과) — 노랑. 실패가 아니다 */
.rpg-card .rpg-step.is-late {
  border-color: var(--grade-attention-line);
  background: var(--grade-attention-bg);
}

.rpg-card .rpg-step.is-late .rpg-step-mark {
  background: var(--grade-attention);
  color: var(--text-invert);
}

.rpg-card .rpg-step.is-late .rpg-step-state {
  color: var(--grade-attention);
}

/* 실패 — 빨강 */
.rpg-card .rpg-step.is-fail {
  border-color: var(--grade-consult-line);
  background: var(--grade-consult-bg);
}

.rpg-card .rpg-step.is-fail .rpg-step-mark {
  background: var(--grade-consult);
  color: var(--text-invert);
}

.rpg-card .rpg-step.is-fail .rpg-step-state {
  color: var(--grade-consult);
}

/* 스피너 — 진행 중 표식 안에서만 돈다 */
.rpg-card .rpg-spin {
  width: 55%;
  height: 55%;
  border: 3px solid rgba(255, 255, 255, .45);
  border-top-color: var(--text-invert);
  border-radius: var(--r-pill);
  animation: rpg-spin .8s linear infinite;
}

@keyframes rpg-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ----- 안내 / 오류 줄 ----- */
.rpg-card .rpg-note,
.rpg-card .rpg-error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--blue-line);
  border-radius: var(--r-md);
  background: var(--blue-soft);
  color: var(--text-sub);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 600;
  line-height: 1.45;
  word-break: keep-all;
}

/* [ai-retry] 해설만 실패 — 오류(빨강)가 아니라 주의(노랑). 리포트 본문은 멀쩡하다 */
.rpg-card .rpg-warn {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--grade-attention-line);
  border-radius: var(--r-md);
  background: var(--grade-attention-bg);
  color: var(--text-sub);
  font-size: clamp(13px, calc(12.13px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 600;
  line-height: 1.45;
  text-align: left;
}

.rpg-card .rpg-warn-ico {
  flex: none;
  font-size: 1.1em;
  color: var(--grade-attention);
}

.rpg-card .rpg-error {
  border-color: var(--grade-consult-line);
  background: var(--grade-consult-bg);
  color: var(--grade-consult);
}

.rpg-card .rpg-note-ico,
.rpg-card .rpg-error-ico {
  flex: none;
  font-size: 1.1em;
  line-height: 1.35;
}

.rpg-card .rpg-foot .btn {
  flex: 1 1 0;
}

/* 완료 발 — [닫기](보조) + [상세리포트 보기](주). 자동 이동 대신 이 버튼으로 넘어간다.
   주버튼을 넓게 잡아(1 : 1.8) 어느 쪽이 다음 걸음인지 크기로도 드러낸다.
   두 버튼 높이는 flex 기본 stretch 로 btn-lg(58px)에 맞춰 나란해진다. */
.rpg-card .rpg-foot.is-done {
  display: grid;
  grid-template-columns: 1fr 1.8fr;
  gap: var(--sp-3);
}

.rpg-card .rpg-foot.is-done .btn {
  flex: none;
}

/* [ai-retry] 해설만 실패한 발 — [닫기] · [상세리포트 보기] · [다시 시도] 셋.
   주행동은 [다시 시도] 라 조금 넓게 잡는다. 셋 다 44px 이상(.btn 기본). */
.rpg-card .rpg-foot.is-failed {
  display: grid;
  grid-template-columns: .9fr 1.3fr 1.2fr;
  gap: var(--sp-2);
}

.rpg-card .rpg-foot.is-failed .btn {
  flex: none;
  padding: 0 var(--sp-2);
  font-size: clamp(15px, calc(13.69px + 0.337vw), 18px);
  /* 390px→15px, 1280px→18px */
}

/* 보조 버튼도 시니어 기준을 지킨다 — 글자만 한 단계 작다 */
.rpg-card .rpg-foot .rpg-close {
  border-radius: var(--r-lg);
  font-size: clamp(16px, calc(14.25px + 0.449vw), 20px);
  /* 390px→16px, 1280px→20px */
  font-weight: 700;
}

/* 모바일 — 아래에서 올라오는 시트(장소 선택 모달과 같은 결) */
@media (max-width: 560px) {
  .rpg-modal {
    place-items: end stretch;
    padding: 0;
  }

  .rpg-modal .rpg-card {
    width: 100%;
    max-height: 92dvh;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    animation: sheet-up .22s cubic-bezier(.2, .7, .3, 1) both;
  }

  /* 390px 에서는 설명 줄을 접어 4단계가 한 화면에 다 보이게 한다 */
  .rpg-card .rpg-step-desc {
    display: none;
  }

  /* [ai-retry] 버튼 3개는 390px 한 줄에 안 들어간다 — 주행동을 위로 올리고 둘을 아래에 나눈다 */
  .rpg-card .rpg-foot.is-failed {
    grid-template-columns: 1fr 1fr;
  }

  .rpg-card .rpg-foot.is-failed .rpg-again {
    grid-column: 1 / -1;
    grid-row: 1;
  }
}

@media (prefers-reduced-motion: reduce) {

  .rpg-modal .rpg-card {
    animation: none;
  }

  .rpg-card .rpg-spin {
    animation-duration: 2.4s;
  }
}


/* ============================================================
   [field-guard] 측정 입력 — 선택 항목 안내 / 두 칸 관계 오류 (2026-08-17)
   ------------------------------------------------------------
   · .form-optional-note … "신장·체중만 입력해도 저장됩니다" (체성분기 없는 현장)
   · .form-cross-error   … 혈압 전치처럼 **칸 하나에 붙일 수 없는** 오류.
                           칸별 .row-error 와 톤을 맞추되 폼 폭 전체를 쓰고 아이콘을 단다.
   ============================================================ */

.measure-user .form-optional-note {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--blue-line);
  border-radius: var(--r-md);
  background: var(--blue-soft);
  color: var(--text-sub);
  font-size: clamp(14px, calc(13.12px + 0.225vw), 16px);
  /* 390px→14px, 1280px→16px */
  font-weight: 600;
  line-height: 1.45;
  word-break: keep-all;
}

.measure-user .form-cross-error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  margin: var(--sp-3) 0 0;
  padding: var(--sp-3);
  border: 1px solid var(--grade-consult-line);
  border-radius: var(--r-md);
  background: var(--grade-consult-bg);
  color: var(--grade-consult);
  font-size: clamp(14px, calc(13.12px + 0.225vw), 16px);
  /* 390px→14px, 1280px→16px */
  font-weight: 700;
  line-height: 1.5;
  word-break: keep-all;
}


/* ============================================================
   [ai-cancel] 진행 모달 탈출구 — 늦어질 때만 나오는 [그만두기] (2026-08-17)
   ------------------------------------------------------------
   실패 통보가 아니라 "기다릴지 말지" 를 묻는 자리라 오류색을 쓰지 않는다.
   안내 한 줄 위, 버튼 한 줄 아래(세로 배치) — 담당자가 문장을 읽고 누르게.
   ============================================================ */

.rpg-card .rpg-foot.is-slow {
  display: grid;
  gap: var(--sp-2);
}

.rpg-card .rpg-slow-text {
  margin: 0;
  color: var(--text-sub);
  font-size: clamp(14px, calc(13.12px + 0.225vw), 16px);
  /* 390px→14px, 1280px→16px */
  font-weight: 600;
  line-height: 1.45;
  text-align: center;
  word-break: keep-all;
}

.rpg-card .rpg-foot.is-slow .rpg-cancel {
  flex: none;
  min-height: 52px;
  border-radius: var(--r-lg);
  font-size: clamp(16px, calc(14.25px + 0.449vw), 20px);
  /* 390px→16px, 1280px→20px */
  font-weight: 700;
}


/* ============================================================
   [a11y-senior] 시니어 기준 — 터치 타깃 44px / 모바일 본문 하한 14px (2026-08-17)
   ------------------------------------------------------------
   대상은 **모바일 폭(≤700px)** 뿐이다. 데스크톱은 clamp 최대값 구간이라 이미 기준을
   넘고, 노트북 무스크롤 압축(높이 기준 분기)과도 부딪히지 않는다.
   ============================================================ */

/* 로그아웃 — 38px 였다. 데스크톱에서도 44px 로 올린다(글자 크기는 그대로) */
.app-header .logout-btn {
  min-height: 44px;
}

@media (max-width: 700px) {

  /* 아이콘만 남는 모바일에서도 정사각 44px 를 지킨다 */
  .app-header .logout-btn {
    min-width: 44px;
    min-height: 44px;
  }

  /* 장소 칩 — 측정 전 반드시 누르는 버튼이라 로그아웃과 같은 기준으로 */
  .app-header .nav-place-chip {
    min-height: 44px;
  }

  /* ----- 본문 하한 14px ----- */
  /* 홈 카드 설명 · 홈 안내박스 · 폼 힌트/오류는 실제로 읽는 문장이다.
     최대값은 건드리지 않고 좁은 화면의 하한만 12~13px → 14px 로 올린다. */
  .menu-grid .menu-desc,
  .menu-grid .menu-recent,
  .home-page>.info-box .info-body,
  .survey-page .survey-hint,
  .measure-user .row-hint,
  .measure-user .row-error,
  .measure-user .form-prefill-note,
  .field-hint,
  .field-error {
    font-size: 14px;
  }
}


/* ===== [frontend-dev] 사전문진 열람 (.survey-view) — 2026-08-19 계약 §3-0 ===== */
/* 본인 문진 답변을 12문항(+3-1) 목록으로 보여주는 화면.
   노트북(1366~1536px)에서 스크롤을 줄이려고 문항 목록을 2열 그리드로 접는다.
   색·간격은 global.css :root 토큰만 참조한다(하드코딩 금지). */

/* --- 상단 탭 개수. global.css 의 repeat(3,1fr) 을 덮는다 ---
   3개 → 4개(2026-08-19 사전문진) → 5개(2026-08-24 만족도 조사, 계약 §10-6). */
.tabbar {
  grid-template-columns: repeat(5, 1fr);
}

/* 칸이 늘수록 390px 에서 아이콘+글자 가로 배치가 빠듯하다.
   global.css 의 세로 배치 분기(≤380px)를 480px 까지 넓혀 글자가 잘리지 않게 한다.
   (481px 이상에서는 한 칸이 96px 넘어 가로 배치가 들어간다 — 5칸 기준 계산) */
@media (max-width: 480px) {
  .tabbar .tab-item {
    flex-direction: column;
    gap: 2px;
  }
}

/* 5칸이 되면 한 칸이 390px 에서 78px — 탭 이름이 두 줄로 접히면 높이가 무너진다.
   장소 탭은 이미 [places] 블록에서 말줄임 처리되어 있고, 나머지는 접힘만 막으면 된다.
   ("만족도 조사"는 탭바에서만 "만족도"로 줄여 쓴다 — components/TabBar.jsx TABS.short) */
.tabbar .tab-name {
  white-space: nowrap;
}

.survey-view {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  /* 헤더와 너무 붙어 보인다는 지시(2026-08-24) — 상단 살짝 띄움 */
  margin-top: var(--sp-4);
  /* wide 레이아웃은 폭 상한을 페이지가 스스로 갖는다 — 홈(.home-page)과 같은 1500px.
     (2026-08-21 사용자 지적: 상한 없이 전체 폭으로 퍼졌던 것 수정) */
  max-width: var(--content-max);
  margin-inline: auto;
  width: 100%;
}

/* --- 머리말 --- */
.survey-view .survey-view-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
}

.survey-view .svh-art {
  flex: 0 0 auto;
  width: clamp(64px, calc(50.25px + 3.539vw), 110px);
  /* 390px→64px, 1280px→96px */
  height: auto;
}

/* --- 마지막 수정일 + 수정하기 --- */
.survey-view .survey-view-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
}

.survey-view .svb-date {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-2);
  font-size: clamp(16px, calc(14.25px + 0.449vw), 20px);
  /* 390px→16px, 1280px→20px */
  font-weight: 800;
  color: var(--text);
}

.survey-view .svb-label {
  font-size: clamp(14px, calc(12.65px + 0.348vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 700;
  color: var(--text-sub);
}

.survey-view .svb-edit {
  flex: 0 0 auto;
  min-width: 132px;
}

/* --- 문항 목록 --- */
.survey-view .sv-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-3);
  list-style: none;
  margin: 0;
  padding: 0;
}

.survey-view .sv-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: var(--tap-min);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
}

/* 3-1(조건부)은 부모 문항에 딸린 항목임을 왼쪽 굵은 선으로 알린다 */
.survey-view .sv-row.is-sub {
  border-left: 4px solid var(--blue-line);
  background: var(--surface-soft);
}

.survey-view .sv-q {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  min-width: 0;
}

.survey-view .sv-no {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--blue-soft);
  color: var(--blue-dark);
  font-size: clamp(13px, calc(11.65px + 0.348vw), 16px);
  /* 390px→13px, 1280px→16px */
  font-weight: 800;
}

.survey-view .sv-q-text {
  font-size: clamp(15px, calc(13.65px + 0.348vw), 18px);
  /* 390px→15px, 1280px→18px */
  font-weight: 700;
  line-height: 1.45;
  color: var(--text);
}

.survey-view .sv-cond {
  display: block;
  font-size: clamp(13px, calc(12.1px + 0.225vw), 15px);
  /* 390px→13px, 1280px→15px */
  font-weight: 600;
  color: var(--text-mute);
}

.survey-view .sv-a {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 46%;
  padding: 6px var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--blue-soft);
  border: 1px solid var(--blue-line);
  color: var(--blue-dark);
  font-size: clamp(15px, calc(13.65px + 0.348vw), 18px);
  /* 390px→15px, 1280px→18px */
  font-weight: 800;
  text-align: right;
}

.survey-view .sv-a-mark {
  font-size: 14px;
  line-height: 1;
}

/* 미응답 / 해당 없음 — 회색으로 낮춘다 */
.survey-view .sv-a.is-muted {
  background: var(--surface-sunken);
  border-color: var(--line);
  color: var(--text-mute);
  font-weight: 700;
}

/* 정본 선택지에 없는 옛 코드 — 값을 그대로 보이되 강조하지 않는다 */
.survey-view .sv-a.is-unknown {
  background: var(--surface-sunken);
  border-color: var(--line-strong);
  color: var(--text-sub);
}

.survey-view .sv-a.is-muted .sv-a-mark,
.survey-view .sv-a.is-unknown .sv-a-mark {
  display: none;
}

/* --- 안내 한 줄 + 하단 버튼 --- */
.survey-view .sv-note {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  background: var(--blue-soft);
  border: 1px solid var(--blue-line);
  font-size: clamp(14px, calc(12.65px + 0.348vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 600;
  line-height: 1.5;
  color: var(--text-sub);
}

.survey-view .sv-foot {
  display: flex;
  justify-content: center;
}

.survey-view .sv-foot .btn {
  min-width: 220px;
}

/* 빈 상태(문진 미작성)의 [작성하기] 버튼 여백 */
.survey-view .state-block .btn {
  margin-top: var(--sp-2);
  min-width: 200px;
}

/* --- 넓은 화면: 2열로 접어 세로 스크롤을 줄인다 --- */
@media (min-width: 901px) {
  .survey-view .sv-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-3) var(--sp-4);
  }
}

/* --- 좁은 화면: 답변 칩을 문항 아래로 내린다(가로로 눌려 두 줄 나는 것 방지) --- */
@media (max-width: 560px) {
  .survey-view .survey-view-head {
    align-items: flex-start;
  }

  .survey-view .survey-view-bar {
    flex-direction: column;
    align-items: stretch;
  }

  .survey-view .svb-edit {
    width: 100%;
  }

  .survey-view .sv-row {
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-2);
  }

  .survey-view .sv-a {
    max-width: 100%;
    align-self: flex-start;
    margin-left: calc(30px + var(--sp-2));
    text-align: left;
  }

  .survey-view .sv-foot .btn,
  .survey-view .state-block .btn {
    width: 100%;
  }
}


/* ============================================================
   [consent] 민감정보 수집·이용 동의 (2026-08-21 · 계약 §9)
   ① 동의 모달 (.consent-modal — 측정 입력 진입 시 최초 1회)
   ② 문서 3페이지 (.doc-page — /consent/sensitive · /terms · /privacy)

   참고 디자인(비헬씨)의 **구성**만 가져오고 색은 전부 사용자 앱 토큰이다
   (참고본의 주황 브랜드색은 쓰지 않는다 — 계약 §9-5 말미).
   시니어 대상이라 본문 글자는 앱 기본보다 한 단계 크게 잡는다.
   ============================================================ */

/* ===== ① 동의 모달 ===== */
.modal-card.consent-modal {
  width: min(460px, calc(100% - 32px));
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
  text-align: center;
}

.consent-modal .consent-title {
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line);
  font-size: clamp(20px, calc(17.37px + 0.674vw), 26px);
  /* 390px→20px, 1280px→26px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--text);
}

.consent-modal .consent-lead {
  padding: var(--sp-5) var(--sp-1);
  border-bottom: 1px solid var(--line);
  font-size: clamp(16px, calc(14.25px + 0.449vw), 20px);
  /* 390px→16px, 1280px→20px */
  font-weight: 500;
  line-height: 1.5;
  color: var(--text-sub);
}

/* 체크박스 행 — 행 전체가 44px 이상이라 시니어도 정확히 누른다 */
/* [2026-08-21 사용자 재지시] 링크 2개는 **항상 체크박스 우측 같은 줄** — 줄바꿈 금지.
   자리는 체크 문구가 두 줄로 접히며 내어 준다(min-width:0). */
.consent-modal .consent-check-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-4) 0 var(--sp-3);
}

.consent-modal .consent-check {
  display: flex;
  flex: 1 1 0;
  min-width: 0;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap-min);
  padding: 0 var(--sp-1);
  border-radius: var(--r-md);
  cursor: pointer;
  text-align: left;
}

.consent-modal .consent-check:hover {
  background: var(--surface-sunken);
}

/* 실제 input 은 지우지 않고 시각적으로만 가린다(키보드 포커스·스크린리더 유지) */
.consent-modal .consent-check input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.consent-modal .consent-check-box {
  position: relative;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border: 2px solid var(--line-strong);
  border-radius: 8px;
  background: var(--surface);
  transition: background .15s ease, border-color .15s ease;
}

.consent-modal .consent-check-box::after {
  content: "";
  position: absolute;
  left: 7px;
  top: 2px;
  width: 7px;
  height: 13px;
  border: solid var(--text-invert);
  border-width: 0 3px 3px 0;
  transform: rotate(45deg) scale(.4);
  opacity: 0;
  transition: opacity .12s ease, transform .12s ease;
}

.consent-modal .consent-check input:checked~.consent-check-box {
  background: var(--blue);
  border-color: var(--blue);
}

.consent-modal .consent-check input:checked~.consent-check-box::after {
  opacity: 1;
  transform: rotate(45deg) scale(1);
}

.consent-modal .consent-check input:focus-visible~.consent-check-box {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

.consent-modal .consent-check-text {
  font-size: clamp(15px, calc(13.69px + 0.337vw), 18px);
  /* 390px→15px, 1280px→18px */
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
}

/* 상세 문서로 가는 화살표 — 체크와 분리된 별도 버튼(누른다고 동의되지 않는다) */
.consent-modal .consent-more {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--r-pill);
  background: var(--surface-sunken);
  color: var(--text-sub);
  font-size: clamp(20px, calc(17.37px + 0.674vw), 26px);
  /* 390px→20px, 1280px→26px */
  font-weight: 700;
  line-height: 1;
}

.consent-modal .consent-more:hover {
  background: var(--blue-soft);
  color: var(--blue);
}

/* 라운드 박스 링크 2개 — 나란히 */
/* 홈 안내박스 우측 — 약관·처리방침 라운드 링크 (2026-08-21 사용자 확정 위치) */
.home-page .info-box .home-doc-links {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  align-self: center;
}

.home-page .info-box .home-doc {
  min-height: 34px;
  padding: 0 12px;
  border: 1px solid var(--blue-line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--text-sub);
  white-space: nowrap;
  font-size: clamp(12px, calc(11.12px + 0.225vw), 14px);
  /* 390px→12px, 1280px→14px */
  font-weight: 600;
  cursor: pointer;
}

.home-page .info-box .home-doc:hover {
  color: var(--blue);
  border-color: var(--blue);
}

/* 좁은 폭 — 안내 문구 아래 줄로 흘러 우측 정렬 유지 */
@media (max-width: 640px) {
  .home-page .info-box {
    flex-wrap: wrap;
  }

  .home-page .info-box .home-doc-links {
    flex: 1 1 100%;
    justify-content: flex-end;
  }
}

/* [2026-08-21 사용자 재지시] 링크 2개는 화면 폭과 무관하게 체크박스 우측 고정 */
.consent-modal .consent-doc-links {
  display: flex;
  flex: 0 0 auto;
  gap: 6px;
  margin-left: auto;
}

.consent-modal .consent-doc {
  min-height: 34px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-soft);
  color: var(--text-sub);
  white-space: nowrap;
  font-size: clamp(11.5px, calc(10.62px + 0.225vw), 13.5px);
  /* 390px→11.5px, 1280px→13.5px — 우측 보조 링크라 본문보다 한 단계 작게 */
  font-weight: 600;
  letter-spacing: -.02em;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.consent-modal .consent-doc:hover {
  background: var(--blue-soft);
  border-color: var(--blue-line);
  color: var(--blue);
}

.consent-modal .consent-error {
  margin-bottom: var(--sp-3);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  font-weight: 600;
  color: var(--danger);
}

.consent-modal .consent-actions {
  display: grid;
  gap: var(--sp-2);
}

/* [닫기] 는 주버튼보다 낮은 위계 — 테두리만 남긴다 */
.consent-modal .consent-close {
  border-color: var(--line);
  color: var(--text-sub);
}

.consent-modal .consent-close:hover:not(:disabled) {
  background: var(--surface-sunken);
  border-color: var(--line-strong);
  color: var(--text);
}


/* ===== ② 문서 페이지 (/consent/sensitive · /terms · /privacy) ===== */
.doc-page {
  max-width: 900px;
  margin: 0 auto;
  padding-bottom: var(--sp-6);
}

.doc-page .doc-topbar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.doc-page .back-btn {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--text-sub);
  font-size: clamp(24px, calc(20.49px + 0.899vw), 32px);
  /* 390px→24px, 1280px→32px */
  font-weight: 700;
  line-height: 1;
}

.doc-page .back-btn:hover {
  background: var(--blue-soft);
  border-color: var(--blue-line);
  color: var(--blue);
}

.doc-page .doc-heading {
  min-width: 0;
  font-size: clamp(21px, calc(17.49px + 0.899vw), 29px);
  /* 390px→21px, 1280px→29px */
  font-weight: 700;
  letter-spacing: -.04em;
  line-height: 1.2;
  color: var(--text);
}

.doc-page .doc-version {
  margin: var(--sp-2) 0 var(--sp-4) calc(var(--tap-min) + var(--sp-2));
  font-size: clamp(13px, calc(11.69px + 0.337vw), 16px);
  /* 390px→13px, 1280px→16px */
  font-weight: 600;
  color: var(--text-mute);
}

.doc-page .doc-body {
  padding: var(--sp-6) var(--sp-6) var(--sp-5);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.doc-page .doc-lede {
  margin-bottom: var(--sp-4);
  font-size: clamp(20px, calc(16.49px + 0.899vw), 28px);
  /* 390px→20px, 1280px→28px */
  font-weight: 700;
  letter-spacing: -.04em;
  line-height: 1.3;
  color: var(--text);
}

.doc-page .doc-h3 {
  margin: var(--sp-6) 0 var(--sp-3);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
  font-size: clamp(17px, calc(15.25px + 0.449vw), 21px);
  /* 390px→17px, 1280px→21px */
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--text);
}

/* 첫 소제목은 위 구분선을 생략한다(리드 문단 바로 아래라 선이 두 겹으로 보인다) */
.doc-page .doc-lede+.doc-h3,
.doc-page .doc-body>.doc-h3:first-child {
  margin-top: var(--sp-4);
  padding-top: 0;
  border-top: 0;
}

.doc-page .doc-p {
  margin-bottom: var(--sp-3);
  font-size: clamp(15px, calc(13.69px + 0.337vw), 18px);
  /* 390px→15px, 1280px→18px */
  line-height: 1.7;
  color: var(--text-sub);
}

.doc-page .doc-muted {
  color: var(--text-mute);
}

.doc-page .doc-note {
  font-size: .88em;
  color: var(--text-mute);
}

.doc-page .doc-list {
  margin: 0 0 var(--sp-3);
  padding-left: 1.15em;
  font-size: clamp(15px, calc(13.69px + 0.337vw), 18px);
  /* 390px→15px, 1280px→18px */
  line-height: 1.65;
  color: var(--text-sub);
}

.doc-page .doc-list li {
  margin-bottom: var(--sp-2);
}

.doc-page .doc-list li::marker {
  color: var(--blue);
}

/* 표는 좁은 화면에서 가로로만 스크롤한다(페이지 본문은 가로 스크롤 금지) */
.doc-page .doc-table-scroll {
  margin: var(--sp-3) 0 var(--sp-4);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.doc-page .doc-table {
  width: 100%;
  min-width: 460px;
  border-collapse: collapse;
  border: 1px solid var(--line);
  font-size: clamp(14px, calc(12.69px + 0.337vw), 17px);
  /* 390px→14px, 1280px→17px */
  line-height: 1.6;
}

.doc-page .doc-table th,
.doc-page .doc-table td {
  padding: var(--sp-3);
  border: 1px solid var(--line);
  vertical-align: top;
  text-align: left;
  color: var(--text-sub);
}

.doc-page .doc-table thead th {
  background: var(--blue-soft);
  color: var(--text);
  font-weight: 700;
  text-align: center;
  vertical-align: middle;
}

.doc-page .doc-table tbody th {
  width: 34%;
  background: var(--surface-sunken);
  color: var(--text);
  font-weight: 700;
}

.doc-page .doc-table .doc-list {
  margin: 0;
  font-size: 1em;
}

.doc-page .doc-callout {
  margin: var(--sp-4) 0;
  padding: var(--sp-4);
  border: 1px solid var(--blue-line);
  border-radius: var(--r-md);
  background: var(--blue-soft);
  font-size: clamp(15px, calc(13.69px + 0.337vw), 18px);
  /* 390px→15px, 1280px→18px */
  line-height: 1.65;
  color: var(--text);
}

.doc-page .doc-callout strong {
  color: var(--blue-dark);
}

.doc-page .doc-foot {
  display: flex;
  justify-content: center;
  margin-top: var(--sp-5);
}

.doc-page .doc-ok {
  min-width: 220px;
}

@media (max-width: 700px) {
  .doc-page .doc-body {
    padding: var(--sp-5) var(--sp-4) var(--sp-4);
    border-radius: var(--r-lg);
  }

  .doc-page .doc-version {
    margin-left: 0;
  }

  .doc-page .doc-ok {
    width: 100%;
  }

  .modal-card.consent-modal {
    padding: var(--sp-5) var(--sp-4) var(--sp-4);
  }
}

/* ===== 터치 키패드 대비 — 측정 입력 하단 여백 (2026-08-21 사용자 지시) =====
   태블릿·키오스크에서 화면 키패드가 올라오면 아래쪽 입력칸·저장 버튼이 가려졌다.
   터치 입력이 **하나라도 있는** 기기에 적용한다(any-pointer: coarse).
   [2026-08-21 기준 변경] 처음에는 "주 입력이 손가락"(hover:none and pointer:coarse)으로
   잡았지만, 실증 키오스크(윈도우 10 터치 + 마우스 인식 장치)는 주 입력이 마우스로
   판정돼 빠질 수 있다 — 터치 가능성만 있으면 여백을 준다. 이 여백은 측정 입력 화면
   한정이라 터치 노트북에서의 부담도 작다.
   [2026-08-26 사용자 지시] **모바일(≤759px)은 제외한다** — 휴대폰은 any-pointer:coarse 가
   항상 참이라 42vh(예: 844px 화면에서 354px)가 늘 붙어, 저장 버튼 아래로 화면 절반에
   가까운 빈 배경이 이어졌다. 폭 조건은 이 블록의 모바일 분기(위 max-width:759px)와
   같은 자리에서 끊는다 — 태블릿·키오스크(760px+)의 키패드 대비는 그대로 유지된다. */
@media (any-pointer: coarse) and (min-width: 760px) {
  .measure-user .form-card {
    margin-bottom: 42vh;
  }
}



/* ===== [satisfaction] 만족도 조사 (계약 §10-6, 2026-08-24) =====
   라우트 하나(app/(user)/satisfaction)에서 열람·작성·완료 3상태를 그린다.

   재사용 원칙 — 새 스킨을 만들지 않는다:
     · 작성(form)  … 사전문진 스텝의 .survey-page 카드/라디오 스킨을 그대로 얹고,
                      폭 상한만 .survey-page.satisfaction-page 로 덮는다(2단 선택자라 우선순위 승).
     · 열람(view)  … 사전문진 열람 .survey-view 목록(sv-row/sv-q/sv-a)을 그대로 쓴다.
   여기서는 만족도에만 필요한 차이(5지선다 가로 배치·구분 라벨·미응답 표시·완료 안내)만 더한다.
   색·간격은 global.css :root 토큰만 참조한다(하드코딩 금지). */

/* --- 작성: 와이드 폭. 사전문진(820px)과 달리 선택지 5개가 한 줄에 들어가야 한다 --- */
.survey-page.satisfaction-page {
  max-width: var(--content-max);
  gap: var(--sp-4);
}

/* 진행 표시는 스크롤 내내 붙어 있는다 — 단일 페이지라 "몇 개 남았는지"가 유일한 이정표다 */
.satisfaction-page .stf-progress {
  position: sticky;
  top: 0;
  z-index: 5;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

/* 문항 머리 — [번호] 문항 …………… [구분 라벨] */
.satisfaction-page .question-head {
  align-items: center;
}

.satisfaction-page .question-head .question-text {
  flex: 1 1 auto;
  min-width: 0;
}

.satisfaction-page .stf-q-short {
  flex: 0 0 auto;
  padding: 4px var(--sp-3);
  border: 1px solid var(--blue-line);
  border-radius: var(--r-pill);
  background: var(--blue-soft);
  color: var(--blue-dark);
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
  white-space: nowrap;
}

/* 5지선다 한 줄 — 1500px 폭에서 가장 긴 라벨("전혀 그렇지 않다")도 한 줄에 들어간다 */
.satisfaction-page .choice-list {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

/* 좁아지면 랩 — 한 칸이 라벨을 못 담기 전에 2열로 접는다 */
@media (max-width: 900px) {
  .satisfaction-page .choice-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 420px) {
  .satisfaction-page .choice-list {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* [제출]을 눌렀을 때 남아 있던 미응답 문항 — 스크롤로 데려온 뒤 눈에 띄게 표시한다 */
.satisfaction-page .stf-q.is-missing {
  border-color: var(--warn);
  box-shadow: 0 0 0 3px var(--warn-bg);
}

/* 미응답이 있는 동안의 [제출하기] — 비활성처럼 보이되 클릭은 살아 있다
   (누르면 첫 미응답 문항으로 데려간다. 진짜 disabled 면 아무 반응이 없어 시니어가 막힌다) */
.satisfaction-page .btn.is-inactive {
  opacity: .5;
}

/* 문항이 10개라 하단 버튼까지 스크롤이 길다 — 좁은 화면에서는 카드 여백을 줄인다 */
@media (max-width: 560px) {
  .satisfaction-page .survey-question {
    padding: var(--sp-4);
  }

  .satisfaction-page .stf-q-short {
    display: none;
  }
}

/* --- 완료(done) — 새 라우트 없이 같은 자리에서 바뀐다 --- */
.satisfaction-view .stf-done {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-8) var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  text-align: center;
}

.satisfaction-view .stf-done-art {
  width: clamp(140px, 22vw, 240px);
  height: auto;
}

.satisfaction-view .stf-done-title {
  font-size: clamp(21px, calc(17.93px + 0.787vw), 28px);
  /* 390px→21px, 1280px→28px */
  font-weight: 800;
  letter-spacing: -.03em;
  color: var(--blue-dark);
}

.satisfaction-view .stf-done-desc {
  font-size: clamp(15px, calc(13.65px + 0.348vw), 18px);
  /* 390px→15px, 1280px→18px */
  font-weight: 600;
  line-height: 1.6;
  color: var(--text-sub);
}

.satisfaction-view .stf-done-acts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}

.satisfaction-view .stf-done-acts .btn {
  min-width: 200px;
}

@media (max-width: 560px) {
  .satisfaction-view .stf-done-acts .btn {
    width: 100%;
  }
}


/* ===== [satisfaction] 머리말 일러스트 확대 (2026-08-24 사용자 지시) =====
   사전문진 열람·만족도 조사 헤더의 클립보드 그림이 작다는 지적 — 두 화면 모두 한 단계 키운다.
   비율은 이미지가 유지하고(높이 auto) 폭만 clamp 로 올린다. 모바일에서 과해지지 않도록
   하한도 같은 비율(약 +22%)로만 올렸다. */

/* 열람 화면(.survey-view) — 사전문진 열람과 만족도 열람이 같은 클래스를 쓴다.
   종전: clamp(64px, calc(50.25px + 3.539vw), 110px) → 390px 64px / 1280px 96px / 상한 110px */
.survey-view .svh-art {
  width: clamp(78px, calc(61.2px + 4.31vw), 134px);
  /* 390px→78px, 1280px→117px, 상한 134px */
}

/* 만족도 작성 화면의 머리말 그림.
   .survey-page 를 공유하는 사전문진 스텝 화면은 지시 대상이 아니라 건드리지 않으려고
   .satisfaction-page 로 좁혀 덮는다(종전: clamp(56px, 7vw, 86px)).
   ※ 560px 이하에서는 위 07 구역이 이 그림을 감추므로 모바일 영향은 없다. */
.satisfaction-page .survey-header-art {
  /* [2026-08-24 사용자 지시 "더 크게"] 105 → 132px */
  width: clamp(80px, 10.5vw, 132px);
}


/* ============================================================
   [dash-v2] 측정이력 · 대시보드 탭 — 2열 6카드 (2026-08-27)

   경위: 사용자 참고 시안대로 대시보드를 전면 개편했다(_workspace/plan_dashboard_v2.md).
     · 폐기: 지표 3카드(.metric-grid) · AI 분석 카드(.ai-summary, 종합 점수 링 + 태그 7종) ·
       권장사항 3카드(.advice-grid). 링 규칙은 이미 삭제했고(파일 중간 [design-ai-card] 묘비),
       나머지 세 계열의 규칙은 plan §3-3 지시대로 이번 커밋에서 남겨 뒀다.
     · 신설: .dash-grid > .dash-card 6장 — 등급 / 주요 요약 / 위험지수 / 통합 추이 /
       가이드 요약 / 맞춤 포인트.

   분담(plan §3 클래스 계약): 도형·값은 JSX, 색·크기·배치는 이 파일.
   JSX 가 인라인 CSS 변수로만 값을 넘긴다 — .risk-seg 는 --w(세그먼트 폭, 단위 없는 수),
   .risk-pin 은 --pos(위험점수 0~100, **단위 없는 수**. 기존 .ai-score --score 와 같은 규약).
   ============================================================ */


/* ===== [dash-v2] 그리드 · 카드 공통 ===== */
/* 1500px 최대 폭은 .history-page 가 이미 잡는다(위 [frontend-dev] 측정이력 콘텐츠 폭).
   여기서는 그 안을 두 장씩 끊어 넣는다.

   [2026-08-27 사용자 지시 "행마다 카드 폭 비율을 시안대로"] grid → flex-wrap 으로 바꿨다.
   grid 의 열 트랙은 모든 행이 공유하므로 행마다 다른 비율(1:2 / 1:1.2 / 1.2:1)을 낼 수 없다.
   33분할 트랙 + span 으로 흉내낼 수는 있지만 거터가 32번 생겨 gap 이 폭을 다 먹는다.
   flex-wrap 은 카드마다 width 를 직접 주므로 **마크업 변경 없이** 행별 비율이 나온다
   (비율은 아래 [행별 폭 비율] 블록). 한 행의 두 카드 폭 합 = 100% − gap 이라 정확히
   두 장씩 접히고, 같은 줄의 카드는 flex 기본 stretch 로 높이를 맞춘다 — 보이는 결과는
   grid 때와 같고 .dash-grade/.dash-risk 의 margin-bottom:auto 빈 공간 배분도 그대로 통한다. */
.dash-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
}

/* --col = 이 카드가 한 행에서 차지하는 비율(0~1). 기본 .5 는 비율을 지정하지 않은 카드가
   생겼을 때의 폴백이라 종전 5:5 로 선다.
   빼는 .5px 은 반올림 여유다 — 두 카드 폭이 각각 올림되어 합이 100% 를 1/64px 이라도
   넘기면 둘째 카드가 다음 줄로 접힌다. 눈에 보이지 않는 값으로 그 사고만 막는다. */
.dash-grid>.dash-card {
  flex: 0 0 auto;
  width: calc((100% - var(--sp-4) - .5px) * var(--col, .5));
}

/* grid 가 아니라 flex 열이다 — 한 행의 두 카드는 서로 높이를 맞춰 늘어나는데(그리드 기본
   stretch), 짧은 카드(등급·위험지수)가 옆의 긴 카드(요약·추이) 높이까지 늘면 아래쪽에
   빈 공간이 크게 남는다. flex 라야 `margin-bottom: auto` 두 개로 그 여유를 제목 아래와
   본문 아래에 반씩 나눠 줄 수 있다(아래 [빈 공간 배분] 규칙). grid 의 align-content 로는
   트랙 밖 여유를 아이템이 흡수하지 못한다. 세로 1열이라 보이는 결과는 grid 때와 같다. */
.dash-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
  padding: var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.dash-card>.dash-card-title {
  font-size: clamp(18px, calc(15.37px + 0.674vw), 24px);
  /* 390px→18px, 1280px→24px */
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1.25;
  color: var(--text);
}

/* [빈 공간 배분] 짧은 카드(등급·위험지수) 전용. auto 마진 두 개가 남는 세로 여유를
   "제목 아래"와 "본문 아래"에 반씩 나눠 가져 본문이 카드 가운데에 선다.
   제목은 여전히 카드 맨 위 — 옆 카드(요약·추이)의 제목과 같은 줄에서 시작한다.
   :last-child 로 잡아 두면 리포트 없는 회원의 빈 상태(.state-block) 한 덩어리일 때도 그대로 통한다. */
.dash-grade>.dash-card-title,
.dash-grade>:last-child,
.dash-risk>.dash-card-title,
.dash-risk>:last-child {
  margin-bottom: auto;
}

/* 리포트가 없는 신규 회원의 빈 상태 — 공용 .state-block 이 카드 안에 들어오면
   바깥 여백(sp-8)이 카드 padding 과 겹쳐 카드가 과하게 길어진다. 안쪽용으로만 줄인다. */
.dash-card .state-block {
  padding: var(--sp-5) var(--sp-4);
}


/* ===== [dash-v2] 행별 폭 비율 (2026-08-27) ===== */
/* 참고 시안은 행마다 두 카드 폭이 다르다 — 그 행의 주인공이 넓다.
     1행  등급 : 주요 요약   = 1   : 2.6  → .28 / .72      (요약은 미니카드 4칸이 한 줄로 들어간다)
     2행  위험지수 : 추이    = 1   : 1.2  → .4545 / .5455  (추이는 차트라 가로가 길수록 읽힌다)
     3행  가이드 : 포인트    = 1.2 : 1    → .5455 / .4545  (가이드는 아이콘+두 줄 문장 4행)

   [2026-08-27 사용자 지시 ①②] 1행을 1:2(.3333/.6667) → **1:2.6(.28/.72)** 로 좁혔다.
   요약 4칸을 2×2 에서 **1×4 한 줄**로 바꾸라는 지시(②)와 한 세트다 — 4칸이 한 줄에 서려면
   요약 카드가 그만큼 넓어야 한다. 1500px 실측으로 등급 카드가 견디는지 확인했다:
     등급 카드 폭 484 → 407px, 안쪽 글자 폭 365px
     표정 아이콘 136 · 등급 "상담 필요" 165 · 설명 "전문기관 상담을 권장합니다." 240px
   가장 긴 설명도 365px 안에 한 줄로 들어간다. 1366px(안쪽 327px)에서도 같다.

   nth-child 가 아니라 카드 클래스에 준다 — 카드 순서가 바뀌어도 비율이 카드를 따라가고,
   규칙만 읽어도 어느 카드가 넓은지 알 수 있다.
   각 행의 합이 정확히 1.0 이라 (100% − gap) 을 남김없이 나눠 갖는다.
   ※ 780px 이하 1열 스택에서는 아래 [반응형]이 width:100% 로 덮는다. */
.dash-grid>.dash-grade {
  --col: .28;
}

.dash-grid>.dash-summary {
  --col: .72;
}

.dash-grid>.dash-risk {
  --col: .4545;
}

.dash-grid>.dash-trend {
  --col: .5455;
}

.dash-grid>.dash-guide {
  --col: .5455;
}

.dash-grid>.dash-points {
  --col: .4545;
}


/* ===== [dash-v2] 종합 건강위험 등급 (.dash-grade) ===== */
/* 등급색 3종(--grade-ink/tint/line)만 갈아끼우는 구조. 기본값은 정상(초록)이고,
   JSX 가 카드에 is-* 를 붙인다(.ai-score 가 쓰던 is-attention/caution/consult 와 같은 어휘).
   :has() 갈래는 프론트가 modifier 를 카드가 아니라 안쪽 아이콘에 붙였을 때를 위한 보험이다. */
.dash-card.dash-grade {
  --grade-ink: var(--grade-normal);
  --grade-tint: var(--grade-normal-bg);
  --grade-line: var(--grade-normal-line);
  align-items: center;
  text-align: center;
}

.dash-grade.is-attention,
.dash-grade:has(.is-attention) {
  --grade-ink: var(--grade-attention);
  --grade-tint: var(--grade-attention-bg);
  --grade-line: var(--grade-attention-line);
}

.dash-grade.is-caution,
.dash-grade:has(.is-caution) {
  --grade-ink: var(--grade-caution);
  --grade-tint: var(--grade-caution-bg);
  --grade-line: var(--grade-caution-line);
}

.dash-grade.is-consult,
.dash-grade:has(.is-consult) {
  --grade-ink: var(--grade-consult);
  --grade-tint: var(--grade-consult-bg);
  --grade-line: var(--grade-consult-line);
}

/* 제목만 카드 왼쪽 끝에 붙여 옆 카드(.dash-summary)와 같은 줄에서 시작하게 한다
   (카드가 align-items:center 라 제목도 가운데로 몰리는 것을 stretch 로 되돌린다) */
.dash-grade>.dash-card-title {
  align-self: stretch;
  text-align: left;
}

/* 큰 표정 아이콘 — 시안의 주인공. 이모지라 line-height 1 로 상자를 글리프에 맞춘다 */
.dash-grade .grade-face {
  display: grid;
  place-items: center;
  width: clamp(96px, calc(78.47px + 4.494vw), 136px);
  height: clamp(96px, calc(78.47px + 4.494vw), 136px);
  margin-top: var(--sp-4);
  border: 2px solid var(--grade-line);
  border-radius: 50%;
  background: var(--grade-tint);
  font-size: clamp(48px, calc(39.24px + 2.247vw), 68px);
  /* 390px→48px, 1280px→68px */
  line-height: 1;
}

.dash-grade .grade-label {
  margin-top: var(--sp-2);
  font-size: clamp(26px, calc(20.74px + 1.348vw), 38px);
  /* 390px→26px, 1280px→38px */
  font-weight: 800;
  letter-spacing: -.04em;
  line-height: 1.1;
  color: var(--grade-ink);
}

.dash-grade .grade-desc {
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 600;
  line-height: 1.5;
  color: var(--text-sub);
}

.dash-grade .grade-note {
  margin-top: var(--sp-1);
  font-size: clamp(12px, calc(10.9px + 0.281vw), 14.5px);
  /* 390px→12px, 1280px→14.5px */
  font-weight: 600;
  color: var(--text-mute);
}


/* ===== [dash-v2] 주요 요약 4칸 (.dash-summary > .mini-grid > .mini-card) ===== */
/* 판정색은 --mini-tone 하나로 흐른다(.mini-state 글자색). 값은 아래 [상태 톤] 블록이 정한다.
   is-none 은 미측정 — 회색으로 두어 "정상"으로 오독되지 않게 한다.
   2×2 는 모바일에서도 유지한다(plan §3-3 4항): 4칸이 1열로 늘어지면 카드가 너무 길어진다.
   데스크톱(≥1300px) 1×4 는 아래 [1×4 한 줄] 참고.

   [2026-08-27 사용자 지시 "참고 시안 레이아웃 그대로"] 칸 안을 시안 배치로 다시 짰다.
     name   ← [작은 아이콘 + 지표명] 한 행
     val    ← 값(가장 큼)
     unit   ← 단위가 값 **아랫줄**로 내려온다(.mini-value 를 세로 flex 로 돌려서 만든다)
     sub    ← 보조값(체성분·혈당만)
     state  ← 판정(상태색)
     date   ← 측정일. 시안에는 없지만 "언제 값인지"는 버릴 수 없어 **최하단 최소 크기**로 남긴다
   전 항목 가운데 정렬(시안). 자식이 조건부(sub/state/date 는 미측정 칸에서 아예 없다)라
   nth-child 배치는 쓸 수 없다 — 클래스별 named area 라야 빠진 칸이 자기 자리만 비운다.
   row-gap 0 도 같은 이유다: 빈 행에 gap 이 붙으면 미측정 칸만 아래가 뜬다. 줄 간격은
   각 항목의 margin-top 이 책임진다.

   [묘비 — 종전 머리 행 2단] 직전 판(2026-08-27 오전)은 `"name date" / "val val" / …` 로
   측정일을 지표명 옆에 올려 한 줄을 줄였고, 620px 이하와 1300px 이상에서만 세로 스택으로
   되돌렸다. 시안이 측정일을 최하단에 두므로 그 2단 배치와 두 개의 되돌림 분기를 함께 걷어냈다
   — 이제 전 폭이 같은 한 줄짜리 템플릿이다. */
/* flex:1 — 요약 카드는 옆 등급 카드 높이에 맞춰 늘어나는데, 그리드가 제 내용만큼만 차지하면
   그 여유가 카드 아래 빈 띠로 남았다(1500px 실측 376px 카드에 약 110px). 그리드가 여유를 받고,
   칸은 아래 align-content:center 로 그 안에서 내용을 세로 가운데에 세운다 — 시안의 두툼한
   카드 비례가 그대로 나오고 빈 띠가 사라진다. */
.dash-summary .mini-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* 2×2 일 때 두 행 높이를 같게 맞춘다 — 그리드 행은 서로 독립이라 보조행이 두 줄로 접히는
     칸이 있는 행만 두꺼워지고, 그러면 아래 행 카드의 지표명·판정 y 가 위 행과 어긋난다
     ("4칸의 상단/하단 y 가 모두 일치" 지시). 1fr 로 묶으면 네 칸이 같은 높이가 된다. */
  grid-auto-rows: minmax(0, 1fr);
  flex: 0 1 auto;
  /* 카드에 남는 세로는 그리드가 먹지 않고 위아래로 반씩 흘려보낸다 — 칸 안이 촘촘해지고
     (위 .mini-card align-content:center) 요약 카드 안에서 4칸 묶음이 가운데에 선다. */
  margin-block: auto;
  gap: var(--sp-3);
}

.dash-summary .mini-card {
  /* --ico-d = 아이콘 칩 지름. 시안의 "작은 아이콘"이라 지표명 글자 두 배 남짓으로 잡는다. */
  /* --sub-h = 보조행(.mini-sub) 한 줄 높이 = 보조값 글자(13.5~18px) x line-height 1.3 + 여백 */
  --sub-h: clamp(50px, calc(17.4px + 0.67vw), 26px);
  /* 390px→20px, 1280px→26px */
  --ico-d: clamp(48px, calc(44.5px + 0.9vw), 56px);
  /* 390px→48px, 1280px→56px — [2026-09-02 사용자 지시] 아이콘 칩·글리프 추가 확대
     (24~32 → 32~40 → **48~56px**). 1×4 타이트 구간만 아래 [칩 하한 보정]이 되돌린다. */
  --mini-tone: var(--text-mute);
  /* --ico-ink = 아이콘 칩 색(아래 [미니카드 아이콘 칩]). 지표별 값은 nth-child 가 덮는다 */
  --ico-ink: var(--text-sub);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* [2026-08-27 사용자 지시 "상단·하단 고정"] 네 칸은 보조행(.mini-sub) 유무가 달라 종전
     가운데 정렬에서는 지표명·판정의 y 가 칸마다 어긋났다(들쭉날쭉). 행 역할을 못 박고
     **남는 세로를 중간의 빈 행(gap) 하나가 통째로 먹게** 해서, 위 두 조각은 상단에
     아래 두 조각은 하단에 고정한다 — 보조행이 있든 없든 네 칸의 y 가 일치한다.
     빈 행이 1fr 이라 카드가 늘어나도 늘어난 만큼 그 행만 커진다. */
  /* [2026-08-27 사용자 "수치와 상태 사이가 너무 벌어졌다"] 종전에는 중간에 1fr 빈 행을 두어
     위/아래로 밀어붙였다 — 4칸 y 는 맞았지만 값과 판정 사이에 100px 넘는 대공백이 생겼다.
     이제 빈 행 없이 다섯 줄을 **촘촘히 묶고 묶음째 세로 가운데**에 세운다(align-content:center).
     y 정렬은 다른 방법으로 지킨다 — 보조행(.mini-sub)에 한 줄 최소 높이를 줘서 보조값이 없는
     칸(혈압·산소포화도)도 같은 자리를 차지하게 한다. 네 칸의 콘텐츠 높이가 같아지므로
     가운데 정렬해도 판정·측정일 y 가 일치한다. */
  /* 보조행 최소 높이는 **행 트랙**에 준다 — .mini-sub 요소에 min-height 를 걸면 그 요소가
     아예 없는 칸(혈압·산소포화도)에는 아무 효과가 없어 네 칸 콘텐츠 높이가 어긋난다
     (실측: 지표명 y 가 21 ↔ 33 으로 12px 벌어졌다 = 보조행 한 줄만큼). 트랙에 주면 빈 행도
     자리를 차지해, 가운데 정렬해도 네 칸의 판정·측정일 y 가 일치한다. */
  grid-template-rows: auto auto minmax(var(--sub-h), auto) auto auto;
  grid-template-areas:
    "name"
    "val"
    "sub"
    "state"
    "date";
  justify-items: center;
  align-content: center;
  row-gap: 0;
  min-width: 0;
  /* 위아래 패딩을 sp-3 → sp-5 로 키워 지표명·측정일이 카드 가장자리에 붙지 않게 한다(지시) */
  padding: var(--sp-6) var(--sp-3);
  /* [2026-08-27 사용자 지시 "하늘색 배경 빼고 테두리 박스로"]
     --surface-soft(#F8FAFF) 면 → 흰 면 + 테두리. 부모 카드도 흰색이라 이제 경계를 지는 것은
     테두리 하나뿐이라, 종전 --line(#dadeeb) 을 --line-strong(#D3D9E8) 으로 한 단계 올려
     네 칸이 흰 바탕 위에서 각각 박스로 읽히게 한다(옅은 면이 하던 몫을 선이 넘겨받는다).

     [묘비 — 좌측 4px 상태 액센트 바] `border-left: 4px solid var(--mini-ink)` 를 없앴다.
     사용자가 "시안에 없으니 제거 여부 재량"으로 넘긴 건이고, 세 가지가 같은 답을 가리킨다:
       · 시안에 없다.
       · 칸 안이 전부 가운데 정렬로 바뀌었다 — 왼쪽만 4px 두꺼운 테두리는 글자 중심선을
         상자 중심에서 2px 밀어 정렬이 어긋나 보인다(테두리가 border-box 안쪽이므로).
       · 판정을 상태색으로 말하는 수단이 이미 있다 — .mini-state 가 20px/800 에 4.5:1 이상
         대비로 서 있다(아래 [상태 톤] 실측표). 색면과 글자가 같은 말을 두 번 하던 구조였다.
     되돌리려면 이 자리에 그 한 줄을 도로 넣고 --mini-tone 옆에 --mini-ink 를 살리면 된다. */
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  text-align: center;
}

/* [상태 톤] (2026-08-27 사용자 피드백 ③ "warn 앰버가 흰 배경에서 잘 보이는지 확인")
   실측했더니 등급 4색을 **글자색**으로 쓰는 것이 문제였다. 당시 카드 배경 --surface-soft
   (#F8FAFF) 기준 명도대비:
     정상 #16A34A → 2.86:1   (AA 4.5:1 미달, 큰글씨 3:1 도 미달)
     관심 #CA8A04 → 2.84:1   (사용자 지적 그대로 — 앰버가 흰 배경에서 뜬다)
     상담 #DC2626 → 4.62:1   (유일하게 통과)
   등급색 자체는 바꾸지 않는다(다른 화면·리포트와 어휘·색이 묶여 있다). 대신 **면(4px 액센트
   바)과 글자를 분리**해 글자만 같은 색상의 한 단계 어두운 값으로 내린다. 면은 넓어서 밝은
   원색이 잘 읽히고, 글자는 획이 가늘어 더 어두운 값이 필요하다 — 디자인시스템에서 500(면)/
   700(글자)을 나눠 쓰는 것과 같은 분담이다.
     정상 #15803D → 4.80:1 ✓
     관심 #A16207 → 4.50:1 ✓
     상담 #DC2626 → 4.62:1 ✓ (원색 그대로 — 이미 통과라 굳이 어둡게 하지 않는다)
   세 톤이 초록/황갈/적으로 색상환에서 충분히 떨어져 "확실히 갈린다"(피드백 ③ 후반).
   ※ 2026-08-27 카드 면이 흰색(--surface)으로 바뀌면서 세 값 모두 4.86 / 4.55 / 4.68:1 로
     조금씩 올라갔다 — 위 선택은 더 안전한 쪽으로만 움직였으므로 값은 그대로 둔다. */
.dash-summary .mini-card.is-ok {
  --mini-tone: #15803D;
}

.dash-summary .mini-card.is-warn {
  --mini-tone: #A16207;
}

.dash-summary .mini-card.is-danger {
  /* 유일하게 원색 그대로 — 토큰을 그대로 참조해 등급색이 바뀌면 글자도 같이 따라간다 */
  --mini-tone: var(--grade-consult);
}

.dash-summary .mini-card.is-none {
  --mini-tone: var(--text-mute);
}

/* 시안의 머리 행 — [작은 아이콘 + 지표명]. 칩이 이 안의 첫 자식이고 그 뒤에 지표명이 온다.
   flex 한 행이라 칩과 글자의 세로 중심이 맞고, 칸 안에서 통째로 가운데에 선다
   (.mini-card 의 justify-items:center 가 이 상자를 가운데로 보내고, 안쪽은 이 flex 가 잡는다). */
.dash-summary .mini-name {
  grid-area: name;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 0;
  font-size: clamp(16px, calc(13.37px + 0.674vw), 22px);
  /* 390px→16px, 1280px→22px — [2026-08-27 사용자 지시] 확대(종전 14.5~19px) */
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.25;
  color: var(--text-sub);
}


/* ===== [dash-v2] 미니카드 아이콘 칩 (.mini-ico) ===== */
/* [2026-08-27 사용자 지시] 시안처럼 작은 원형 컬러 칩 — 지표별 옅은 배경 + 아이콘.
   칩 색은 그 지표의 **추이선 색(--chart-*)** 을 그대로 쓴다. 주요 요약 4칸과 통합 추이·
   변화 탭이 같은 지표를 같은 색으로 불러 화면 사이에서 색이 어긋나지 않는다.
   배경은 그 색의 14% 틴트를 color-mix 로 만든다 — 틴트 값을 따로 적어 두면 잉크색이
   바뀔 때 한쪽만 남는다.

   판정색(--mini-ink: 왼쪽 액센트 바 + .mini-state)과는 역할이 다르다.
   칩 = "무슨 지표인가", 액센트 = "판정이 어떤가". 그래서 칩 색은 is-ok/warn/danger 에
   따라 변하지 않는다 — 둘 다 상태색이면 한 칸에 같은 말이 두 번 적힌다.

   칸 순서는 summaryMiniCards() 가 [혈압 · 혈당 · 산소포화도 · 체성분] 으로 고정한다.
   지표를 가리키는 클래스가 마크업에 없어 nth-child 로 짚는다(순서가 곧 계약이다).
   체성분은 체중·BMI 와 한 계열이라 --chart-weight 를 쓴다.
   실제 글리프: 🩺 혈압 · 🩸 혈당 · 💨 산소포화도 · 💪 체성분.
   자리잡기는 이 규칙이 하지 않는다 — 칩은 .mini-name 의 첫 자식이고, 그 flex 행이 소유한다.

   [2026-08-27 사용자 피드백 ① "아이콘을 2배로"] 종전 24~30px → 지름을 --ico-d 로 뽑고
   1100px 이상에서 2배(54~60px)로 덮는다(아래 [2배 칩]). 이 기본값(30~44px)은 그 미만 폭에서
   쓰는 크기다 — 칩이 .mini-name 의 flex 자식이라 여기서 더 키우면 머리 행이 그만큼 두꺼워져
   피드백 ②(높이 축소)와 정면으로 부딪힌다. 좁은 폭에서는 옆으로 뺄 자리도 없다(390px 기준
   칸 안쪽 글자 폭이 132px 뿐이고, 60px 칩을 왼쪽에 세우면 "111/74 mmHg" 가 들어가지 않는다). */
.dash-summary .mini-ico {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--ico-d);
  height: var(--ico-d);
  border-radius: 50%;
  background: color-mix(in srgb, var(--ico-ink) 14%, #fff);
  font-size: clamp(25px, calc(23.2px + 0.46vw), 29px);
  /* 390px→25px, 1280px→29px — 이모지 글리프 기준. 칩 지름의 약 52%(종전 비율 유지) */
  line-height: 1;
  /* SVG 아이콘(currentColor)이 오는 경우를 위해 잉크색도 함께 흘린다 */
  color: var(--ico-ink);
}

.dash-summary .mini-grid>.mini-card:nth-child(1) {
  /* 혈압 */
  --ico-ink: var(--chart-systolic);
}

.dash-summary .mini-grid>.mini-card:nth-child(2) {
  /* 혈당 */
  --ico-ink: var(--chart-glucose);
}

.dash-summary .mini-grid>.mini-card:nth-child(3) {
  /* 산소포화도 */
  --ico-ink: var(--chart-spo2);
}

.dash-summary .mini-grid>.mini-card:nth-child(4) {
  /* 체성분 — 골격근량/BMI 둘 다 체중 계열 */
  --ico-ink: var(--chart-weight);
}

/* 값 + 단위. [2026-08-27 사용자 지시 "시안처럼 값 아래에 단위"] 종전에는 둘이 한 글줄에
   baseline 으로 나란히 섰다(`116/70 mmHg`). 시안은 값 아래에 단위가 따로 놓인다.
   마크업은 그대로다 — 단위 span 이 .mini-value 의 자식이므로, 이 상자를 **세로 flex** 로
   돌리면 [숫자 텍스트][단위 span] 두 줄이 된다(숫자는 익명 flex 아이템이 된다).
   JSX 가 단위 앞에 공백 하나를 달고 오는데(` mmHg`), 줄 첫머리 공백은 접히므로 그대로 둔다.
   가로가 좁은 1×4 칸에서 오히려 유리하다 — 값과 단위가 각자 한 줄을 온전히 쓴다. */
.dash-summary .mini-value {
  grid-area: val;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: var(--sp-2);
  font-size: clamp(27px, calc(21.3px + 1.461vw), 40px);
  /* 390px→27px, 1280px→40px */
  font-weight: 800;
  letter-spacing: -.045em;
  line-height: 1.02;
  color: var(--text);
}

/* 단위 줄 — 값보다 한참 작고 흐리다(시안). 세로 flex 라 값 바로 아래 한 줄로 앉는다 */
.dash-summary .mini-unit {
  margin-top: 2px;
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1.15;
  color: var(--text-mute);
}

/* 미측정 칸은 값 자리에 "미측정" 세 글자가 들어온다 — 큰 숫자 급으로 두면 측정된 칸보다
   눈에 띄어 위계가 뒤집힌다. 본문 급 회색으로 낮춘다. */
.dash-summary .mini-card.is-none .mini-value {
  font-size: clamp(16px, calc(13.81px + 0.562vw), 21px);
  /* 390px→16px, 1280px→21px */
  font-weight: 700;
  letter-spacing: -.02em;
  color: var(--text-mute);
}

/* 체성분 칸의 둘째 줄(골격근 · 체지방률처럼 값이 두 개인 경우) */
.dash-summary .mini-sub {
  grid-area: sub;
  margin-top: 2px;
  /* (보조행 최소 높이는 위 .mini-card 의 grid-template-rows 가 맡는다) */
  min-height: 1.3em;
  font-size: clamp(13.5px, calc(11.53px + 0.506vw), 18px);
  /* 390px→13.5px, 1280px→18px */
  font-weight: 700;
  line-height: 1.3;
  color: var(--text-sub);
}

/* 판정 — 시안의 카드 최하단 문구(측정일만 그 아래). 위계상 이름(19px)보다 크고 값(40px)보다
   작다. 굵기도 800 이라 같은 크기의 이름 줄과 확실히 갈라 선다.
   색은 --mini-tone(글자용 어두운 값)이다 — 위 [상태 톤] 실측표 참고. 좌측 액센트 바를 걷어낸
   지금은 이 글자가 칸의 판정색을 말하는 **유일한** 수단이라 대비 기준을 더 엄격히 지킨다. */
.dash-summary .mini-state {
  grid-area: state;
  /* 값 묶음(이름·값·단위·보조)과 판정 사이의 절제된 간격 — 종전 1fr 대공백을 대신한다 */
  margin-top: var(--sp-3);
  font-size: clamp(16.5px, calc(14.09px + 0.618vw), 22px);
  /* 390px→16.5px, 1280px→22px — [2026-08-27 사용자 지시] 추가 확대(종전 15~20px, 굵기 유지) */
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.25;
  color: var(--mini-tone);
}

/* latest 는 전체 기간의 최신값이라 "언제 값인지"를 밝힌다(plan §1-1행 우).
   [2026-08-27 사용자 지시] 시안에는 이 줄이 없다. 그래도 지우지 않는 이유 —
   주요 요약의 값은 "최근 6개월 안의 마지막 측정"이라 **언제 잰 값인지가 값의 뜻을 바꾼다**
   (석 달 전 혈압과 어제 혈압은 같은 숫자라도 다른 정보다). 대신 지시대로 칸의 **최하단에
   가장 작은 급**으로 내려 시안의 시각 위계를 흐리지 않게 한다: 10.5~13px 은 이 화면에서
   가장 작은 글자이고, 판정(20px)과 두 배 이상 벌어져 눈이 먼저 가지 않는다. */
.dash-summary .mini-date {
  grid-area: date;
  white-space: nowrap;
  font-size: clamp(12.5px, calc(11.4px + 0.281vw), 15px);
  /* 390px→12.5px, 1280px→15px — [2026-08-27 사용자 지시] 확대(종전 10.5~13px).
     그래도 칸 안에서 가장 작은 급이라 판정(22px)과 위계가 뒤집히지 않는다. */
  font-weight: 600;
  line-height: 1.3;
  color: var(--text-mute);
}


/* ===== [dash-v2] 건강위험 지수 바 (.dash-risk) ===== */
/* 축은 위험 방향(낮을수록 좋다) — 종전 "종합 건강 점수" 게이지와 방향이 반대라서
   .risk-note 문구가 필수다(plan §1-2행 좌).
   폭: 세그먼트 4개가 flex-grow 로 나눠 갖는다. 정본은 JSX 가 서버 컷(criteria.grades.cuts)
   에서 계산해 넣는 인라인 --w 이고, 아래 is-* 의 --w 기본값(30/20/25/25)은 그 값을 주지
   않는 경우의 폴백이다 — report.css .score-track .seg:nth-child() 와 같은 분담. */
.dash-card.dash-risk {
  /* [2026-08-27] 레이더 오버레이의 위치 기준(containing block). 넓은 폭에서 .risk-radar 가
     absolute 로 이 카드의 padding box 를 기준으로 우측 상단에 앉는다 — 이 한 줄이 없으면
     기준이 조상 어딘가로 밀려 그림이 화면 밖으로 날아간다(실측: top −621px). */
  position: relative;
  /* --radar-w = 레이더 그림 **폭**. 높이가 아니라 폭인 것이 핵심이다 — 프론트가
     ResponsiveContainer 를 aspect=1 로 쓰므로 폭이 곧 높이다(높이를 따로 주면 두 값이 싸운다).
     이 기본값은 세로 스택(1240px 미만) 용이고, 오버레이에서는 아래 분기가 덮는다.
     상한 280px 근거: 세로 스택은 레이더가 본문 아래 전폭에 붙는 자리라, 폭을 풀어 두면
     카드 안쪽 폭만큼 정사각이 되어 카드가 두 배로 길어진다(실측 1200px 화면에서 481px). */
  --radar-w: clamp(240px, 55vw, 330px);
  /* 390px→240px, 600px 이상 330px — [2026-08-27 사용자 "그림이 너무 작다"] 종전 200~280px
     에서 같은 비율(1.2배)로 올렸다. 390px 칸 안쪽이 332px 이라 240px 은 여유가 남는다. */
  /* [2026-08-27 사용자 지시 "리포트 판정 게이지 스타일로 재스킨"]
     마커 치수 한 벌. report.css 13번 구역(.gauge-marker/.marker-dot/.marker-value)의 언어를
     옮기되, 리포트 게이지는 트랙 12px·점 14px 인쇄용 축소판이고 이 막대는 화면 전용 18~26px
     이라 치수는 그 비율로 키웠다(점 = 막대 두께에 거의 맞먹게 — 막대 위에 "앉은" 것으로 읽힌다).
       --dot-d    점 바깥 지름(box-sizing:border-box 라 흰 링 포함한 값)
       --dot-ring 흰 링 두께. report .marker-dot 의 4px(14px 점) = 지름의 28.6% 를 그대로 적용
       --bub-h    값 말풍선 상자 높이 어림값. 아래 .risk-bar 의 위 여백을 여기서 되짚는다
       --bub-gap  말풍선 아래끝 ↔ 점 위끝 사이. report .marker-value 의 2px 을 화면 크기에 맞춰 5px
       --marker-ink 마커 잉크. report.css :root --marker-ink(#17233d) 복제 — 복제 이유는
                    아래 --seg-* 와 같다(main.css 는 report.css 없이 로드되는 페이지도 쓴다). */
  --dot-d: clamp(18px, calc(15.37px + 0.674vw), 24px);
  /* 390px→18px, 1280px→24px */
  --dot-ring: clamp(4px, calc(3.12px + 0.225vw), 5px);
  /* 390px→4px, 1280px→6px(상한 5px) */
  --bub-h: clamp(21px, calc(19.1px + 0.483vw), 25px);
  /* 390px→21px, 1280px→25px — 말풍선 글자 14.5px×1.3 + 세로 padding 4 + 테두리 2 */
  --bub-gap: 5px;
  --marker-ink: #17233d;
  /* [2026-08-27 사용자 "그래프 채움을 블루/스카이 톤으로"] 레이더 도형 잉크.
     프론트 계약상 **선·점·방사 그라디언트 stop 이 이 토큰 하나를 따라오므로** 여기 한 줄로
     도형 전체가 바뀐다(기본 폴백은 --grade-caution 주황이었다).
     값은 새 색을 만들지 않고 기존 --chart-weight(#2563EB)를 그대로 쓴다 — 이 화면에서 이미
     체중·BMI 선에 쓰는 블루라 대시보드 안에서 색이 한 벌로 유지된다.
     · 방사 그라디언트(중심 .5 → 바깥 .08)를 태우면 중심의 진한 블루가 바깥으로 가면서
       스카이 톤으로 자연스럽게 풀린다(같은 색상의 알파 감쇠라 색이 탁해지지 않는다).
     · 격자 --chart-grid(#E7EAF3)·축 라벨 --text-sub 와 색상이 갈려 도형이 격자 위에 뜬다.
     · 위험 방향 오독 우려 — 블루는 등급 4색(녹/황/주/적) 어디에도 속하지 않는 중성색이라
       "좋다/나쁘다"로 읽히지 않는다. 축의 해석은 캡션("좁을수록 좋습니다")이 맡는다. */
  --risk-radar-ink: var(--chart-weight);
  /* --donut-d = 위험점수 도넛 링 바깥 지름. 레이더(240px)와 시각 균형을 맞춘 값이고,
     카드 min-height 540px 안에서 설명 아래 ~ 막대 위 여백에 들어간다. */
  --donut-d: clamp(120px, calc(106.8px + 3.4vw), 150px);
  /* 390px→120px, 1280px→150px */
  /* [세그 램프 한 벌] (2026-08-27 정리) 종전에는 이 네 색이 .risk-seg 규칙 안에 하드코딩돼
     있었다. 이제 칩(.risk-stage-chip)·활성 라벨(.risk-labels .is-active)도 같은 구간색을
     쓰므로 정의 지점을 하나로 모은다 — 세 곳에 흩어 두면 report.css 와 맞추라는 아래 주석을
     따를 때 한 곳이 빠진다. --*-ink 는 그 구간색 위/틴트 위에 올릴 **글자용** 어두운 값이다
     (미니카드 --mini-ink/--mini-tone 과 같은 면·글자 분담). */
  --seg-low: #c6daf3;
  --seg-mid: #8db4e2;
  --seg-high: #c96a1e;
  --seg-vhigh: #c62f21;
  --seg-low-ink: #1B4F8F;
  --seg-mid-ink: #164780;
  --seg-high-ink: #8F4A12;
  --seg-vhigh-ink: #A0231A;
}

.dash-risk .risk-bar {
  position: relative;
  display: flex;
  /* 막대 위로 솟는 것은 이제 점의 위 절반 + 그 위 값 말풍선이다(종전 지도핀 + 꼬리에서 교체).
     점은 막대 중심에 앉으므로 위로 나오는 높이는 반지름뿐이다. 종전 실측 41px(1280px 이상)
     ↔ 새 값 42px 로 세로 리듬이 유지된다. */
  margin-top: calc(var(--bub-h) + var(--bub-gap) + var(--dot-d) / 2);
  /* [2026-08-27 사용자 피드백 ④ "위험지수 카드가 허전하다"] 14~20px → 18~26px.
     옆 카드(.dash-trend)가 추이 3행이라 길어서 이 카드가 그 높이까지 늘어난다 — 막대가
     가늘면 남는 세로가 그대로 빈 띠로 보인다. 두께는 구간색 네 조각의 면적도 같이 키운다. */
  height: clamp(18px, calc(14.49px + 0.899vw), 26px);
  /* 조각 사이 흰 틈 — 색 대비가 약한 이웃(옅은 블루 ↔ 중간 블루)도 경계가 선으로 보인다.
     report.css .score-track 이 같은 이유로 2px gap 을 둔다. 아래 .risk-labels 에도 같은
     gap 을 줘서 라벨이 조각과 어긋나지 않게 한다. */
  gap: 2px;
}

/* [2026-08-27 사용자 지시 "각 조각 끝이 둥근 캡슐형(현재는 모서리 각짐)"]
   종전에는 리포트 트랙(.report .track)처럼 **양 끝 조각의 바깥쪽만** 둥글렸다 — 안쪽 경계는
   직각이라 26px 두께에서 네모 토막 네 개로 보였다(리포트는 트랙이 12px 이라 그 각이 눈에
   띄지 않는다). 이제 조각마다 캡슐이다. 두 가지가 같이 좋아진다:
     · 각 구간이 "한 덩어리"로 읽혀 라벨(낮음/보통/높음/매우 높음)과 1:1 로 맞는다
     · 아래 활성 강조의 아웃라인이 캡슐 윤곽을 따라 그려진다 — 직각 조각에 알약 아웃라인을
       두르면 선이 상자로 보인다(리포트 .seg.is-current 는 조각이 직각이라 상자 선이 맞았다)
   조각 사이 흰 틈(위 gap 2px)은 그대로라 캡슐끼리 붙지 않는다.
   ※ overflow:hidden 으로 라운드를 만들지 않는 것은 종전과 같은 이유다 — 흐름 밖 마커가 잘린다.

   [채움] 기본값은 그 구간색의 30% 틴트(= 비활성). 원색은 아래 :has() 브리지가 현재 구간에만
   되돌린다. 네 틴트의 명도가 여전히 단조 감소(0.90 → 0.80 → 0.68 → 0.59)라 "낮음→매우 높음"
   램프 순서는 흐려지지 않는다.
   45% 로 먼저 잡았다가 30% 로 내린 이유(1500px 실측): 구간 램프의 첫 조각(--seg-low #c6daf3,
   명도 0.68)은 원래 옅은 색이라, 이웃한 --seg-mid 의 45% 틴트(0.72)보다 오히려 밝지 않았다.
   현재 구간이 "낮음"일 때 활성 조각이 비활성 이웃보다 밝아 보여 강조가 뒤집혔다. 30% 로
   내리면 비활성 mid 가 0.80 이 되어 활성 low(0.68)가 확실히 더 진하다.
   --seg-tone 을 조각마다 나눠 두면 채움·아웃라인·활성 되돌림이 색 이름을 한 번만 적는다. */
.dash-risk .risk-seg {
  flex: var(--w, 25) 1 0;
  min-width: 0;
  border-radius: var(--r-pill);
  background: var(--seg-tone, var(--seg-low));
}

/* [2026-08-27 사용자 지시 "보통/높음 구분이 약하다"] 등급 4색(녹·노·주·적) → 상세리포트
   위험점수 막대와 같은 **세그먼트 램프**로 교체했다.
   종전 노랑 #CA8A04 → 주황 #EA580C 은 색상환에서 이웃이라 "보통 ↔ 높음" 경계가 뭉쳤다.
   새 램프는 블루 2단계에서 주황으로 건너뛰므로 그 경계가 색상 자체로 갈린다.

   값 출처: front/public/styles/report.css :root 의 --seg-0 ~ --seg-3
            (2026-08-17 --seg-0 조정 · 2026-08-18 --seg-3 조정 반영본, 복제 2026-08-27).
   복제하는 이유: main.css 는 report.css 없이 로드되는 페이지(홈·문진·측정)에서도 쓰이는
   독립 파일이라 다른 페이지 CSS 의 토큰에 의존하면 안 된다. **report.css 쪽 값이 바뀌면
   여기도 같이 고칠 것** — 두 막대가 같은 위험 축을 그리므로 색이 갈라지면 안 된다.
     --seg-0  #c6daf3  옅은 블루   L* 86.1   낮음
     --seg-1  #8db4e2  중간 블루   L* 72.1   보통       ΔL* 14.0
     --seg-2  #c96a1e  짙은 주황   L* 54.9   높음       ΔL* 17.2
     --seg-3  #c62f21  선명한 적   L* 44.0   매우 높음   ΔL* 10.9
   명도가 단조 감소하는 램프라 흑백 인쇄·색각이상에서도 네 단계가 순서대로 읽힌다.
   ※ 값 자체는 위 .dash-card.dash-risk 의 --seg-* 한 곳에만 적혀 있다(2026-08-27 정리). */
.dash-risk .risk-seg.is-low {
  --w: 30;
  --seg-tone: var(--seg-low);
}

.dash-risk .risk-seg.is-mid {
  --w: 20;
  --seg-tone: var(--seg-mid);
}

.dash-risk .risk-seg.is-high {
  --w: 25;
  --seg-tone: var(--seg-high);
}

.dash-risk .risk-seg.is-vhigh {
  --w: 25;
  --seg-tone: var(--seg-vhigh);
}


/* ===== [dash-v2] 현재 구간 강조 — 칩 → 세그먼트 :has() 브리지 (2026-08-27) ===== */
/* 리포트 게이지의 .seg.is-current 는 JSX 가 조각에 직접 클래스를 붙여 만든다. 대시보드
   마크업에는 그 클래스가 없지만, **칩과 세그먼트가 같은 수식자 어휘(is-low/mid/high/vhigh)를
   쓰기 때문에** 칩에서 판정을 끌어와 같은 조각을 짚을 수 있다(핀 색 정합에 쓴 것과 같은 수법).
   JSX 무수정으로 리포트와 같은 "현재 구간" 표시를 얻는다.

   3단 캐스케이드다 — 아래로 갈수록 좁다:
     1) .risk-seg              원색      ← 칩이 없는 경우(구버전 마크업·리포트 없음)의 폴백.
                                           칩 없이 막대만 있으면 종전 램프 그대로 보인다.
     2) :has(칩) .risk-seg     45% 틴트  ← 칩이 있으면 네 조각을 모두 뒤로 물린다
     3) :has(칩.is-X) .is-X    원색+선   ← 그중 현재 구간만 앞으로 끌어낸다
   특정도 (0,2,0) < (0,3,0) < (0,5,0) 라 순서가 뒤집히지 않는다.

   아웃라인 색·불투명도는 report.css .seg.is-current 복제다(rgba(23,35,61,.55), 안쪽 오프셋).
   두께만 1.5 → 2px 로 올렸다 — 리포트 트랙은 12px 이고 이 막대는 18~26px 이라, 같은 1.5px
   이면 조각 면적 대비 선이 가늘어 강조가 약하다(실측: 1500px 에서 1.5px 는 1px 로 반올림돼
   렌더된다). 그림자가 아니라 선이라 흑백 인쇄에서도 현재 구간이 남는다 — 리포트가 그 이유로
   inset 그림자에서 아웃라인으로 옮긴 결정을 그대로 따른다. */
.dash-risk:has(.risk-stage-chip) .risk-seg {
  background: color-mix(in srgb, var(--seg-tone, var(--seg-low)) 30%, #fff);
}

.dash-risk:has(.risk-stage-chip.is-low) .risk-seg.is-low,
.dash-risk:has(.risk-stage-chip.is-mid) .risk-seg.is-mid,
.dash-risk:has(.risk-stage-chip.is-high) .risk-seg.is-high,
.dash-risk:has(.risk-stage-chip.is-vhigh) .risk-seg.is-vhigh {
  background: var(--seg-tone);
  outline: 2px solid rgba(23, 35, 61, .55);
  outline-offset: -2px;
}

/* ===== [dash-v2] 현재 위치 마커 — 점 + 값 말풍선 (2026-08-27 재스킨) ===== */
/* [묘비] 종전은 "숫자를 품은 지도핀"이었다 — 잉크 알약(--pin-d 24~32px) + 아래로 뾰족한 꼬리
   (::before 흰 삼각형 9px 위에 ::after 잉크 삼각형 7px)를 막대 위 5px 에 띄우고, 흰 링 2px +
   그림자로 세그먼트 위에서 형태를 지켰다. 등급별 핀 색 규칙(.risk-pin.is-attention/-caution/
   -consult)과 칩에서 색을 끌어오던 :has() 3줄도 그 구조의 부품이었다.
   사용자 지시로 **리포트 판정 게이지의 마커 언어**(report.css 13번 구역)로 교체한다 —
   막대 **위에 앉는 점** + 그 위 **값 말풍선**. 리포트와 대시보드가 "지금 여기"를 한 가지
   모양으로 말하게 된다(리포트 안에서 게이지·혈압 분류 그래프가 이미 같은 언어를 쓴다).

   [잉크 고정] 점·말풍선의 잉크는 구간색을 따라가지 않고 --marker-ink(#17233d) 한 벌이다.
   리포트가 그렇게 확정한 이유가 여기에도 그대로 적용된다:
     · 마커는 "축 위의 좌표"지 "판정"이 아니다. 판정은 이미 칩·활성 세그·활성 라벨 세 곳이
       구간색으로 말한다 — 마커까지 구간색이면 같은 말이 네 번이고, 정작 좌표가 흐려진다.
     · 구간색은 명도가 제각각(낮음 L*86 ↔ 매우높음 L*44)이라 잉크를 그 색으로 두면 자기
       구간 위에서 대비가 무너진다. 종전 실측이 그 문제를 그대로 보여 준다(잉크 대비
       낮음 2.31:1 · 보통 1.37:1 · 높음 1.06:1 · 매우 1.14:1 — 흰 링에 기대 버티고 있었다).
       고정 잉크 #17233d 는 네 구간색 위에서 6.6~12.5:1 이라 어디에 서든 같은 세기로 읽힌다.
   그래서 직전에 넣었던 구간색 :has() 3줄은 여기서 폐기하고, 같은 수법을 **활성 세그먼트
   강조**(위 [현재 구간 강조] 블록)로 옮겼다 — 강조가 필요한 곳은 마커가 아니라 구간이다.

   [가로 위치] --pos 는 **단위 없는 위험점수(0~100)** 다(JSX 가 style={{"--pos": riskScore}} 로
   넘긴다 — 기존 .ai-score --score 와 같은 규약).
   양 끝에서 점이 막대 밖으로 반쯤 걸치지 않도록 반지름만큼 안쪽으로 물린다 — 리포트 JSX 가
   .gauge-marker 에 `clamp(12px, …, calc(100% - 12px))` 로 넣는 가드와 같은 값·같은 뜻이고,
   여기서는 마크업을 못 고치므로 CSS 가 --dot-d 로 계산한다. 이 가드가 말풍선 넘침도 같이
   해결한다(말풍선이 점 중앙 정렬이라, 점이 안쪽으로 들어온 만큼 말풍선도 따라 들어온다).

   [세로 위치] top 50% + translateY(-50%) = 점 중심이 막대 중심. 리포트도 마커를 트랙 위에
   **얹어** 어느 구간인지가 좌표가 아니라 겹침으로 보이게 한다. */
.dash-risk .risk-pin {
  position: absolute;
  left: clamp(calc(var(--dot-d) / 2), calc(var(--pos, 0) * 1%), calc(100% - var(--dot-d) / 2));
  top: 50%;
  z-index: 1;
  width: var(--dot-d);
  height: var(--dot-d);
  transform: translate(-50%, -50%);
  /* 흰 링은 그림자가 아니라 실제 테두리다(리포트와 같은 결정) — 지면에도 선으로 찍힌다 */
  border: var(--dot-ring) solid var(--surface);
  border-radius: 50%;
  background: var(--marker-ink);
  /* 바깥 잉크선 — 옅은 구간(낮음 #c6daf3)이나 45% 틴트 위에서도 흰 링의 윤곽이 남는다.
     report .marker-dot 의 1.25px rgba(23,35,61,.5) 복제. */
  outline: 1.25px solid rgba(23, 35, 61, .5);
  line-height: 1;
  pointer-events: none;
}

/* 값 말풍선 — 점 바로 위. 흰 배경 + 잉크 테두리 + 잉크 숫자(report .marker-value 언어).
   absolute 라 점 크기를 밀지 않는다.
   bottom 기준면이 점의 **padding box**(= 흰 링 안쪽)라 링 두께를 되더해야 말풍선 아래끝이
   점의 바깥 윤곽에 맞는다 — 그 위에 --bub-gap 을 띄운다.
   색을 반전(잉크 배경 + 흰 글자)하지 않는 이유: 말풍선이 카드 배경 위에 뜨는데, 잉크 덩어리는
   막대보다 무거워 시선이 숫자로 먼저 가 버린다. 흰 알약은 축 위에서 가볍게 떠 있고 테두리로
   점과 한 몸임을 말한다(리포트가 같은 판단으로 흰 알약을 쓴다). */
.dash-risk .risk-pin-num {
  position: absolute;
  bottom: calc(100% + var(--dot-ring) + var(--bub-gap));
  left: 50%;
  transform: translateX(-50%);
  padding: 2px 7px;
  border: 1px solid var(--marker-ink);
  border-radius: var(--r-pill);
  background: var(--surface);
  white-space: nowrap;
  font-size: clamp(11.5px, calc(10.2px + 0.337vw), 14.5px);
  /* 390px→11.5px, 1280px→14.5px */
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1.3;
  color: var(--marker-ink);
}

/* 낮음 / 보통 / 높음 / 매우 높음 — 세그먼트와 같은 폭으로 나눠 각 조각 아래 가운데 선다.
   폭 폴백은 위 .risk-seg 와 같은 30/20/25/25 를 nth-child 로 준다(라벨에는 is-* 가 없다).
   --lab-tone/--lab-ink 도 같은 nth-child 가 나눠 준다 — 현재 구간(.is-active)만 그 색을 쓴다. */
.dash-risk .risk-labels {
  display: flex;
  /* 막대와 같은 gap — 없으면 조각 경계와 라벨 경계가 최대 6px 어긋난다 */
  gap: 2px;
  margin-top: var(--sp-2);
  font-size: clamp(13px, calc(11.69px + 0.337vw), 16px);
  /* 390px→13px, 1280px→16px */
  font-weight: 700;
  color: var(--text-mute);
}

.dash-risk .risk-labels>span {
  flex: var(--w, 25) 1 0;
  min-width: 0;
  text-align: center;
}

.dash-risk .risk-labels>span:nth-child(1) {
  --w: 30;
  --lab-ink: var(--seg-low-ink);
}

.dash-risk .risk-labels>span:nth-child(2) {
  --w: 20;
  --lab-ink: var(--seg-mid-ink);
}

.dash-risk .risk-labels>span:nth-child(3) {
  --w: 25;
  --lab-ink: var(--seg-high-ink);
}

.dash-risk .risk-labels>span:nth-child(4) {
  --w: 25;
  --lab-ink: var(--seg-vhigh-ink);
}

/* 현재 구간 라벨 — 구간색 + 밑줄(report .legend>span.is-current 언어).
   [2026-08-27 재스킨] 종전에는 옅은 구간색 알약을 깔았다. 알약은 조각 폭 전체를 차지해
   막대 아래에 **두 번째 막대**처럼 보였고, 이제 활성 세그먼트 자체가 원색 + 아웃라인으로
   앞에 나오므로 그 아래에서 또 한 겹 채색하면 강조가 두 번 겹친다. 밑줄은 폭을 글자만큼만
   쓰므로 라벨 행이 축 아래 얇은 한 줄로 남는다(리포트가 같은 이유로 밑줄을 쓴다).
   ※ 알약이 사라져 span 의 padding 3px·border-radius 도 위에서 함께 걷어냈다.
   밑줄 간격은 리포트 2px 보다 1px 넓다 — 여기 라벨은 13~16px 로 리포트(9.5~10.5px)보다 커서
   같은 2px 이면 글자에 붙어 보인다. */
.dash-risk .risk-labels>span.is-active {
  font-weight: 800;
  color: var(--lab-ink);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}


/* ===== [dash-v2] 현재 구간 칩 · 설명 (.risk-stage-chip / .risk-stage-desc) ===== */
/* [2026-08-27 사용자 피드백 ④ "위험지수 카드가 허전하다"] 종전 이 카드에는 막대·라벨·한 줄
   안내뿐이라, 옆 카드(추이 3행) 높이에 맞춰 늘어난 만큼이 빈 공간으로 남았다. 프론트가
   제목 아래에 [현재 구간 칩] + [구간 설명 한 줄]을 추가했고 여기서 그 두 조각을 조판한다.
   DOM: 제목 → .risk-stage-chip(안에 -cap "현재 구간" + -name "낮음") → .risk-stage-desc →
        막대 → 라벨 → .risk-note.

   칩 색: 구간색을 **그대로 배경에 깔지 않고** 30% 틴트 + 같은 색상의 어두운 글자로 만든다.
   원색을 배경에 쓰면 네 구간의 명도가 제각각이라(낮음 L*86 ↔ 매우높음 L*44) 글자색을 흰↔검
   으로 갈아끼워야 하고, 그러면 "높음" 구간에서 흰 글자 대비가 3.77:1 로 떨어져 작은 자막이
   읽히지 않는다. 틴트 + 어두운 글자로 통일하면 네 구간이 같은 짜임이면서 대비가 모두 통과한다:
     낮음 #1B4F8F on 30% #c6daf3 = 7.49:1
     보통 #164780 on 30% #8db4e2 = 7.57:1
     높음 #8F4A12 on 30% #c96a1e = 4.68:1
     매우 #A0231A on 30% #c62f21 = 4.66:1
   테두리는 같은 색 70% 라 칩이 막대의 어느 조각을 가리키는지 색으로 이어진다. */
.dash-risk .risk-stage-chip {
  /* 카드가 flex 세로열이라 기본 stretch 로 두면 칩이 카드 폭을 다 먹는다 — 내용 폭으로 세운다.
     프론트가 칩을 래퍼 안에 넣더라도 inline-flex 가 같은 결과를 낸다(align-self 는 무효가 될 뿐). */
  align-self: flex-start;
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid color-mix(in srgb, var(--stage-tone, var(--seg-low)) 70%, #fff);
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--stage-tone, var(--seg-low)) 30%, #fff);
  color: var(--stage-ink, var(--seg-low-ink));
}

.dash-risk .risk-stage-chip.is-low {
  --stage-tone: var(--seg-low);
  --stage-ink: var(--seg-low-ink);
}

.dash-risk .risk-stage-chip.is-mid {
  --stage-tone: var(--seg-mid);
  --stage-ink: var(--seg-mid-ink);
}

.dash-risk .risk-stage-chip.is-high {
  --stage-tone: var(--seg-high);
  --stage-ink: var(--seg-high-ink);
}

.dash-risk .risk-stage-chip.is-vhigh {
  --stage-tone: var(--seg-vhigh);
  --stage-ink: var(--seg-vhigh-ink);
}

/* 자막 "현재 구간" — 색은 구간명과 같게 두고 **크기·굵기로만** 급을 나눈다.
   중성색(--text-sub)으로 낮추면 매우높음 칩 배경에서 3.60:1 까지 떨어져 작은 글자가 흐려진다. */
.dash-risk .risk-stage-chip-cap {
  font-size: clamp(12.5px, calc(10.97px + 0.393vw), 16px);
  /* 390px→12.5px, 1280px→16px */
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.2;
}

/* 구간명 "낮음/보통/높음/매우 높음" — 시니어 가독 목적으로 카드 안에서 제목 다음으로 크다 */
.dash-risk .risk-stage-chip-name {
  font-size: clamp(22px, calc(18.49px + 0.899vw), 30px);
  /* 390px→22px, 1280px→30px */
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1.15;
}

/* 구간 설명 한 줄 — 칩이 "무엇인지", 이 문장이 "그게 무슨 뜻인지"를 맡는다 */
.dash-risk .risk-stage-desc {
  font-size: clamp(14px, calc(12.25px + 0.449vw), 18px);
  /* 390px→14px, 1280px→18px */
  font-weight: 600;
  line-height: 1.55;
  color: var(--text-sub);
}

/* [빈 공간 배분 — 위험지수 카드 전용] (2026-08-27 피드백 ④ 마무리)
   위 [빈 공간 배분] 공통 규칙은 남는 세로를 "제목 아래"와 "본문 아래"에 반씩 나눈다. 이 카드는
   이제 머리 덩어리가 [제목 + 칩 + 설명] 세 조각이라, 제목 바로 아래에서 갈라 놓으면 제목만
   위에 떨어져 남고 칩이 카드 한가운데로 내려온다. 갈라 놓는 지점을 설명문 아래로 옮겨
   머리 덩어리는 위에 붙이고, 남는 공간은 막대 위/아래로만 흐르게 한다.
   :has() 로 좁힌 이유 — 리포트가 없는 회원은 칩·설명 대신 .state-block 한 덩어리만 오는데,
   그때는 공통 규칙(제목 아래 auto)이 그대로 살아 빈 상태가 카드 가운데에 서야 한다. */
.dash-risk:has(.risk-stage-chip)>.dash-card-title {
  margin-bottom: 0;
}

/* 자손 결합자다(종전 `>`) — 레이더가 붙은 회원은 설명문이 .risk-body 안으로 한 겹 들어간다.
   그 경우 auto 마진이 .risk-body 의 flex 열 안에서 같은 일을 한다(막대 그룹을 아래로 민다). */
.dash-risk .risk-stage-desc {
  margin-bottom: auto;
}


/* ===== [dash-v2] 위험지수 카드 — 우측 레이더 오버레이 (2026-08-27 사용자 지시) ===== */
/* [묘비 — 직전 판의 grid 2열] 카드를 `"title title" / "body radar"` 그리드로 갈라 좌 본문 /
   우 레이더 두 열로 세웠었다(열 트랙 clamp(180px,35%,240px) · grid-template-rows auto
   minmax(0,1fr) 로 제목 행 고정 · .risk-body/.risk-radar 의 grid-area · 1300px 전환점).
   사용자가 "영역을 나누지 말고 위험분포 그래프는 우측에 **레이어 형태로 띄워서**" 로 정정했다.
   열을 나누면 본문(막대·라벨·콜아웃)이 카드 폭의 60%만 쓰게 되는데, 이 카드의 주인공은
   가로로 긴 위험 축이라 그 축을 줄이는 대가가 컸다. 이제 본문은 카드 폭을 그대로 쓰고,
   레이더는 흐름 밖으로 나가 **칩·설명 오른쪽 + 막대 위**의 빈 자리에 떠 있다.
   함께 죽은 규칙: 제목의 grid-area, 그리드 행/열 트랙, 레이더 열 좌우 인셋(padding 0 8px).

   [자리] 카드가 위치 기준이 된다(.dash-card.dash-risk 의 position:relative).
     top    = 카드 padding(20) + 제목 한 줄(30) + 카드 gap(12) → 제목 바로 아래에서 시작
     right  = 카드 padding 안쪽
   세로로는 막대 위 여백에서 멈춘다 — 본문이 `.risk-stage-desc` 의 auto 마진으로 막대 그룹을
   카드 아래쪽으로 밀어 두기 때문에 그 위가 통째로 비어 있다(이 카드가 옆 추이 카드 높이에
   맞춰 늘어나면서 생기던 바로 그 빈 자리다).
   가로로는 칩(내용 폭 ~170px)·설명(~250px)의 오른쪽이라 본문 글자와 부딪히지 않는다.

   [겹침 안전 폭] 카드가 좁아지면 설명문이 길어져(줄바꿈) 오버레이 밑으로 파고든다. 실측으로
   하한을 정했다 — 아래 [세로 스택] 분기 주석 참조. 그 미만에서는 흐름 배치로 되돌린다. */
.dash-risk .risk-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  gap: var(--sp-3);
  min-width: 0;
}

/* 오버레이 — 기본은 흐름 배치(세로 스택)다. 넓은 폭에서만 아래 [오버레이] 분기가 띄운다.
   기본을 흐름으로 두는 쪽이 안전하다: 분기가 안 걸리는 폭에서 그림이 본문 위에 겹쳐 앉는
   사고가 구조적으로 일어나지 않는다. */
.dash-risk .risk-radar {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-0);
  min-width: 0;
}

/* 그림 칸(.risk-radar-chart). **높이를 주지 않는다** — ResponsiveContainer 가 aspect=1 이라
   폭 하나로 정사각이 결정되고, 여기에 height 를 겹쳐 주면 두 값이 어긋나 그림이 눌린다. */
.dash-risk .risk-radar-chart {
  width: 100%;
  max-width: var(--radar-w);
  min-width: 0;
}

/* 캡션 — "무엇을 그린 그림인지" 한 줄. 그림을 누르지 않는 최소 급 */
.dash-risk .risk-radar-cap {
  font-size: clamp(13px, calc(11.9px + 0.281vw), 15.5px);
  /* 390px→13px, 1280px→15.5px — [2026-08-27 사용자 "캡션이 잘 안 보인다"] 한 급 올렸다.
     색도 --text-mute → --text-sub 로 본문급. 굵기는 600 그대로 두고 크기·색만 올린 것은
     "과하게 하지 말 것" 조건 때문이다 — 카드 제목(24px/700)이나 구간명과 급이 겹치지 않고,
     그림 아래 한 줄 설명으로 남는다. */
  font-weight: 600;
  line-height: 1.4;
  text-align: center;
  /* [2026-08-28] 오버레이 200px 폭에서 캡션이 글자 중간에서 접혔다("…좋습니/다").
     한글 기본 word-break 는 음절 단위라 아무 데서나 끊는다 — keep-all 로 공백(어절)
     단위 줄바꿈만 허용해 "영역별 위험 분포 · / 좁을수록 좋습니다" 로 깔끔히 갈라진다.
     한 줄이 들어가는 폭에서는 종전 그대로 한 줄이다. */
  word-break: keep-all;
  color: var(--text-sub);
}

/* 레이더 격자·축 톤 — 채움/선 색은 프론트가 인라인 var() 로 주입하고(계약), 여기서는
   **배경 격자와 축 글자**만 화면 톤에 맞춘다. recharts 는 이 둘에 인라인 스타일을 주지
   않아 CSS 로만 잡힌다(global.css 의 툴팁 규칙과 같은 분담).
   SVG 라 글자색은 color 가 아니라 fill 이다. */
.dash-risk .risk-radar .recharts-polar-grid-angle line,
.dash-risk .risk-radar .recharts-polar-grid-concentric-polygon,
.dash-risk .risk-radar .recharts-polar-grid-concentric-circle {
  stroke: var(--chart-grid);
}

/* [2026-08-27 레이더 고도화 후속] 축 라벨이 recharts 기본 tick → 프론트 커스텀
   <text class="risk-radar-tick"> (tspan 2줄 규칙 분리)로 바뀌며 종전 셀렉터
   .recharts-polar-angle-axis-tick-value 가 죽었다 — 클래스만 갈아탄다. 미지정이면
   페이지 19px 검정을 상속받아 "대사증후군"이 7px 잘린다(프론트 실측: 12px대면 잘림 0). */
.dash-risk .risk-radar .risk-radar-tick {
  font-size: clamp(10.5px, calc(9.62px + 0.225vw), 13px);
  /* 390px→10.5px, 1280px→13px */
  font-weight: 700;
  fill: var(--text-sub);
}

/* 최외곽 링(=100점 자리)만 한 단계 진하게 — 도형이 닿는 "만점 경계"가 격자와 구분된다.
   concentric 그룹의 path 는 안쪽부터 0·25·50·75·100 순서라 last-child 가 최외곽이다
   (프론트 실측). 색은 카드 테두리급 --line-strong — 격자(--chart-grid)보다 한 단계 진한 기존 값. */
.dash-risk .risk-radar .recharts-polar-grid-concentric path:last-child {
  stroke: var(--line-strong);
}

/* 축 라벨이 그림 상자 밖으로 나가도 잘리지 않게 한다 — PolarAngleAxis 라벨은 반지름 바깥에
   그려져 상자 경계에서 잘린다(실측: 긴 라벨 앞 두 글자가 날아갔다). outerRadius 는 JSX
   프롭이라 CSS 로 줄일 수 없으므로 흘러나오게 두고, 그 자리를 아래 오버레이의 right 여백과
   카드 padding 이 받는다. */
.dash-risk .risk-radar .recharts-surface {
  overflow: visible;
}

/* ===== [dash-v2] 위험점수 도넛 게이지 (.risk-donut) — 2026-08-27 사용자 지시 ===== */
/* 카드 **왼쪽 빈 영역**(설명문 아래 ~ 막대 위, 레이더 오버레이 왼쪽)에 들어간다. 그 자리는
   본문의 `.risk-stage-desc { margin-bottom: auto }` 가 막대 그룹을 아래로 밀어 만든 여백이고,
   오른쪽은 레이더가 이미 덮고 있다 — 남은 왼쪽 사각형이 도넛 자리다.

   마크업 계약: .risk-donut > svg(트랙 circle + 진행 circle) + .risk-donut-num + .risk-donut-max.
   숫자 두 조각이 svg 의 **형제**라 도형과 겹쳐 세우려면 이 상자가 기준이 되어야 한다 —
   .risk-donut 을 position:relative 그리드로 잡고 svg 만 absolute 로 깔면 숫자 두 줄이
   그리드 가운데에 쌓인다(리포트 지면 종합점수 링과 같은 짜임).

   [자리잡기] .risk-body 는 세로 flex 열이다.
     margin-block: auto → 설명과 막대 사이 남는 세로의 가운데에 선다(desc 의 auto 와 나눠 갖는다)
     가로는 아래 [오버레이 tier] 가 레이더 폭을 뺀 **왼쪽 영역의 정중앙**으로 보낸다.
     좁은 폭(스택)에서는 레이더가 흐름으로 내려오므로 그냥 카드 가운데(align-self:center)다. */
.dash-risk .risk-donut {
  position: relative;
  display: grid;
  place-items: center;
  align-content: center;
  flex: 0 0 auto;
  align-self: center;
  width: var(--donut-d);
  height: var(--donut-d);
  margin-block: auto;
}

/* 링 도형 — 상자를 꽉 채우고 흐름에서 빠진다. viewBox 120×120 이라 어떤 지름에서도
   stroke-width 10 이 비례로 스케일된다. 이 규칙이 없으면 SVG 가 기본 폭(부모 전폭 556px)을
   먹어 카드 왼쪽 영역을 통째로 차지한다(프론트 보고 증상). */
.dash-risk .risk-donut-arc {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* 숫자 상자 — 계약에 없던 추가 래퍼(p.risk-donut-value). 링 **중앙**에 절대배치하고
   안쪽에서 점수/최대치를 세로로 쌓는다(span 두 개라 기본 인라인이면 옆으로 붙는다). */
.dash-risk .risk-donut-value {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  margin: 0;
  text-align: center;
}

/* 트랙 — 옅은 중성. 다른 차트 격자와 같은 톤이라 화면 안에서 한 벌로 읽힌다.
   두 circle 은 마크업에 `stroke="currentColor"` **프레젠테이션 속성**으로 와 있어 CSS 선언이
   그대로 이긴다(프레젠테이션 속성은 저자 스타일시트보다 우선순위가 낮다).
   stroke-opacity 는 프론트가 자리표시로 .15 를 걸어 둔 값이라 1 로 되돌린다 — 색을 여기서
   정하는 이상 불투명도까지 두 곳에서 나눠 가지면 톤이 어긋난다. */
.dash-risk .risk-donut-track {
  stroke: var(--chart-grid);
  stroke-opacity: 1;
}

/* 진행 링 — 현재 구간색. 값은 아래 :has() 브리지가 --risk-donut-ink 로 넣는다.
   기본값은 칩이 없는 경우(리포트 없음·점수 없음)의 폴백이라 중성 회색으로 둔다.
   채움 길이(dasharray, pathLength 100 정규화)는 프론트가 --score 로 이미 정확히 그린다 —
   CSS 는 크기·색·배치만 맡는다. */
.dash-risk .risk-donut-fill {
  stroke: var(--risk-donut-ink, var(--line-strong));
  stroke-opacity: 1;
}

/* 중앙 점수 — 지면 종합점수 링처럼 크게. 색은 본문 잉크(구간색은 링이 이미 말한다) */
.dash-risk .risk-donut-num {
  display: block;
  font-size: clamp(26px, calc(22.5px + 0.9vw), 34px);
  /* 390px→26px, 1280px→34px */
  font-weight: 800;
  letter-spacing: -.04em;
  line-height: 1;
  color: var(--text);
}

.dash-risk .risk-donut-max {
  display: block;
  margin-top: 2px;
  font-size: clamp(12px, calc(10.9px + 0.281vw), 14.5px);
  /* 390px→12px, 1280px→14.5px */
  font-weight: 700;
  line-height: 1;
  color: var(--text-mute);
}

/* 구간색 브리지 — 칩·활성 세그·활성 라벨과 **같은 값**을 쓴다(핀 정합·세그 강조에 쓴 것과
   같은 수법: 판정을 이미 들고 있는 칩에서 :has() 로 끌어온다, 마크업 무수정).
   진행 링은 면적이 넓은 색면이라 글자용 어두운 --seg-*-ink 가 아니라 **원색 --seg-*** 를 쓴다
   (미니카드 [상태 톤]에서 정리한 면/글자 분담과 같다). */
.dash-risk:has(.risk-stage-chip.is-low) {
  --risk-donut-ink: var(--seg-low);
}

.dash-risk:has(.risk-stage-chip.is-mid) {
  --risk-donut-ink: var(--seg-mid);
}

.dash-risk:has(.risk-stage-chip.is-high) {
  --risk-donut-ink: var(--seg-high);
}

.dash-risk:has(.risk-stage-chip.is-vhigh) {
  --risk-donut-ink: var(--seg-vhigh);
}


/* 카드 수준 auto 마진 배분(위 [빈 공간 배분])은 그대로 살린다 — 레이더가 흐름 밖으로 나가면
   본문이 카드 세로를 혼자 쓰므로, .risk-stage-desc 의 auto 가 막대 그룹을 아래로 밀어
   오버레이가 앉을 위쪽 공간을 만든다. 그 배분이 이 레이아웃의 전제다. */


/* ===== [dash-v2] 레이더 오버레이 전환 — 1261px 이상 (2026-08-27 재조정) ===== */
/* 그림을 240~260px 로 키우면서 전환점을 1240 → 1300px 으로 재산정했다.
   조건: 설명 "위험 요인이 적은 안정 구간입니다."(250px)가 오버레이 왼쪽에서 한 줄로 남아야
   한다 → 카드 안쪽 ≥ 그림(240) + 간격(16) + 설명(250) = 506px.
   `카드 = (vw − 48) × .4545`, `안쪽 = 카드 − 42` 이므로 vw ≥ 1276px → 여유를 두고 1300px.
   실측 확인: 1300px 안쪽 520 → 설명 자리 264px ✓ / 1366px 294px ✓ / 1500px 355px ✓.
   1300px 미만은 위 기본 규칙대로 흐름에 남아 본문 아래 세로 스택이 된다 — 겹침이 원천 봉쇄된다.
   ※ 주요 요약 1×4 전환점과 같은 값이 됐다 — "넓은 데스크톱 배치"를 한 지점에서 켠다. */
@media (min-width: 1261px) {
  .dash-risk:has(.risk-radar) .risk-radar {
    position: absolute;
    /* [2026-08-27 버그 수정] 카드 padding(sp-5) + 제목 한 줄(24px×1.25=30). 종전에는 카드
       gap(sp-3)까지 더해 12px 더 내려와 있었다 — 제목과 붙지 않는 선에서 최대한 끌어올린다. */
    top: calc(var(--sp-1) + 10px);
    right: var(--sp-5);
    z-index: 1;
    width: var(--radar-w);
  }

  /* 도넛 가로 위치 — 레이더가 덮는 오른쪽(그림 폭 + 간격)을 뺀 **왼쪽 영역의 정중앙**.
     `100%` 는 .risk-body 폭(= 카드 안쪽)이라 폭이 바뀌어도 식 하나로 따라온다.
     이 분기 밖(1300px 미만 스택)에서는 레이더가 흐름으로 내려오므로 기본 align-self:center 로
     카드 가운데에 선다 — 여기서 margin-left 를 주면 좁은 폭에서 음수가 되어 왼쪽으로 삐져나간다
     (실측 390px x=−5px). 그래서 이 한 줄은 반드시 이 미디어 블록 안에 있어야 한다. */
  .dash-risk:has(.risk-radar) .risk-donut {
    align-self: flex-start;
    margin-left: calc((100% - var(--radar-w) - var(--sp-4) - var(--donut-d)) / 2);
  }

  .dash-card.dash-risk:has(.risk-radar) {
    /* 오버레이가 차지한 오른쪽 폭만큼 본문 글줄을 물려 둔다 — 막대·라벨·콜아웃은 카드 폭을
       그대로 쓰고(사용자 지시), **글자만** 오버레이 밑으로 들어가지 않게 한다.
       칩·설명에만 걸리는 이유는 아래 규칙이 그 둘만 짚기 때문이다. */
    --radar-w: clamp(270px, 40%, 290px);
    /* [2026-08-27 사용자 "왜 또 작아졌어"] 겹침 수정 때 220px 로 줄였던 것을 240~260px 로
       되돌린다. 여유는 폭을 깎는 대신 아래 min-height 로 만든다(맞바꿈). */

    /* [말풍선 여유] 직전 검증이 놓친 것 — "오버레이 아래끝 ↔ **막대 위끝** 4px" 로 쟀는데,
       핀은 막대 위로 더 솟는다. 막대 위끝 위로 [점 반지름 12 + 말풍선 gap 5 + 말풍선 높이 25]
       ≈ 42px 이 더 있고, 그 구간에 캡션이 물렸다(사용자 스크린샷: 점수 64 = 막대 우측 64%,
       오버레이가 걸친 구간). 기준을 **말풍선 상단**으로 바꿔 다시 세운다:
         가용 세로 = 말풍선 상단 − 오버레이 top
                   = (막대 위끝 − 42) − (20 + 30)
       막대 위끝은 카드 높이에 따라 내려오므로 아래 min-height 로 그 값을 확보한다.
       오버레이 높이 = 그림(폭과 같음) + gap 8 + 캡션 22 = 폭 + 30.
       240px 기준 → 높이 270, 아래끝 321. 말풍선 상단(368)이 그보다 아래라 겹치지 않는다. */
    min-height: 500px;
    /* 480px(옆 추이 카드 높이) → 500 → **540px**. 폭을 240px 로 되돌리면 오버레이 높이가
       240 + 30 = 270 이 되어 아래끝이 51 + 270 = 321px 이다. 말풍선 상단이 그보다 위에 있으면
       안 되므로 카드를 60px 더 키워 막대·핀을 그만큼 내렸다 — 실측 말풍선 상단 368px,
       여유 47px(요구 ≥20px). 같은 행의 추이 카드도 stretch 로 함께 540px 이 된다. */
  }

  .dash-risk:has(.risk-radar) .risk-stage-chip,
  .dash-risk:has(.risk-radar) .risk-stage-desc {
    max-width: calc(100% - var(--radar-w) - var(--sp-4));
  }
}



/* ===== [dash-v2] 미니카드 칩 하한 보정 — 1251~1340px (2026-09-02) ===== */
/* 1×4 가 켜지는 1251px 부터 칸이 가장 좁다. 머리 행은 [칩 + 6px + 지표명]이고 가장 긴 이름은
   "산소포화도"(22px 기준 110px)다. 실측 칸 안쪽 글자 폭:
     1251px 163px · 1300px 168px · 1340px 174px · 1366px 187px · 1500px 211px
   48px 칩이면 머리 행이 164px 라 1251~1340px 에서 칸을 넘긴다(1251px 기준 1px 초과).
   그 구간만 42px 로 낮춘다 — 42 + 6 + 110 = 158px 로 1251px 에서도 5px 여유가 남는다.
   1341px 부터는 기본 48~56px 이 그대로 산다(1366px 실측 여유 15px). */
@media (min-width: 1251px) and (max-width: 1340px) {
  .dash-summary .mini-card {
    --ico-d: 42px;
  }

  .dash-summary .mini-ico {
    font-size: clamp(21px, calc(20.5px + 0.04vw), 22px);
    /* 1251px→21px, 1340px→21.05px — 42px 칩의 약 52%(위 비율 유지) */
  }
}


/* ===== [dash-v2] 좁은 오버레이 구간 폭 보정 — 1261~1319px (2026-08-27) ===== */
/* 전환점을 1300 → 1261px 로 내리면서(사용자: "1280px 키오스크에서도 오버레이 유지") 새로
   오버레이가 켜지는 1261~1319px 구간을 다시 쟀다. 이 구간은 카드가 좁은데 --radar-w 는
   270~290px(사용자 라이브 조정값)이라 설명문 자리가 모자란다:
     1261px → 카드 안쪽 509px − 그림 270 − 간격 16 = 설명 자리 **223px**  (필요 250px, 부족)
   설명 두 줄을 허용하는 대신 **그 구간에 한해 그림을 240px 로 줄여** 한 줄을 지켰다
   (사용자 조건: 한 줄 유지 우선). 보정 후 1261px → 509 − 240 − 16 = **253px** ✓.
   상한 1319px 인 이유: 1320px 부터는 안쪽이 536px 을 넘어 270px 그림으로도 설명 250px 이
   확보된다 — 그 위로는 사용자가 키운 270~290px 이 그대로 산다. */
@media (min-width: 1261px) and (max-width: 1319px) {
  .dash-card.dash-risk:has(.risk-radar) {
    --radar-w: clamp(220px, 34%, 240px);
  }
}


/* "낮을수록 좋습니다 · 위험점수 N점" — 점수 방향이 종전과 반대임을 화면이 직접 말한다.
   [2026-08-27 사용자 피드백 "맨 텍스트로 덩그러니 떠 있다"] 옅은 중성 틴트 스트립 +
   ⓘ 아이콘의 안내 콜아웃으로 감쌌다.
   색은 **중성**(--surface-sunken 면 + --line 테두리 + --text-sub 글자)이다 — 등급색·구간색을
   쓰면 카드 안에서 판정을 말하는 네 번째 색면이 되어(칩·활성 세그·활성 라벨에 이어) 축의
   판정 신호를 흐린다. 이 줄은 판정이 아니라 "축을 읽는 법" 안내다.
   자리: 카드가 flex 세로열이라 stretch 로 카드 폭을 가득 채운다 — 스트립이라 그게 맞다.
   [빈 공간 배분]의 `.dash-risk>:last-child { margin-bottom: auto }` 가 이 요소를 잡으므로
   margin-top 은 카드 gap(sp-3)에 맡기고 여기서 따로 주지 않는다(종전 sp-2 는 그 위에 얹혀
   라벨과의 간격만 20px 로 벌리던 값이라, 스트립이 된 지금은 gap 하나로 충분하다). */
.dash-risk .risk-note {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  font-size: clamp(12.5px, calc(11.19px + 0.337vw), 15.5px);
  /* 390px→12.5px, 1280px→15.5px */
  font-weight: 600;
  line-height: 1.45;
  color: var(--text-sub);
}

/* ⓘ 아이콘 — 글리프(ℹ U+2139)가 아니라 CSS 로 그린 원 + 소문자 i 다.
   Windows 10 에서 ℹ 는 폰트에 따라 이모지 프레젠테이션(파란 사각 아이콘)으로 떨어지거나
   글리프가 없어 두부(􏰀)로 나온다 — 시니어 화면에서 확인할 수 없는 위험을 지지 않는다.
   원은 border-radius 50% + 1.5px 테두리, 안의 i 는 content 로 넣은 ASCII 'i' 라
   어떤 시스템 폰트에도 있다. flex 아이템이라 글줄이 두 줄로 접혀도 세로 중앙에 남는다. */
.dash-risk .risk-note::before {
  content: "i";
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 1.35em;
  height: 1.35em;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  font-size: clamp(11px, calc(9.9px + 0.281vw), 13.5px);
  /* 390px→11px, 1280px→13.5px — 본문보다 한 급 작다(아이콘이 글자를 눌러선 안 된다) */
  font-weight: 800;
  font-style: italic;
  line-height: 1;
  /* 이탤릭 i 가 원 안에서 오른쪽으로 쏠리는 것을 되돌린다 */
  text-indent: -.06em;
  opacity: .75;
}

/* 점수 조각 — 프론트가 숫자 부분만 <strong class="risk-note-score"> 로 감싼다.
   안내 문장 안에서 유일하게 회원마다 달라지는 값이라 본문색 + 800 으로 한 급 올린다
   (구간색이 아니다 — 위 블록 주석의 "중성" 원칙). */
.dash-risk .risk-note-score {
  font-weight: 800;
  letter-spacing: -.01em;
  color: var(--text);
}

/* 카드 안의 보조 안내 한 줄 (예: 맞춤 포인트 카드의 "데이터가 더 쌓이면…") */
.dash-card .dash-note {
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 600;
  line-height: 1.55;
  color: var(--text-mute);
}


/* ===== [dash-v2] 주요 건강지표 추이 (.dash-trend) ===== */
/* 혈압(수축기)·혈당·산소포화도. 색은 recharts 가 --chart-* 를 그대로 쓴다.

   [묘비 2026-08-27] "3선 한 차트 + 공용 범례" 구성이 폐기됐다. 아래 세 규칙이 그 부품이었다:
     .dash-trend .metric-legend      — 카드 안에서만 범례 글자 크기를 키우던 덮개
     .dash-trend .metric-legend-line — 같은 범례의 선 조각(16×3)을 점(12×12)으로 바꾸던 덮개
     .dash-trend .dash-trend-chart   — 단일 차트 높이 clamp(190px, 19vw, 260px)
   범례는 이제 행마다 .trend-row-head 가 겸하고, 차트는 행마다 .trend-row-chart 다.
   ※ 공용 .metric-legend / .metric-legend-line 과 .change-card .metric-legend 는 변화 탭이
      계속 쓴다 — 지운 것은 .dash-trend 로 스코프된 덮개 세 개뿐이다. */


/* ===== [dash-v2] 추이 3행 (.trend-rows > .trend-row) ===== */
/* [2026-08-27 사용자 지시] 3선을 한 축에 겹쳐 그리던 통합 차트를 **지표당 한 행**으로 나눴다.
   세 지표의 대역(수축기 90~190 / 혈당 70~200 / SpO₂ 90~100)이 한 축을 공유하면 SpO₂ 선이
   바닥에 눌려 변화가 보이지 않았다 — 행을 나누면 지표마다 자기 축을 갖는다.
   한 행 = [.trend-row-head 색점+이름+단위] + [.trend-row-chart 작은 추이].

   행 높이: 트랙을 auto 로 두고 각 행이 제 내용만큼만 차지하게 한다. 세 행 모두 x축 라벨을
   이고 있으므로 세 행이 같은 높이 가산(--trend-axis-h)을 받는다 — 균등해야 하는 것은 행
   총높이가 아니라 **플롯 높이**다(아래 .trend-row-chart 규칙).
   카드 채우기: flex:1 로 카드의 남는 세로를 .trend-rows 가 받고, grid 기본 align-content:
   stretch 가 그 여유를 auto 트랙 세 개에 똑같이 나눠 준다 — 옆 카드(.dash-risk)가 더 길어
   카드가 늘어나도 아래에 빈 띠가 남지 않는다. */
.dash-card.dash-trend {
  /* --trend-plot-h = 한 행의 플롯(선이 그려지는 그림) 높이. 세 행이 이 값 하나를 공유한다.
     --trend-axis-h = 각 행이 이고 있는 일자(MM.DD) 라벨 x축 높이(recharts XAxis 기본값). */
  /* [2026-08-27 사용자 지시 ④ "추이 행 높이를 눈에 띄게 줄여라"] 58~86px → 44~62px.
     행마다 일자 라벨 x축(--trend-axis-h)을 따로 이고 있어 카드 총높이는 (플롯+30)×3 이다 —
     플롯을 24px 줄이면 카드는 72px 줄어든다. 실측 552 → 426px(−23%).
     44px 하한은 선의 오르내림이 살아 있는 최소치다(1500px 실측: 세 지표 모두 진폭이 보인다).
     라벨·행 머리는 이 값과 무관하다 — .trend-row 가 `auto minmax(0,1fr)` 두 트랙이라 머리는
     제 높이를 갖고, x축 30px 은 아래 .trend-row-chart 가 따로 되더한다. */
  --trend-plot-h: clamp(44px, calc(36.11px + 2.022vw), 62px);
  /* 390px→44px, 1280px→62px */
  --trend-axis-h: 30px;
}

.dash-trend .trend-rows {
  display: grid;
  grid-auto-rows: auto;
  flex: 1 1 auto;
  min-width: 0;
}

/* [2026-09-02 추이 1행 전환] 지표 3행 → 위험도 점수 1행이 되면서 이 구조가 어긋났다.
   auto 트랙이 여러 개일 때는 flex:1 로 받은 카드 여유를 stretch 가 트랙들에 나눠 줘서 고르게
   찼는데, 트랙이 하나면 그 행은 제 내용(플롯 + 축)만큼만 차지하고 **남는 높이가 전부 카드
   아래 빈 띠로 몰린다**(frontend 스크린샷).
   [채택안 ①: 플롯을 키워 카드를 채운다] 행을 1fr 로 늘리고 그 안에서 차트가 남는 세로를
   전부 가져간다. ②(행 중앙 배치 + 카드 높이 축소)를 고르지 않은 이유 — 이 카드는 같은 행
   위험지수 카드와 stretch 로 높이를 맞추는데(레이더·도넛이 그 높이를 전제로 자리를 잡는다),
   카드를 줄이면 그 두 도형의 배치 계산을 다시 세워야 한다. 늘리는 쪽은 행 균형을 건드리지
   않으면서 추이 가독까지 올린다 — 한 지표만 남았으니 플롯이 클수록 좋다.
   1행일 때만 걸리게 `.trend-row` 가 하나뿐인 경우로 좁힌다(인접 형제가 없으면 1행이다) —
   나중에 지표가 다시 여러 행으로 돌아가면 위 auto 트랙 배분이 그대로 되살아난다.
   min-height 로 종전 높이를 바닥값으로 남겨, 카드가 짧아지는 폭에서도 플롯이 눌리지 않는다. */
.dash-trend .trend-rows:not(:has(.trend-row+.trend-row)) {
  /* [2026-09-04 보정] auto-rows 1fr 는 두 번째 자식(.dash-note 한 줄 캡션)에도 절반을
     나눠 줘 카드 하단 절반이 캡션 아래 빈 띠로 남았다(1500px 실측 195.6px 낭비 — frontend
     검증 FAIL). 명시 템플릿 [차트 1fr / 노트 auto]로 차트 행만 남는 세로를 갖게 한다.
     노트가 없는 회원(자식 1개)도 두 번째 트랙이 빈 트랙이라 무해하다. */
  grid-template-rows: minmax(0, 1fr) auto;
}

.dash-trend .trend-rows:not(:has(.trend-row+.trend-row)) .trend-row-chart {
  height: 100%;
  min-height: calc(var(--trend-plot-h) + var(--trend-axis-h));
}

.dash-trend .trend-row {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-content: center;
  gap: 4px;
  min-width: 0;
  padding: var(--sp-2) 0;
}

/* 행 사이 옅은 구분선 — .guide-row 와 같은 어휘(가이드 카드와 같은 줄 나눔으로 읽힌다) */
.dash-trend .trend-row+.trend-row {
  border-top: 1px solid var(--line);
}

.dash-trend .trend-row:first-child {
  padding-top: 0;
}

.dash-trend .trend-row:last-child {
  padding-bottom: 0;
}

/* 머리 라벨 — 좌측 정렬. 색점이 곧 아래 선 색이라 범례를 겸한다.
   [2026-08-27 사용자 피드백 ⑤ "행 머리 텍스트가 작다"] 12.5~15.5 → 14.5~19px.
   카드 제목(.dash-card-title 18~24px)의 바로 아래 급이라, 세 행이 무엇을 그린 그림인지
   차트를 보기 전에 먼저 읽힌다. 미니카드 지표명(.mini-name)과 같은 clamp 를 써서
   두 카드의 "지표 이름" 급이 화면에서 같은 크기로 선다. */
.dash-trend .trend-row-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  font-size: clamp(14.5px, calc(12.53px + 0.506vw), 19px);
  /* 390px→14.5px, 1280px→19px */
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.3;
  color: var(--text-sub);
}

/* 색점 — 아래 선 색과 같아 이 점 하나가 행의 범례를 겸한다.
   색은 프론트가 인라인 background 로 시리즈의 --chart-* 를 넣고(하드코딩 금지 규칙),
   크기·모양(원형)은 여기가 소유한다 — 공용 .metric-legend-line 과 같은 분담이다.
   글자가 커진 만큼(피드백 ⑤) 10 → 12px — 점이 그대로면 이름 옆에서 눌려 보인다. */
.dash-trend .trend-row-dot {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
}

/* 지표명은 머리 라벨의 크기(위 .trend-row-head)를 그대로 쓰고 톤만 올린다 —
   본문색 + 800 이라 옆의 단위(회색 600)와 급이 갈린다(피드백 ⑤ "가독 확보").
   nowrap 은 "혈압(수축기)"가 괄호 앞에서 접혀 단위가 둘째 줄로 밀리는 것을 막는다. */
.dash-trend .trend-row-name {
  min-width: 0;
  white-space: nowrap;
  font-weight: 800;
  color: var(--text);
}

/* 단위는 이름보다 한 급 낮다 — "혈압(수축기) mmHg" 에서 뒤쪽이 보조 정보임을 보인다 */
.dash-trend .trend-row-unit {
  font-size: clamp(12.5px, calc(10.97px + 0.393vw), 16px);
  /* 390px→12.5px, 1280px→16px */
  font-weight: 600;
  letter-spacing: 0;
  white-space: nowrap;
  color: var(--text-mute);
}

/* 차트 칸. recharts ResponsiveContainer 는 부모 높이가 미정이면 0 으로 접히므로
   트랙 크기에 맡기지 않고 height 를 직접 준다. 이 값이 곧 플롯(그림) 높이다. */
.dash-trend .trend-row-chart {
  width: 100%;
  min-width: 0;
  /* recharts XAxis 가 차트 높이에서 30px 을 먼저 떼 가므로 그만큼을 되돌려 준다 —
     이 가산이 없으면 --trend-plot-h 가 "플롯 + 축" 을 합친 값이 되어 그림이 그만큼 눌린다.
     축 높이는 화면 폭을 따라 변하지 않는 recharts 기본값이라 clamp 대상이 아니다.

     [2026-08-27 두 번째 지시] 종전에는 이 가산을 맨 아래 행(.trend-row.is-last)에만 줬다.
     x축 라벨이 월 슬롯이라 맨 아래 행에서만 그렸기 때문이다. 이제 세 행이 각자 일자(MM.DD)
     라벨을 그리므로 가산도 세 행 공통이다 — 프론트가 is-last 클래스 부여 자체를 없앴다.
     [묘비] `.dash-trend .trend-row.is-last .trend-row-chart` 가 이 가산을 갖고 있었다.
     이관 전 실측(라벨이 세 행에 다 붙은 뒤): 1366px 플롯 44px · 390px 플롯 16px 로 눌려 있었다. */
  height: calc(var(--trend-plot-h) + var(--trend-axis-h));
}


/* ===== [dash-v2] 생활관리 가이드 요약 (.dash-guide > .guide-row) ===== */
/* 4행 목록이라 카드 gap 을 0 으로 두고 행마다 위아래 여백 + 구분선을 준다
   (gap 을 남기면 구분선이 여백 한가운데 떠서 행과 붙지 않는다). */
.dash-card.dash-guide {
  gap: 0;
}

/* [2026-08-27 피드백 ⑥ 일관 점검] sp-2 → sp-3. 다른 다섯 카드는 .dash-card 의 gap(sp-3)이
   제목과 본문 사이를 벌리는데 이 카드만 gap 을 0 으로 두고 padding 으로 대신하고 있어
   제목 아래 간격이 혼자 4px 좁았다. 같은 값으로 맞춘다(행 구분선 위치는 그대로). */
.dash-guide>.dash-card-title {
  padding-bottom: var(--sp-3);
}

/* 아이콘은 두 줄(항목명 + 지침)을 세로로 가로지른다 — 문장이 길어 접혀도 정렬이 유지된다 */
.dash-guide .guide-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: var(--sp-3);
  padding: var(--sp-3) 0;
}

.dash-guide .guide-row+.guide-row {
  border-top: 1px solid var(--line);
}

.dash-guide .guide-row:last-child {
  padding-bottom: 0;
}

.dash-guide .guide-icon {
  display: grid;
  place-items: center;
  grid-row: 1 / span 2;
  width: clamp(40px, calc(34.74px + 1.348vw), 52px);
  height: clamp(40px, calc(34.74px + 1.348vw), 52px);
  border: 1px solid var(--blue-line);
  border-radius: 50%;
  background: var(--blue-soft);
  font-size: clamp(19px, calc(16.37px + 0.674vw), 25px);
  /* 390px→19px, 1280px→25px */
  line-height: 1;
}

.dash-guide .guide-name {
  grid-column: 2;
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.3;
  color: var(--text);
}

.dash-guide .guide-text {
  grid-column: 2;
  font-size: clamp(13px, calc(11.25px + 0.449vw), 17px);
  /* 390px→13px, 1280px→17px */
  font-weight: 600;
  line-height: 1.55;
  color: var(--text-sub);
}


/* ===== [dash-v2] 맞춤 관리 포인트 (.dash-points > .point-list) ===== */
/* 도형은 리포트와 같은 컴포넌트(parts.jsx NoteCheckIcon, currentColor 로 그린다)를 쓰지만
   스타일은 영역별로 따로 소유한다 — report.css 의 .report .note-check 는 .report 스코프라
   여기서 걸리지 않고, v8 보존 원칙상 그 파일은 한 줄도 건드리지 않는다(plan §6).
   규격은 리포트와 같다: 아이콘 1em(글줄보다 작아 li 높이를 늘리지 않는다) · 색은 --blue ·
   li 가 [아이콘 | 텍스트] flex 두 칸뿐이라 두 줄로 접히는 항목은 아이콘 폭만큼 자동
   들여쓰기(hanging indent)된다.
   윗여백 .3em = (line-height 1.6em − 아이콘 1em) / 2 — 아이콘 중심을 첫 줄 글자 중심에 맞춘다
   (리포트는 line-height 1.3 이라 .15em 이었다. 값이 다른 것은 글줄 높이가 다르기 때문이다). */
.dash-points .point-list {
  display: grid;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 600;
  line-height: 1.6;
  color: var(--text);
}

.dash-points .point-list li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
}

.dash-points .point-check {
  flex: 0 0 auto;
  width: 1em;
  height: 1em;
  margin-top: .3em;
  color: var(--blue);
}


/* ===== [dash-v2] 반응형 ===== */
/* 780px — 2열 → 1열. 변화 탭(.change-grid)과 같은 전환점이라 두 탭이 같은 폭에서 접힌다.
   카드 순서가 곧 우선순위다(등급 → 요약 → 위험 → 추이 → 가이드 → 포인트, plan §8).
   행별 비율(--col)은 두 카드가 한 줄에 설 때만 뜻이 있다 — 1열에서는 width 로 덮는다
   (선택자 특정도가 위 [행별 폭 비율]과 같고 뒤에 있어 이긴다). */
@media (max-width: 780px) {
  .dash-grid>.dash-card {
    width: 100%;
  }
}

/* ===== [dash-v2] 1행 비율 — 2×2 구간 재조정 (2026-08-27 사용자 지시) ===== */
/* 기본 비율 .28/.72 는 요약 4칸이 **한 줄(1×4)** 로 설 때를 위한 값이다. 1250px 이하로 내려와
   2×2 로 접히면 요약은 그 폭이 필요 없어지는데(칸이 두 배 넓어질 뿐이다) 등급 카드는 계속
   28% 에 묶여 표정 원·등급·설명이 비좁게 눌린다(사용자 스크린샷 ~1180px).
   이 구간만 **.36/.64** 로 옮긴다 — 등급 카드가 약 29% 넓어지고, 요약은 11% 줄지만 2×2 라
   칸당 폭은 오히려 종전 1×4 때보다 넓다.
   구간 하한 781px 인 이유: 780px 이하는 아래 [반응형]이 두 카드를 100% 폭으로 덮으므로
   비율 자체가 뜻을 잃는다. 상한 1250px 은 요약 1×4 전환점(1251px)과 맞물린다 — 배치가
   바뀌는 지점과 비율이 바뀌는 지점을 같게 두어야 그 경계에서 한 번만 움직인다. */
@media (min-width: 781px) and (max-width: 1250px) {
  .dash-grid>.dash-grade {
    --col: .36;
  }

  .dash-grid>.dash-summary {
    --col: .64;
  }
}

/* 620px — 체성분 칸의 보조값("골격근량 · 체지방률 22.6%")이 이 폭부터 두 줄로 접힌다.
   보조행 최소 높이를 두 줄로 올려 네 칸의 콘텐츠 높이를 다시 같게 맞춘다(y 정렬 조건).
   [묘비 620px] 미니카드 머리 행(지표명 | 측정일) 2단을 이 폭에서 1열로 되돌리던 분기가
   있었다. 시안 재배치로 **전 폭이 1열 템플릿**이 되면서(위 [주요 요약 4칸] 참고) 이 분기가
   기본값과 같아져 지웠다. */
@media (max-width: 620px) {
  .dash-summary .mini-card {
    --sub-h: clamp(36px, calc(30.8px + 1.34vw), 48px);
    /* 두 줄 — 390px→36px, 1280px→48px */
  }
}

/* 480px — 카드 안쪽 여백을 한 단계 줄여 .mini-card 2×2 가 눌리지 않게 한다.
   390px 기준 실측: 카드 안쪽 폭 ≈ 390 − 24(페이지) − 32(카드) = 334px → 칸당 163px. */
@media (max-width: 480px) {
  .dash-card {
    padding: var(--sp-4);
  }

  .dash-summary .mini-grid {
    gap: var(--sp-2);
  }

  .dash-summary .mini-card {
    padding: var(--sp-2) var(--sp-3);
  }
}

/* [묘비 — 미니카드 2배 아이콘 메달리온 (1100~1299px)] 2026-08-27 오전 사용자 지시 "아이콘을
   2배로" 로 만들었던 분기다. 칩을 absolute 로 흐름에서 빼 세로 자리를 0 으로 만들고
   (`--ico-d: clamp(54px,…,60px)` · `padding-left: calc(--ico-d + 24px)` · 칩 `position:absolute;
   left/top: var(--sp-3)` · 글리프 `clamp(28px,…,31px)`), 칩이 이름·값 두 줄을 세로로 가로지르는
   왼쪽 메달리온이 되게 했다.
   같은 날 오후 사용자가 **참고 시안 레이아웃 그대로**를 지시했고, 시안의 머리 행은
   [작은 아이콘 + 지표명] 이다 — 메달리온이 들어설 자리가 없다. 칩은 기본값(24~32px 인라인)
   하나로 통일하고 이 분기를 걷어냈다.
   ※ 되살릴 일이 생기면 카드에 position:relative 를 함께 돌려놔야 한다(칩의 컨테이닝 블록).
     그 선언도 시안 재배치에서 함께 빠졌다. */


/* ===== [dash-v2] 주요 요약 1×4 한 줄 — 1300px 이상 (2026-08-27 사용자 지시 ②) ===== */
/* "참고 시안은 4칸이 한 줄이다" — 2×2 를 1×4 로 편다. 위 [행별 폭 비율]의 .28/.72 와 한 세트다.

   [2026-08-27 사용자 지시] 전환점을 1300 → **1251px** 로 내렸다("1300 직하에서 바뀌지 말고
   1250px 이하에서만 2×2 로"). 종전 1300 근거는 시안 재배치·타이포 확대(판정 22px·아이콘 40px)
   **이전** 레이아웃 실측이라 지금 배치로 다시 쟀다 — 가장 넓은 조각은 판정 "식후 정상 범위"다.
   1251~1300px 구간을 지키려고 값·단위·보조값 clamp 하한만 한 단계 내렸다(값 34→32px,
   단위·보조 15→14px). 지표명 22px·판정 22px·아이콘 40px 은 그대로 간다.
   1250px 이하는 2×2 로 돌아간다(칸이 두 배 넓어진다).

   한 줄이 되면 카드 안 미니카드 **행이 하나뿐**이라, 칸마다 여러 줄로 쌓아도 요약 카드는
   종전 2×2(두 행)보다 낮다.
   ※ 칸 안 배치(1열 세로 스택)는 이제 전 폭 공통이라 여기서 다시 선언하지 않는다 — 이 분기가
     하는 일은 **그리드를 4열로 펴는 것**과, 좁아진 칸에 맞춰 값·단위·보조값을 한 급 낮추는
     것 둘뿐이다. */
@media (min-width: 1251px) {
  .dash-summary .mini-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  /* 값·단위·보조값만 한 급 낮춘다 — 지표명(19px)·판정(20px)·측정일(13px)은 원래 폭이
     좁아 그대로 둔다. 위계(이름 < 값 > 상태)는 값 34px 대 상태 20px 로 여전히 유지된다. */
  .dash-summary .mini-value {
    font-size: clamp(32px, calc(11.2px + 1.62vw), 37px);
    /* 1251px→32.5px, 1500px→37px — 하한을 34 → 32px 로 내렸다(전환점 1300→1251px 재실측) */
  }

  .dash-summary .mini-unit,
  .dash-summary .mini-sub {
    font-size: clamp(14px, calc(1.5px + 1vw), 16.5px);
    /* 1251px→14px, 1500px→16.5px — 보조값 "골격근량 · 체지방률 22.6%" 가 한 줄에 남는 크기 */
  }
}


/* ===========================================================================
   [frontend-dev] login-id-field — 회원가입 "아이디 + [중복확인]" 한 행 (2026-09-03)
   계약 §1 기능 A. 입력칸과 버튼이 늘 같은 줄에 서고, 390px 에서도 줄바꿈되지 않게
   버튼 폭만 줄인다(입력칸이 남는 폭을 전부 가져간다).
   토큰·컴포넌트는 기존 것 재사용 — .field / .input-wrap / .field-input / .btn.
   =========================================================================== */

/* 아이디는 로그인 키라 2열 그리드에서도 한 줄을 다 쓴다(1열 폭에서는 무해) */
.signup-field-grid > .signup-id-field {
  grid-column: 1 / -1;
}

.field-check-row {
  display: flex;
  align-items: stretch;
  gap: var(--sp-3);
}

/* 입력칸이 남는 폭을 전부 가져간다 — min-width:0 이 없으면 flex 기본 최소폭 때문에
   좁은 화면에서 버튼을 밀어내 가로 스크롤이 생긴다 */
.field-check-row .input-wrap {
  flex: 1 1 auto;
  min-width: 0;
}

/* 버튼 높이를 입력칸(58px)에 맞춰 아랫변을 나란히 둔다 */
.field-check-row .field-check-btn {
  flex: 0 0 auto;
  min-height: 58px;
  padding: 0 var(--sp-5);
  border-radius: var(--r-md);
  white-space: nowrap;
  font-size: clamp(15px, calc(13.25px + 0.449vw), 19px);
  /* 390px→15px, 1280px→19px */
  font-weight: 700;
}

/* 사용 가능 안내 — 오류(빨강)와 대비되게 초록. 색만으로 구분하지 않도록 문구가 그대로 뜻을 말한다 */
.field-hint.is-ok {
  color: var(--success);
  font-weight: 700;
}

/* 좁은 화면: 버튼 좌우 여백만 줄여 한 줄을 지킨다(줄바꿈시키지 않는다) */
@media (max-width: 480px) {
  .field-check-row {
    gap: var(--sp-2);
  }

  .field-check-row .field-check-btn {
    padding: 0 var(--sp-3);
  }
}
