* { box-sizing: border-box; }

body {
  margin: 0;
  /* Pretendard. 굵기 매핑과 대체 폰트는 fonts.css에 있다 */
  font-family: var(--font-body);
  color: #1f2430;
  background: #0f1420;
  word-break: keep-all;
  overflow-wrap: break-word;
}

.page {
  position: relative;
  min-height: 844px; /* 기준 캔버스 높이 (vh 금지) */
  overflow-x: hidden;
  /* 위쪽은 구름 하늘(랜딩·게스트 입력 폼 공통), 이미지가 끝나는 아래쪽은
     그 마지막 줄 색(#e3d8c5)으로 이어붙여 경계선이 안 보이게 한다.
     입력 폼이 주인공인 화면이라 하늘 위에 흰 베일을 한 겹 덮어 배경을 살짝 눌렀다
     (이미지 자체의 opacity를 낮추면 아래 색까지 같이 흐려져서 이음매가 드러난다) */
  background-color: #e3d8c5;
  background-image: linear-gradient(rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.3)),
    url("assets/scene/sky.png");
  background-position: top center, top center;
  background-size: 100% 100%, 100% auto;
  background-repeat: no-repeat, no-repeat;
}

/* ============ 커버 화면 (레퍼런스 이미지 그대로 사용) ============ */

/* 표지는 "한 화면에 다 들어와야 하는" 화면이다. START 가 이미지 안에 그려져 있어서
   이미지가 다 보이지 않으면 누를 것이 안 보인다.
   그런데 기준 캔버스(844)보다 실제로 보이는 높이가 짧다 - 아이폰 사파리는 주소창·하단바를
   빼고 660~730px 쯤만 준다. 그래서 예전에는 START 를 보려면 스크롤을 내려야 했다
   (390x664 에서 START 아래끝이 778px, 114px 모자랐다).

   --canvas-vh 는 canvas.js 가 넣어 주는 "실제로 보이는 높이를 캔버스 좌표로 바꾼 값" 이다.
   캔버스 안이라 vh 를 직접 쓸 수 없어서(transform: scale 때문에 배율만큼 커진다) 이 값을 쓴다.
   화면이 844 보다 짧으면 그만큼 표지를 줄여서 통째로 넣는다.

   커버는 1206x2190(1:1.816) 이고 기기별로 보이는 비율은 1:1.475(아이폰 SE)~1:1.92(갤럭시)라,
   통째로 넣으면 좌우에 여백이 남았다(아이폰 SE 73px). 다른 화면은 폭에 꽉 차는데
   표지만 여백이 있어 어색했다. 그래서 폭을 항상 꽉 채우고 넘치는 만큼 "위 하늘" 을 자른다.

   위만 자르는 게 안전한 이유: 지붕 꼭지점이 그림의 26.53% 에서 시작한다.
   아래를 자르면 START 와 푸터가 날아가므로 아래는 손대지 않는다.
   다만 위 하늘에는 로고가 들어가야 해서, 자를 수 있는 양에 한계가 있다.
   그 한계를 넘는 기기(폴더블 펼친 화면)에서는 꽉 채우기를 포기하고 그림을 줄인다
   (아래 --cover-w 2번 항). 그러면 좌우에 여백이 생기고 .cover::before 가 메운다.

   아래 값들은 이 규칙을 푼 것이다 (자식들이 물려받아 쓴다):
   --cover-w    표지 폭. 화면이 아주 길면(갤럭시) 폭도 같이 키워 세로를 채운다.
   --cover-h    그때의 표지 높이 (= 폭 x 1.816).
   --cover-crop 화면 위로 잘려 나가는 양. */
.cover {
  position: relative;
  /* min-height 가 아니라 고정 높이다. 넘치는 하늘을 잘라야 해서 늘어나면 안 된다 */
  height: var(--canvas-vh, 844px);
  /* 집은 그림 안에서 이미 가운데다. 지붕 꼭지점이 49.88% 로, 그림 중앙(50%)과 1px 차이다
     (건물 좌우 끝으로 재면 48.92% 가 나오는데 처마·그림자가 좌우로 달라서 그렇다.
      기준은 꼭지점이다). 그래서 그림을 좌우로 밀 필요가 없다.
     상한 430px: 화면이 아주 길면 폭을 키워 세로를 채우는데, 키운 만큼 좌우가 잘린다.
     건물이 좌 10.95% / 우 13.02% 에서 시작하므로 한쪽 5% 까지만 잘리게 여기서 멈춘다. */
  /* 표지 폭을 정하는 규칙. 셋 중 가장 작은 값이 이긴다.
     1) 390px  - 화면 폭에 꽉 채운다 (기본. 화면이 아주 길면 430px 까지 키운다)
     2) (보이는높이 - 20) / 1.6248  - "로고가 지붕 꼭지점 위 하늘에 다 들어가는" 폭.
        위를 많이 잘라내는 기기(폴더블 펼친 화면)에서는 하늘이 모자라 로고가
        지붕에 얹혔다. 그럴 때는 꽉 채우기를 포기하고 집·START 까지 통째로 줄인다.
        1.6248 은 "로고(16%) + 위아래 여백 20px 이 꼭지점(26.53%) 위에 들어간다" 를
        풀어서 나온 값이다. 로고 크기를 바꾸면 이 값도 다시 구해야 한다.
     하한 300px: 더 줄이면 도로 여백(폭의 12%)이 푸터(34.7px)보다 좁아져 START 를 덮는다. */
  /* !! 아래 세 값은 계산식을 못 읽는 브라우저를 위한 고정값이다.
        진짜 계산은 이 규칙 다음의 @supports 블록에서 덮어쓴다.
        무효한 커스텀 속성은 var() 의 기본값도 안 먹어서, 이걸 쓰는 쪽(top/height)이
        통째로 auto 가 된다 - 실제로 로고가 원래 크기로 튀어나온 적이 있다. */
  --cover-w: 390px;
  --cover-h: 708px;
  --cover-crop: 0px;
  /* 그림이 화면보다 짧을 때(위 상한에 걸린 경우) 남는 자리를 위아래로 나눠 가운데 둔다.
     넘칠 때는 0 이라 아래에 딱 붙는다. */
  padding: 0 0 calc(max(0px, var(--canvas-vh, 844px) - var(--cover-h)) / 2);
  display: flex;
  flex-direction: column;
  align-items: center;
  /* 아래(도로·START·푸터)를 바닥에 맞추고, 넘치는 위쪽 하늘을 잘라낸다 */
  justify-content: flex-end;
  text-align: center;
  background: #c9a79e; /* 커버 이미지 하단(도로) 색과 이어지도록 */
  overflow: hidden;
  transition: opacity 0.2s ease;
}
/* 계산식(중첩 clamp/min/max)을 다 읽는 브라우저에서만 진짜 값으로 덮는다.
   못 읽으면 위 고정값(390 / 708 / 0)이 남아, 폭 390 으로 꽉 차고 자르지 않는다. */
@supports (width: clamp(300px, min(max(390px, calc(100px / 1.816)), calc((100px - 20px) / 1.6248)), 430px)) {
  .cover {
    --cover-w: clamp(
      300px,
      min(
        max(390px, calc(var(--canvas-vh, 844px) / 1.816)),
        calc((var(--canvas-vh, 844px) - 20px) / 1.6248)
      ),
      430px
    );
    --cover-h: calc(var(--cover-w) * 1.816);
    --cover-crop: max(0px, calc(var(--cover-h) - var(--canvas-vh, 844px)));
  }
}

/* 표지가 줄어들면 좌우에 여백이 생긴다. 같은 그림을 흐리게 깔아 메운다.
   흐리지 않게 깔면 말풍선·화분이 잘린 채 겹쳐 보여서 실수처럼 보인다.
   inset 을 음수로 줘서 흐림 때문에 가장자리가 비치는 것도 막는다. */
.cover::before {
  content: '';
  position: absolute;
  inset: -8%;
  background: url('assets/hero-village.png') center / cover no-repeat;
  filter: blur(22px);
  z-index: 0;
}
/* 표지 본체는 흐린 배경 위에 온다 */
.cover > .cover-image-wrap { position: relative; z-index: 1; }
.cover.fade-out { opacity: 0; }

/* 이미지 크기에 맞춰 줄어들어야 한다 - 타이틀·START 자리가 이 상자의 % 로 잡혀 있어서
   상자가 이미지보다 크면 그 좌표가 전부 어긋난다 */
.cover-image-wrap {
  position: relative;
  /* 항상 화면 폭을 꽉 채운다. 화면이 아주 길면 폭을 더 키워 세로를 채우고
     좌우가 조금 잘린다 (그래도 건물은 가운데라 안 잘린다) */
  width: var(--cover-w, 402px);
  max-width: none;
  flex: none;
  /* !! margin: 0 auto 를 쓰면 안 된다. flex 항목이 컨테이너보다 넓을 때 auto 마진은
        0 으로 풀려서 왼쪽에 붙는다 (가운데 정렬은 부모의 align-items: center 가 한다) */
  margin: 0;
  line-height: 0;
}
.cover-image {
  display: block;
  /* 폭은 부모(.cover-image-wrap)가 정하고, 높이는 비율대로 따라간다.
     높이를 제한하지 않는다 - 넘치는 만큼 위가 잘리는 게 이 화면의 규칙이다.
     !! min-width / max-height 로 크기를 묶으면 안 된다. 둘이 충돌하면
        img 의 기본 object-fit 이 fill 이라 그림이 눌린 채 늘어난다. */
  width: 100%;
  height: auto;
  max-width: none;
  max-height: none;
}

/* ── 로고 둥둥 ──────────────────────────────────────────────────
   커버(hero-village.png)에서 로고를 오려내 cover-title.png로 빼고, 커버의 그 자리는
   하늘로 메웠다. 그래서 로고만 따로 천천히 위아래로 뜬다.
   위치·크기는 오려낸 좌표 그대로다 (원본 1206x2190 안에서 x 210, y 119, 폭 867).
   좌표는 눈대중이 아니라 cover-title.png 를 커버에 픽셀 매칭해서 찾은 값이다.
   transform의 %는 이 이미지 자신의 크기 기준이라, 화면이 커져도 움직이는 폭이 같이 커진다. */
.cover-title {
  position: absolute;
  /* 지붕 꼭지점(그림의 49.88%)에 'VILLAGE' 의 두 번째 L 과 A 사이를 맞춘다.
     그 틈은 로고 그림 안에서 x 387~390 / 867 = 44.81% 지점이다
     (cover-title.png 의 흰 글자면만 골라 글자 덩어리 사이 빈 칸을 재서 찾았다.
      V 와 I 는 붙어 있어 덩어리가 7개가 아니라 6개로 잡힌다).
     그래서 왼쪽 끝을 꼭지점에 두고 자기 폭의 44.81% 만큼 왼쪽으로 당긴다. */
  left: 49.88%;
  transform: translateX(-44.81%);
  /* 원래 자리는 위에서 5.43% ~ 24.15% 이고, 지붕 용마루가 26.53% 라 2.38% 만 비어 있다.
     화면이 짧으면 위가 잘려 로고가 날아가므로, 잘린 만큼 아래로 내리고 그만큼 줄인다.
     기준선은 33.2%(지붕 어깨선) - 용마루(26.53%)로 잡으면 아이폰 SE 에서 로고가 33px 까지
     작아져 읽히지 않는다. 대신 아주 짧은 화면에서는 로고 아래가 지붕에 조금 얹힌다.
     가로 모드처럼 하늘이 아예 안 보이면 0 이 되어 사라진다. */
  /* 뒤의 +8px 은 눈으로 보고 조금 내린 값이다. 앞의 max 는 "잘린 만큼 따라 내려오기" 다.
     크기 상한도 이 값을 그대로 빼서 계산하므로, 내리면 그만큼만 작아지고 지붕은 안 밟는다. */
  /* 기본값 - 계산식이 안 먹어도 이 자리·이 크기로 뜬다 (아래 @supports 참고) */
  top: 5.43%;
  width: 71.89%;
  height: auto;
  /* 16% 가 기본 크기다(그림에 박혀 있던 원래 크기는 18.72% 인데 커 보여 줄였다.
     폭으로는 390 기준 280px -> 239px).
     가운데 항은 지붕 꼭지점(26.53%)에 얹히지 않게 하는 상한이라, 하늘이 좁을수록 작아진다.
     !! 하한 11% 가 꼭 필요하다. 폴더블처럼 보이는 높이가 짧은 기기에서는 이 상한이
        0 에 가까워져 로고가 몇 px 짜리로 쪼그라들었다(실제 제보). 그런 기기에서는
        작아지는 대신 지붕 위에 얹히게 둔다 - 박공은 넓은 초록 면이라 글자가 읽힌다. */
  pointer-events: none;
  animation: logo-float 4.5s ease-in-out infinite;
}
/* 화면이 짧을 때 '잘린 만큼 내려오고 그만큼 작아지는' 규칙.
   clamp/min/max 를 다 읽는 브라우저에서만 켠다 - 못 읽으면 위 고정 크기가 남는다.
   (height 로 줄이고 width:auto 로 비율을 따라가게 하는 구조라, 둘을 같이 켜야 한다) */
@supports (height: clamp(11%, calc(26.53% - max(5.43%, calc(0px + 8px)) - 4px), 16%)) {
  .cover-title {
    --logo-top: calc(max(5.43%, calc(var(--cover-crop, 0px) + 8px)) + 8px);
    top: var(--logo-top);
    height: clamp(11%, calc(26.53% - var(--logo-top) - 4px), 16%);
    width: auto;
  }
}
/* transform 은 가운데 정렬에 쓰고 있어서, 둥둥 뜨는 건 translate 속성으로 따로 준다
   (둘을 한 transform 에 넣으면 애니메이션이 가운데 정렬을 덮어쓴다) */
@keyframes logo-float {
  0%, 100% { translate: 0 -3%; }
  50% { translate: 0 3%; }
}
/* 기기에서 '움직임 줄이기'를 켜두면 정지한다 (윈도우: 설정 > 접근성 > 시각 효과 > 애니메이션 효과) */
@media (prefers-reduced-motion: reduce) {
  .cover-title { animation: none; }
}

/* 이미지 안의 "시작하기" 버튼 위치에 맞춘 투명 클릭 영역 (실제 접근 가능한 버튼) */
.cover-cta-hit {
  position: absolute;
  /* 이미지에 그려진 START 알약 위치. 1206x2190 안에서 x 280~914, y 1861~2045 이고,
     예전 커버의 알약 패치를 새 커버에 픽셀 매칭해서 찾았다(오차 2.4/255). */
  left: 23.22%;
  right: 24.21%;
  top: 84.98%;
  height: 8.40%;
  border: none;
  background: rgba(20, 40, 40, 0);
  cursor: pointer;
  border-radius: 999px;
  transform: scale(1) translateY(0);
  transition: transform 0.12s ease, background 0.12s ease, box-shadow 0.12s ease;
  -webkit-tap-highlight-color: transparent;
}
.cover-cta-hit:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 2px;
}
/* 누르는 순간: 살짝 눌리면서 어두워지고, 안쪽으로 그림자가 들어가 "꾹" 눌리는 느낌 */
.cover-cta-hit:active,
.cover-cta-hit.is-pressed {
  transform: scale(0.96) translateY(2px);
  background: rgba(20, 40, 40, 0.14);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.18);
}

.fade-in { animation: fadeIn 0.25s ease; }
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.hero {
  position: relative;
  z-index: 1;
  max-width: 420px;
  margin: 0 auto;
  /* 생년월일 입력 화면이 844px 캔버스 안에 다 들어와야 해서 위아래를 줄였다 */
  padding: 30px 22px 24px;
  text-align: center;
}

.eyebrow {
  margin: 0;
  font-size: 14.5px;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.02em;
  color: #5c6270;
}

.title {
  margin: 6px 0 10px;
  font-size: 47px;
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -0.03em;
  color: #1a1a2e;
}

/* FATE VILLAGE 타이틀 이미지 (랜딩이미지_타이틀2.png).
   원본 1206x645 안에서 글자는 (170,97)~(1028,549) = 859x453 이고 사방이 투명 여백이다.
   여백을 그대로 쓰면 위아래로 30%가 빈 채 자리를 먹으므로, 배경 이미지로 깔고
   글자 영역만 보이게 잘라낸다. 배율 s = 타이틀폭 / 859.
   크기는 --title-w(글자 폭) 하나만 고치면 나머지가 따라온다. */
.title-image {
  --title-w: 162px;
  width: var(--title-w);
  height: calc(var(--title-w) * 453 / 859);
  /* 위: '초대받았어요'와, 아래: 서브타이틀과 각각 띄운다.
     아래 값을 줄이면 서브타이틀이 로고쪽으로 붙는다 (그 아래 전체가 같이 올라온다) */
  margin: 18px auto 16px;
  background-image: url('assets/title-fate-village.png');
  background-repeat: no-repeat;
  background-size: calc(var(--title-w) * 1206 / 859) auto;
  background-position:
    calc(var(--title-w) * -170 / 859)
    calc(var(--title-w) * -97 / 859);
}

.subtitle {
  margin: 0 0 18px;
  font-size: 15.5px;
  /* 얇으면 배경 구름에 묻혀서 조금 올렸다. 700 까지 가면 제목과 다툰다 */
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: -0.015em;
  color: #4d5262;
}

.card {
  background: rgba(255, 255, 255, 0.86);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: 22px;
  padding: 20px 20px;
  box-shadow: 0 20px 40px rgba(30, 30, 60, 0.14);
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.field { display: flex; flex-direction: column; gap: 5px; }
.field label {
  font-size: 14.5px;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: -0.015em;
  color: #2a2f3d;
}
/* 라벨 옆 보조 안내 - 라벨보다 약하게 두어 본 이름을 가리지 않는다 */
.field label .label-sub { font-weight: 500; color: #8a8f9c; }

.field input[type="text"],
.field select {
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid #dfe1e8;
  font-size: 17px;
  line-height: 1.4;
  letter-spacing: -0.015em;
  font-family: inherit;
  background: #fff;
  color: #1f2430;
}
/* 아직 안 채운 칸의 안내 글자. YYYY·MM·DD 는 브라우저 기본 회색(#757575)에 맡겨져
   있었고 그 값은 브라우저마다 다르다. 시간대 셀렉트의 안내 문구와 같은 색으로 고정해,
   "안 채운 칸" 과 "입력한 값(#1f2430)" 이 색으로 구분되게 한다.
   opacity 는 파이어폭스가 기본 0.54 를 넣어서 1 로 되돌린다.
   셀렉트는 빈 값이 required 를 어기므로 :invalid 로 잡는다 (모름 체크로 disabled 되면
   원래 흐린 색이니 그때는 건드리지 않는다). */
.field input::placeholder {
  color: #8a8f9c;
  opacity: 1;
}
.field select:invalid:not(:disabled) {
  color: #8a8f9c;
}
.field input:focus,
.field select:focus {
  outline: none;
  border-color: #111318;
  box-shadow: 0 0 0 3px rgba(17, 19, 24, 0.15);
}

/* 생년월일 칸은 type="text" + inputmode="numeric" 이다 (type="number" 아님).
   number 를 쓰면 값이 유효하지 않을 때 el.value 가 빈 문자열로 나와서, 화면에 남은
   한글·영문을 코드가 보지도 지우지도 못한다. 게다가 e / + / - / . 는 그냥 통과하고
   maxlength 도 안 먹는다. 숫자만 받는 일은 forminput.js 가 처리한다.
   inputmode 가 숫자라서 휴대폰에서는 그대로 숫자 키패드가 올라온다. */

.field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'><path fill='%231f2430' d='M7 10l5 5 5-5z'/></svg>");
  background-repeat: no-repeat;
  background-position: right 13px center;
  padding-right: 38px;
}

.hint {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.6;
  letter-spacing: -0.01em;
  color: #8a8f9c;
}
.error { margin: 0; font-size: 13.5px; color: #e0554f; min-height: 14px; }
/* 제출 자체가 실패했을 때(서버·네트워크) 쓰는 자리. 칸 하나의 문제가 아니므로
   버튼 바로 위에서 가운데로 보여준다. 빈 동안 자리를 차지하지 않게 min-height 를 0 으로 둔다 */
.error-form {
  min-height: 0;
  margin: 0 0 8px;
  text-align: center;
  line-height: 1.45;
}
.error-form:empty { margin: 0; }

.date-row { display: flex; align-items: center; gap: 6px; }
.date-row input {
  flex: 1;
  min-width: 0;
  text-align: center;
}
.date-unit {
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: #5c6270;
  flex-shrink: 0;
}

/* 시간대 안내 문구('태어난 시간대를 선택해주세요')는 17px 에서 196px 다.
   '모름' 을 같은 줄에 두면 셀렉트 글자 자리가 188px 뿐이라 문구가 잘렸다.
   셀렉트는 한 줄을 다 쓰고, '모름' 은 아래 안내 문구 줄 오른쪽으로 내렸다.
   (셀렉트 글꼴·크기·여백은 생년월일 칸과 같은 규칙을 그대로 쓴다) */
.time-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.unknown-check {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 14.5px;
  font-weight: 600;
  color: #5c6270;
  white-space: nowrap;
}
.unknown-check input { width: 16px; height: 16px; }

.gender-row { display: flex; gap: 8px; }

/* 개인정보 수집·이용 동의 - 제출 버튼 바로 위 */
.consent-field { margin-bottom: 4px; }
.field label.consent-check {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 14.5px;
  /* label 기본값(700)이 상속돼서 두껍다. 동의 문구는 읽고 넘기는 줄이라 얇게 둔다 */
  font-weight: 400;
  line-height: 1.45;
  cursor: pointer;
}
.consent-check input {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  accent-color: #111318;
}
.field label.consent-check b { font-weight: 500; }

/* 개인정보처리방침 / 이용약관 - 동의 문구 바로 아래.
   체크박스(18px) + gap(8px) 만큼 들여써서 동의 문구와 왼쪽 끝을 맞춘다 */
.policy-link {
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: #8a8f9c;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.policy-link:active { color: #111318; }

.radio-chip {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 0;
  border: 1px solid #dfe1e8;
  border-radius: 12px;
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: #4d5262;
  cursor: pointer;
  background: #fff;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.radio-chip input { position: absolute; opacity: 0; pointer-events: none; }
.radio-chip:has(input:checked) {
  border-color: #111318;
  color: #111318;
  background: #eeeff1;
}

.submit-btn {
  margin-top: 4px;
  padding: 15px;
  border: none;
  border-radius: 14px;
  background: #111318;
  color: #fff;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.02em;
  cursor: pointer;
  box-shadow: 0 10px 20px rgba(17, 19, 24, 0.24);
  transition: transform 0.1s ease;
}
.submit-btn:active { transform: scale(0.98); }

.result-link {
  display: block;
  text-align: center;
  text-decoration: none;
  box-sizing: border-box;
}

/* ── 커버 푸터 ─────────────────────────────────────────────────
   커버 배경(#c9a79e, 이미지 하단 도로색)에 얹히므로 회색이 아니라
   같은 계열의 어두운 갈색을 쓴다. margin-top:auto 로 화면 맨 아래에 붙는다. */
.cover-footer {
  /* 이미지 하단 도로 영역에 얹는다. START(=이미지의 84.98~93.38%) 아래로
     6.62% 가 비어 있고, 그 자리에 두 줄이 들어가야 한다. line-height:0 인 부모라 되돌린다.
     !! 도로 여백은 이미지에 비례해 줄지만 이 글자는 px 이라 안 줄어든다.
        가장 짧은 화면(아이폰 SE, 보이는 높이 575)에서 도로 여백이 38px 이라
        푸터를 33px 안에 넣어야 START 를 밟지 않는다. 줄간격을 좁혀 맞췄다. */
  position: absolute;
  left: 0;
  right: 0;
  bottom: 3px;
  line-height: 1.35;
  text-align: center;

  /* 글자 크기는 표지 폭(.cover 의 --cover-w)을 따라간다. 폭에 꽉 채우게 된 뒤로는
     표지가 390px 아래로 줄지 않아서 실제로는 거의 고정이지만, 화면이 아주 길어
     표지가 커지는 기기(갤럭시)에서는 같이 커진다. */
}
.cover-policy {
  display: flex;
  align-items: center;
  justify-content: center;
  /* 390px 표지에서 11.5px 이 되도록 잡은 비율. 아주 작아지는 것만 막는다 */
  gap: max(4px, calc(var(--cover-w, 390px) * 0.018));
  margin: 0;
  font-size: max(7px, calc(var(--cover-w, 390px) * 0.0295));
  line-height: 1.35;
  letter-spacing: -0.01em;
}
.cover-policy .policy-link {
  color: rgba(58, 38, 32, 0.78);
  text-decoration: none;
}
.cover-policy .policy-link:active { color: #2b1c17; }
.policy-dot { color: rgba(58, 38, 32, 0.4); }
.cover-copy {
  margin: 2px 0 0;
  /* 390px 표지에서 10.5px */
  font-size: max(6.5px, calc(var(--cover-w, 390px) * 0.0269));
  letter-spacing: 0.02em;
  color: rgba(58, 38, 32, 0.5);
}

.hidden { display: none !important; }

.result-panel {
  position: fixed;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(15, 20, 32, 0.55);
}
.result-card {
  width: 100%;
  max-width: 380px;
  background: #fff;
  border-radius: 20px;
  padding: 26px 24px;
  text-align: center;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25);
}
.result-badge {
  display: inline-block;
  margin: 0 0 10px;
  padding: 4px 12px;
  border-radius: 20px;
  background: #eeeff1;
  color: #111318;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0;
}
.result-card h2 {
  margin: 0 0 14px;
  font-size: 21px;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: -0.025em;
  color: #1a1a2e;
}
.result-list { margin: 0 0 14px; display: flex; flex-direction: column; gap: 8px; }
.result-list > div {
  display: flex;
  justify-content: space-between;
  font-size: 15.5px;
  line-height: 1.6;
  letter-spacing: -0.015em;
}
/* 항목 이름(생년월일·태어난 시간·성별·일주·오행)은 읽고 넘기는 줄이라 얇게 둔다 */
.result-list dt { color: #8a8f9c; font-weight: 400; }
.result-list dd { margin: 0; font-weight: 700; color: #1f2430; }
.result-note {
  font-size: 13.5px;
  line-height: 1.65;
  letter-spacing: -0.01em;
  color: #8a8f9c;
  margin: 0 0 16px;
}
/* '생성중' 상태 - 누르는 버튼이 아니다. 3초 뒤 호스트 페이지로 넘어간다 (landing.js) */
.result-link.is-loading {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 글자와 아이콘 사이. 활성 버튼의 .house-icon margin 과 같은 값이어야 네 상태가 같아 보인다 */
  gap: 3px;
  pointer-events: none;
  background: #e8eaee;   /* 아직 안 채워진 부분 - 아주 연한 회색 */
  border-radius: 14px;   /* .submit-btn과 같은 값 */
  isolation: isolate;    /* 아래 loading-label의 blend가 버튼 밖으로 안 새게 */
}
/* 연한 회색 위에서는 어둡게, 검정이 차오른 부분에서는 밝게 - 글자 하나로 양쪽을 다 읽히게 한다.
   흰 글씨로 고정하면 채워지기 전 3초 동안 글자가 안 보이고,
   검은 글씨로 고정하면 채워진 뒤에 안 보인다. */
.result-link.is-loading .loading-label {
  color: #fff;
  mix-blend-mode: difference;
}
/* 왼쪽에서 오른쪽으로 검정이 차오른다. 3초 = landing.js의 BUILDING_DURATION과 같은 값이라,
   다 채워지는 순간 버튼이 활성화된다. 둘 중 하나만 바꾸면 어긋난다. */
/* 채워지는 면이 스스로 둥근 모서리를 갖고, 왼쪽부터 드러나는 방식으로 잘린다.
   예전에는 부모의 overflow:hidden 으로 잘랐는데, iOS 사파리가 border-radius +
   overflow:hidden 안의 transform 자식을 안 잘라내서 폰에서만 버튼이 직사각형으로 보였다.
   (부모에 mask 를 씌우는 우회법도 있지만, 그러면 버튼 그림자까지 같이 잘린다) */
.result-link.is-loading::before {
  content: '';
  position: absolute;
  inset: 0;
  background: #111318;
  border-radius: inherit;
  -webkit-clip-path: inset(0 100% 0 0);
  clip-path: inset(0 100% 0 0);
  animation: fill-progress 3s linear forwards;
}
/* 글자·아이콘은 채워지는 면 위에 있어야 한다 */
.result-link.is-loading > * {
  position: relative;
  z-index: 1;
}
@keyframes fill-progress {
  to {
    -webkit-clip-path: inset(0 0 0 0);
    clip-path: inset(0 0 0 0);
  }
}
/* 다 채워지고 한 박자 쉰 뒤에 뜨는 활성 버튼 - 글자만 툭 갈리지 않게 살짝 밝히며 들어온다 */
.result-link.just-activated {
  animation: cta-appear 0.28s ease-out;
}
@keyframes cta-appear {
  from { opacity: 0.4; }
  to { opacity: 1; }
}
/* 집을 짓는 연출 - 집은 가만히 있고 망치만 뚝딱뚝딱 내려친다 */
.build-icons {
  display: inline-flex;
  align-items: flex-end;
  gap: 1px;
}
/* 글자와 아이콘 사이는 3px 한 값으로만 벌린다 (네 상태 모두 같게).
   .build-icons / .write-icons 안에 들어간 아이콘은 묶음이 이미 간격을 갖고 있어서 0으로 둔다 */
.house-icon { display: inline-block; margin-left: 3px; }
/* 로딩 버튼 안에서는 부모(flex)의 gap 이 간격을 맡는다 - 여기서 또 주면 두 번 들어간다 */
.build-icons .house-icon, .write-icons .house-icon { margin-left: 0; }
.hammer-icon {
  display: inline-block;
  /* 손잡이 끝을 축으로 삼아야 머리가 왼쪽 집 쪽으로 내려찍힌다 */
  transform-origin: 85% 85%;
  animation: hammer-swing 1.5s ease-in-out infinite;
}
/* 천천히 들어올렸다가 툭 내려치고 살짝 튕긴다.
   드는 구간(0~45%)을 내려치는 구간(45~58%)보다 길게 잡아야 망치 무게가 느껴진다. */
@keyframes hammer-swing {
  0%   { transform: rotate(-20deg); }  /* 내려친 상태에서 시작 */
  30%  { transform: rotate(34deg); }   /* 천천히 들어올림 */
  45%  { transform: rotate(40deg); }   /* 정점에서 멈칫 */
  58%  { transform: rotate(-20deg); }  /* 내려침 */
  66%  { transform: rotate(-9deg); }   /* 반동 */
  74%  { transform: rotate(-20deg); }
  100% { transform: rotate(-20deg); }  /* 반 박자 쉼 */
}

/* 준비중 안내 모달 - 캔버스 밖(.overlay-layer)에 두어야 fixed 기준이 화면이 된다 */
.notice-modal {
  position: fixed;
  inset: 0;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(15, 20, 32, 0.55);
}
/* 방침 본문이 길어서 카드 안에서 스크롤된다.
   높이 기준은 vh가 아니라 부모(.overlay-layer)다 - 그 층은 canvas.js가 화면 크기로 잡아준다.
   (캔버스 안에서는 vh를 쓰지 않는다. canvas.css 참고) */
.notice-card {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 330px;
  max-height: 100%;
  padding: 22px 20px 18px;
  border-radius: 20px;
  background: #fff;
  text-align: center;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25);
}
.notice-title {
  margin: 0 0 8px;
  font-size: 17px;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: -0.02em;
  color: #1a1a2e;
}
/* 스크롤되는 문서 본문. 읽는 글이라 좌측 정렬로 되돌린다 */
.notice-doc {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  margin: 4px 0 16px;
  padding-right: 4px;
  text-align: left;
  overscroll-behavior: contain;
}
.notice-doc:focus-visible { outline: 2px solid #111318; outline-offset: 2px; }
.notice-h {
  margin: 16px 0 5px;
  font-size: 13.5px;
  font-weight: 700;
  line-height: 1.45;
  letter-spacing: -0.01em;
  color: #1a1a2e;
}
.notice-h:first-child { margin-top: 0; }
.notice-p {
  margin: 0 0 4px;
  font-size: 13px;
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: -0.01em;
  color: #5a606e;
  word-break: keep-all;
}
.notice-ul {
  margin: 0;
  padding-left: 15px;
  list-style: none;
}
.notice-ul li {
  position: relative;
  margin: 0 0 4px;
  font-size: 13px;
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: -0.01em;
  color: #5a606e;
  word-break: keep-all;
}
/* 가운데 점 하나로 항목을 표시한다 - 기본 불릿은 글줄과 높이가 안 맞는다 */
.notice-ul li::before {
  content: '·';
  position: absolute;
  left: -12px;
  color: #a8adba;
}

/* 약관·방침 원문의 표. 원문은 가로로 넓어 390 폭에 칸을 나란히 둘 수 없다.
   그래서 '항목 이름'을 위에, '내용'을 아래에 쌓고 왼쪽에 선을 그어 한 덩어리로 묶는다. */
.notice-dl {
  margin: 6px 0 10px;
  padding-left: 10px;
  border-left: 2px solid #e4e7ee;
}
.notice-dl dt {
  margin: 8px 0 2px;
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: #3a4050;
  word-break: keep-all;
}
.notice-dl dt:first-child { margin-top: 0; }
.notice-dl dd {
  margin: 0;
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.65;
  letter-spacing: -0.01em;
  color: #5a606e;
  word-break: keep-all;
}

.notice-body {
  margin: 0 0 18px;
  font-size: 14.5px;
  font-weight: 400;
  line-height: 1.6;
  letter-spacing: -0.01em;
  color: #6a6f7d;
}
.notice-close {
  width: 100%;
  padding: 13px;
  border: none;
  border-radius: 12px;
  background: #111318;
  color: #fff;
  font-size: 15.5px;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: -0.015em;
  cursor: pointer;
}


/* 게스트 화면 푸터. 커버와 달리 밝은 배경(하늘/베이지) 위라 갈색 대신 회색을 쓴다.
   폼 아래 흐름에 그대로 두면 되므로 커버처럼 겹쳐 얹지 않는다 */
.page-footer {
  max-width: 420px;
  margin: 0 auto;
  padding: 4px 22px 26px;
  text-align: center;
}
.page-footer .cover-policy .policy-link { color: #6a6f7d; text-decoration: none; }
.page-footer .cover-policy .policy-link:active { color: #111318; }
.page-footer .policy-dot { color: #b9bec8; }
.page-footer .cover-copy { color: #9198a5; }

/* 입력 폼(.hero) 안에 들어간 경우. 부모가 이미 폭·좌우 여백을 잡고 있어서
   그대로 두면 여백이 두 번 들어간다. 위쪽만 띄우고 나머지는 부모에 맡긴다 */
.hero > .page-footer {
  max-width: none;
  padding: 16px 0 4px;
}

/* ── 양력 / 음력 ──────────────────────────────────────────────
   라벨 줄 오른쪽에 작은 칩 두 개. 사주는 양력으로만 계산하므로
   음력을 고르면 서버가 양력으로 바꿔 저장한다 */
.label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}
.label-row > label { margin: 0; }
.cal-toggle {
  display: inline-flex;
  padding: 2px;
  border: 1px solid #dfe3ea;
  border-radius: 999px;
  background: #f3f5f8;
}
.cal-chip {
  position: relative;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 500;
  color: #6a6f7d;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.cal-chip input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.cal-chip:has(input:checked) {
  background: #fff;
  font-weight: 700;
  color: #111318;
  box-shadow: 0 1px 3px rgba(17, 19, 24, 0.12);
}
.cal-chip:has(input:focus-visible) { outline: 2px solid #111318; outline-offset: 1px; }

/* 윤달 칸. 그 해 그 달에 윤달이 있을 때만 나타난다 (forminput.js) */
.leap-check {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 8px 0 0;
  font-size: 13.5px;
  font-weight: 400;
  color: #47505f;
  cursor: pointer;
}
.leap-check input { width: 17px; height: 17px; margin: 0; accent-color: #111318; }
.leap-check em { font-style: normal; font-size: 12.5px; color: #8a8f9c; }

/* 활성된 뒤에도 계속 눌러 달라고 알린다. 한 번 반짝이는 것만으로는
   "이제 눌러도 된다" 가 전해지지 않아 화면에서 멈추는 사람이 있었다.
   크기를 크게 흔들면 멀미가 나니 그림자와 1% 배율로만 숨을 쉬게 한다. */
.result-link.is-ready {
  animation: cta-breathe 2.2s ease-in-out 0.4s infinite;
}
@keyframes cta-breathe {
  0%, 100% { transform: scale(1); box-shadow: 0 6px 18px rgba(17, 19, 24, 0.18); }
  50% { transform: scale(1.012); box-shadow: 0 10px 26px rgba(17, 19, 24, 0.28); }
}
/* 집 아이콘만 살짝 통통 - 글자가 흔들리면 읽기 힘들다 */
.result-link.is-ready .house-icon {
  display: inline-block;
  animation: cta-hop 2.2s ease-in-out 0.4s infinite;
}
@keyframes cta-hop {
  0%, 62%, 100% { transform: translateY(0); }
  70% { transform: translateY(-3px); }
  78% { transform: translateY(0); }
}
/* 움직임을 줄여 달라고 설정한 사람에게는 멈춘 상태로 둔다 */
@media (prefers-reduced-motion: reduce) {
  .result-link.is-ready,
  .result-link.is-ready .house-icon { animation: none; }
}

/* 윤달 안내는 라벨 아래 줄로 내린다. 한 줄에 붙이면 라벨이 밀려
   '윤달이에요' 가 어디서 끝나는지 안 보인다 */
.leap-check { align-items: flex-start; }
.leap-check input { margin-top: 2px; }
.leap-check em {
  display: block;
  margin-top: 1px;
}

/* .field label 이 700 이라 .leap-check 만으로는 못 이긴다 (동의 체크와 같은 방식).
   물어보는 줄이라 제목만큼 무겁게 둘 이유가 없어 한 단계 낮춘다 */
.field label.leap-check { font-weight: 600; }
/* 안내 문구는 .hint('정확한 시간을 모르면…') 와 같은 성격이라 굵기를 맞춘다 */
.field label.leap-check em { font-weight: 400; }

/* ── 첫 화면 로딩 안내 ───────────────────────────────────────────────
   표지 그림(hero-village.png, 2.3MB)이 오기 전까지 화면을 덮는다. 캔버스 밖에
   있어서 canvas.js 를 기다리지 않고 CSS 만으로 첫 화면부터 뜬다
   (vh 는 캔버스 밖이라 써도 된다).

   배경은 커버(.cover)와 같은 색으로 맞춘다. 안내가 사라질 때 색이 튀지 않는다.

   애니메이션으로 서서히 띄우지 않는다. 처음에는 opacity:0 에서 페이드인으로
   넣었는데, 애니메이션이 돌지 않는 환경에서는 안내가 끝까지 투명한 채로 남아
   빈 화면이 그대로 보였다 (헤드리스 크롬에서 실제로 그랬다). 고치려던 문제를
   더 숨기기 어려운 형태로 만드는 셈이라 그냥 바로 보이게 둔다.
   캐시된 경우 landing.js 가 곧바로 지우므로 오래 보이지 않는다. */
.boot {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #c9a79e;
}
.boot-text {
  margin: 0;
  font-size: 14.5px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: rgba(58, 38, 32, 0.62);
}
/* 다 불러오면 자리까지 없앤다 (덮개가 남아 있으면 터치가 막힌다) */
.boot.is-done { display: none; }

/* 표지가 떠 있는 동안에만 페이지를 "보이는 높이" 로 맞춘다 (hero.js 가 붙였다 뗀다).
   844 로 두면 표지를 화면에 맞춰 줄여도 페이지가 그보다 길어서 스크롤이 남는다.
   폼 화면은 844 안에 들어오도록 짜여 있어서 그대로 둔다. */
.page.is-cover {
  min-height: var(--canvas-vh, 844px);
}

/* ── 다크 모드를 "우리가 직접 처리한다" 고 선언한다 ──────────────────
   위쪽 color-scheme: only light 만으로는 삼성 인터넷이 페이지를 강제로 어둡게
   만드는 것을 막지 못했다 (갤럭시 실기기에서 확인 - 크롬은 막힌다).
   브라우저는 사이트가 다크 테마를 갖고 있으면 자기 판단으로 색을 바꾸는 걸
   그만두므로, 블록을 두고 값은 밝은 쪽과 똑같이 넣는다.
   자동 반전만 멈추고 화면은 그대로 두는 것이 목적이다.

   !! 여기 값을 바꾸면 안 된다. 위(라이트)와 반드시 같아야 한다.
      다르게 적으면 다크 모드를 켠 기기에서만 화면이 달라진다.
      위쪽 색을 고치면 여기도 같이 고쳐야 한다. */
@media (prefers-color-scheme: dark) {
  body {
    color: #1f2430;
    background: #0f1420;
  }
  .page {
    background-color: #e3d8c5;
    background-image: linear-gradient(rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.3)),
      url("assets/scene/sky.png");
  }
  .cover {
    background-color: #c9a79e;
  }
  .card {
    background: rgba(255, 255, 255, 0.86);
    border-color: rgba(255, 255, 255, 0.7);
  }
  /* background 축약형을 쓰면 background-image 까지 지워진다 - select 의 드롭다운
     화살표가 다크 모드를 켠 기기에서만 사라졌다. 색만 다시 적는다. */
  .field input[type="text"],
  .field select {
    background-color: #fff;
    color: #1f2430;
    border-color: #dfe1e8;
  }
}
