@charset "UTF-8";
/*
   Pretendard 자체 호스팅. 이 줄이 없으면 아래 --body/--display 의 첫 후보가
   아무 데서도 안 잡혀서 Windows 는 맑은 고딕으로 떨어진다 — 실제로 그 상태였다.

   가변 폰트(font-weight: 45 920) 한 벌이라 이 파일이 쓰는 400/500/600/700/800 이
   전부 정확한 굵기로 나온다. 정적 5벌(3.8MB)을 받는 것보다 작다.

   동적 서브셋이라 @font-face 가 92 개고 각자 unicode-range 를 갖는다. 브라우저는
   그 페이지에 실제로 쓰인 글자가 든 조각만 받는다(한글 폰트는 통짜로 받으면 700KB 대).
   조각이 92 개라 규칙도 92 개 — app.css 에 인라인하면 900 줄이 늘어나므로 파일을 나눴다.

   <link> 가 아니라 @import 인 이유: 템플릿 13 개가 전부 app.css 만 링크하고 있어서,
   여기 한 줄이면 전 화면에 붙는다. 대신 app.css 를 받은 뒤에야 이 파일을 받으러 가는
   왕복 한 번이 더 든다 — 동일 출처 정적 파일이고 font-display: swap 이라 그동안에도
   글자는 폴백으로 보이므로 화면이 비지 않는다.
*/
@import url("/css/pretendard-a2aecb8b109c1eda50cd1ab81fcada9e.css");

/* ------------------------------------------------------------------
   공연 예매 - 공통 스타일
   컨셉: 어두운 객석 + 조명 받는 무대.

   배경을 보라 섞인 남색이 아니라 "중성 근검정"으로 둔 이유:
   이 서비스의 주인공은 포스터 이미지다. 배경에 색기가 있으면 포스터 색과
   싸워서 탁해 보인다. 배경에서 색을 빼야 콘텐츠가 유일한 색이 된다.

   변수명은 예전 그대로 두고 값만 교체했다. 기존 규칙들이 전부 자동으로
   새 값을 따라오므로 화면별 수정이 필요 없다.
   ------------------------------------------------------------------ */
:root {
  /* "로고개선: UI 디자인 및 색감 개선" 커밋이 이 값들을 아래(옛 --radius 뒤)에
     새 :root 블록으로 다시 선언해서, 여기 있던 원래 값(#0B0B0C/#FFC940 등)은
     화면에 전혀 안 쓰이는 죽은 선언이었다 — CSS 커스텀 프로퍼티는 나중 선언이
     그냥 덮어쓰므로 문법 오류 없이 조용히 무효화된다. 두 블록을 하나로 합쳤다. */
  --ink:        #111214;   /* 객석 어둠 (중성, 살짝 웜톤) */
  --ink-soft:   #1A1B1E;   /* 카드/표면 */
  --ink-raised: #232428;   /* 한 단계 더 뜬 표면 (입력창 등) */
  --paper:      #F8F4EC;   /* 본문 (아이보리) */
  --brass:      #E9B44C;   /* 무대 조명 (웜 골드) */
  --brass-dim:  #B58A38;
  /*
     라이트 배경(--paper) 위에서 글자로 쓰는 브라스.

     --brass 와 --brass-dim 은 둘 다 어두운 배경 위에 놓일 것을 전제로 고른 값이라
     아이보리 위에서는 각각 대비 1.73 / 2.88 이다. 본문 텍스트 기준(WCAG 1.4.3 의 4.5)에
     한참 못 미쳐서, 라이트 화면에서 강조하려고 브라스를 쓰면 오히려 안 읽힌다.
     같은 색상(hue)을 유지하면서 명도만 내려 4.5 를 넘긴 값이다(측정 4.51).

     채움색으로 쓸 때(브라스 버튼, 어두운 면 위 글자)는 계속 --brass 를 쓴다 —
     그쪽은 배경이 어둡거나 자기가 배경이라 이 문제가 없다.
  */
  --brass-ink:  #8F6A15;
  --rose:       #F26D6D;   /* 매진 */
  --teal:       #63B7AF;
  --plum:       #8D7AAE;
  --mute:       #B6B0A7;   /* 보조 텍스트 */
  --line:       rgba(248, 244, 236, 0.09);
  --line-strong: rgba(248, 244, 236, 0.18);

  /* 명조체를 버리고 Pretendard 하나로 통일. 한글은 자간을 벌리면 즉시 올드해진다.
     첫 후보가 위에서 @import 로 실어온 자체 호스팅 가변 폰트다. 그 다음 'Pretendard' 는
     방문자 PC 에 이미 깔려 있는 경우로, 웹폰트를 한 조각도 안 받고 끝난다. */
  --display: 'Pretendard Variable', 'Pretendard', -apple-system, 'Malgun Gothic', sans-serif;
  --body: 'Pretendard Variable', 'Pretendard', -apple-system, 'Malgun Gothic', sans-serif;

  /*
     좌석 등급 색. 이 화면에는 포스터가 없어서 "배경에서 색을 빼 포스터를 유일한 색으로
     둔다"는 위 원칙이 적용되지 않는다 — 여기서는 등급이 곧 콘텐츠다.

     종전에는 등급을 앰버 한 색의 투명도 + 테두리 굵기로만 구분했는데 둘 다 무너져 있었다.
     측정값: 좌석 대비 명암비가 VIP 11.88 / R 6.03 / S 2.28 / A 1.23 로 S·A 가 WCAG
     1.4.11(비텍스트 3.0) 미달이었고, 굵기도 맞춤 배율에서 R·S·A 가 전부 0.67px,
     확대해도 R 과 S 가 똑같이 1.33px 라 4단계가 실제로는 3단계로 접혀 있었다.
     콘서트홀 기준 2,481석 중 1,619석(65%)이 서로 구별되지 않는 상태였다.

     그래서 색상(hue)으로 가르되 **명도도 같이 단조 감소**시킨다. 색상만으로 가르면
     색각 이상이나 흑백 출력에서 등급 순서가 사라진다. 네 색 모두 배경 대비 3.0 을 넘는다
     (VIP 9.08 / R 5.33 / S 4.06 / A 3.37). 더 선명한 팔레트도 검토했지만
     (#FFC940/#F0835A/#4E8FBF/#5F5F69) 무대 조명 연출과 부딪혀 이쪽으로 낮췄다.
  */
  --grade-vip: #D9A93F;
  --grade-r:   #BF7150;
  --grade-s:   #4A7791;
  --grade-a:   #646470;
  /* 예매 불가 좌석은 무채색으로 뒤로 물린다. 종전엔 판매 완료가 --rose 였는데,
     살 수 없는 좌석이 가장 눈에 띄는 색을 갖는 건 시선 유도가 거꾸로 된 것이다. */
  --seat-held: #3A3A42;
  --seat-sold: #212125;

  /*
     모서리 반경 스케일. 종전엔 값이 10종류로 흩어져 있었고, 더 문제는 화면별로 갈렸다는
     것이다 — 목록·상세·로그인·결제는 둥근데 관리자 대시보드·마이페이지·예매 CTA·배지는
     각진 채로 남아 같은 앱 안에 세대가 둘 섞여 보였다. 아래 넷 안에서만 고른다.

       --radius     카드·패널 (통계 카드, 차트 상자, 예매 내역 줄, 필터 패널)
       --radius-sm  버튼·입력 (결제 버튼, 관리자 필터, 취소 버튼)
       --radius-xs  작은 컨트롤 (비밀번호 표시 토글, 인라인 코드, 지도)
       999px        알약 — 칩·필터·배지처럼 "고르는 것"

     예외는 셋뿐이고 이유가 분명하다. ① 좌석·범례 견본(3px)과 미니맵 블록(1~2px)은 요소
     자체가 10~13px 이라 위 값을 쓰면 모서리가 면적을 다 먹는다(견본은 좌석과 같아야 하므로
     같은 값을 쓴다). ② 카카오 로그인 버튼(12px)은 공식 디자인 가이드 수치라 우리 스케일에
     맞추면 안 된다. ③ 무대는 반원 곡선이라 반경이 아니라 형태다.
  */
  --radius: 14px;
  --radius-sm: 10px;
  --radius-xs: 6px;
  /* 극장 예매 UI 톤앤무드 리디자인(2026-08-06) 전용 — 라이트 배경 화면(홈 등)의 포스터는
     기존 카드보다 더 각지게(6px), 다크 패널(요약 카드 등)은 더 크게(16px) 간다.
     화면을 하나씩 이 톤으로 옮기는 동안에만 쓰이므로 전환이 끝나면 --radius 계열로
     정리할 수 있다. */
  --radius-poster: 6px;
  --radius-panel: 16px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background:
    linear-gradient(180deg, #171619 0%, var(--ink) 420px),
    var(--ink);
  color: var(--paper);
  font-family: var(--body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }

.wrap { max-width: 1080px; margin: 0 auto; padding: 40px 24px 96px; }
/* 좌석 배치도 전용. 1,080px 은 목록·상세의 읽기 폭 기준인데, 한 줄 41석짜리 홀에는
   좁아서 좌석이 필요 이상으로 작아진다. 격자에는 폭을 더 준다. */
.wrap-wide { max-width: 1440px; }

/* 스크롤해도 따라오는 반투명 헤더. 배경을 반투명 + blur 로 깔아
   아래 콘텐츠가 비쳐 지나가게 한다. */
.masthead {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
  margin-bottom: 36px;
  background: rgba(17, 18, 20, 0.78);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
}
.masthead h1 {
  font-family: var(--display);
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.02em;
  margin: 0;
}
.masthead .brandlink { display: inline-flex; align-items: center; }
.masthead .logo-mark {
  width: 28px;
  height: 18px;
  margin-right: 10px;
  color: var(--brass);
  flex-shrink: 0;
}
.masthead .tag {
  font-size: 13px;
  letter-spacing: -0.01em;
  color: var(--mute);
}
.masthead .authnav {
  margin-left: auto;
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px; row-gap: 6px;
  font-size: 13px; color: var(--mute);
}
.masthead .authnav a { color: var(--brass); }
/* 이전엔 navdivider 가 authnav 의 독립 flex 아이템이었는데, 좁은 화면에서 authnav 가
   줄바꿈되면 구분선만 첫 줄 끝에 홀로 남고 로그인/닉네임 묶음은 다음 줄로 떨어졌다.
   navactions(로그인/닉네임+로그아웃 묶음)의 ::before 로 옮기면 묶음과 항상 같이 움직인다. */
.masthead .navactions { display: inline-flex; align-items: center; gap: 10px; }
.masthead .navactions::before { content: ''; flex: none; width: 1px; height: 16px; background: var(--line-strong); }
.masthead .authnav form { margin: 0; }
.masthead .authnav .linklike {
  background: none; border: 0; padding: 0;
  font: inherit; color: var(--brass); cursor: pointer;
}
/* 좁은 화면에서 pill 이 내용 폭보다 더 눌리면 "로그인" 같은 두세 글자 텍스트가
   공백 없이 한 글자씩 줄바꿈된다(CJK 기본 줄바꿈 규칙). flex 아이템은 기본
   min-width:auto 라 내용보다 눌릴 수 있으므로, 줄바꿈 대신 pill 전체가
   다음 줄로 넘어가게 막는다. */
.masthead .authnav .navlink,
.masthead .authnav .who,
.masthead .authnav .linklike,
.masthead .authnav .btn-login,
.masthead .authnav .btn-signup {
  white-space: nowrap;
}

/* ---- 내비 링크(공연 찾기 · 마이페이지) ----

   화면 스코프에 두지 않는다. 헤더가 공용 조각이라 여기 스타일도 공용이어야 한다 —
   알약 호버가 body.page-home 에만 걸려 있어서 홈에서만 눌리는 범위가 보였다.

   호버에 배경을 깔면 어디까지가 누르는 영역인지 보인다. 글자색만 바뀌면 그 범위를
   글자 폭으로 짐작해야 한다. */
.masthead .authnav .navlink {
  padding: 8px 14px;
  border-radius: 999px;
  transition: background .18s, color .18s;
}
/* 지금 보고 있는 구역. 색만으로 알리지 않도록 마크업에서 aria-current="page" 를 같이 준다. */
.masthead .authnav .navlink.active { font-weight: 700; }

/* 마이페이지 링크의 안 읽은 알림 수. 마이페이지 탭 배지(.mypagetabs .tabbadge)와
   같은 생김새(로즈 배경 + 흰 글자 — "확인할 게 남았다"를 브라스와 구분)를 쓰지만,
   여기 있는 이유가 다르다 — 예전엔 이 배지가 마이페이지 탭 안에만 있어서 마이페이지를
   직접 열어보기 전엔 새 알림이 왔는지 알 방법이 없었다. 헤더는 화면마다 톤(다크/라이트)이
   갈리지만 로즈+흰 글자는 둘 다에서 그대로 읽혀서 톤별 오버라이드가 필요 없다. */
.masthead .authnav .navbadge {
  display: inline-block;
  min-width: 16px;
  margin-left: 5px;
  padding: 0 5px;
  border-radius: 999px;
  background: #B23A3A;
  color: #fff;
  font-size: 11px; font-weight: 700; line-height: 16px;
  text-align: center;
  vertical-align: 2px;
}

/* 닉네임 앞 이니셜 원. */
.masthead .authnav .who { display: inline-flex; align-items: center; gap: 7px; }
.masthead .authnav .who-avatar {
  flex: none;
  width: 24px; height: 24px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700;
  line-height: 1;
}

/* ---- 전환 대기 표시 (진행 막대 · 눌린 버튼) ----

   전체 페이지 로드라 링크를 누른 순간부터 다음 화면까지 화면에 아무 변화가 없다.
   막대는 그 사이를 메운다. 자세한 사정은 fragment/navprogress.html 주석 참고.

   z-index 는 이 파일에서 가장 높다(모달 오버레이 70, 토스트 60). 이동은 그 위에
   뜬 것들까지 전부 걷어내고 벌어지는 일이라 무엇에도 가리면 안 된다. */
.navprogress {
  position: fixed;
  top: 0; left: 0;
  z-index: 90;
  height: 3px;
  width: 0;
  background: var(--brass);
  opacity: 0;
  pointer-events: none;
}
.navprogress.show {
  opacity: 1;
  /*
     끝을 모르는 진행이라 92% 에서 멈춰 세운다 — 100% 까지 갔다가 화면이 안 바뀌면
     "다 됐는데 안 나온다"로 읽힌다. 실제 완료는 새 문서가 그려지는 것이다.

     width 를 클래스에도 적어 두는 이유: 모션 최소화 설정에서는 전역 규칙이
     animation 을 끄는데, 그러면 막대가 0 폭으로 남아 아무것도 안 보인다. 이건 장식이
     아니라 "지금 이동 중"을 알리는 표시라 모션을 껐다고 사라지면 안 된다. 애니메이션이
     도는 환경에서는 아래 keyframes 가 이 값을 덮어 0 에서부터 자라난다.
  */
  width: 92%;
  animation: navprogress-grow 8s cubic-bezier(.12, .78, .2, 1) forwards;
}
@keyframes navprogress-grow {
  from { width: 0; }
  to   { width: 92%; }
}

/* 눌린 폼 버튼. disabled 를 안 쓰는 이유는 navprogress.html 주석 참고 —
   두 번째 제출은 스크립트가 막고, 여기서는 "받았다"만 보인다.

   cursor: progress 는 안 쓴다. pointer-events 를 끈 요소는 히트 테스트에서 빠져
   자기 커서가 안 뜨고(부모 것이 뜬다), 애초에 각 버튼의 cursor: pointer 규칙이
   명시도로 이 클래스를 이긴다 — 적어 두면 안 먹는 선언만 남는다. */
.is-pending {
  opacity: .7;
  pointer-events: none;
}

/* ---- 화면 전환 (View Transitions) ----

   같은 출처끼리의 문서 이동을 브라우저가 이어 그리게 한다. 위 진행 막대가 "기다리는
   동안"을 메운다면, 이건 새 화면이 들어오는 순간의 뚝 끊김을 없앤다 — 카드를 눌렀는데
   화면이 통째로 깜빡이고 다른 페이지가 나타나는 것과, 누른 포스터가 그대로 커지면서
   상세가 되는 것은 같은 이동이라도 다르게 읽힌다.

   지원하지 않는 브라우저(파이어폭스 등)는 이 규칙을 무시하고 종전대로 즉시 이동한다.
   기능이 아니라 마감이라 폴백을 따로 만들 게 없다.

   포스터를 잇는 이름(view-transition-name)은 스크립트가 누른 순간에 붙인다 — 이유는
   fragment/navprogress.html 의 해당 블록 주석 참고(한 문서에 같은 이름이 둘이면
   전환이 통째로 무효가 된다). */
@view-transition { navigation: auto; }

/* 기본값(.25s)보다 조금 짧게. 이동은 이미 기다린 뒤에 벌어지는 일이라, 페이드가 길면
   그만큼 화면이 늦게 도착하는 것으로 느껴진다. */
::view-transition-old(root),
::view-transition-new(root) { animation-duration: .22s; }

/* 모션 최소화 설정. 전역 `* { animation: none }` 규칙은 ::view-transition-* 의사요소에
   닿지 않으므로(문서 트리 밖에 있다) 여기서 따로 끈다 — 안 끄면 모션을 껐다고 한
   사용자에게 화면 전체가 움직이는 전환만 남는다. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* ---- 헤더 검색 (아이콘 → 헤더 안 둘째 줄) ----

   접힌 상태는 아이콘 하나뿐이라 종전 헤더의 무게를 바꾸지 않고, 누르면 헤더 안에서
   한 줄이 더 열린다.

   왜 헤더 "안"인가: .masthead 는 sticky 반투명 바다. 검색 줄을 바 아래에 겹쳐 띄우면
   그 뒤로 본문이 비쳐 지나가면서 입력한 글자와 본문 글자가 겹쳐 읽힌다. 헤더의 한
   줄로 넣으면 배경 blur 를 그대로 물려받는다.

   flex-wrap 은 .searchopen 일 때만 켠다. 헤더는 좁은 화면에서 authnav 가 "내부적으로"
   줄바꿈되도록 오래 다듬어 온 자리라(.navactions ::before 주석 참고), 상시로 켜 두면
   닫혀 있을 때도 내비 묶음이 통째로 다음 줄로 떨어지는 경우가 생긴다. 닫혀 있는
   동안에는 규칙이 아예 안 걸리게 두는 편이 안전하다. */
.masthead.searchopen { flex-wrap: wrap; }

.masthead .navsearch-toggle {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  padding: 0;
  background: none; border: 0; border-radius: 999px;
  color: var(--mute); cursor: pointer;
  transition: background .18s, color .18s;
}
/* [hidden] 이 display:inline-flex 에 지므로 되받는다. 스크립트가 없는 브라우저에서
   아이콘이 그대로 보이면 눌러도 아무 일 없는 죽은 버튼이 된다. */
.masthead .navsearch-toggle[hidden] { display: none; }
.masthead .navsearch-toggle:hover { background: rgba(248, 244, 236, .08); color: var(--paper); }
/* 열려 있는 동안은 내비의 "지금 여기"(.navlink.active)와 같은 방식으로 눌린 표시를 한다. */
.masthead.searchopen .navsearch-toggle { background: rgba(248, 244, 236, .10); color: var(--paper); }

/* 홈 히어로 검색과 같은 밑줄형. 흰 알약을 하나 더 만들면 헤더에 없던 면이 생겨서
   로고·내비와 층이 어긋난다. 밑줄은 헤더의 아래 테두리와 같은 언어라 덜 튄다. */
.masthead .navsearch {
  flex: 0 0 100%;
  display: flex; align-items: center; gap: 12px;
  margin-top: 14px;
  padding-bottom: 12px;
  border-bottom: 2px solid var(--line-strong);
  transition: border-color .25s;
  animation: navsearch-in .22s ease;
}
.masthead .navsearch[hidden] { display: none; }
.masthead .navsearch:focus-within { border-color: var(--brass); }
.masthead .navsearch svg { flex: none; color: var(--mute); }
.masthead .navsearch input[type="text"] {
  flex: 1; min-width: 0;
  background: none; border: 0; outline: none;
  font: inherit;
  /* 16px 아래로 내리지 말 것 — iOS 사파리는 그보다 작은 입력칸에 초점이 가면
     화면을 확대해 버리고, 확대된 상태로 헤더만 남는다. */
  font-size: 16px;
  color: var(--paper);
}
.masthead .navsearch input::placeholder { color: rgba(248, 244, 236, .45); }
.masthead .navsearch button {
  flex: none;
  padding: 9px 20px;
  background: var(--brass); color: var(--ink);
  border: 0; border-radius: 999px;
  font: inherit; font-size: 14px; font-weight: 700;
  white-space: nowrap; cursor: pointer;
  transition: transform .2s, background .2s;
}
.masthead .navsearch button:hover { background: #f2c874; }
.masthead .navsearch button:active { transform: scale(.94); }
/* 열릴 때만 도는 한 번짜리. hidden 이 걷히면서 display 가 none → flex 로 바뀌므로
   매번 처음부터 다시 재생된다. 모션 최소화 설정에서는 전역 규칙이 꺼 준다. */
@keyframes navsearch-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- 페이지네이션 ---------- */
.pager {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-top: 40px;
}

/* ---------- 공연 목록 ---------- */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 28px;
}
.card { display: block; }
/* 카드 진입 리빌. JS(IntersectionObserver)가 지원되고 모션을 끄지 않았을 때만
   .grid 에 .reveal 을 붙이므로, JS 가 없거나 실패해도 카드는 기본값(불투명)으로
   항상 보인다 — 애니메이션이 "안 보이면 정상, 보이면 보너스"가 아니라 거꾸로
   "JS 없으면 카드가 영영 안 보임"이 되면 안 된다. */
.grid.reveal .card {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .45s ease, transform .45s ease;
}
.grid.reveal .card.is-visible { opacity: 1; transform: none; }
.card .poster {
  aspect-ratio: 3 / 4;
  background: var(--ink-soft);
  border: 1px solid var(--line);
  overflow: hidden;
  margin-bottom: 12px;
}
.card .poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card h3 { font-size: 15px; margin: 0 0 4px; font-weight: 600; }
.card .meta { font-size: 13px; color: var(--mute); }

/* ---------- 상세 ---------- */
.detail { display: grid; grid-template-columns: 280px 1fr; gap: 40px; }
/* 이 h2(공연명)의 실제 타이포는 아래 body.page-detail h2 가 정한다 — 타입 선택자가
   하나 더 붙어(body.page-detail h2) 명시도가 이 규칙보다 높아서 항상 이겼다.
   같은 값(30px)을 겹쳐 써서 문제가 안 드러났을 뿐, 죽은 규칙이라 지웠다. */
.spec { border-top: 1px solid var(--line); margin-top: 24px; }
.spec div { display: flex; gap: 20px; padding: 11px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.spec dt { width: 84px; color: var(--mute); flex: none; margin: 0; }
/* 관람료 "VIP 180,000원 · R 144,000원 · …". 등급+금액은 붙여 두고 구분점에서만 줄을 바꾼다 —
   그냥 두면 숫자와 '원' 사이가 끊겨 "144,000" 다음 줄에 "원" 만 남는다(실측 360px). */
.spec .fee { white-space: nowrap; }
.spec .feesep { white-space: normal; }

/* ---------- 좌석 배치도 (시그니처) ---------- */
/*
   인원수 선택과 확대 버튼은 상단 헤더(.masthead) 바로 아래에 고정한다. 63줄짜리 콘서트홀에서
   좌석을 고르다가 인원수를 바꾸거나 확대로 전환하려면 매번 맨 위로 스크롤해야 했다.

   top 은 헤더 실제 높이를 써야 한다 — 고정폭이 아니다. .masthead 도 sticky(top:0)라서, 이 값이
   비어 있으면(0) 스크롤 중 두 sticky 요소가 같은 위치를 다투며 겹친다. 헤더 높이는 부제(공연명+
   일시)가 좁은 화면에서 줄바꿈되면 달라지므로 JS 가 실측해 --header-h 로 넘긴다.
*/
.partysize {
  display: flex; align-items: center; gap: 10px;
  justify-content: center;
  margin-bottom: 12px;
  font-size: 13px; color: var(--mute);
  position: sticky;
  top: var(--header-h, 65px);
  z-index: 15;
  padding: 10px 0;
  background: rgba(11, 11, 12, 0.72);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid var(--line);
}
.partysize label { color: var(--paper); font-weight: 600; }
/* 헤더 내비와 같은 문제·같은 해법. flex 아이템 기본 min-width:auto 라 좁은 화면에서
   내용보다 눌리는데, 공백 없는 CJK 라벨("인원수", "전체 보기")은 그때 글자 단위로
   쪼개진다 — 실측 375px 에서 라벨이 16px 폭 / 62px 높이(세 줄)까지 갔다. 줄바꿈 대신
   요소 통째로 다음 줄로 넘어가게 막는다(아래 media query 의 flex-wrap 과 한 쌍). */
.partysize label, .partysize .zoomtoggle { white-space: nowrap; }
.partysize select {
  background: var(--ink-soft); color: var(--paper);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 5px 10px; font: inherit;
}
.partysize .hint { color: var(--mute); font-size: 12px; }
/* 좌석 크기 전환. 기본은 폭 맞춤(가로 스크롤 없음)이고, 누르면 눌러서 고르기 좋은 크기로 키운다. */
.zoomtoggle {
  background: var(--ink-soft); color: var(--paper);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 5px 13px; font: inherit; font-size: 12px;
  cursor: pointer;
}
.zoomtoggle:hover { border-color: var(--brass-dim); }
.zoomtoggle[aria-pressed="true"] { border-color: var(--brass); color: var(--brass); }

/*
   맞춤 모드에서 좌석이 누르기엔 작을 때만(2,505석 홀은 11px 까지 내려간다) 이 버튼을
   주요 액션으로 올린다. 그 순간 할 수 있는 의미 있는 행동이 확대뿐이라서다 —
   좌석을 아직 못 골랐으니 예매 버튼도 비활성이고, 강조가 겹치지 않는다.
   좌석이 충분히 큰 홀에서는 이 클래스가 안 붙어 조용한 보조 버튼으로 남는다.
*/
.zoomtoggle--suggest {
  background: var(--brass); border-color: var(--brass);
  color: var(--ink); font-weight: 700;
  padding: 7px 18px; font-size: 13px;
  box-shadow: 0 0 0 3px rgba(233, 180, 76, .18), 0 4px 14px rgba(233, 180, 76, .35);
}
.zoomtoggle--suggest:hover { border-color: var(--brass); filter: brightness(1.08); }

.stage {
  position: relative;
  margin: 8px auto 44px;
  max-width: 640px;
  height: 70px;
  border-radius: 50% 50% 0 0 / 100% 100% 0 0;
  border-top: 2px solid var(--brass);
  box-shadow: 0 0 24px 1px rgba(255,201,64,.35), inset 0 18px 30px -14px rgba(255,201,64,.5);
  background: linear-gradient(to bottom, rgba(255,201,64,.22), transparent 80%);
  display: grid;
  place-items: center;
  font-family: var(--display);
  letter-spacing: 0.5em;
  font-size: 14px;
  color: var(--brass);
  padding-left: 0.5em;
}
.stage::after {
  content: '';
  position: absolute;
  top: 100%; left: 50%;
  width: 92%; height: 130px;
  transform: translateX(-50%);
  background: linear-gradient(to bottom, rgba(255,201,64,.14), transparent);
  clip-path: polygon(38% 0, 62% 0, 100% 100%, 0% 100%);
  pointer-events: none;
}

/*
   좌석 격자 크기는 --seat-size 하나로 결정된다. 간격/통로 폭이 여기서 파생되므로
   좌석 맵 스크립트는 이 변수 하나만 바꿔서 가장 긴 줄을 컨테이너 폭에 맞춘다.
   JS 가 없거나 실패해도 clamp() 기본값으로 종전과 같은 크기가 나온다.
*/
.housescroll {
  --seat-size: clamp(20px, 4vw, 26px);
  --seat-gap: calc(var(--seat-size) * 0.28);
  --aisle-gap: calc(var(--seat-size) * 1.25);
  /* 구역(열) 사이. 통로보다 넓어야 "다른 구역"으로 읽힌다. 열이 여덟 개인 홀(콘서트홀 3층,
     세종 3층)에서는 이 값이 곧 좌석에 쓸 폭을 깎으므로 통로(1.25)보다 조금만 넓게 잡는다. */
  --zone-gap: calc(var(--seat-size) * 1.4);
  /* 열마다 하나씩 붙는 고정 폭이라 열이 많은 홀에서 그대로 누적된다. 두 자리 열 번호가
     들어가는 최소 폭으로 줄였다. */
  --rowlabel-w: 16px;

  /* 툴팁 여유. 등급·가격 툴팁은 좌석 중앙 기준으로 펼쳐지므로 줄 양끝 좌석에서는
     격자 폭을 넘어간다. 안 보이는 상태(opacity:0)에서도 레이아웃에는 잡혀서, 여유를
     두지 않으면 좌석이 전부 보이는데도 가로 스크롤바가 생긴다.
     좌석 크기 계산도 이 값을 뺀 폭으로 하므로 여기만 고치면 양쪽이 같이 따라온다.

     여유는 구역이 아니라 zoneline(가로로 나란히 놓이는 열들의 묶음) 양끝에만 준다.
     구역마다 주면 세 열짜리 홀에서 좌석에 쓸 폭이 여유로만 380px 넘게 빠진다. 열 사이
     툴팁은 --zone-gap 이 받아내고, 넘치는 만큼은 이웃 구역 위로 겹쳐 뜬다(z-index:5). */
  --tooltip-bleed: 64px;

  /* 맞춤 모드에서는 가로로 넘칠 일이 없지만(폭에 맞춰 좌석을 줄인다), 확대 모드와 아주 좁은
     화면을 위해 남겨둔다. 넘치더라도 페이지 본문이 통째로 옆으로 밀리지 않아야 한다. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: 4px;

  /*
     스크롤 막대는 숨긴다(기능은 그대로). 좌석도는 지도처럼 끌거나 밀어서 보는 화면이고,
     막대가 격자 옆에 붙으면 좌석 격자의 경계처럼 읽혀서 방해가 된다.
     모바일은 원래 겹침(overlay) 막대라 잠깐 보였다 사라지지만, 여기서 같이 눌러둔다.
  */
  scrollbar-width: none;              /* Firefox */
  -ms-overflow-style: none;           /* 구형 Edge */

  /*
     한 손가락 스크롤은 브라우저 기본 동작에 맡기고(관성이 살아 있다), 두 손가락은 우리가
     받아 좌석 크기를 바꾼다. pan-x pan-y 는 pinch-zoom 을 빼서, 격자 안에서의 두 손가락
     제스처가 페이지 확대로 가로채이지 않게 한다. 격자 밖에서는 페이지 확대가 그대로 된다.
  */
  touch-action: pan-x pan-y;
}
/* Chrome·Safari 는 위 두 속성으로 안 되고 이 의사요소를 없애야 한다. */
.housescroll::-webkit-scrollbar { width: 0; height: 0; display: none; }

/*
   확대 모드에서는 격자가 지도처럼 동작한다 — 높이를 고정해 컨테이너 안에서 상하좌우로
   스크롤되게 하고, 마우스로 끌어 움직인다(pan). 높이를 안 주면 세로는 페이지 스크롤이라
   끌어 움직일 대상이 없다.

   overscroll-behavior: contain — 지도 안에서 휠을 굴리다 끝에 닿았다고 페이지가 따라
   움직이면, 좌석을 보다가 화면이 통째로 튀어 나간다.
*/
.housescroll.zoomed {
  overflow: auto;
  height: min(72vh, 860px);
  overscroll-behavior: contain;
  cursor: grab;
  /*
     지도 모드에서는 텍스트 선택을 아예 끈다. 끌기 시작(.dragging) 뒤에만 끄면 늦다 —
     문턱(4px)을 넘기 전에 이미 선택이 시작되고, 브라우저가 선택을 따라가려고 컨테이너를
     자동 스크롤해서 세로가 엉뚱한 곳으로 튄다(실측: 9 로 가야 할 scrollTop 이 866).
     좌석도에서 글자를 선택할 일도 없다.
  */
  user-select: none;
  /*
     스크롤이 잦은 요소라고 미리 알려서 별도 합성 레이어를 앞서 만들어둔다. 이게 없으면
     처음 끌 때 브라우저가 그제서야 레이어를 만드느라 첫 프레임이 끊긴다. 지도 모드에서만
     켠다 — 상시로 켜두면 페이지 전체가 이 레이어 하나를 위한 메모리를 계속 들고 있는다.
  */
  will-change: scroll-position;
}
.housescroll.zoomed.dragging { cursor: grabbing; }

/*
   핀치 중 미리보기용 래퍼. 손가락을 움직이는 동안은 좌석 크기를 건드리지 않고 이 요소만
   scale 한다 — 합성(compositing)만 하므로 레이아웃이 없다. 좌석 크기를 직접 바꾸면 2,481개가
   전부 다시 배치돼서 한 단계에 300ms 가 걸렸고, 핀치가 끊겨 보였다.
   origin 을 0 0 으로 고정해야 스크롤 보정 계산이 간단해진다(콘텐츠 좌표 X -> k*X).
*/
/*
   좌석 격자 전체를 감싸는 상자. 폭을 내용에 맞춘다(max-content).

   기본값(block, 폭 = 스크롤러 폭)이면 좁은 밴드가 "화면 폭" 안에서 가운데를 잡고, 넓은
   밴드만 그 밖으로 넘쳐 흐른다. 그래서 확대해서 가로로 넓어지는 순간 밴드마다 중심이
   달라진다 — 실측(2,015석 홀, 확대): 전체 폭 2,492 인데 1층은 중앙 1,269 인 반면
   무대 뒤 1층 P 와 2층은 707 이라 539px 씩 왼쪽으로 붙었다. 무대는 무대 뒤 밴드를
   따라가므로 같이 밀린다.

   내용 폭으로 두면 모든 밴드가 같은 좌표계를 공유해 중심이 하나로 모인다.
   min-width: 100% 는 반대 경우를 위한 것이다 — 좌석이 화면보다 좁은 홀에서 상자가
   쪼그라들면 밴드들이 왼쪽으로 몰린다.
*/
#housecontent { transform-origin: 0 0; width: max-content; min-width: 100%; }
.housescroll.pinching #housecontent { will-change: transform; }

/*
   끌기·핀치 중에는 좌석의 :hover 를 완전히 죽인다.

   setPointerCapture 는 pointermove 이벤트만 scroller 로 돌릴 뿐, :hover 는 여전히 커서
   바로 아래 있는 좌석에 그대로 걸린다(스펙상 포인터 캡처가 :hover 를 대신 가져가지
   않는다). 그래서 빠르게 끌면 지나가는 좌석마다 transform·border-color 전환(.1s)이
   연달아 걸려 버벅였다. pointer-events: none 이면 히트테스트 자체에서 빠져서 :hover 가
   아예 안 걸린다 — transition: none 만으로는(전환 없이 즉시 바뀌긴 해도) 좌석마다 스타일을
   다시 계산하는 비용 자체는 남는다.
*/
.housescroll.dragging .seat,
.housescroll.pinching .seat {
  transition: none;
  pointer-events: none;
}

/*
   무대 기준 배치. 무대 뒤 구역(합창석 등)은 무대 위쪽에, 정면 구역은 무대 아래에 놓는다.
   zoneline 하나가 한 층(밴드)이고, 그 안의 열(zone)들이 가로로 나란히 놓인다 — 실제 좌석도가
   한 층을 A~E 같은 좌우 구역으로 나누기 때문이다. 열 하나에 구역이 여럿이면 위에서 아래로 쌓인다.
   좌석도에 층을 적지 않은 홀은 열이 하나뿐인 밴드가 되어 종전과 똑같이 쌓인다.
*/
.zoneline { display: flex; gap: var(--zone-gap); padding: 0 var(--tooltip-bleed); }
/* 층 이름을 못 얻어 머릿글이 없는 홀에서만 쓰인다(머릿글이 있으면 .bandname 이 간격을 낸다). */
.zoneline + .zoneline { margin-top: 44px; }
/*
   무대 뒤 밴드와 무대 사이. 실제로 붙어 있는 자리라 좁게 두지만, 종전 값(14px, 게다가
   .stage 자신의 8px 이 이기는 경우가 있었다)은 너무 좁았다 — 실측으로 가장 아래 좌석과
   무대 사이가 6px, 좌석 한 칸의 절반이었다. 아래쪽 여백이 40px 인 것과 견주면 무대가
   위쪽 좌석에만 달라붙은 것처럼 보인다.

   좌석 크기에 비례시킨다. 고정값으로 두면 확대했을 때(좌석 12px → 24px) 좌석만 커지고
   틈은 그대로라 다시 붙어 보인다. 호(arc) 때문에 줄 양끝이 아래로 처지는 것도 이 값이
   흡수한다.
*/
.zoneline + .stage { margin-top: calc(var(--seat-size) * 2); }

/*
   층 머릿글.

   종전에는 층 경계를 알려주는 게 구역 이름뿐이었는데, 층 사이 간격과 같은 열 안 구역 사이
   간격이 **둘 다 44px** 이라 "층이 바뀌었다"가 "옆 구역으로 넘어갔다"와 같은 무게로 읽혔다.
   실측으로 확인한 값이고, 2층→3층 경계가 그나마 보였던 건 마침 등급색이 달라서였다.

   그래서 층은 이름을 크게 한 번(구역마다 되풀이하지 않고) + 전체 폭 구분선으로 세우고,
   층 사이 간격을 구역 사이(44px)보다 확실히 벌린다(72px).

   position: sticky; left: 0 은 .floorname 과 같은 이유다 — 가로 스크롤로 오른쪽 구역을
   보고 있어도 지금 몇 층인지가 왼쪽에 붙어 있어야 한다.
*/
.bandname {
  position: sticky;
  left: 0;
  width: fit-content;
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 72px var(--tooltip-bleed) 18px;
  font-family: var(--display);
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--paper);
  /* 층 바로가기가 여기로 점프한다. 두 sticky 바(헤더+인원수 줄)에 가리지 않게 —
     실제 합산 높이는 JS 가 --sticky-h 로 채운다(.floor 와 같은 값을 쓴다). */
  scroll-margin-top: var(--sticky-h, 130px);
}
/* 무대 바로 앞뒤 밴드는 무대와의 간격을 좁게 — 무대 뒤 좌석은 무대에 붙어 있고,
   1층 머릿글이 무대에서 멀어지면 무대와 객석이 따로 노는 것처럼 보인다. */
#housecontent > .bandname:first-child { margin-top: 0; }
.stage + .bandname { margin-top: 26px; }
.bandname em {
  font-style: normal;
  font-size: 12px;
  font-weight: 500;
  color: var(--mute);
}
.bandname em b { color: var(--brass); font-weight: 700; }
.zone { display: flex; flex-direction: column; }
/*
   열 묶음을 가운데로. justify-content: center 는 쓰면 안 된다 — 내용이 컨테이너보다 넓어지는
   순간 양쪽으로 똑같이 넘쳐서 앞쪽 좌석이 음수 좌표로 나가고, 가로 스크롤은 0 아래로 못 가서
   그 좌석들을 누를 수가 없다. auto 마진은 남는 공간이 없으면 0 으로 떨어지므로 왼쪽이 잘리지
   않는다 (.house 의 align-items 와 같은 이유다).
*/
.zoneline > .zone:first-child { margin-left: auto; }
.zoneline > .zone:last-child { margin-right: auto; }

/* 층 블록. 층 이름은 가로 스크롤을 해도 왼쪽에 붙어 있어야 지금 몇 층인지 안 놓친다. */
.floor + .floor { margin-top: 44px; }
/* 층 바로가기(#floor-N)로 점프했을 때 목적지가 헤더+인원수 줄(둘 다 sticky) 밑에 깔리지
   않도록 여백을 둔다. 두 sticky 바의 실제 합산 높이는 JS 가 --sticky-h 로 채운다. */
.floor { scroll-margin-top: var(--sticky-h, 130px); }
/* 구역 이름. 층을 뗀 나머지라 대개 알파벳 한 글자("A")다 — 층 머릿글(.bandname, 20px/800,
   본문색)보다 확실히 아래 단계로 보이게 작고 흐리게 둔다. 종전에는 여기가 "1층 A" 를
   통째로 들고 있어서 층 이름이 가장 작은 글씨로 다섯 번 반복됐다. */
.floorname {
  position: sticky;
  left: 0;
  width: fit-content;
  margin: 0 0 14px;
  font-family: var(--display);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .12em;
  color: var(--brass-dim);
}

/*
   층 바로가기. 층별 잔여석을 같이 보여줘서 어느 층을 볼지 먼저 정할 수 있게 한다.

   항목이 구역(20개)에서 층(넷)으로 줄면서 낱개 칩을 늘어놓는 대신 한 덩어리(세그먼트
   컨트롤)로 묶었다 — 개수가 적고 서로 배타적인 선택지라 하나의 컨트롤로 읽히는 게 맞다.
   테두리와 배경을 바깥 컨테이너가 한 번만 갖고 안쪽 항목은 배경만 바뀐다.

   width: fit-content + margin-inline auto 로 가운데 정렬한다. justify-content 로는
   안 된다 — 컨테이너가 블록이라 폭이 100% 여서 테두리가 화면 전체로 늘어난다.
*/
.floornav-slot { display: flex; justify-content: center; }
/* 알약 배경색을 슬롯에 변수로 둔다 — 좁은 화면에서 넘친 칩을 가리는 그라데이션
   (.floornav-slot::before/::after)이 알약과 같은 색이어야 하는데, 그 그라데이션은
   슬롯에 얹히므로 색을 알약에서 위로 물려받을 수 없다(커스텀 속성은 아래로만 흐른다).
   한 곳에서 정하고 양쪽이 같이 읽는다. */
.floornav-slot { --floornav-bg: var(--ink-soft); }
.floornav {
  display: flex; flex-wrap: wrap; gap: 4px;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto 24px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--floornav-bg, var(--ink-soft));
}
/*
   원래 자리를 지나 스크롤하면 화면 왼쪽에 세로로 붙는다.

   위쪽에 고정(sticky)하지 않은 이유: 헤더와 인원수 줄이 이미 고정돼 있어서 층 줄까지
   얹으면 좌석이 보이는 높이가 그만큼 줄어든다. 좌석을 고르는 화면에서 세로 공간은
   가장 아까운 자원이다. 옆으로 빼면 세로를 한 픽셀도 안 먹는다.

   오른쪽이 아니라 왼쪽인 것은 미니맵(.minimap)이 오른쪽 아래에 고정돼 있어서다.
   z-index 는 미니맵(11)보다 낮게 둬서, 둘이 만나는 일이 생겨도 지도가 위로 온다.

   좁은 화면에서는 감춘다 — 좌석 격자가 화면 폭을 다 쓰므로 옆에 띄우면 좌석을 덮는다.
   그 경우엔 원래처럼 맨 위 줄을 쓰면 된다.
*/
.floornav.is-docked {
  position: fixed;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  flex-direction: column;
  flex-wrap: nowrap;
  /* 콘서트홀은 칩이 20개까지 나온다. 화면을 넘기지 않게 잘라 스크롤한다. */
  max-height: 72vh;
  overflow-y: auto;
  margin: 0;
  border-radius: var(--radius-sm);
  background: rgba(11, 11, 12, .86);
  backdrop-filter: blur(8px);
  border-color: var(--line-strong);
  z-index: 10;
}
.floornav.is-docked .floorchip { justify-content: space-between; }
.floorchip {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 7px 16px;
  border-radius: 999px;
  font-size: 13px;
  color: var(--paper);
  transition: background .12s, color .12s, transform .12s;
}
.floorchip:hover { background: rgba(255, 255, 255, .07); }
.floorchip.active,
.floorchip[aria-current="true"] {
  background: rgba(233, 180, 76, .13);
  color: var(--brass);
  transform: translateY(-1px);
}
.floorchip b { color: var(--brass); font-weight: 700; }
.floorchip i { color: var(--mute); font-style: normal; }

.house {
  display: flex; flex-direction: column; gap: var(--seat-gap);
  /* 호로 올라간 첫 줄 양끝이 구역 이름을 덮지 않도록. 가장 넓은 줄(44석)의 상승분만큼 준다.
     --i/--n 이 없는 경우(조각을 거치지 않고 렌더된 좌석)의 안전한 기본값도 여기 둔다 — 호가 0 이 된다. */
  padding-top: calc(var(--seat-size) * 1.2);
  --i: 0;
  --n: 1;
  /*
     align-items: center 를 쓰면 안 된다. 줄이 컨테이너보다 넓어지는 순간(좌석을 최소
     크기까지 줄여도 안 들어가는 대형 홀 + 좁은 화면) 양쪽으로 똑같이 넘쳐서 줄의 앞부분이
     음수 좌표로 나간다. 가로 스크롤은 0 아래로는 못 가므로 그 좌석들은 눌러볼 수가 없다.
     auto 마진은 남는 공간이 없으면 0 으로 떨어지기 때문에 왼쪽이 잘리지 않는다.
  */
  align-items: flex-start;
}
.row { display: flex; align-items: center; gap: var(--seat-gap); }
/*
   부채꼴 구역(줄마다 좌석 수가 다름, 예: 1층 P)의 짧은 줄을 긴 줄과 같은 중심에 맞춘다.
   미는 대상은 라벨이다 — 라벨이 밀리면 그 뒤 좌석도 같이 밀리므로 중앙정렬은 그대로 되고,
   번호는 자기 줄에 붙어 있게 된다.

   여기까지 오는 데 두 번 갈아탔다.
   ① 처음엔 .row 전체를 margin-inline:auto 로 중앙정렬했다. 줄 상자를 통째로 미는 것이라
      라벨이 겹치거나 잘렸다(부채꼴 P 구역에서 윗줄 좌석 위에 라벨이 올라탔다).
   ② 그래서 라벨을 x=0 에 고정하고 첫 좌석만 밀었다. 겹침은 사라졌지만 짧은 줄일수록
      번호가 자기 줄에서 멀어져, 1층 P 실측으로 34~49px(좌석 네 칸)까지 떨어졌다 —
      번호가 어느 줄 것인지 알아볼 수 없는 상태가 됐다.
   ③ 지금은 라벨에 여백을 준다. 번호는 부채꼴을 따라 계단식으로 들어가지만 항상 자기 줄에
      붙어 있고, ①의 겹침도 안 난다 — 줄 상자를 미는 게 아니라 줄 안에서 라벨 앞에만
      여백이 생기는 것이라 라벨이 다른 줄 영역으로 넘어갈 일이 없다.

   좌석 크기 1px 당 슬롯 폭은 seat-size + seat-gap 이다(줄 안 간격이 이 비율이므로).
   통로(aisle)가 있는 줄은 이 계산이 근사치가 된다 — 통로 좌석 사이 간격이 더 넓어서 실제
   폭과 살짝 어긋날 수 있지만, 부채꼴 구역은 대개 규모가 작아 통로가 없다.
   가장 긴 줄(n = max-n)은 결과가 0 이라 기존 홀(모든 줄이 같은 길이)은 전혀 안 바뀐다.
*/
.row .rowlabel {
  margin-left: calc((var(--max-n, var(--n)) - var(--n)) / 2 * (var(--seat-size) + var(--seat-gap)));
  /*
     라벨은 줄의 왼쪽 끝에 붙어 있으므로 그 자리 좌석과 같은 높이여야 한다. 기본 정렬
     (align-items:center)은 줄 상자의 수직 중앙에 두는데, 좌석은 호를 그리느라 양끝이
     들리거나 처져서 끝자리는 중앙과 다르다 — 40석 줄에서 실측 21.9px(확대 기준),
     번호가 자기 옆 좌석에서 떠 보였다.

     첫 좌석의 u 는 -(n-1)/2 이고 오프셋은 u² 에 비례하므로, 여기서도 같은 식을 쓰되
     길이로 계산한다. transform 은 레이아웃을 안 건드리므로 좌석 위치는 그대로다.
  */
  transform: translateY(calc(
      ((var(--n) - 1) / 2) * ((var(--n) - 1) / 2) * var(--seat-size) * var(--arc-unit, 0)));
  font-family: var(--display);
  width: var(--rowlabel-w);
  text-align: right;
  font-size: clamp(11px, 2.6vw, 15px);
  color: var(--brass-dim);
  flex: none;
  /* 가로로 스크롤해도 행 문자는 왼쪽에 붙어 있어야 몇 번째 줄을 고르는지 알 수 있다.
     좌석이 뒤로 지나가므로 배경을 깔아 가린다. 툴팁(z-index:5)보다는 아래에 둔다.
     스크롤이 없는 구역(부채꼴 P 처럼 화면에 다 들어오는 경우)에서는 sticky 가 아무 일도
     하지 않으므로 위 margin 자리에 그대로 놓인다 — 번호가 자기 줄에 붙어 보이는 건 그래서다. */
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--ink);
}

/*
   무대를 감싸는 홀(정면에 좌·우 구역이 있는 홀)에서는 행 라벨과 구역 이름을 고정하지 않는다.
   sticky 는 스크롤 컨테이너 왼쪽을 기준으로 잡히므로 열이 여러 개면 오른쪽 구역의 라벨까지
   전부 왼쪽 끝으로 몰려 다른 구역 좌석 위에 겹쳐 뜬다. 열이 하나인 홀에서는 종전대로 붙어 있다.
*/
.housescroll.has-sides .rowlabel {
  position: static;
  /*
     고정을 풀면 라벨이 줄과 함께 흐르므로, 좌석 위를 지나갈 수 있다. 부채꼴 구역에서
     실제로 겹친다 — 좌석은 호(--arc-y)를 그리느라 줄 양끝이 좌석 높이의 90% 가까이
     들리는데(확대 시 22px, 줄 간격은 30px) 그만큼 옆 줄 라벨 높이까지 파고든다.
     실측: 24px 확대에서 「4」가 5열 2번을, 「2」가 3열 1번을 16x13px 가렸다.

     배경을 깔아둔 채로 두면 그 좌석이 지워져 보이고, 라벨이 위에 있으니 그 부분은
     클릭도 안 먹는다 — 고를 수 있는 좌석이 화면에서 사라지는 셈이라 그냥 미관 문제가
     아니다. 배경을 없애 좌석이 그대로 보이게 하고, 클릭은 통과시킨다. 숫자는 좌석
     위에 겹쳐 그려지므로 어두운 좌석에서도 읽히도록 외곽선을 얇게 깐다.

     sticky 인 홀(열이 하나)에서는 이 규칙이 적용되지 않는다 — 거기서는 라벨이 화면
     왼쪽에 고정된 채 좌석이 뒤로 지나가므로 불투명 배경이 있어야 읽힌다.
  */
  background: none;
  pointer-events: none;
  text-shadow:
     0 0 3px var(--ink),  0 0 3px var(--ink),
     1px 0 0 var(--ink), -1px 0 0 var(--ink),
     0 1px 0 var(--ink),  0 -1px 0 var(--ink);
}
body.page-seatmap.tone-light .housescroll.has-sides .rowlabel {
  /* 라이트 톤에서는 종이색으로 테두리를 깐다 — 어두운 잉크로 깔면 글자가 뭉개진다. */
  text-shadow:
     0 0 3px var(--paper),  0 0 3px var(--paper),
     1px 0 0 var(--paper), -1px 0 0 var(--paper),
     0 1px 0 var(--paper),  0 -1px 0 var(--paper);
}
/*
   구역 이름도 같은 이유로 고정을 풀고, 그 구역의 좌석 폭 가운데로 옮긴다.

   position: absolute 로 완전히 빼는 게 핵심이다. static 인 채로 width: 100% 나
   overflow-wrap: anywhere 를 줘도 안 됐다 — 시도해봤지만 .zone 자체가 폭이 정해지지 않은
   (shrink-to-fit) 컨테이너라서, 그 안에서는 % 도, "줄바꿈 가능하니 최소 폭을 줄여도 된다"는
   힌트도 전부 콘텐츠 기준으로 되돌아갔다(리사이틀홀 왼쪽사이드처럼 좌석이 2칸뿐인 구역에서
   "1층 왼쪽사이드" 라는 긴 이름이 좌석 폭 83px 보다 넓어(134px) 구역 전체를 그 이름 폭만큼
   넓혀버리는 걸 실측으로 세 번 확인했다). 정상 흐름(normal flow) 안에 있는 한, 이 이름이
   조상의 자동 크기 계산에 계속 끼어드는 구조 자체가 문제였다.

   position: absolute 는 요소를 정상 흐름에서 완전히 빼므로, 사양상 조상의 shrink-to-fit
   계산에 전혀 기여하지 않는다 — 그래서 .zone 의 폭이 순수하게 좌석(.house) 쪽으로만
   결정되고, 이름은 그 확정된 폭 가운데에 얹힌 뒤 넘치면 알아서 줄바꿈된다.

   흐름에서 빠지며 비는 자리는 .floor 의 padding-top 으로 미리 확보해야 한다. 좁은 구역에서
   두 줄로 감쌀 수 있어(예: "1층" / "왼쪽사이드") 두 줄 분량으로 넉넉히 잡는다 — line-height
   를 여기서 같이 고정해 두는 이유도 이 계산을 예측 가능하게 하려는 것이다. 구역 이름
   font-size 를 바꾸면 이 값도 같이 맞춰야 한다.
*/
.housescroll.has-sides .floor { position: relative; padding-top: 57px; }
/*
   구역 이름이 없는 구역에서는 그 자리를 비워두지 않는다.

   이름(.floorname)은 구역이 여럿일 때만 그린다 — 구역이 하나뿐이면 밴드 머릿글과 같은
   글자가 되므로 템플릿이 아예 안 그린다(seatzone.html 의 shortName 검사). 그런데 위
   padding 은 그대로 남아서, 없는 글자 자리로 57px 을 버리고 있었다. 실측: 2,015석 홀의
   「1층 P」 밴드에서 제목과 첫 좌석 사이가 89px 이고 그중 57px 이 이것이었다(좌석 띠
   자체가 161px 인데 그 절반이 넘는 빈칸이다).

   :has() 를 못 쓰는 브라우저에서는 종전대로 여백이 남는다 — 화면이 조금 성길 뿐
   깨지지는 않는다.
*/
.housescroll.has-sides .floor:not(:has(> .floorname)) { padding-top: 0; }
/*
   구역 블록을 열 가운데에 놓는다.

   종전에는 블록이 열 폭에 맞춰 늘어나 있었다. 그래서 ① 좌석은 블록 왼쪽에 붙어
   좁은 구역일수록 왼쪽으로 치우쳐 보였고, ② 이름은 "그 구역"이 아니라 "그 열에서 가장
   넓은 구역" 기준으로 가운데 잡혔다. 실측(2,015석 홀)으로 A 구역이 열 중앙에서 82px,
   B 52px, C 21px 벗어나 있었고 가장 넓은 구역만 맞았다.

   블록이 자기 내용 폭만 갖게 하고(align-self 가 stretch 를 끈다) 가운데로 모으면 둘 다
   해결된다 — 전 구역이 열 중앙 기준 같은 값이 된다.

   여기서 align-self: center 는 써도 된다. .zone 의 폭은 그 안에서 가장 넓은 구역이
   정하므로 어떤 구역도 열보다 넓어질 수 없다 — 내용이 컨테이너보다 넓어질 때 양쪽으로
   넘쳐 앞쪽 좌석이 음수 좌표로 나가는 문제(.house 의 align-items 주석 참고)는 안 생긴다.
*/
.housescroll.has-sides .floor { align-self: center; }
.housescroll.has-sides .floorname {
  position: absolute;
  top: 0;
  /*
     줄번호 칸(라벨 + 그 뒤 간격)의 절반만큼 오른쪽으로 민다. 블록 폭에는 번호 칸이
     들어 있어서 그냥 50% 로 두면 이름이 "번호+좌석" 기준으로 가운데가 되고, 눈에는
     좌석 덩어리보다 왼쪽으로 치우쳐 보인다(실측 10px). 구역 이름은 좌석 위에 있어야
     한다.
  */
  left: calc(50% + (var(--rowlabel-w) + var(--seat-gap)) / 2);
  transform: translateX(-50%);
  width: 100%;
  margin: 0;
  line-height: 1.25;
  text-align: center;
}

/*
   부채꼴 호(arc). 실제 극장 줄은 무대를 중심으로 휘어 있어서 양끝이 무대에 더 가깝다.
   좌석마다 줄 중앙에서 몇 칸 떨어졌는지(--u)의 제곱만큼 무대 쪽으로 당긴다.

   translateY 만 쓰는 게 핵심이다 — transform 은 레이아웃에 영향을 주지 않으므로 줄 너비가
   그대로고, 좌석 크기 자동 맞춤 계산(가장 넓은 줄 기준)을 건드리지 않는다.

   --u 를 줄 길이로 정규화하지 않는다. 정규화하면 5석짜리 좌우 블록도 40석짜리 중앙 블록과
   같은 깊이로 휘어서 짧은 줄만 급하게 꺾인다. 절대 칸 수로 재면 짧은 줄은 거의 평평하다.

   계수(좌석 크기의 0.24%/칸²)는 줄 간격(좌석 크기의 1.28배)보다 줄별 휘는 정도의 차이가 작게
   유지되는 값으로 잡았다. 부채꼴 구역은 줄마다 길이가 달라서, 이 차이가 줄 간격을 넘으면 뒷줄
   양끝이 앞줄을 파고든다. 44석 줄이 좌석 크기의 1.2배쯤 올라간다.

   깊이를 var(--seat-size) 가 아니라 %로 적는 게 중요하다. translateY 의 %는 그 요소 자신의
   높이 기준이고 좌석 높이가 곧 --seat-size 이므로 결과는 같은데, 좌석 2,481개의 커스텀 속성이
   --seat-size 에 의존하지 않게 된다. 의존하게 두면 확대·축소로 --seat-size 를 바꿀 때마다
   모든 좌석의 --u/--arc-y 가 무효화돼서 한 단계에 700ms 가 걸렸다(핀치가 사실상 멈춤).
*/
/*
   --arc-unit 은 --arc-strength 와 같은 값을 %가 아닌 순수 숫자로 둔 것이다(0.24% = 0.0024).
   좌석은 %를 쓴다(자기 높이 기준이라 --seat-size 에 안 매여서 확대가 빠르다). 하지만 줄번호
   라벨은 높이가 좌석과 달라서(글자 크기 기준) 같은 %를 주면 다른 거리만큼 움직인다.
   라벨은 홀당 수십 개뿐이라 --seat-size 에 의존해도 확대 성능에 영향이 없다.
*/
.house { --arc-strength: -0.24%; --arc-unit: -0.0024; }
/* 무대 뒤 구역은 무대가 아래쪽에 있으니 반대로 휜다. */
.zoneline-rear .house { --arc-strength: 0.24%; --arc-unit: 0.0024; }

.seat {
  --u: calc(var(--i) - (var(--n) - 1) / 2);
  --arc-y: calc(var(--u) * var(--u) * var(--arc-strength));
  transform: translateY(var(--arc-y));
  width: var(--seat-size);
  height: var(--seat-size);
  aspect-ratio: 1 / 1;
  /*
     테두리를 아예 쓰지 않고 채움으로만 등급을 나타낸다. 좌석 사이는 --seat-gap 이
     이미 벌려주므로 칸 경계를 그릴 테두리가 필요 없고, 10px 좌석에서 1px 테두리는
     면적의 36% 를 먹어 채움색을 못 알아보게 만든다.
  */
  border: 0;
  background: var(--seat-fill, var(--ink-soft));
  border-radius: 3px;
  cursor: pointer;
  padding: 0;
  position: relative;
  touch-action: manipulation;
  transition: transform .1s, background .1s, box-shadow .1s;
}
/* 호 오프셋을 유지한 채로 살짝 더 들어올린다. --arc-y 를 빼먹으면 hover 때 좌석이 평평한
   자리로 튀어 내려간다. 테두리를 없앴으므로 강조는 바깥 링(box-shadow)으로 준다 —
   링은 레이아웃을 차지하지 않아서 좌석 크기 자동 맞춤 계산에 영향을 주지 않는다. */
.seat:hover:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(calc(var(--arc-y) - 2px));
  box-shadow: 0 0 0 max(2px, calc(var(--seat-size) * 0.12)) var(--paper);
}
.seat:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
/* .seat 는 부채꼴 배치 때문에 hover 여부와 무관하게 항상 transform 이 걸려 있어서
   스스로 스태킹 컨텍스트를 만든다. 그 안의 툴팁(::after, z-index:5)은 이 컨텍스트
   밖의 형제 요소와 비교될 때는 .seat 자신이 z-index:auto 인 채로(사실상 0) 취급된다.
   줄 번호 라벨(.rowlabel, z-index:2, position:sticky)은 이보다 높아서, 줄 첫 좌석의
   툴팁이 라벨 배경에 가려 잘려 보였다. 툴팁이 보이는 동안만 좌석 자체를 라벨보다
   위로 올려 스태킹 컨텍스트째로 끌어올린다. */
.seat:hover, .seat:focus-visible { z-index: 6; }

/* 등급·가격 툴팁 */
.seat::before, .seat::after { opacity: 0; transition: opacity .12s, transform .12s; pointer-events: none; z-index: 5; }
.seat::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--brass-dim);
  padding: 6px 10px;
  font-size: 12px;
  white-space: nowrap;
}
.seat::before {
  content: '';
  position: absolute;
  bottom: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--brass-dim);
}
.seat:hover::after, .seat:focus-visible::after { opacity: 1; transform: translateX(-50%) translateY(0); }
.seat:hover::before, .seat:focus-visible::before { opacity: 1; }
/* 등급은 채움 색으로 구분한다. 근거와 측정값은 :root 의 --grade-* 주석 참고. */
.seat.grade-VIP { --seat-fill: var(--grade-vip); }
.seat.grade-R   { --seat-fill: var(--grade-r); }
.seat.grade-S   { --seat-fill: var(--grade-s); }
.seat.grade-A   { --seat-fill: var(--grade-a); }

/*
   예매 불가 좌석. 등급색을 무채색으로 덮어 화면 뒤로 물린다 — 어느 등급이었는지는
   살 수 없는 이상 결정에 쓰이지 않는 정보다. 둘은 서로 다른 명도로 구분한다
   (결제 대기는 풀릴 수 있고, 판매 완료는 끝난 좌석이라 가장 어둡다).
*/
.seat[data-status="HELD"] { --seat-fill: var(--seat-held); cursor: not-allowed; }
.seat[data-status="SOLD"] { --seat-fill: var(--seat-sold); cursor: not-allowed; }
/*
   고른 좌석. 등급색 넷 중 무엇과도 겹치지 않아야 해서 흰 채움 + 브라스 링으로 둔다.
   종전처럼 브라스로 칠하면 VIP 등급색과 같아져 "고른 좌석"과 "VIP 좌석"이 구별되지 않는다
   — 링 두께를 좌석 크기에 비례시켜 10px 맞춤 배율에서도 링이 보인다.
*/
.seat.selected {
  --seat-fill: var(--paper);
  box-shadow: 0 0 0 max(2px, calc(var(--seat-size) * 0.14)) var(--brass);
}
/*
   통로. 실제 극장처럼 블록 사이에 여백을 준다. 어느 좌석 뒤에 통로가 오는지는
   좌석 생성 시점에 seat.aisle_after 로 기록되므로 화면은 그 값만 따른다.

   여기에 점선 구분선을 ::after 로 그리고 있었는데, ::after 는 위에서 등급·가격 툴팁으로
   이미 쓰는 자리다. .seat.aisle::after 가 .seat::after 보다 구체적이어서 통로 좌석마다
   툴팁이 점선으로 덮여 사라지고 있었다(5석마다 한 석씩 가격이 안 보였다).
   여백만으로도 통로는 충분히 읽히므로 점선을 뺐다.
*/
.seat.aisle { margin-right: var(--aisle-gap); }

/* ---------- 확대 모드 미니맵 ----------
   예매 바 위에 떠 있어야 하므로 --footer-h(바 실측 높이)를 기준으로 띄운다. 고정값을 쓰면
   범례가 여러 줄로 접히거나 고른 좌석 칩이 늘어날 때 바에 가린다 — 좌석 격자와 같은 이유다.
   z-index 는 예매 바(10)보다 위, 그래야 지도를 보면서 바를 넘겨다볼 수 있다. */
/*
   position: fixed 라 화면 우하단 그 자리에 늘 있고, 그 아래 좌석은 완전히 가려져
   클릭이 안 된다(elementsFromPoint 로 실측 — 클릭이 항상 .minimap 으로 간다).
   패널 크기(위 MINI_MAX_W/H)를 줄이고 opacity 로 옅게 만들어 겹치는 영향을 줄인다.
   opacity 를 .minimap-zone 에 개별로 걸지 않고 패널 전체에 거는 이유: 등급 블록의
   배경·글자색은 서로 대비 4.6 이상으로 맞춘 값인데(아래 .minimap-zone 주석 참고),
   전체에 한 번에 걸면 둘 다 같은 배경 쪽으로 같이 옅어져서 그 상대 대비가 거의
   유지된다 — 블록 하나에만 걸면 그 계산이 깨진다.
*/
.minimap {
  position: fixed;
  right: 20px;
  bottom: calc(var(--footer-h, 150px) + 16px);
  z-index: 11;
  padding: 6px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: rgba(11, 11, 12, .86);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .5);
  opacity: .82;
  cursor: pointer;
  /* 미니맵 위 끌기는 브라우저 스크롤이 아니라 우리가 처리한다. */
  touch-action: none;
}
.minimap:hover, .minimap:focus-within { opacity: 1; }
.minimap-inner { position: relative; }
/*
   구역 블록. 그 구역에서 가장 많은 등급의 색을 깔아 확대 중에도 어디가 어느 등급인지 보이고,
   블록 안에는 구역을 가르는 글자("1층 C" 의 C)를 넣어 어느 구역인지도 알 수 있게 한다.
   전체 이름은 블록이 16~29px 라 안 들어가므로 title 로 넘긴다(마우스를 올리면 보인다).

   글자색을 등급마다 나누는 이유: 등급 팔레트가 명도 단조 감소라 밝은 두 등급(VIP·R)에는
   어두운 글자가, 어두운 두 등급(S·A)에는 밝은 글자가 맞는다. 그렇게 하면 네 등급 모두
   글자 대비가 4.6 이상이 된다 — 한 색으로 통일하면 반대쪽 두 등급에서 4.5 미만으로 떨어진다.
   (블록에 opacity 를 걸면 안 된다. 자식인 글자까지 같이 흐려져서 이 계산이 무너진다.)
*/
.minimap-zone {
  position: absolute;
  border-radius: 1px;
  display: grid;
  place-items: center;
  font-family: var(--display);
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  overflow: hidden;
}
.minimap-zone.grade-VIP { background: var(--grade-vip); color: var(--ink); }
.minimap-zone.grade-R   { background: var(--grade-r);   color: var(--ink); }
.minimap-zone.grade-S   { background: var(--grade-s);   color: var(--paper); }
.minimap-zone.grade-A   { background: var(--grade-a);   color: var(--paper); }
/* 무대. 방향을 잡아주는 기준점이라 등급색보다 또렷하게 둔다. */
.minimap-stage { position: absolute; border-radius: 1px; background: var(--brass); opacity: .9; }
/*
   지금 화면에 보이는 범위. 흰 테두리 1.5px + 옅은 채움이었는데, 패널 전체가 opacity .82로
   가라앉아 있고 등급 블록도 밝은 색(VIP·R)이 섞여 있어 흰 테두리가 묻혀 보였다 — 정작
   미니맵에서 가장 중요한 정보(지금 보고 있는 곳)가 가장 안 보이는 역설이었다.
   이 화면에서 "지금 활성" 을 나타내는 유일한 색(브라스)로 바꾸고 글로우를 얹어
   패널의 다른 어떤 블록보다 위에 뜨게 했다.
*/
.minimap-view {
  position: absolute;
  border: 2px solid var(--brass);
  border-radius: 2px;
  background: rgba(233, 180, 76, .16);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .4), 0 0 8px rgba(233, 180, 76, .6);
  /* 포인터를 안 받는다 — 위치 판정은 미니맵 전체에서 하고(상자 안/밖), 상자가 이벤트를
     가로채면 상자 위에서 시작한 끌기가 미니맵까지 안 온다. */
  pointer-events: none;
}
/* 끌 수 있다는 걸 커서로 알린다. 상자가 pointer-events 를 안 받으므로 미니맵에 건다. */
.minimap { cursor: grab; }
.minimap.is-dragging { cursor: grabbing; }

@media (max-width: 720px) {
  /* 좁은 화면에서는 지도 자체가 좁으니 미니맵도 자리를 덜 차지하게 줄인다. */
  .minimap { right: 12px; bottom: calc(var(--footer-h, 150px) + 10px); }
  /*
     크기(JS 의 MINI_MAX_W/H)를 줄여도 겹침 자체는 없앨 수 없다 — position:fixed 로
     좌석 스크롤 영역 안쪽에 얹히는 구조라(위 미니맵 크기 계산 주석 참고), 화면에
     남는 세로 공간을 최대로 쓰는 이 화면에서 미니맵이 좌석과 안 겹치는 "빈 자리"는
     애초에 없다. 그래서 완전히 안 겹치게 만드는 대신, 평소 불투명도를 낮춰 겹친
     자리의 좌석 색이 비쳐 보이게 한다 — 적어도 "여기 좌석이 있다"는 건 알 수 있고,
     조금만 스크롤하면 그대로 눌린다. hover/focus 시 원래 불투명도(.82)로 돌아온다
     (터치 기기는 hover 가 없지만, 드래그 시작 시 focus-within 이 걸려 잡는 동안은 또렷해진다).
  */
  .minimap { opacity: .5; }
}

/* 가격표. 예매 바 안에 고정으로 들어간다. 바깥 여백은 바가 갖고 있다. */
.legend {
  display: flex; flex-direction: column; align-items: center;
  max-width: 1080px; margin: 0 auto 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}
/* 좌석 상태 표시. 한 번 익히면 되는 정보라 고정하지 않고 본문 맨 아래에 둔다. */
.seatstatus { margin-top: 36px; }
.legend-row {
  display: flex; gap: 22px; justify-content: center; flex-wrap: wrap;
  font-size: 12px; color: var(--mute);
}
.legend-row span { display: flex; align-items: center; gap: 7px; }
.legend-row i { width: 13px; height: 13px; border-radius: 3px; display: inline-block; }
.legend-price { color: var(--brass); font-weight: 700; margin-left: 2px; }

/* ---------- 등급 필터 (가격표가 곧 필터) ----------
   가격표 항목이 버튼이다. 예산을 먼저 정하고 그 안에서 자리를 고르는 게 실제 구매 순서라,
   등급을 누르면 그 등급만 남기고 나머지 좌석을 뒤로 물린다. .legend-row 의 gap 이 22px 라
   버튼 자체 padding 과 겹쳐 넓어 보이므로 이 줄만 간격을 좁힌다. */
/* "범례일 뿐" 이라는 첫인상을 깨는 안내. 다른 sticky 힌트(.partysize .hint)와 같은 톤. */
.legend-hint { margin: 0 0 6px; font-size: 11px; color: var(--mute); }
.legend-grades { gap: 6px; }
.legend-grades button {
  display: flex; align-items: center; gap: 7px;
  font: inherit; font-size: 12px; color: var(--paper);
  background: none; border: 1px solid transparent; border-radius: 999px;
  padding: 5px 11px; cursor: pointer;
  transition: background .12s, border-color .12s, opacity .12s;
}
.legend-grades button:hover:not(:disabled) { border-color: var(--line-strong); }
/* 눌린 상태는 이 화면의 다른 활성 표시(층 바로가기 칩·확대 버튼)와 같은 브라스 언어를 쓴다 —
   종전엔 옅은 흰색 배경뿐이라 "필터가 걸렸다"는 신호가 다른 활성 상태보다 약했다. */
.legend-grades button[aria-pressed="true"] {
  background: rgba(233, 180, 76, .14);
  border-color: var(--brass);
  color: var(--brass);
}
/* 이번 회차에 그 등급 좌석이 아예 없으면 누를 게 없다. */
.legend-grades button:disabled { opacity: .35; cursor: default; }

/*
   필터가 걸리면 고르지 않은 등급을 물린다.

   좌석마다 클래스를 붙이지 않고 컨테이너 속성 하나로 제어한다 — 3,022석 홀에서 토글할
   때마다 좌석 전부를 순회하며 classList 를 건드리면 그만큼 스타일을 다시 계산한다.
   `~=` 는 공백으로 구분된 낱말 매칭이라 data-grade-filter="R S" 같은 값이 그대로 먹는다.
   속성이 아예 없으면(= 아무것도 안 고른 상태) 어떤 규칙도 안 걸려 전체가 보인다.

   pointer-events 까지 끄는 이유: 물린 좌석을 실수로 고르면 필터를 건 의미가 없다.

   이미 고른 좌석은 예외여야 한다 — 안 그러면 필터를 건 뒤 그 좌석을 해제할 방법이 없어진다.
   그 예외를 별도 규칙(`... .seat.selected { opacity: 1 }`)으로 덧씌우려다 실패했다.
   무력화 쪽은 `:not([data-grade-filter~="…"])` 때문에 클래스 3 + 속성 2 = (0,5,0)인데
   예외 쪽은 (0,4,0)이라 명시도에서 진다. **그래서 예외를 덧씌우지 않고 무력화 대상에서
   아예 빼는 방식으로 적는다** — 규칙이 하나뿐이라 명시도를 따질 일이 없어진다.
   (JS 의 .click() 은 pointer-events 를 무시하고 발생하므로 이 결함은 스크립트 조작으로는
    드러나지 않는다. 계산된 스타일을 직접 읽어야 잡힌다.)
*/
.housescroll[data-grade-filter]:not([data-grade-filter~="VIP"]) .seat.grade-VIP:not(.selected),
.housescroll[data-grade-filter]:not([data-grade-filter~="R"])   .seat.grade-R:not(.selected),
.housescroll[data-grade-filter]:not([data-grade-filter~="S"])   .seat.grade-S:not(.selected),
.housescroll[data-grade-filter]:not([data-grade-filter~="A"])   .seat.grade-A:not(.selected) {
  opacity: .16;
  pointer-events: none;
}

/* 미니맵도 같이 물린다. 필터를 걸었을 때 "그 등급이 홀 어디에 있나"가 바로 보이는 게
   이 기능의 실제 값어치라, 지도만 바뀌고 미니맵이 그대로면 절반만 동작하는 셈이다. */
.minimap[data-grade-filter]:not([data-grade-filter~="VIP"]) .minimap-zone.grade-VIP,
.minimap[data-grade-filter]:not([data-grade-filter~="R"])   .minimap-zone.grade-R,
.minimap[data-grade-filter]:not([data-grade-filter~="S"])   .minimap-zone.grade-S,
.minimap[data-grade-filter]:not([data-grade-filter~="A"])   .minimap-zone.grade-A {
  opacity: .22;
}
/* 범례 견본은 좌석과 같은 방식(채움)이어야 한다 — 견본과 좌석이 다른 규칙으로 그려지면
   범례를 보고 지도를 못 읽는다. 그래서 .seat 과 같은 --grade-* 토큰을 쓴다. */
.legend-grade {
  width: 13px; height: 13px; border-radius: 3px; display: inline-block;
  border: 0;
}
/* 좌석 상태 견본(선택함 · 결제 대기 · 판매 완료). 등급 견본(.legend-grade)과 같은 크기·모양을
   쓰되, 고른 좌석의 표시 방식은 .seat.selected 와 반드시 같아야 해서 규칙을 따로 둔다. */
.legend-sample { width: 13px; height: 13px; border-radius: 3px; display: inline-block; }
.legend-sample--selected { background: var(--paper); box-shadow: 0 0 0 2px var(--brass); }
.legend-sample--held { background: var(--seat-held); }
.legend-sample--sold { background: var(--seat-sold); }
/* 라이트에서는 고른 좌석이 안쪽 잉크 링으로 바뀐다(.seat.selected 참고). 견본도 같이 뒤집는다. */
body.tone-light .legend-sample--selected { box-shadow: inset 0 0 0 2px var(--ink); }
/* 예매 불가 견본은 라이트 배경과 명도가 가까워서(그러라고 물린 색이다) 견본만 덩그러니
   두면 빈 칸으로 보인다. 좌석은 격자 안에 있어 자리로 읽히지만 견본은 혼자 떠 있다. */
body.tone-light .legend-sample--held,
body.tone-light .legend-sample--sold { box-shadow: 0 0 0 1px rgba(17, 18, 20, .18); }

.legend-grade.grade-VIP { background: var(--grade-vip); }
.legend-grade.grade-R   { background: var(--grade-r); }
.legend-grade.grade-S   { background: var(--grade-s); }
.legend-grade.grade-A   { background: var(--grade-a); }

/* ---------- 예매 바 (좌석 표시·가격표 + 고른 좌석 + 예매 버튼) ---------- */
.bookbar {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: 10;                 /* 좌석 툴팁(z-index:5)보다 위에 와야 가격표가 가려지지 않는다 */
  background: rgba(17, 18, 20, .90);
  backdrop-filter: blur(8px);
  border-top: 1px solid var(--line);
  padding: 12px 24px calc(12px + env(safe-area-inset-bottom));
}
.bookbar.is-ready {
  border-top-color: rgba(233, 180, 76, .36);
  box-shadow: 0 -18px 46px rgba(0, 0, 0, .28);
}
/*
   고정 바에 가려지는 만큼 본문 아래에 여백을 준다. 바 높이는 내용에 따라 달라지므로
   JS 가 실측해 --footer-h 로 넘긴다(없으면 대략값으로 버틴다).
*/
body.page-seatmap { padding-bottom: calc(var(--footer-h, 150px) + 20px); }
.bookbar .inner {
  max-width: 1080px; margin: 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.bookbar .picked {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "list summary" "hint summary";
  align-items: center;
  gap: 4px 20px;
  font-size: 14px; color: var(--mute); flex: 1; min-width: 0;
}
.bookbar .picked strong { color: var(--paper); font-weight: 600; }
.bookbar .pickedlist {
  grid-area: list;
  margin: 0;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.bookbar .pickedlist:not(:has(.pickchip)) { margin: 0; }
.bookbar .pickchip {
  display: inline-flex; align-items: center;
  background: var(--ink-soft); border: 1px solid var(--brass-dim);
  color: var(--paper); font-size: 12px; font-weight: 600;
  padding: 5px 10px; border-radius: 999px; white-space: nowrap;
}
.bookbar .pickedhint {
  grid-area: hint;
  margin: 0;
  font-size: 12px;
  color: var(--mute);
}
.bookbar .pickedsummary {
  grid-area: summary;
  min-width: 170px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: rgba(248, 244, 236, .045);
  text-align: right;
}
.bookbar .pickedcount {
  display: block;
  font-size: 12px;
  color: var(--mute);
}
.bookbar .pickedtotal {
  display: block;
  margin-top: 2px;
  font-family: var(--display);
  font-size: 20px;
  line-height: 1.1;
  font-weight: 800;
  color: var(--brass);
  font-variant-numeric: tabular-nums;
}
.bookbar .pickedtotal strong { color: inherit; font: inherit; }
/*
   예매 CTA. `.bookbar button` 으로 두면 안 된다 — 바 안에 버튼이 하나뿐이라는 전제가 깔린
   선택자인데, 가격표가 등급 필터 버튼이 되면서 그것들까지 브라스로 칠해버렸다. 게다가
   가격 글자색도 브라스(--legend-price)라 같은 색끼리 겹쳐 가격이 아예 안 보였다.
   이 규칙이 노리는 건 언제나 .inner 안의 「예매하기」 하나다.
*/
.bookbar .inner button {
  font-family: var(--body); font-size: 15px; font-weight: 600;
  background: var(--brass); color: var(--ink);
  border: 0; border-radius: var(--radius-sm); padding: 13px 30px; cursor: pointer;
  flex: none;
}
.bookbar .inner button:disabled { background: var(--ink-soft); color: var(--mute); cursor: not-allowed; }

.empty-state {
  text-align: center; padding: 80px 20px; color: var(--mute);
}
.empty-state p { margin: 0 0 6px; }

/* ---------- 로그인 / 회원가입 ----------
   폼만 왼쪽 위에 덩그러니 떠 있으면 미완성처럼 보인다.
   화면 중앙에 카드 하나로 띄우고(폼 + 소셜 로그인이 한 덩어리로 읽히게),
   입력창 경계를 또렷하게 해서 누를 수 있는 요소로 읽히게 한다. */
.authwrap {
  max-width: 400px;
  margin: 0 auto;
  min-height: calc(100vh - 200px);
  display: flex; flex-direction: column; justify-content: center;
}

/* ---- 에러 페이지(403/404/5xx) ----

   종전에는 .authwrap 을 그대로 썼는데, 그건 로그인 폼 한 장을 담는 400px 칸이라
   **헤더까지 그 폭에 갇혔다** — 로고 "객석"이 두 줄로 접히고 내비가 줄바꿈되면서
   로그인·회원가입이 서로 붙어버렸다. 폭은 .wrap(1080px) 을 그대로 쓰고 본문만 좁힌다.

   색은 전부 라이트 톤 기준이다. 종전에는 body 에 클래스가 없어 다크로 남아 있었고,
   본문 색이 인라인 style 로 박혀 있어서 톤 전환에서 통째로 빠져 있었다 —
   아이보리 배경이 됐을 때 --brass 링크는 대비 1.72, 큰 상태코드(--brass-dim)는 2.88 로
   사실상 안 보인다. 아래 값은 전부 #F8F4EC 위에서 실측해 고른 것이다. */
body.page-error { background: var(--paper); color: var(--ink); }
.errorbody { max-width: 520px; padding: 48px 0 0; }

/* 상태코드. 큰 글씨(3.0 기준)지만 --brass-dim(2.88)은 그마저도 못 넘어서 --brass-ink(4.51)를 쓴다. */
.errorcode {
  font-family: var(--display);
  font-size: 56px; line-height: 1;
  color: var(--brass-ink);
  margin: 0 0 16px;
}
.errorheading {
  font-size: 17px; font-weight: 700; color: var(--ink);
  margin: 0 0 8px;
}
/* 다음에 뭘 할 수 있는지 알려주는 줄이라 흐리게 두면 안 된다.
   앱의 보조 텍스트 관행(.55 → 4.04)보다 한 단계 진하게 잡아 본문 기준(4.5)을 넘긴다. */
.errordetail { font-size: 15px; color: rgba(17, 18, 20, .6); margin: 0; }
/* 사유도 읽으라고 띄우는 줄이라 같은 기준을 적용한다 — .55 는 4.04 로 못 미친다. */
.errorreason { font-size: 13px; color: rgba(17, 18, 20, .6); margin: 10px 0 0; }

.errorlinks { margin: 28px 0 0; font-size: 15px; font-weight: 700; }
.errorlinks a { color: var(--brass-ink); }
.errorlinks a:hover { color: var(--ink); }
/* 링크 사이 가운뎃점. 읽을 내용이 아니라 구분 기호라 스크린리더에서 뺀다(aria-hidden). */
.errorlinks .errorsep { color: rgba(17, 18, 20, .3); margin: 0 8px; font-weight: 400; }
.authcard {
  background: var(--ink-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 32px 28px;
  /* 카드를 배경에서 확실히 띄운다. 어두운 배경에서는 테두리만으로는 경계가 약하다. */
  box-shadow: 0 18px 44px rgba(0, 0, 0, .38);
}
.authhead { text-align: center; margin-bottom: 24px; }
.authhead h2 {
  font-family: var(--display);
  font-size: 24px; font-weight: 800; letter-spacing: -0.02em;
  margin: 0 0 6px;
}
.authhead p { margin: 0; font-size: 13px; color: var(--mute); }

/* 좌석 선택 중 넘어왔을 때만 뜨는 안내. 경고가 아니라 안심시키는 톤이라 브라스색을 쓴다. */
.authnotice {
  background: rgba(255, 201, 64, .08);
  border: 1px solid rgba(255, 201, 64, .24);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin: 0 0 16px;
  font-size: 13px; color: var(--brass);
}
.autherror {
  background: rgba(255, 92, 122, .08);
  border: 1px solid rgba(255, 92, 122, .28);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin: 0 0 16px;
  font-size: 13px; color: var(--rose);
}

.authform {
  display: flex; flex-direction: column; gap: 16px;
}
.authform label {
  display: flex; flex-direction: column; gap: 8px;
  font-size: 13px; color: var(--mute);
}
/* 라벨 텍스트("이메일")와 같은 크기·색이면 안내문이 필드명처럼 읽혀 구분이 안 된다. */
.authform .hint { font-size: 12px; }
.authform input {
  font-family: var(--body); font-size: 15px;
  background: var(--ink-raised); color: var(--paper);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  width: 100%;
  transition: border-color .12s, box-shadow .12s;
}
.authform input:hover { border-color: var(--mute); }
.authform input:focus-visible {
  outline: none;
  border-color: var(--brass);
  box-shadow: 0 0 0 3px rgba(255, 201, 64, .16);
}
/*
   제출 버튼만 브라스로 채운다 — 비밀번호 표시 토글도 button 이라 타입으로 구분해야 한다.

   크기를 소셜 버튼보다 키우는 게 핵심이다. 브라스(43°)와 카카오 노랑(54°)은 색상차가
   11도뿐이고 명암비도 1.2 라 색으로는 절대 안 갈린다. 게다가 종전엔 소셜 버튼이 아이콘
   때문에 더 커서(52 vs 45px) **주요 액션이 보조 액션보다 작고 어두운** 상태였다 —
   위계가 뒤집혀 있었다. 브랜드 색은 가이드가 정한 값이라 못 건드리므로, 우리 쪽이 가진
   유일한 지렛대인 크기·굵기로 되잡는다.
*/
.authform button[type="submit"] {
  font-family: var(--body); font-size: 16px; font-weight: 700;
  background: var(--brass); color: #0B0B0C;
  border: 0; border-radius: var(--radius-sm);
  padding: 16px; cursor: pointer;
  margin-top: 4px;
  transition: filter .12s;
}
.authform button[type="submit"]:hover { filter: brightness(1.08); }

/* 비밀번호 입력창 안에 겹쳐 놓는 표시/숨김 토글. */
.pwfield { position: relative; display: block; }
.pwfield input { padding-right: 62px; }
.pwtoggle {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; border-radius: var(--radius-xs);
  color: var(--mute); font-family: var(--body); font-size: 12px;
  padding: 6px 8px; cursor: pointer;
  transition: color .12s;
}
.pwtoggle:hover { color: var(--paper); }

.authlink {
  font-size: 13px; color: var(--mute); text-align: center;
  margin: 20px 0 0;
}
.authlink a { color: var(--brass); font-weight: 700; }
/* 로그인 버튼 바로 아래에 붙는 "비밀번호를 잊으셨나요?" 처럼, 카드 하단이 아니라
   폼 안에서 다음 요소와 이어질 때는 위 여백을 좁힌다. */
.authlink--tight { margin: 12px 0 0; }

/* 결제 화면의 단일 주요 액션. 예전엔 .authform 의 카드 스타일을 빌려 쓰고 있었는데,
   .authform 이 폼 레이아웃 전용으로 정리되면서 전용 클래스로 분리했다. */
.paybtn {
  display: block; width: 100%;
  font-family: var(--body); font-size: 15px; font-weight: 700;
  background: var(--brass); color: #0B0B0C;
  border: 0; border-radius: var(--radius-sm);
  padding: 16px; cursor: pointer;
  text-align: center;
  transition: filter .12s;
}
.paybtn:hover { filter: brightness(1.08); }
.paybtn:disabled {
  background: var(--ink-raised); color: var(--mute);
  cursor: not-allowed; filter: none;
}
/* 결제할 수 없는 상태에서 대신 놓는 이동 링크. 주요 액션이 아니므로 테두리만 쓴다. */
.paybtn--ghost {
  background: none; color: var(--paper);
  border: 1px solid var(--line-strong);
}
.paybtn--ghost:hover { border-color: var(--brass); color: var(--brass); filter: none; }

.oauthdivider {
  display: flex; align-items: center; gap: 12px;
  margin: 24px 0 16px;
  color: var(--mute); font-size: 12px;
}
.oauthdivider::before, .oauthdivider::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}
/*
   소셜 로그인은 보조 경로인데, 실측하면 여전히 주요 액션(로그인 버튼)을 이긴다 —
   상대휘도가 로그인 브라스(rgb(233,180,76)) 0.505 인데 카카오 옐로(#FEE500) 는 0.771 로
   1.53배 밝다. 브랜드 색은 가이드가 정한 값이라 못 바꾸고, 종전엔 폭을 똑같이 100% 로
   맞춰서 두 버튼의 면적도 같았다 — 밝기와 면적이 둘 다 같으면 더 밝은 색이 이긴다.
   가로로 나란히 놓아 버튼 하나당 폭을 절반으로 줄이면 면적×밝기 값이 로그인 버튼보다
   작아져서(0.505×전체폭 > 0.771×반폭) 위계가 실측으로도 뒤집힌다.
*/
.oauthbuttons { display: flex; flex-direction: row; gap: 10px; }
/*
   line-height 를 고정하는 이유: 아이콘(15px)이 글자 줄 높이보다 커서 버튼 높이를 밀어올려,
   종전에는 소셜 버튼이 로그인 버튼보다 7px 더 컸다. 줄 높이를 아이콘 이상으로 못박으면
   아이콘이 높이에 관여하지 않아 padding 만으로 크기가 결정된다.
*/
.oauthbtn {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  font-family: var(--body); font-size: 13px; font-weight: 600;
  line-height: 17px;
  border: 0; border-radius: var(--radius-sm);
  padding: 11px 8px; cursor: pointer; text-align: center;
  white-space: nowrap;
  transition: filter .12s;
}
.oauthbtn:hover { filter: brightness(1.06); }
/* 카카오 로그인 버튼은 공식 디자인 가이드 스펙(배경 #FEE500, 심볼/텍스트 #000000, 모서리 12px)을 그대로 따른다.
   https://developers.kakao.com/docs/ko/kakaologin/design-guide */
.oauthbtn-kakao { background: #FEE500; color: rgba(0, 0, 0, 0.85); border-radius: 12px; }
.oauthbtn-naver { background: #03C75A; color: #FAFAFA; }
.oauthicon { width: 15px; height: 15px; flex-shrink: 0; }

/* ---------- 관리자 대시보드 ---------- */
.dashfilter { display: flex; align-items: flex-end; gap: 16px; margin-bottom: 32px; }
.dashfilter label {
  display: flex; flex-direction: column; gap: 6px;
  font-size: 12px; color: var(--mute);
}
.dashfilter input {
  font-family: var(--body); font-size: 14px;
  background: var(--ink-soft); color: var(--paper);
  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 8px 10px;
}
.dashfilter input:focus-visible { outline: 2px solid var(--brass); outline-offset: 1px; }
.dashfilter button {
  font-family: var(--body); font-size: 14px; font-weight: 600;
  background: var(--brass); color: var(--ink);
  border: 0; border-radius: var(--radius-sm); padding: 9px 20px; cursor: pointer;
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 16px;
  margin-bottom: 40px;
}
.card-stat {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--ink-soft);
  padding: 20px;
  display: flex; flex-direction: column; gap: 8px;
}
.card-stat .label {
  font-size: 12px; letter-spacing: 0.08em;
  color: var(--mute); text-transform: uppercase;
}
.card-stat strong {
  font-family: var(--display); font-size: 26px;
  color: var(--brass); font-weight: 700;
}

.chartgrid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; }
/*
   min-width:0 이 없으면 좁은 화면에서 칸이 캔버스보다 작아지지 못한다. 그리드 아이템은
   기본 min-width:auto(=min-content)인데, Chart.js 는 아직 리사이즈 전이면 캔버스를 기본
   300px 로 들고 있어서 칸의 min-content 가 300+패딩·테두리=342px 로 굳는다. 그러면 칸이
   못 줄고 → 캔버스도 줄 이유가 없어 그대로 멈춘다(서로가 서로를 붙잡는 상태).
   실측: 360px 폭에서 쓸 수 있는 자리는 327px 인데 칸이 342px 라 6px 가 넘쳐 페이지
   전체에 가로 스크롤이 생겼다. 375px 에서는 우연히 들어가서 안 드러났는데, 안드로이드
   상당수가 360px 다. max-width 는 Chart.js 리사이즈 전에도 캔버스가 칸을 밀지 않게 하는 보험.
*/
.chartbox { border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; min-width: 0; }
.chartbox canvas { max-width: 100%; }
.chartbox h3 {
  font-family: var(--display); font-size: 15px; font-weight: 600;
  margin: 0 0 16px; color: var(--paper);
}
.chartbox-wide { grid-column: 1 / -1; }

/* ---------- 마이페이지 예매 내역 ---------- */
.reslist { display: flex; flex-direction: column; gap: 16px; }
.resrow {
  display: flex; justify-content: space-between; align-items: center;
  gap: 24px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--ink-soft);
  padding: 20px 24px;
}
/* 클릭하면 포스터+정보+좌석 팝업(.resmodal)이 뜬다. cursor/hover/focus-visible 로 눌리는
   영역이라는 걸 알려준다 — 옆 .resaction(취소·결제하기 버튼)은 그대로 각자 동작한다. */
.resinfo { flex: 1; min-width: 0; cursor: pointer; border-radius: var(--radius-xs); display: flex; gap: 16px; align-items: center; }
/* 글자 칸. min-width:0 이 있어야 긴 공연명이 flex 칸을 밀어내지 않고 제자리에서 줄바꿈된다. */
.restext { min-width: 0; flex: 1; }
/* 포스터 썸네일. 실제 포스터가 3:4 라 그 비율을 그대로 쓴다 — cover 로 잘라 넣으면
   제목이 큼직하게 박힌 포스터가 많아 위아래가 날아간다. */
.resthumb {
  flex: none;
  position: relative;
  width: 54px; aspect-ratio: 3 / 4;
  border-radius: var(--radius-poster);
  overflow: hidden;
  background: var(--ink);
}
.resthumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 포스터가 없을 때만 보인다. 목록 카드(.posterfallback)와 같은 발상이되 54px 이라
   첫 글자만 남긴다. */
.resthumb-fallback { display: none; }
.resthumb.no-img { background: linear-gradient(140deg, #2a2b2f, #17181b); }
.resthumb.no-img .resthumb-fallback {
  display: flex; align-items: center; justify-content: center;
  position: absolute; inset: 0;
  font-family: var(--display); font-size: 20px; font-weight: 700;
  color: rgba(248, 244, 236, .5);
}
.resinfo:hover .restitle { color: var(--brass); }
.resinfo:focus-visible { outline: 2px solid var(--brass); outline-offset: 4px; }
.restitle { font-family: var(--display); font-size: 17px; font-weight: 600; margin: 0 0 6px; transition: color .12s; }
.resmeta { font-size: 13px; color: var(--paper); margin: 0 0 4px; }
/* 숫자와 '원' 사이가 끊기면 두 줄이 된다 — 공연 정보 표의 .fee(422행), 결제 화면의
   .mb-price strong 과 같은 문제. */
.resmeta .resamount { white-space: nowrap; }
.resno { font-size: 12px; color: var(--mute); margin: 0; }
/* 공연 상세·길찾기. resno 와 같은 톤(작고 흐림)이 기본이고, 실제로 다른 화면으로
   보낸다는 걸 알리는 밑줄만 얹는다 — 위 두 줄(제목·메타)과 같은 강조를 주면 이
   행이 예매 정보의 일부처럼 읽혀서 "눌러서 어딘가로 간다"는 게 묻힌다. */
.reslinks { margin: 6px 0 0; }
.reslink {
  font-size: 12px; color: var(--mute);
  border-bottom: 1px solid var(--line);
  margin-right: 12px;
}
.reslink:hover { color: var(--brass); border-color: var(--brass-dim); }

/* ---- 예매 요약 팝업 ----
   공유 시트(공연 상세)와 같은 배경+dialog 구조 — 그때 정한 opacity/pointer-events 전환,
   Esc·배경·닫기 버튼 세 가지로 닫는 규칙을 그대로 따른다. 예매 건마다 모달을 복제하지
   않고 하나만 두고 클릭한 행의 data-* 값으로 내용을 갈아끼운다. */
.resmodal-backdrop {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(0, 0, 0, .55);
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease;
}
.resmodal-backdrop.open { opacity: 1; pointer-events: auto; }

.resmodal {
  position: fixed; top: 50%; left: 50%; z-index: 71;
  width: min(340px, calc(100vw - 40px));
  transform: translate(-50%, -50%) scale(.96);
  background: var(--ink-soft);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 20px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
}
.resmodal.open { opacity: 1; pointer-events: auto; transform: translate(-50%, -50%) scale(1); }

.resmodal-close {
  position: absolute; top: 14px; right: 14px; z-index: 1;
  display: flex; padding: 4px;
  background: none; border: none; color: var(--mute);
  cursor: pointer; transition: color .12s;
}
.resmodal-close:hover { color: var(--paper); }

.resmodal-poster {
  position: relative; width: 100%; aspect-ratio: 3 / 4;
  border-radius: var(--radius-sm); overflow: hidden;
  border: 1px solid var(--line); background: var(--ink-raised);
  margin-bottom: 16px;
}
.resmodal-poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
.resmodal-posterfallback {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--display); font-size: 48px; font-weight: 800;
  color: var(--brass-dim); opacity: .55;
  background: linear-gradient(160deg, #1B1B20 0%, #131316 100%);
}

.resmodal-body h3 { margin: 0 0 16px; font-size: 18px; font-weight: 700; color: var(--paper); padding-right: 24px; }
.resmodal-spec { margin: 0; }
.resmodal-spec div { display: flex; gap: 16px; padding: 10px 0; border-top: 1px solid var(--line); font-size: 14px; }
.resmodal-spec div:first-child { border-top: none; padding-top: 0; }
.resmodal-spec dt { width: 56px; flex: none; color: var(--mute); margin: 0; }
.resmodal-spec dd { margin: 0; color: var(--paper); }

.resaction { display: flex; flex-direction: column; align-items: flex-end; gap: 10px; flex: none; }
.badge {
  font-size: 12px; letter-spacing: 0.05em;
  padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px;
  color: var(--mute); white-space: nowrap;
}
.badge-CONFIRMED { color: var(--brass); border-color: var(--brass-dim); }
.badge-CANCELED, .badge-EXPIRED { color: var(--rose); opacity: .75; }
.refundhint { font-size: 12px; color: var(--mute); margin: 0; text-align: right; }

.btn-pay, .btn-cancel, .btn-delete {
  font-family: var(--body); font-size: 13px; font-weight: 600;
  border: 0; border-radius: var(--radius-sm); padding: 8px 16px; cursor: pointer; white-space: nowrap;
}
.btn-pay { background: var(--brass); color: var(--ink); }
.btn-cancel, .btn-delete { background: transparent; border: 1px solid var(--line); color: var(--mute); }
.btn-cancel:hover { border-color: var(--rose); color: var(--rose); }
.btn-delete:hover { border-color: var(--line-strong); color: var(--paper); }
.btn-cancel:disabled, .btn-delete:disabled { opacity: .5; cursor: not-allowed; }

@media (max-width: 720px) {
  .masthead .logo-mark { width: 22px; height: 14px; margin-right: 6px; }
  .detail { grid-template-columns: 1fr; }
  .chartgrid { grid-template-columns: 1fr; }
  .dashfilter { flex-wrap: wrap; }
  .resrow { flex-direction: column; align-items: flex-start; }
  .resaction { flex-direction: row; align-items: center; width: 100%; justify-content: space-between; }
  /*
     좁은 화면의 세로 예산 되찾기.

     실측(375×812): 위쪽 고정물 332px + 아래 예매 바 235px 를 빼면 좌석 지도에 남는 건
     233px — 화면의 29% 뿐이었다. 게다가 지도 높이 하한이 280px 라 아래 47px 가 예매 바
     뒤로 들어갔다. 좌석을 고르는 화면에서 세로가 가장 아까운 자원이라는 건 이미
     `.floornav.is-docked` 주석에서 세운 원칙인데, 넓은 화면에서만 지키고 있었다.
     (옆에 붙이는 방식은 DOCK_MIN_WIDTH 미만에서 꺼지므로 좁은 화면엔 적용되지 않는다.)

     범인은 줄바꿈으로 두 줄이 된 띠 두 개였다 — 층 바로가기 84px, 등급 가격표 68px.
     둘 다 "가로로 늘어선 짧은 목록"이라 모바일에서는 접는 대신 가로로 스크롤시킨다.
     칩을 지우거나 글자를 줄이지 않으므로 정보는 그대로다.
  */
  .floornav:not(.is-docked) {
    flex-wrap: nowrap;
    width: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;              /* Firefox */
    -ms-overflow-style: none;           /* 구형 Edge */
  }
  .floornav:not(.is-docked)::-webkit-scrollbar { width: 0; height: 0; display: none; }
  .floornav:not(.is-docked) .floorchip { flex: none; }

  .bookbar .legend-grades {
    flex-wrap: nowrap;
    width: 100%;
    justify-content: flex-start;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .bookbar .legend-grades::-webkit-scrollbar { width: 0; height: 0; display: none; }
  .bookbar .legend-grades button { flex: none; }
  /* 띠가 한 줄이 된 만큼 위아래 여백도 같이 조인다. */
  .bookbar .legend { margin-bottom: 6px; padding-bottom: 6px; }

  /*
     ---- 가로 스크롤 띠의 "옆에 더 있다" 표시 ----

     위에서 두 띠를 가로 스크롤로 바꾸면서 스크롤바까지 같이 껐는데(디자인상 맞다 —
     모바일 오버레이 스크롤바는 어차피 스크롤 중에만 잠깐 보인다), 그 결과 넘친 항목이
     있다는 신호가 화면에서 완전히 사라졌다.

     실측(390px): 등급 가격표는 327px 칸에 527px 이 들어 있어 A석 버튼이 통째로 화면
     밖이었고, 층 바로가기는 325px 칸에 495px 이라 4층짜리 홀의 3층 칩이 안 보였다.
     둘 다 잘린 자리가 딱 떨어져서 "여기서 끝"처럼 보인다 — 등급 필터에 A석이 아예
     없는 줄 알고, 3층 좌석은 찾지 못한다.

     그래서 넘치는 쪽 가장자리에서 내용을 흐린다. 잘린 글자가 사라져 가는 모양 자체가
     "계속된다"는 신호다. 띠를 덮는 별도 요소를 얹지 않고 mask 로 처리하는 이유는,
     스크롤 컨테이너 안에 넣은 오버레이는 내용과 같이 흘러가 버리고 밖에 두려면
     감싸는 요소가 하나 더 필요하기 때문이다.

     양쪽 폭은 JS 가 스크롤 위치를 보고 --fade-l/--fade-r 로 넘긴다(seat-map.html).
     기본값 0 이면 gradient 가 곧바로 불투명해져 아무것도 안 흐려지므로, JS 가 없거나
     넘치지 않을 때는 지금까지와 똑같이 보인다.

     두 띠의 방식이 다른 이유는 띠 자신이 그려지는 물건인지가 다르기 때문이다.
     등급 가격표(.legend-grades)는 배경도 테두리도 없는 맨 줄이라 mask 를 걸면 딱
     버튼만 흐려진다. 층 바로가기(.floornav)는 테두리 있는 알약이라 같은 방법을 쓰면
     알약 자체가 가장자리에서 녹아 "내용이 이어진다"가 아니라 "덜 그려졌다"로 보인다
     (실제로 걸어보고 확인했다). 그래서 그쪽은 알약 배경색 그라데이션을 위에 얹어
     칩만 덮는다.
  */
  .bookbar .legend-grades {
    --fade-l: 0px;
    --fade-r: 0px;
    -webkit-mask-image: linear-gradient(to right,
        transparent 0, #000 var(--fade-l),
        #000 calc(100% - var(--fade-r)), transparent 100%);
    mask-image: linear-gradient(to right,
        transparent 0, #000 var(--fade-l),
        #000 calc(100% - var(--fade-r)), transparent 100%);
  }

  /*
     알약 안쪽에만 얹는다 — 위아래 1px 은 테두리, 아래 24px 은 .floornav 의 margin-bottom
     이라 슬롯 높이에 포함돼 있다. 오른쪽 끝을 둥글리는 건 알약과 같은 색을 칠하기
     때문이다: 각진 채로 두면 둥근 마구리 바깥으로 색이 삐져나와 모서리가 각져 보인다.

     슬롯에 얹는 이유: 스크롤 컨테이너(.floornav) 안에 넣은 것은 내용과 같이 흘러가
     가장자리에 머물지 못한다. 절대 위치라 flex 인 슬롯의 배치에는 끼어들지 않는다.
  */
  .floornav-slot { position: relative; }
  .floornav-slot::before,
  .floornav-slot::after {
    content: '';
    position: absolute; top: 1px; bottom: 25px;
    z-index: 1; pointer-events: none;
  }
  .floornav-slot::before {
    left: 1px; width: var(--fade-l, 0px);
    border-radius: 999px 0 0 999px;
    background: linear-gradient(to right, var(--floornav-bg), transparent);
  }
  .floornav-slot::after {
    right: 1px; width: var(--fade-r, 0px);
    border-radius: 0 999px 999px 0;
    background: linear-gradient(to left, var(--floornav-bg), transparent);
  }

  .bookbar { padding-bottom: calc(14px + env(safe-area-inset-bottom)); }
  .bookbar .inner { flex-direction: column; align-items: stretch; }
  .bookbar .picked {
    grid-template-columns: 1fr;
    grid-template-areas: "list" "hint" "summary";
    gap: 8px;
  }
  .bookbar .pickedsummary {
    min-width: 0;
    text-align: left;
  }
  /* 예매 CTA 만 전폭으로. 등급 필터 버튼까지 잡으면 가격표가 세로로 네 줄이 된다. */
  .bookbar .inner button { width: 100%; }
}

/* 모션 최소화 설정에서는 전환뿐 아니라 애니메이션도 멈춘다.
   transition 만 껐을 때 포스터 shimmer(무한 반복)가 계속 돌아가는 문제가 있었다. */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ==================================================================
   공연 목록(page-list) / 공연 상세(page-detail).

   레이아웃은 bluesquare.kr 을 참고한 그대로(필터 pill, 카드 그리드, 라벨-값 행)
   두고, 색·타이포만 공통 다크 토큰으로 맞췄다. 좌석 배치도의 무대 조명 연출이
   어두운 배경에 기대고 있어서, 화면마다 테마가 갈리면 같은 서비스로 안 읽힌다.
   ================================================================== */
body.page-list .wrap, body.page-detail .wrap { max-width: 1120px; }

/* ---- 로그인 · 회원가입 알약 (헤더 공통) ----

   **화면 스코프에 두지 않는다.** 헤더 자체가 공용 조각(fragment/header)이라 그 안의
   버튼 스타일도 공용이어야 한다 — 종전에는 body.page-list/.page-detail 로 묶여 있어서,
   그 클래스가 없는 에러 페이지(403/404/5xx)에서는 알약이 아예 안 그려지고 브라스 글자
   두 개가 붙어 나왔다. 공유 시트를 조각으로 뺄 때 겪은 것과 같은 함정이다.

   로그인은 텍스트 링크, 회원가입은 채움 CTA로 역할을 분리한다. 둘 다 버튼처럼 두면
   헤더에서 "계정 만들기"보다 "로그인"이 같은 무게로 읽혀 처음 온 사람의 다음 행동이
   흐려진다. 높이와 글자 크기는 맞추되, 로그인에는 테두리와 배경을 두지 않는다. */
.authbtns { display: flex; align-items: center; gap: 8px; }
.authbtns a {
  display: inline-flex; align-items: center;
  min-height: 38px;
  border-radius: 999px;
  font-size: 13px; font-weight: 600;
  transition: color .12s, background .12s, transform .2s, box-shadow .12s;
}
.authbtns .btn-login {
  padding: 9px 8px;
  color: var(--brass) !important;
}
.authbtns .btn-login:hover { color: var(--paper) !important; }
.authbtns .btn-signup {
  padding: 9px 16px;
  border: 1px solid var(--brass);
  background: var(--brass);
  color: var(--ink) !important;
  box-shadow: 0 4px 14px rgba(233, 180, 76, .18);
}
.authbtns .btn-signup:hover {
  background: #f2c874;
  box-shadow: 0 6px 18px rgba(233, 180, 76, .28);
}

/* ---- 모바일 필터 토글 버튼 (기본 숨김, 860px 이하에서만 노출) ---- */
body.page-list .mobile-filter-btn {
  display: none;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
  padding: 10px 18px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--ink-soft);
  color: var(--paper);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
body.page-list .mobile-filter-btn em { font-style: normal; color: var(--mute); }

/* ---- 필터 패널 ---- */
body.page-list .filterpanel {
  background: var(--ink-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 24px 28px;
  margin-bottom: 32px;
}
body.page-list .fp-head { display: none; }
body.page-list .fp-close { border: 0; background: none; font-size: 22px; line-height: 1; color: var(--mute); cursor: pointer; }

body.page-list .fp-keyword {
  display: flex; align-items: center; gap: 10px;
  background: var(--ink); border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 12px 20px; margin-bottom: 20px;
  transition: border-color .12s;
}
body.page-list .fp-keyword:focus-within { border-color: var(--brass); }
body.page-list .fp-keyword svg { flex: none; }
body.page-list .fp-keyword input {
  border: 0; outline: 0; flex: 1; min-width: 0;
  font: inherit; font-size: 14px; background: none; color: var(--paper);
}
body.page-list .fp-keyword input::placeholder { color: var(--mute); }

body.page-list .fp-grid { display: flex; flex-direction: column; gap: 14px; }
body.page-list .fp-row { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
body.page-list .fp-label { flex: none; width: 76px; font-size: 13px; font-weight: 600; color: var(--mute); }
body.page-list .fp-col-wide .fp-row { align-items: flex-start; }
/* fp-row 는 flex-start 라 라벨이 맨 위에 붙는데, 드롭다운(select)은 라벨보다 키가 커서
   그대로 두면 라벨이 위로 치우쳐 보인다. 드롭다운 한 줄 높이 기준으로 세로 중앙에 오도록 내려준다. */
body.page-list .fp-col-wide .fp-label { margin-top: 9px; }

/* 드물게 쓰는 조건은 native details로 접는다. 스크립트 없이도 열고 닫히며, 적용 중인
   조건이 있을 때는 서버가 open 속성을 내려줘 현재 결과의 근거를 숨기지 않는다.

   왼쪽 들여쓰기를 없앴다 — 94px 는 위 지역·날짜 드롭다운 시작 위치에 맞춘 값인데,
   그러면 장르·진행 현황·지역·날짜 라벨이 전부 왼쪽 0에서 시작하는 것과 이 줄만
   어긋나서 필터 패널의 한 항목이 아니라 어쩌다 붙은 링크처럼 보였다. 좁은 화면
   레이아웃(아래 미디어쿼리)은 이미 margin-left: 0 이라 이 정렬이 원래도 맞는
   기본값이었다 — 데스크톱만 예외로 들여져 있었을 뿐이다. */
body.page-list .fp-advanced {
  border-top: 1px solid var(--line);
}
body.page-list .fp-advanced summary {
  display: flex; align-items: center; gap: 8px;
  min-height: 42px;
  color: var(--mute); font-size: 13px; font-weight: 700;
  cursor: pointer; list-style: none;
}
body.page-list .fp-advanced summary::-webkit-details-marker { display: none; }
/* 화살표(::after)는 항상 "상세 조건" 글자 바로 옆에 붙어야 한다 — 소스 순서로는
   span → em(적용됨 배지, 조건이 있을 때만) → ::after 라서, 그대로 두면 배지가 있을 때
   화살표가 배지 뒤로 밀려나 글자와 멀어진다. flex order 로 화살표를 배지보다
   앞에 오게 한다(열림·닫힘 상태와 무관하게 항상 이 순서). */
body.page-list .fp-advanced summary::after {
  content: "⌄";
  order: 1;
  font-size: 16px; line-height: 1;
  transition: transform .16s ease;
}
body.page-list .fp-advanced[open] summary::after { transform: rotate(180deg); }
body.page-list .fp-advanced summary em {
  order: 2;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(233, 180, 76, .16);
  color: var(--brass);
  font-size: 11px; font-style: normal;
}
body.page-list .fp-advanced-body {
  display: grid;
  gap: 12px;
  padding: 2px 0 16px;
}

body.page-list .fp-pills { display: flex; flex-wrap: wrap; gap: 8px; }
body.page-list .cattab {
  position: relative;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 8px 16px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-size: 13px; color: var(--mute);
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
body.page-list .cattab input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
body.page-list .cattab:hover { border-color: var(--paper); color: var(--paper); }
/* 선택된 필터는 강조색으로 채운다. 어두운 배경에서 앰버 위 검정 글씨가 대비가 가장 크다. */
body.page-list .cattab.active {
  background: var(--brass); border-color: var(--brass);
  color: #0B0B0C; font-weight: 700;
}

/* flex: 1 1 0%; min-width: 0 이 필요한 이유: 안의 fp-hint 가 flex-basis:100% 라서
   이 컨테이너의 "내용 기준 너비"를 브라우저가 이상하게 크게 잡아버려, 실제로는
   한 줄에 다 들어가는데도 fp-row 가 불필요하게 줄바꿈되는 버그가 있었다. */
body.page-list .fp-dropdowns { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; flex: 1 1 0%; min-width: 0; }
body.page-list .fp-dropdowns select {
  appearance: none;
  -webkit-appearance: none;
  font: inherit; font-size: 13px; color: var(--paper);
  border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 8px 34px 8px 16px;
  background: var(--ink) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23A1A1AA'/%3E%3C/svg%3E") no-repeat right 14px center;
  cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: border-color .12s;
}
body.page-list .fp-dropdowns select:hover { border-color: var(--paper); }
/* 드롭다운을 펼쳤을 때의 목록은 OS 가 그리므로 배경/글자색을 따로 지정해야 한다. */
body.page-list .fp-dropdowns select option { background: var(--ink-soft); color: var(--paper); }
/* 선택값 텍스트 길이에 따라 각 드롭다운 폭이 들쭉날쭉해지지 않도록 고정폭을 준다. */
/* 선택값 길이가 바뀌어도 필터 줄의 폭이 흔들리지 않게 각 드롭다운 폭을 고정한다. */
body.page-list .fp-dropdowns select[name="area"] { width: 140px; }
body.page-list .fp-dropdowns select[name="venue"] { width: 200px; }
body.page-list .fp-dropdowns select[name="month"] { width: 120px; }
body.page-list .fp-dropdowns select[name="dayOfWeek"] { width: 120px; }
body.page-list .fp-dropdowns select[name="timeSlot"] { width: 140px; }
/* 지역·장소 그룹과 분명히 구분되도록 간격을 주되, 행 맨 끝까지는 밀지 않는다.
   고정값이라 지역/공연장 값 길이와 상관없이 위치가 항상 동일하다. */
body.page-list .fp-hint { font-size: 12px; color: var(--mute); opacity: .7; flex-basis: 100%; }
/* 지역을 고르면 힌트 문구가 사라지면서 줄바꿈 위치가 바뀌어 패널 전체 높이가 들쭉날쭉했다.
   엘리먼트 자체(= flex-basis:100% 의 줄바꿈 효과)는 항상 유지하고 텍스트만 숨긴다. */
body.page-list .fp-hint--hidden { visibility: hidden; }

body.page-list .fp-apply {
  display: none;
  width: 100%; margin-top: 20px;
  background: var(--brass); color: #0B0B0C; border: 0; border-radius: 999px;
  padding: 14px; font: inherit; font-weight: 700; font-size: 15px; cursor: pointer;
}
body.page-list .fp-backdrop { display: none; }

/* ---- 카드 그리드 ---- */
body.page-list .grid { gap: 32px 24px; }
/*
   카드를 세로 플렉스로 두고 meta 를 바닥에 붙인다(.meta { margin-top: auto }).
   그리드 항목은 기본이 stretch 라 한 행의 카드 높이가 이미 같으므로, 이렇게 하면
   제목 줄 수가 달라도 <b>메타 줄이 행 안에서 가지런히 놓인다</b>. 제목에 2줄 높이를
   못박는 방법과 결과는 같은데, 제목이 한 줄인 카드에 빈 줄이 생기지 않는다.
   포스터는 flex: none 으로 고정한다 — 플렉스 항목은 기본적으로 줄어들 수 있어서
   그냥 두면 aspect-ratio 로 잡은 높이가 눌린다.
*/
body.page-list .card { display: flex; flex-direction: column; }
body.page-list .card .poster {
  position: relative;
  flex: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--ink-soft);
}
/*
   로딩 중 shimmer.

   주의: 이 애니메이션을 .poster 자체에 걸면 안 된다. 무한 애니메이션이 걸린
   요소는 별도 합성 레이어로 올라가는데, 그러면 자식 <img> 가 배경 뒤로 묻혀
   포스터가 통째로 안 보인다(실제로 그렇게 만들었다가 잡음).
   그래서 shimmer 는 뒤에 깔리는 ::before 로 분리하고, 이미지에 z-index 를 줘서
   항상 위에 오게 한다. 이미지가 도착하면 자연히 덮인다.
*/
body.page-list .card .poster::before {
  content: '';
  position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(100deg, #1A1B1E 30%, #28292E 50%, #1A1B1E 70%);
  background-size: 220% 100%;
  animation: poster-shimmer 1.4s ease-in-out infinite;
}
@keyframes poster-shimmer {
  from { background-position: 180% 0; }
  to   { background-position: -80% 0; }
}
body.page-list .card .poster img { position: relative; z-index: 1; }
/*
   포스터에 살짝 깊이를 준다 — "배경에서 색을 빼 포스터를 유일한 색으로 둔다"는
   원칙은 그대로 두고(듀오톤·블러로 포스터 자체를 죽이지 않는다), 가장자리만
   은은하게 눌러 액자 같은 인상을 준다. 이미지(z-index:1)보다 위에 와야 해서
   z-index:2 를 준다 — inset box-shadow 를 .poster 자체에 걸면 자식 요소가
   그 위에 그려져(포스터 shimmer 와 같은 함정) 안 보인다.
*/
body.page-list .card .poster::after {
  content: '';
  position: absolute; inset: 0; z-index: 2;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .08), inset 0 -48px 48px -24px rgba(0, 0, 0, .28);
  pointer-events: none;
}
body.page-list .card .poster.no-img {
  display: grid; place-items: center;
  background: var(--ink-soft);
}
body.page-list .card .poster.no-img::before { animation: none; background: none; }
body.page-list .card .posterfallback {
  display: none;
  position: relative; z-index: 1;
  font-family: var(--body); font-size: 34px; font-weight: 700; color: var(--mute);
}
body.page-list .card .poster.no-img .posterfallback { display: block; }

/* 클릭 가능한 카드라는 신호. 포스터만 살짝 확대되고 카드가 떠오른다. */
body.page-list .card .poster img { transition: transform .25s ease; }
body.page-list .card { transition: transform .18s ease; }
body.page-list .card:hover { transform: translateY(-4px); }
body.page-list .card:hover .poster img { transform: scale(1.04); }
body.page-list .card:hover h3 { color: var(--brass); }

/*
   z-index 는 반드시 포스터 이미지(z-index:1)보다 위여야 한다.

   종전엔 배지도 1 이었는데, 같은 값이면 DOM 에서 뒤에 오는 쪽이 위에 그려진다 —
   마크업이 배지 → img 순이라 **포스터가 실제로 있는 카드에서는 배지가 통째로
   이미지 뒤에 깔려 안 보였다.** 포스터가 없는 카드(폴백)만 배지가 보였고, 로컬
   시드가 대부분 폴백이라 화면으로는 오래 안 드러났다. 실데이터가 붙은 홈·목록·찜
   세 화면 모두 같은 증상이었다.

   2 가 아니라 3 인 이유는 .poster::after(가장자리 inset 그림자)가 이미 2 를 쓰기
   때문이다 — 2 로 두면 이번엔 그 그림자가 배지 위에 얹힌다.
*/
body.page-list .card-badge {
  position: absolute; top: 10px; left: 10px; z-index: 3;
  background: rgba(17, 18, 20, .72); color: var(--paper);
  backdrop-filter: blur(4px);
  font-size: 12px; font-weight: 500;
  border-radius: 999px; padding: 5px 12px;
  border: 1px solid rgba(248, 244, 236, .16);
}
/* 예매 가능 여부 배지. 장르 배지 반대쪽 모서리 — 마감 임박 배지(홈 전용)와 같은 자리
   규칙이다. body.page-list 스코프라 목록·홈(둘 다 page-list 클래스를 겹쳐 쓴다) 양쪽에
   그대로 적용된다. 예매 가능(배지 없음)이 기본값이고, 그 외 네 상태만 색으로 가른다 —
   색은 "지금 알아야 할 것"에만 쓴다는 관례를 따라 매진(로즈)·매진임박(브라스)만 눈에
   띄고, 오픈예정·정보없음은 장르 배지와 같은 중립 톤이다(급한 소식이 아니라서). */
body.page-list .card-avail {
  position: absolute; top: 10px; right: 10px; z-index: 3;
  padding: 4px 9px;
  border-radius: 999px;
  font-size: 11px; font-weight: 700;
}
body.page-list .card-avail.avail-soldout { background: var(--rose); color: #fff; }
body.page-list .card-avail.avail-low { background: var(--brass); color: var(--ink); }
body.page-list .card-avail.avail-pending,
body.page-list .card-avail.avail-unknown {
  background: rgba(17, 18, 20, .72); color: var(--paper);
  backdrop-filter: blur(4px);
  font-weight: 500;
  border: 1px solid rgba(248, 244, 236, .16);
}
/*
   제목이 포스터 바로 아래에 오므로 같은 행의 제목은 자동으로 같은 높이에서 시작한다.
   (종전에는 meta 가 위에 있어서 공연장명이 긴 카드만 제목이 한 줄 내려앉았고, 그걸
   meta 에 min-height: 3em 을 박아 막고 있었다 — 순서를 바로잡으니 그 보정이 필요 없어졌다.)

   제목 줄 수가 카드마다 달라 아래 meta 가 어긋나는 건 높이를 못박아 맞추지 않는다.
   그 방법은 대다수 카드에 빈 줄을 남긴다(실측 12개 중 10개가 한 줄). 대신 위 .card 의
   세로 플렉스 + meta 의 margin-top: auto 로 meta 를 바닥에 붙여 정렬한다.
*/
body.page-list .card h3 {
  font-size: 16px; font-weight: 700; color: var(--paper);
  line-height: 1.4;
  margin: 0 0 8px;
  /* 아주 긴 제목만 두 줄에서 자른다. 높이를 2줄로 못박지는 않는다 —
     실측 12개 중 10개가 한 줄이라, 못박으면 대다수 카드가 빈 줄을 떠안는다. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  transition: color .12s;
}
/* 공연장과 기간은 각각 한 줄씩. 한 줄에 이어 붙이면 공연장명이 긴 카드만 접혀서
   높이가 어긋난다 — 줄을 나누면 어떤 이름이 와도 정확히 두 줄이다. */
body.page-list .card .meta {
  /* <p> 의 브라우저 기본 상하 여백(13px)이 그대로 남아 제목과 떨어져 보였다.
     카드 안에서 간격은 이 규칙이 정하는 값이어야 하므로 명시적으로 0 으로 두고
     제목과의 간격은 h3 의 margin-bottom 하나로만 만든다. */
  margin: auto 0 0;   /* 위쪽 auto = 카드 바닥에 붙인다 (위 .card 플렉스 주석 참고) */
  display: grid;
  /* 2px 은 글자 세 줄만 있을 때의 값이었다. 가운데가 알약이 되면서 줄끼리 붙어 보여
     6px 로 넓혔다 — 알약은 자기 padding 만큼 이미 시각적 부피가 있어서, 글자끼리
     붙여 두던 간격을 그대로 쓰면 답답하다. */
  gap: 6px;
  color: var(--mute);
  line-height: 1.5;
}
/* 아주 긴 공연장명("대학로 자유극장 (자유문화발전소)")은 말줄임 — 줄바꿈시키면
   다시 카드 높이가 갈린다. */
body.page-list .card .m-venue {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/*
   날짜 줄. 종전에는 "기간은 공연장보다 한 단계 뒤로" 라며 12px + opacity .75 를 걸었는데,
   그 밑에 이미 color 가 rgba(...,.55) 라 흐리기가 두 번 겹쳤다. 라이트 톤에서 실측하면
   대비가 2.67 로, 카드에서 가장 안 읽히는 글자였다(제목 17.08 · 공연장 4.04). 12px 본문에
   요구되는 4.5 는커녕 큰 글씨 기준 3.0 에도 못 미친다.

   게다가 지금은 이 줄이 「오늘 19:30」·「다음 공연 8/27(수) 19:30」을 말한다 —
   카드에서 가장 행동에 가까운 정보라 공연장보다 뒤에 둘 이유가 없어졌다.
   opacity 를 걷고 크기도 공연장과 같은 13px 로 맞춘다(대비는 tone-light 쪽에서 5.08).
*/
/*
   회차 알약. 메타의 나머지가 전부 맨 글자라, 여기 하나만 "면"을 주면 색이나 굵기를
   올리는 것보다 훨씬 먼저 눈에 걸린다 — 제목과 크기 경쟁을 하지 않고도 띈다.

   grid 안에서 폭이 늘어나지 않게 justify-self 를 준다. 안 주면 알약이 카드 폭 전체로
   늘어나 "면"이 아니라 띠가 된다.
*/
body.page-list .card .m-date {
  justify-self: start;
  display: inline-flex; align-items: center;
  /*
     알약 상자를 포스터 왼쪽 선에 맞춘다. 예전에는 글자 열을 맞추려고 10px 당겼지만,
     진하게 채운 알약이 많아진 뒤로는 글자보다 상자가 먼저 보여 카드 밖으로 튀어나와 보였다.
  */
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12.5px; font-weight: 700;
  letter-spacing: -.01em;
  white-space: nowrap;
}
/*
   공연 기간. 회차 줄(.m-date) 뒤에 한 단계 물러서 놓는다 — 「언제 가지」가 먼저고
   「언제까지 하지」가 다음이다. 종전 .m-date 가 쓰던 12px 자리를 그대로 물려받되,
   흐리기를 두 번 걸던 opacity 는 안 가져온다(그게 대비 2.67 의 원인이었다).
   색은 .meta 가 주는 값을 그대로 쓴다 — 라이트 톤에서 4.04 다.
*/
body.page-list .card .m-date { background: rgba(233, 180, 76, .16); color: var(--brass); }
body.page-list .card .m-date--soon { background: var(--brass); color: var(--ink); }
body.page-list .card .m-run { font-size: 12px; }

/* 주변 공연 찾기 전용. 거리를 공연장과 한 줄에 묶는다 — 별도 줄로 빼면 이 화면
   카드만 한 줄 더 길어져 다른 화면의 카드 높이와 어긋난다. */
body.page-list .card .m-venuerow { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
body.page-list .card .m-venuerow .m-venue { flex: 1 1 auto; min-width: 0; }
body.page-list .card .m-distance {
  flex: none; font-size: 12px; font-weight: 700; color: var(--brass); white-space: nowrap;
}

/*
   공연장·기간 앞에 작은 아이콘. 카드 수가 많아 <svg> 를 카드마다 마크업에 넣으면
   DOM 이 그만큼 불어나므로, 배경 이미지(data URI) 로 한 번만 선언해 모든 카드가
   같은 리소스를 쓴다. 색은 --mute 를 CSS 변수로 못 넣어서(data URI 는 정적 문자열이라
   커스텀 프로퍼티를 못 읽는다) 그 값(#B6B0A7)을 직접 적었다 — --mute 를 바꾸면 같이
   맞춰야 한다. .m-venue 는 display 를 안 건드린다 — inline-flex 로 바꾸면 긴 공연장명의
   말줄임(text-overflow: ellipsis)이 깨진다.
*/
body.page-list .card .m-venue::before,
body.page-list .card .m-date::before {
  content: '';
  display: inline-block;
  width: 11px; height: 11px;
  margin-right: 4px;
  vertical-align: -1px;
  background: no-repeat center / contain;
}
body.page-list .card .m-venue::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23B6B0A7' stroke-width='1.4' stroke-linejoin='round'%3E%3Cpath d='M8 14.2s5-4.3 5-8.2a5 5 0 1 0-10 0c0 3.9 5 8.2 5 8.2z'/%3E%3Ccircle cx='8' cy='6' r='1.6'/%3E%3C/svg%3E");
}
body.page-list .card .m-date::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23B6B0A7' stroke-width='1.4' stroke-linecap='round'%3E%3Crect x='2' y='3.5' width='12' height='10.5' rx='1.5'/%3E%3Cpath d='M2 6.5h12M5.3 2v3M10.7 2v3'/%3E%3C/svg%3E");
}

/* ---- 결과 건수 · 필터 초기화 ---- */
body.page-list .resultbar {
  display: flex; align-items: center; gap: 14px;
  margin-bottom: 20px;
}
body.page-list .resultcount { margin: 0; font-size: 14px; color: var(--mute); }
body.page-list .resultcount strong { color: var(--paper); font-weight: 700; }
body.page-list .resultreset {
  font-size: 13px; color: var(--mute);
  border-bottom: 1px solid var(--line-strong);
  transition: color .12s, border-color .12s;
}
body.page-list .resultreset:hover { color: var(--brass); border-color: var(--brass); }

/* 이 조건 저장. resultreset(텍스트 링크)과 달리 실제 쓰기 동작이라 알약 버튼으로 무게를
   준다 — 결과 초기화는 "링크를 되돌리는" 탐색이고 이건 서버에 뭔가를 만드는 동작이라
   같은 모양이면 안 된다. */
body.page-list .savebtn {
  font: inherit; font-size: 13px; font-weight: 600; color: var(--paper);
  background: none; border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 7px 14px; cursor: pointer;
  transition: border-color .12s, color .12s;
}
body.page-list .savebtn:hover { border-color: var(--brass); color: var(--brass); }
body.page-list .savebtn:disabled { opacity: .5; cursor: not-allowed; }

/* 주변 공연 찾기 — 좌표를 아직 못 받은 첫 진입 안내. 필터패널·카드그리드가 없는
   상태라 가운데 정렬된 안내 하나만 뜬다. */
body.page-list .nearby-prompt {
  max-width: 440px; margin: 56px auto; padding: 0 20px; text-align: center;
}
body.page-list .nearby-prompt h2 { margin: 0 0 10px; font-size: 22px; color: var(--paper); }
body.page-list .nearby-prompt p { margin: 0 0 24px; color: var(--mute); font-size: 14px; line-height: 1.6; }
body.page-list .nearby-find-btn {
  background: var(--brass); color: #0B0B0C; border: 0; border-radius: 999px;
  padding: 14px 32px; font: inherit; font-weight: 700; font-size: 15px; cursor: pointer;
}
body.page-list .nearby-find-btn:disabled { opacity: .6; cursor: not-allowed; }
body.page-list .nearby-error { margin: 16px 0 0; color: var(--rose); font-size: 13px; }

/* 정렬 드롭다운. 결과바 오른쪽 끝에 붙인다 — 목록을 훑다가 "다르게 정렬해볼까" 할 때
   눈이 가는 자리이고, 왼쪽의 건수/초기화와 역할이 달라서 붙여두면 셋이 한 덩어리로 읽힌다.
   모양은 필터 패널의 드롭다운(.fp-dropdowns select)과 같은 규칙을 따른다. */
body.page-list .sortform { margin-left: auto; }
body.page-list .sortform select {
  appearance: none;
  -webkit-appearance: none;
  font: inherit; font-size: 13px; color: var(--paper);
  border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 8px 34px 8px 16px;
  background: var(--ink) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23A1A1AA'/%3E%3C/svg%3E") no-repeat right 14px center;
  cursor: pointer;
  transition: border-color .12s;
}
body.page-list .sortform select:hover { border-color: var(--paper); }
body.page-list .sortform select option { background: var(--ink-soft); color: var(--paper); }

/* 화면에는 안 보이되 스크린리더는 읽는 라벨. 드롭다운만 덩그러니 두면 무엇을 고르는
   자리인지 소리로는 알 수 없다(시각적으로는 값 자체가 "추천순"이라 설명이 필요 없다). */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
body.page-list .emptyreset {
  display: inline-block; margin-top: 16px;
  padding: 10px 22px;
  border: 1px solid var(--line-strong); border-radius: 999px;
  font-size: 13px; font-weight: 600; color: var(--paper);
  transition: border-color .12s, background .12s;
}
body.page-list .emptyreset:hover { border-color: var(--brass); background: rgba(255, 201, 64, .08); }

/* ---- 페이지네이션 (pager-nav/pager-num 은 이 페이지 전용 마크업) ---- */
body.page-list .pager-nav, body.page-list .pager-num {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; height: 32px; padding: 0 8px;
  border: 1px solid var(--line-strong); border-radius: 999px;
  font-size: 13px; color: var(--mute);
  transition: border-color .12s, color .12s;
}
body.page-list .pager-nav:hover, body.page-list .pager-num:hover { border-color: var(--paper); color: var(--paper); }
body.page-list .pager-num.active {
  background: var(--brass); border-color: var(--brass); color: #0B0B0C; font-weight: 700;
}

body.page-list .empty-state { color: var(--mute); }
body.page-list .empty-state code { background: var(--ink-soft); padding: 2px 6px; border-radius: var(--radius-xs); }

@media (max-width: 860px) {
  /* "객석" 로고+태그는 한 줄 유지, authnav("로그인 · 회원가입")는 폭이 좁으면
     제 몫의 공간을 못 받아 두 글자짜리 로고까지 세로로 쪼개지므로 아래 줄로 내린다.
     page-error(403/404/5xx)는 뒤늦게 이 규칙에 빠져 있었다 — showAuthNav=true 를 쓰는
     화면 중 유일하게 안 걸려서, 모바일에서 "객석" 로고가 "객"/"석" 두 줄로 쪼개지고
     있었다(이 fix 가 막으려던 바로 그 증상이 새 화면에서 재현된 것). */
  body.page-list .masthead, body.page-detail .masthead, body.page-error .masthead
    { flex-wrap: wrap; row-gap: 8px; }
  body.page-list .masthead h1, body.page-detail .masthead h1, body.page-error .masthead h1
    { white-space: nowrap; }
  body.page-list .masthead .tag, body.page-detail .masthead .tag, body.page-error .masthead .tag
    { white-space: nowrap; }
  /* justify-content 는 flex-start 다 — 아래 줄로 내려온 authnav 가 로고와 같은
     왼쪽 정렬을 따라야 한 덩어리로 읽힌다. flex-end 로 두면(예전 값) 이 줄 안에서
     authnav 자체가 한 번 더 감기는 좁은 폭(360~375px)에서 두 줄로 쪼개진 내용이
     각각 오른쪽에 따로 붙어, 왼쪽에 빈 칸만 남고 전체가 오른쪽으로 쏠려 보인다. */
  body.page-list .masthead .authnav, body.page-detail .masthead .authnav, body.page-error .masthead .authnav
    { margin-left: 0; flex-basis: 100%; justify-content: flex-start; }

  body.page-list .mobile-filter-btn { display: inline-flex; }
  /* 좁은 화면에서는 건수가 필터 버튼에 이미 붙어 있으므로 중복을 피한다. */
  body.page-list .resultcount { display: none; }
  /* 주변 공연 찾기는 필터 버튼이 없어서 건수를 대신 보여줄 곳이 여기뿐이다 — 숨기면
     "반경 Nkm 이내"(자동으로 넓혀 찾았을 수 있는 실제 반경)를 모바일에서 알 방법이
     없어진다. 클래스 두 개짜리 선택자라 위 규칙보다 우선한다. */
  body.page-list.page-nearby .resultcount { display: block; }
  /* 종전엔 초기화 링크가 없으면 결과바를 통째로 숨겼다(건수만 들어 있어 빈 줄이 됐으므로).
     이제 정렬 드롭다운이 항상 이 안에 있어서 숨기면 모바일에서 정렬할 방법이 사라진다.
     좁은 화면일수록 스크롤이 길어 정렬이 더 필요하므로 그대로 둔다. */
  body.page-list .filterpanel {
    position: fixed; top: 0; right: 0; bottom: 0;
    width: min(340px, 86vw);
    margin: 0;
    border-radius: 0;
    transform: translateX(100%);
    transition: transform .22s ease;
    overflow-y: auto;
    z-index: 40;
  }
  body.page-list .filterpanel.open { transform: translateX(0); }
  body.page-list .fp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
  body.page-list .fp-apply { display: block; }
  body.page-list .fp-label { width: auto; }
  body.page-list .fp-backdrop {
    display: block;
    position: fixed; inset: 0; background: rgba(0, 0, 0, .35);
    opacity: 0; pointer-events: none; transition: opacity .22s ease;
    z-index: 30;
  }
  body.page-list .fp-backdrop.open { opacity: 1; pointer-events: auto; }
  /* 드로어가 열린 동안 배경(카드 목록)이 같이 스크롤되지 않게 잠근다 — 안 잠그면
     손가락으로 필터를 스크롤하려다 뒤에 깔린 카드 목록이 같이 밀려서, 드로어를
     닫았을 때 스크롤 위치가 엉뚱한 곳에 가 있었다. 여닫는 스크립트(list.html)가
     이 클래스만 토글한다. */
  body.page-list.filterdraweropen { overflow: hidden; }
}

/* ==================================================================
   공연 상세 화면 — 레이아웃은 bluesquare.kr 상세(view.php) 참고, 톤은 공통 다크.
   ================================================================== */
body.page-detail .detail {
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: 48px;
  align-items: start;
}
body.page-detail .detail > div:first-child { position: sticky; top: 96px; }

body.page-detail .poster {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--ink-soft);
  box-shadow: 0 16px 40px rgba(0, 0, 0, .5);
}
body.page-detail .poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 장르 배지. 목록·홈 카드의 .card-badge 와 같은 클래스·같은 자리(포스터 좌상단)를
   그대로 쓴다 — 본문 쪽 텍스트 태그였다가(위 h2 규칙 옆 주석 참고) 카드와 일관되게
   포스터 위 오버레이로 옮겼다. z-index 는 아래 .poster::after 그라데이션 위에 오도록. */
body.page-detail .poster .card-badge {
  position: absolute; top: 10px; left: 10px; z-index: 1;
  background: rgba(17, 18, 20, .72); color: var(--paper);
  backdrop-filter: blur(4px);
  font-size: 12px; font-weight: 500;
  border-radius: 999px; padding: 5px 12px;
  border: 1px solid rgba(248, 244, 236, .16);
}
/* 목록 카드와 같은 이유의 같은 처리(위 body.page-list .card .poster::after 참고).
   여기 img 는 z-index 를 안 줬으니 ::after 도 z-index 없이 자연히 위에 온다 —
   position:absolute 인 pseudo-element 가 static 자식보다 나중 페인트 단계라서다. */
body.page-detail .poster::after {
  content: '';
  position: absolute; inset: 0;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .10), inset 0 -64px 64px -32px rgba(0, 0, 0, .30);
  pointer-events: none;
}

/* 포스터가 없을 때. 목록 카드의 폴백과 같은 발상이되, 320px 라 첫 글자만 크게 띄우면
   의미 없는 큰 덩어리가 되므로 공연명을 같이 넣어 정보를 갖게 한다. */
body.page-detail .poster.no-img {
  display: grid;
  place-content: center;
  gap: 14px;
  padding: 28px;
  text-align: center;
  background: linear-gradient(160deg, #1B1B20 0%, #131316 100%);
}
body.page-detail .posterfallback { display: none; }
body.page-detail .poster.no-img .posterfallback { display: grid; gap: 10px; }
body.page-detail .pf-initial {
  font-family: var(--display);
  font-size: 76px; font-weight: 800; line-height: 1;
  color: var(--brass-dim); opacity: .55;
}
body.page-detail .pf-title {
  font-size: 14px; font-weight: 600; line-height: 1.5;
  color: var(--mute);
  /* 긴 공연명이 포스터 밖으로 넘치지 않게 세 줄에서 자른다. */
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---- 포스터 아래 요약 카드 ----
   sticky 칸의 남는 자리를 채운다. 강조는 최저가 하나로 몰고 CTA 는 외곽선으로 둔다 —
   여기서 CTA 까지 채우면 화면에 브라스 덩어리가 둘이 되어 시선이 갈린다. */
body.page-detail .summarycard {
  margin-top: 18px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--ink-soft);
}
body.page-detail .sc-row {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
}
body.page-detail .sc-row + .sc-row { margin-top: 10px; }
body.page-detail .sc-label { font-size: 13px; color: var(--mute); }
body.page-detail .sc-price {
  font-family: var(--display); font-size: 19px; font-weight: 800;
  color: var(--brass);
}
body.page-detail .sc-next { font-size: 14px; font-weight: 600; color: var(--paper); }
body.page-detail .sc-cta {
  display: block; margin-top: 14px; padding: 11px;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  text-align: center; font-size: 14px; font-weight: 600; color: var(--paper);
  transition: border-color .12s, color .12s, background .12s;
}
body.page-detail .sc-cta:hover {
  border-color: var(--brass); color: var(--brass);
  background: rgba(255, 201, 64, 0.08);
}

/* ---- 좁은 화면 하단 고정 예매 바 ----
   위 요약 카드가 숨겨지는 폭에서만 나타나 그 역할을 대신한다(아래 860px 블록).
   기본은 숨김이다 — 넓은 화면에는 이미 카드가 있고, 둘이 같이 뜨면 같은 CTA 가
   화면에 둘이 된다. */
body.page-detail .mobilebar { display: none; }

/* 헤더가 sticky 라 앵커로 점프하면 제목이 그 아래 가린다. */
body.page-detail #schedule-pick { scroll-margin-top: 88px; }

body.page-detail h2 {
  font-family: var(--display); font-size: 40px; font-weight: 800;
  letter-spacing: -0.03em; color: var(--paper); margin: 0 0 32px;
}
/* 장르 배지는 목록·홈 카드와 같은 자리(포스터 좌상단 오버레이)로 옮겼다 — 아래
   body.page-detail .poster .card-badge 참고. 본문 쪽에는 더 이상 안 그린다. */

body.page-detail .detail-heading { font-size: 15px; font-weight: 700; color: var(--paper); margin: 0 0 18px; }
/* .refund-heading 는 아래 unscoped 규칙이 주는데, 이 body.page-detail 스코프 규칙이
   명시도(0,2,1 > 0,1,0)로 항상 이겨서 상세 화면에서는 margin-top 이 한 번도 안 먹었다
   — 결제 화면에서만 조용히 동작하던 버그였다. 상세 화면에도 같은 값을 준다. */
body.page-detail .refund-heading { margin-top: 56px; }

body.page-detail .spec { border-top: 1px solid var(--line); margin: 0 0 48px; }
body.page-detail .spec div { display: flex; gap: 24px; padding: 20px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
body.page-detail .spec dt { width: 96px; flex: none; font-weight: 600; color: var(--mute); margin: 0; }
body.page-detail .spec dd { margin: 0; color: var(--paper); }

/* 공연장 위치 지도. 네이버 지도는 컨테이너에 높이가 없으면 아무것도 그리지 않으므로
   height 를 반드시 지정해야 한다. */
.venue-map {
  width: 100%;
  height: 220px;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  overflow: hidden;
}

.map-link {
  margin-left: 10px;
  font-size: 13px;
  color: var(--brass);
  border-bottom: 1px solid var(--brass-dim);
  white-space: nowrap;
}

.kopis-hint {
  display: block;
  margin-top: 6px;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--mute);
}

/* white-space: pre-line 으로 원문의 줄바꿈(\n)을 그대로 살린다.
   th:text 로 이스케이프해 넣으므로 <br> 없이도 안전하게 줄바꿈이 보인다. */
body.page-detail .description {
  font-size: 14px; line-height: 1.7; color: var(--paper);
  white-space: pre-line;
  margin: 0 0 48px;
}

/* ---- 회차 선택 (날짜 한 줄 + 그 날의 시각들) ----
   날짜를 왼쪽 고정폭 칸으로 빼서, 회차가 많은 공연에서도 세로로 훑으면 날짜만 읽힌다.
   시각 쪽은 wrap 되므로 하루에 회차가 몇 개든 그 날 줄 안에서만 늘어난다. */
body.page-detail .scheduledays {
  border-top: 1px solid var(--line);
}
body.page-detail .scheduleday {
  display: flex; align-items: baseline; gap: 20px;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}
body.page-detail .sd-date {
  width: 96px; flex: none;
  font-size: 14px; font-weight: 700; color: var(--paper);
}
body.page-detail .sd-dow { margin-left: 4px; color: var(--mute); font-weight: 600; }
/* 그 날 전 회차가 매진이면 날짜까지 같이 흐리게 — 훑을 때 건너뛸 날을 바로 알아본다. */
body.page-detail .scheduleday.soldout .sd-date,
body.page-detail .scheduleday.soldout .sd-dow { color: var(--mute); opacity: .6; }

body.page-detail .sd-times { display: flex; flex-wrap: wrap; gap: 8px; }
/* .sd-chip 은 <a>(예매 가능한 회차)와 <button>(매진 → 알림 구독)이 같이 쓰는 기본 알약 모양.
   button 은 기본적으로 배경·테두리·글꼴을 안 물려받으므로 여기서 전부 명시한다. */
body.page-detail .sd-times .sd-chip {
  display: inline-flex; align-items: baseline; gap: 8px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: none;
  color: var(--paper);
  padding: 9px 14px;
  font: inherit; font-size: 14px; font-weight: 600;
  cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
}
body.page-detail .sd-times a.sd-chip:hover {
  border-color: var(--brass); color: var(--brass);
  background: rgba(255, 201, 64, 0.08);
}
body.page-detail .sd-remain { font-size: 12px; font-weight: 500; color: var(--mute); }
body.page-detail .sd-times a.sd-chip:hover .sd-remain { color: var(--brass-dim); }
/* 알림 구독 버튼. 매진이라 링크보다 흐리게 두되(살 수 없다는 사실이 먼저 읽혀야 한다),
   완전히 죽어 있던 종전 .soldout 링크와 달리 실제로 누를 수 있는 액션이라 hover 반응은 남긴다. */
body.page-detail .sd-times .restock-btn {
  border-color: var(--line); color: var(--mute);
}
body.page-detail .sd-times .restock-btn:hover {
  border-color: var(--brass-dim); color: var(--paper);
}
/* 구독 중. 찜 버튼(브라스 강조)과 같은 신호로 "신청됨"을 알린다. */
body.page-detail .sd-times .restock-btn[aria-pressed="true"] {
  border-color: var(--brass); color: var(--brass);
  background: rgba(255, 201, 64, 0.08);
}

body.page-detail .empty-inline { font-size: 14px; color: var(--mute); margin: 0; }

/* ---- 취소 · 환불 규정 (공연 상세 + 결제, 두 화면 공용) ----
   처음엔 page-detail 스코프였는데 결제 화면에서도 같은 목록을 보여주게 되면서 풀었다.
   규정 자체가 Reservation.REFUND_TIERS 하나에서 나오므로 표기도 한 벌만 둔다. */
.refundpolicy {
  list-style: none; margin: 0; padding: 0;
  border-top: 1px solid var(--line);
}
.refundpolicy li {
  display: flex; justify-content: space-between; gap: 20px;
  padding: 15px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
}
.rp-when { color: var(--mute); }
.rp-fee { color: var(--paper); font-weight: 600; }
.refundpolicy .rp-deny .rp-fee { color: var(--rose); }
.refundnote {
  margin: 18px 0 0;
  font-size: 13px; line-height: 1.7; color: var(--mute);
}
/* 상세 화면의 제목 스타일도 결제에서 그대로 쓴다. */
.detail-heading { font-size: 15px; font-weight: 700; color: var(--paper); margin: 0 0 18px; }
/* .detail-heading 뒤에 와야 한다 — 앞에 두면 위 margin 단축 속성이 margin-top 을 0 으로
   되돌려서(같은 명시도라 나중 선언이 이김) 규정 제목이 결제 버튼에 딱 붙는다. */
.refund-heading { margin-top: 56px; }

@media (max-width: 860px) {
  body.page-detail .detail { grid-template-columns: 1fr; gap: 28px; }
  /* margin-inline: auto 가 없으면 이 칸(포스터, 요약 카드는 바로 아래서 숨긴다)이
     320px 로 눌린 채 컨테이너 왼쪽에 붙는다. 640px 안팎(375px 폰보다 훨씬 넓고
     861px 두 칸 레이아웃보다는 좁은 구간)에서는 옆으로 수백 px 가 빈 채 남고
     그 아래로 본문이 이어져 화면이 깨진 것처럼 보인다 — 가운데로 옮겨서 포스터가
     독립된 히어로 이미지로 읽히게 한다. */
  body.page-detail .detail > div:first-child { position: static; max-width: 320px; margin-inline: auto; }
  /* 데스크톱 40px 는 좁은 화면에서 긴 공연명이 한 글자씩 좁게 줄바꿈된다. */
  body.page-detail h2 { font-size: 28px; }
  /* 요약 카드는 sticky 칸의 남는 자리를 채우려고 있는 것인데, 여기서는 칸이 sticky 가 아니라
     제목 위로 올라간다 — 공연명보다 최저가가 먼저 읽히는 이상한 순서가 되고, 같은 정보가
     바로 아래 공연 정보 표에 다시 나온다. 그래서 숨긴다. */
  body.page-detail .summarycard { display: none; }
  /*
     대신 카드가 들고 있던 예매 진입점을 하단 고정 바로 옮긴다. 두 규칙이 같은 860px 를
     쓰는 게 중요하다 — 중단점이 갈리면 카드도 바도 없는 폭이 생긴다.

     면은 요약 카드(tone-light)와 같은 --ink 로 둔다. 이 라이트 화면에서 "어두운 덩어리 =
     예매" 라는 언어가 이미 서 있고, 배경과 같은 색이면 본문이 바 뒤로 지나갈 때 경계가
     사라진다.
  */
  body.page-detail .mobilebar {
    display: flex; align-items: center; gap: 14px;
    position: fixed; left: 0; right: 0; bottom: 0;
    /* 헤더(20)와 같은 층. 토스트(60)·공유 시트(70)보다는 아래라 그 둘은 바를 덮는다. */
    z-index: 20;
    /* 아이폰 홈 인디케이터에 CTA 가 물리지 않게 한다(좌석도 .bookbar 와 같은 처리). */
    padding: 12px 20px calc(12px + env(safe-area-inset-bottom));
    background: var(--ink);
    border-top: 1px solid rgba(248, 244, 236, .12);
  }
  body.page-detail .mb-price { display: flex; flex-direction: column; gap: 2px; }
  body.page-detail .mb-label { font-size: 11.5px; font-weight: 600; color: rgba(248, 244, 236, .55); }
  /* 숫자와 '원' 사이가 끊기면 두 줄이 된다 — 공연 정보 표의 .fee 와 같은 문제다. */
  body.page-detail .mb-price strong {
    font-family: var(--display); font-size: 18px; font-weight: 800;
    color: var(--brass); white-space: nowrap;
  }
  /* 남는 폭을 전부 갖는다. 가격이 미상인 공연에서는 바 전체가 CTA 가 된다. */
  body.page-detail .mb-cta {
    flex: 1; min-width: 0;
    padding: 14px 20px;
    border-radius: 999px;
    background: var(--brass); color: var(--ink);
    text-align: center; font-size: 15px; font-weight: 800;
    white-space: nowrap;
  }
  /* 바에 가리는 만큼 본문 끝(관람평)에 자리를 만드는 건 아래 tone-light 블록의
     .detail padding 에서 한다 — 그쪽이 padding 단축 속성이라 여기서 padding-bottom 만
     따로 주면 명시도가 같고 뒤에 있는 그 규칙이 이긴다(실제로 72px 로 덮였다). */
  /*
     토스트는 bottom:32px 라 바와 겹친다 — z-index 가 위(60 vs 20)여서 찜·알림 안내가
     뜰 때마다 CTA 를 덮는다. 바 높이만큼 올린다.

     바가 없는 공연(예매할 회차 없음)에서도 그냥 올린다. :has(.mobilebar) 로 정확히
     가를 수는 있지만, 그 선택자를 모르는 브라우저는 규칙을 통째로 버려서 토스트가
     바 뒤로 들어간다 — 가리는 쪽이 조금 높이 뜨는 쪽보다 나쁘다.
  */
  body.page-detail .toast { bottom: calc(92px + env(safe-area-inset-bottom)); }
  /*
     위 88px 은 데스크톱 헤더 기준이라 여기서는 모자란다 — 그대로 두면 바의 CTA 로
     점프했을 때 정작 「회차 선택」 제목이 sticky 헤더 뒤로 들어가, 어디로 온 건지
     모르는 채 회차만 보인다.

     값은 좁은 화면 헤더 높이(실측 117px)에 숨 쉴 여백을 더한 것이다. 헤더 높이가
     바뀌면 여기도 같이 바뀌어야 한다 — 처음엔 헤더가 세 줄이던 시절의 160px 를 기준으로
     172px 였는데, 헤더를 두 줄로 압축하면서 55px 이 빈 채로 남았다.
  */
  body.page-detail #schedule-pick { scroll-margin-top: 130px; }
  /* 좁은 화면에서는 날짜를 시각 위로 올린다 — 96px 를 떼면 시각 칩이 들어갈 폭이 안 남는다. */
  body.page-detail .scheduleday { flex-direction: column; align-items: stretch; gap: 10px; }
  body.page-detail .sd-date { width: auto; }

  /* 결제 패널이 sticky 로 붙을 만큼 옆에 공간이 없다 — 주문 정보 아래로 내려서
     보통 흐름으로 되돌린다(결제 직전 마지막 화면이라 아래로 스크롤해도 버튼을
     놓치지 않는다는 전제는 좁은 화면에서 깨지지만, sticky 패널이 화면 폭의 절반을
     먹는 것보다는 낫다). */
  body.page-payment .paylayout { grid-template-columns: 1fr; gap: 32px; }
  body.page-payment .paypanel { position: static; }
}

/* ==================================================================
   결제 화면.

   처음엔 주문명 한 줄 + 버튼 하나가 전부였고, 그다음엔 정보를 다 채웠지만
   로그인 폼용 좁은 칸(.authwrap, 400px)에 밀어넣어 오히려 더 빼곡해졌다.
   상세 화면과 같은 2단 레이아웃(주문 정보 + sticky 결제 패널)으로 바꿨다 —
   실제 커머스 결제 화면들이 쓰는 구성이고, sticky 패널은 상세의 요약 카드에서
   이미 검증한 패턴이다.
   ================================================================== */

/* ---- 진행 단계 ---- */
body.page-payment .paysteps {
  display: flex; align-items: center; gap: 8px;
  list-style: none; margin: 28px 0 32px; padding: 0;
  font-size: 13px; color: var(--mute);
}
body.page-payment .paysteps li { display: flex; align-items: center; gap: 8px; }
body.page-payment .paysteps li + li::before {
  content: ''; width: 16px; height: 1px; background: var(--line-strong);
}
body.page-payment .paysteps .ps-done { color: var(--paper); }
body.page-payment .paysteps .ps-current { color: var(--brass); font-weight: 700; }

body.page-payment .paylayout {
  display: grid;
  grid-template-columns: 1fr 340px;
  gap: 48px;
  align-items: start;
}

/* ---- 주문 정보 (좌측) ---- */
body.page-payment .orderhead {
  display: flex; gap: 20px; margin-bottom: 28px;
}
body.page-payment .poster {
  flex: none; width: 96px; aspect-ratio: 3 / 4;
  position: relative; border-radius: var(--radius-sm); overflow: hidden;
  border: 1px solid var(--line); background: var(--ink-soft);
}
body.page-payment .poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
body.page-payment .posterfallback { display: none; }
body.page-payment .poster.no-img {
  display: grid; place-content: center;
  background: linear-gradient(160deg, #1B1B20 0%, #131316 100%);
}
body.page-payment .poster.no-img .posterfallback { display: block; }
body.page-payment .pf-initial {
  font-family: var(--display); font-size: 30px; font-weight: 800; line-height: 1;
  color: var(--brass-dim); opacity: .55;
}
body.page-payment .orderhead-text { min-width: 0; }
body.page-payment .paytitle {
  font-family: var(--display); font-size: 22px; font-weight: 800;
  letter-spacing: -0.02em; color: var(--paper);
  margin: 0 0 14px;
}
body.page-payment .payspec { margin: 0; }
body.page-payment .payspec div {
  display: flex; gap: 20px; align-items: baseline;
  padding: 7px 0; font-size: 14px;
}
body.page-payment .payspec dt { width: 74px; flex: none; color: var(--mute); margin: 0; }
body.page-payment .payspec dd { margin: 0; color: var(--paper); }

/* 좌석 장당 내역. 결제 패널의 총액은 이 목록을 그대로 합산한 값이다(reservation.amount,
   서버가 이미 계산해 둔 값을 다시 씀 — 화면이 따로 더하지 않는다). */
body.page-payment .payseats {
  list-style: none; margin: 0 0 8px; padding: 0;
  border-top: 1px solid var(--line);
}
body.page-payment .payseats li {
  display: flex; align-items: baseline; gap: 12px;
  padding: 11px 0; border-bottom: 1px solid var(--line);
  font-size: 14px;
}
body.page-payment .ps-seat { color: var(--paper); }
body.page-payment .ps-grade {
  font-size: 12px; font-weight: 700; color: var(--brass-dim);
  border: 1px solid var(--line-strong); border-radius: 999px; padding: 2px 8px;
}
body.page-payment .ps-price { margin-left: auto; color: var(--mute); font-variant-numeric: tabular-nums; }
/* 좌석 연결이 끊긴 예매(만료·취소)의 대체 표기. 스냅샷 한 줄뿐이라 목록 스타일 대신
   본문 텍스트로 조용히 보여준다. */
body.page-payment .payseats-fallback {
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  margin: 0 0 8px; padding: 13px 0;
  font-size: 14px; color: var(--paper);
}

/* ---- 결제 패널 (우측, sticky) ---- */
body.page-payment .paypanel {
  position: sticky; top: 96px;
  display: flex; flex-direction: column; gap: 16px;
  padding: 20px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--ink-soft);
}
body.page-payment .paytimer {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "label clock" "note note";
  align-items: baseline;
  gap: 4px 12px;
  padding: 16px 18px;
  border: 1px solid var(--brass-dim);
  border-radius: var(--radius-sm);
  background: rgba(255, 201, 64, 0.06);
}
body.page-payment .pt-label { grid-area: label; font-size: 13px; color: var(--mute); }
body.page-payment .pt-clock {
  grid-area: clock;
  font-family: var(--display);
  /* 초가 바뀔 때마다 숫자 폭이 달라져 시계가 떨리지 않게 고정폭 숫자를 쓴다. */
  font-variant-numeric: tabular-nums;
  font-size: 26px; font-weight: 800; line-height: 1.1;
  color: var(--brass);
}
body.page-payment .pt-note { grid-area: note; font-size: 12px; color: var(--mute); }

/* 1분 미만. 색만 바꾸고 깜빡이지는 않는다 — 결제 정보를 입력하는 중에 화면이
   깜빡이면 오히려 방해가 된다. */
body.page-payment .paytimer--urgent {
  border-color: var(--rose);
  background: rgba(255, 92, 122, 0.08);
}
body.page-payment .paytimer--urgent .pt-clock { color: var(--rose); }

/* 결제 불가(만료·취소·이미 결제됨). 강조를 걷어 조용한 안내로 만든다. */
body.page-payment .paytimer--dead {
  border-color: var(--line);
  background: var(--ink-raised);
}
body.page-payment .paytimer--dead .pt-clock { color: var(--mute); font-size: 20px; }

body.page-payment .paytotal {
  display: flex; margin: 0; padding: 4px 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
body.page-payment .paytotal div { display: flex; justify-content: space-between; align-items: baseline; width: 100%; padding: 8px 0; }
body.page-payment .paytotal dt { font-size: 14px; color: var(--mute); margin: 0; }
body.page-payment .paytotal dd {
  font-family: var(--display); font-size: 22px; font-weight: 800;
  color: var(--brass); margin: 0;
}

/* 테스트 즉시 결제 폼. 버튼 하나만 들고 있으므로 패널의 세로 간격(gap)이 폼 때문에
   한 칸 더 생기지 않도록 자기 여백을 갖지 않는다. */
body.page-payment .payform { margin: 0; display: contents; }
/* 테스트 모드 안내. 결제 버튼 바로 아래에 붙어서 "이건 진짜 결제가 아니다"를 누르기 전에
   알린다 — 눌러서 확정된 뒤에 알면 늦다. */
body.page-payment .paytestnote {
  margin: -10px 0 0;
  font-size: 12px; line-height: 1.5;
  color: var(--brass);
  text-align: center;
}

/* 결제 전까지는 내부 식별자일 뿐이라 총액·버튼보다 눈에 띄면 안 된다. */
body.page-payment .payno {
  margin: 0; text-align: center;
  font-size: 12px; color: var(--mute); font-variant-numeric: tabular-nums;
}

/* ---- 결제 결과 화면 (완료 · 실패 · 자동환불 셋 공용) ----
   결제 화면과 같은 부품(.orderhead/.payseats/.paytotal/.payno)을 그대로 재사용한다 —
   결과 화면이 결제 화면과 다르게 생기면 "이게 정말 그 예매가 맞나" 다시 눈으로
   맞춰보게 된다. 다만 더 이상 할 일이 남지 않은 화면이라 2단 sticky 대신 가운데
   카드 하나로 좁혀, 로그인 화면과 같은 "결과를 확인하고 다음으로 넘어가는" 느낌을 준다. */
body.page-payment .paydone {
  max-width: 480px; margin: 40px auto 0;
  padding: 32px 28px;
  background: var(--ink-soft); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .38);
}
body.page-payment .paydone-icon {
  width: 52px; height: 52px; margin: 0 auto 16px;
  display: grid; place-items: center;
  border-radius: 999px;
  background: rgba(255, 201, 64, .12);
  border: 1px solid var(--brass-dim);
  color: var(--brass);
}
/* 실패 · 자동환불. 성공과 같은 자리·같은 크기의 아이콘을 색만 rose 로 바꿔 쓴다 —
   "이 화면도 결과 화면 계열이다"는 걸 형태로 알려주고, 안 좋은 결과라는 건 색으로. */
body.page-payment .paydone-icon--fail {
  background: rgba(255, 92, 122, .12);
  border-color: var(--rose);
  color: var(--rose);
}
body.page-payment .paydone-lead {
  font-family: var(--display); font-size: 20px; font-weight: 800;
  color: var(--paper); margin: 0 0 28px; text-align: center;
}
/* 실패·환불 화면은 헤드라인 바로 아래에 사유 문단이 붙으므로, 그 사이 간격은
   좁게 두고(:has 로 뒤에 오는 형제를 보고 판단) 사유 문단 쪽에서 orderhead 까지의
   간격을 낸다. 성공 화면처럼 헤드라인 다음이 바로 orderhead 면 원래 여백(28px)을 쓴다. */
body.page-payment .paydone-lead:has(+ .payfail-code),
body.page-payment .paydone-lead:has(+ .payfail-reason) { margin-bottom: 6px; }
body.page-payment .payfail-code {
  margin: 0 0 6px; text-align: center;
  font-size: 12px; color: var(--mute);
}
body.page-payment .payfail-reason {
  margin: 0 0 28px; text-align: center;
  font-size: 13px; line-height: 1.7; color: var(--mute);
}
body.page-payment .paydone .paytotal { margin-top: 16px; }
body.page-payment .paydone .payno { margin: 10px 0 28px; }
/* 실패 화면은 결제 전이라 결제금액·예매번호가 없다 — 좌석 목록 바로 다음이 버튼이라
   그 사이만 따로 벌린다(있으면 .payno 의 28px 여백을 그대로 쓴다). */
body.page-payment .payseats:has(+ .paydone-actions),
body.page-payment .payseats-fallback:has(+ .paydone-actions) { margin-bottom: 28px; }
body.page-payment .paydone-actions { display: flex; gap: 10px; }
body.page-payment .paydone-actions .paybtn { flex: 1; }

@media (max-width: 560px) {
  body.page-payment .paydone { margin: 24px 0 0; padding: 24px 20px; }
}

/* ==================================================================
   홈 (섹션형 둘러보기) — 극장 예매 UI 톤앤무드 리디자인(2026-08-06)

   목록(/performances)이 조건을 걸어 찾는 화면이라면, 홈은 뭐가 있나 훑는 화면이다.
   이 리디자인부터 홈은 본문을 라이트(--paper)로 두고 히어로·에디토리얼 밴드만
   다크(--ink)로 가는 새 톤을 쓴다 — 다른 화면은 각자의 순서가 오기 전까지 기존
   다크 톤 그대로다. 그래서 아래 규칙은 전부 body.page-home 스코프 안에서만
   .card/.grid 를 오버라이드한다(body.page-list 쪽 원본 다크 카드 규칙은 그대로 두고,
   /performances 목록은 이번 커밋에서 안 건드린다).
   ================================================================== */
body.page-home { background: var(--paper); color: var(--ink); }
body.page-home .wrap { max-width: none; margin: 0; padding: 0; }

/* 헤더. 다른 화면의 .masthead(다크·상시 blur 바)와 달리 라이트 배경 위 sticky blur. */
body.page-home .masthead {
  margin: 0;
  padding: 22px 48px;
  background: rgba(248, 244, 236, .86);
  border-bottom: 1px solid rgba(17, 18, 20, .08);
  -webkit-backdrop-filter: saturate(160%) blur(10px);
  backdrop-filter: saturate(160%) blur(10px);
}
body.page-home .masthead h1 { color: var(--ink); }
body.page-home .masthead .logo-mark { color: var(--ink); width: 30px; height: 19px; }
/* 부제 태그는 홈에서는 헤더를 가볍게 두려고 뺀다(다른 화면은 계속 보여준다). */
body.page-home .masthead .tag { display: none; }
body.page-home .masthead .authnav { color: var(--ink); gap: 20px; font-size: 14.5px; font-weight: 600; }
body.page-home .masthead .authnav .who { color: var(--ink); }
/* 모양(알약 padding·radius)과 색은 공용 규칙이 정한다 — .masthead .authnav .navlink 와
   그 아래 tone-light/page-home 공용 셀렉터. 여기서는 홈에만 있는 것이 없어 비워 둔다. */
body.page-home .masthead .authnav a { color: var(--ink); transition: color .2s; }
body.page-home .masthead .authnav a:hover { color: var(--brass-dim); }
body.page-home .masthead .linklike { color: var(--ink); }
body.page-home .masthead .linklike:hover { color: var(--brass-dim); }
body.page-home .masthead .navactions::before { background: rgba(17, 18, 20, .18); }
/* 홈의 라이트 배경에서는 로그인 링크만 본문 톤으로 낮춘다. 회원가입은 공통 브라스
   CTA를 유지해, 홈에서 다른 화면으로 이동해도 계정 생성 행동의 우선순위가 같다. */
body.page-home .authbtns .btn-login { color: rgba(17, 18, 20, .68) !important; }
body.page-home .authbtns .btn-login:hover { color: var(--ink) !important; }

/* 히어로. 다크 풀블리드 + 미세한 그레인 텍스처(무대 조명 아래 객석 질감).
   첫 시선은 "무엇을 볼까"와 검색에 고정하고, 우측 통계는 서비스가 가진 데이터의 근거로
   한 단계 뒤에 둔다. 검색·통계·콜라주가 같은 크기로 경쟁하면 첫 행동이 흐려진다. */
body.page-home .hero {
  position: relative;
  overflow: hidden;
  background: var(--ink);
  color: var(--paper);
  padding: 96px 48px 84px;
  margin: 0;
}
body.page-home .hero::before {
  content: "";
  position: absolute;
  inset: -10%;
  opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: hero-grain 1.5s steps(4) infinite;
  pointer-events: none;
}
@keyframes hero-grain {
  0%, 100% { transform: translate(0, 0); }
  10% { transform: translate(-1%, -2%); }
  20% { transform: translate(-3%, 1%); }
  30% { transform: translate(2%, -3%); }
  40% { transform: translate(-2%, 3%); }
  50% { transform: translate(3%, 1%); }
  60% { transform: translate(-1%, 2%); }
  70% { transform: translate(1%, -1%); }
  80% { transform: translate(-3%, -2%); }
  90% { transform: translate(2%, 2%); }
}
/* 무대 조명 연출 세 겹. 좌석 배치도가 "어두운 객석 + 조명 받는 무대"를 전제로 만들어져
   있는데(그 화면의 무대 빛무리), 홈 히어로도 같은 배경색이라 같은 어휘를 쓴다.

   목업은 장식용 div 를 넷 두지만 여기서는 하나(.hero-lights)만 쓴다 — 그레인과 브라스
   글로우는 .hero 의 ::before/::after 로, 빔과 로즈 반사광은 .hero-lights 의 두 의사요소로
   얹는다. 빈 요소가 늘수록 스크린리더가 지나가야 할 것만 늘어난다.
   prefers-reduced-motion 은 전역 규칙(* { animation: none !important })이 이미 끈다. */
body.page-home .hero::after {
  content: "";
  position: absolute;
  top: -20%; right: 5%;
  width: 520px; height: 520px;
  background: radial-gradient(circle, rgba(233, 180, 76, .16) 0%, rgba(233, 180, 76, 0) 70%);
  animation: hero-glow 5s ease-in-out infinite;
  pointer-events: none;
}
@keyframes hero-glow {
  0%, 100% { opacity: .16; transform: scale(1); }
  50%      { opacity: .28; transform: scale(1.08); }
}
/* 우하단 로즈 반사광. 순수 장식이라 .hero-inner 뒤에 깔고 pointer-events 를 끈다.
   좌우 스윕 빔은 4차 핸드오프에서 빠졌다 — 커서 스포트라이트가 들어오면서 움직이는
   빛이 둘이 되는데, 하나는 마우스를 따라오고 하나는 제멋대로 지나가서 서로 방해한다. */
body.page-home .hero-lights { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
body.page-home .hero-lights::after {
  content: "";
  position: absolute;
  bottom: -40%; right: -15%;
  width: 50%; height: 160%;
  background: radial-gradient(ellipse, rgba(242, 109, 109, .06) 0%, rgba(242, 109, 109, 0) 65%);
}

/* 커서를 따라다니는 브라스 조명. 무대 위 팔로우 스포트라이트를 흉내낸 것이라
   이 화면의 조명 어휘와 맞는다.

   mix-blend-mode: screen 이라 아래 깔린 것만 밝히고 위 글자는 건드리지 않는다 —
   대신 .hero-inner 가 z-index 로 확실히 위에 있어야 한다(아래 규칙).
   위치는 JS 가 left/top 으로 갱신한다. transform 은 중심 맞추기(-50%)에만 쓰므로
   JS 가 건드리지 않는다 — 둘을 같이 쓰면 서로 덮어쓴다. */
body.page-home .hero-spotlight {
  position: absolute;
  /* 핸드오프 값은 640px 인데 히어로 높이(약 500px)보다 커서 조명이라기보다 배경이
     통째로 밝아지는 것처럼 보였다. 커서 주변에 고이는 크기로 줄인다. */
  width: 280px; height: 280px;
  left: 0; top: 0;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle,
      rgba(233, 180, 76, .38) 0%,
      rgba(233, 180, 76, .12) 45%,
      rgba(233, 180, 76, 0) 70%);
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity .25s ease;
  pointer-events: none;
  z-index: 1;
}

body.page-home .hero-inner {
  position: relative;
  z-index: 2;   /* 스포트라이트(z-index 1) 위. 안 주면 글자가 조명에 씻긴다. */
  max-width: 1240px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(260px, .7fr);
  gap: 72px;
  align-items: end;
}
/* 작고 자간 넓은 라벨. 브라스를 옅게 쓰되 .75 아래로는 안 내린다 —
   #111214 위에서 .75 는 5.99, .70 은 5.36 이고 .60 부터 본문 기준(4.5)에 가까워진다. */
body.page-home .hero-kicker {
  font-size: 12.5px; font-weight: 600; letter-spacing: .2em;
  color: rgba(233, 180, 76, .75); text-transform: uppercase;
  margin: 0 0 22px;
}
/* 큰 헤드라인은 굵기를 낮추고 자간을 더 조인다 — 800 은 이 크기에서 글자끼리 붙어
   덩어리로 보인다. 마지막 줄만 브라스로 빼서 문장의 착지점을 만든다. */
body.page-home .hero h2 {
  font-family: var(--display);
  max-width: none;
  font-size: clamp(42px, 6vw, 80px);
  line-height: .98;
  font-weight: 700;
  letter-spacing: -.04em;
  margin: 0 0 28px;
  white-space: normal;
}
body.page-home .hero h2 .hero-accent { color: var(--brass); }
body.page-home .hero-stat {
  text-align: right;
  border-left: 1px solid rgba(248, 244, 236, .18);
  padding-left: 40px;
}
/* 포스터 콜라주. 실제 수집분 포스터 3장을 조금씩 다르게 기울여 겹친다.
   가운데 장만 z-index 를 올려 앞으로 나오게 해서 겹침 방향이 한쪽으로 쏠리지 않게 한다.
   부유 애니메이션은 장마다 지연을 달리 줘야 세 장이 한 덩어리로 오르내리지 않는다. */
body.page-home .hero-collage {
  display: flex; justify-content: flex-end; gap: 14px;
  margin-bottom: 24px;
}
body.page-home .hero-collage span {
  width: 76px; aspect-ratio: 3 / 4;
  border-radius: 8px;
  background-color: #1a1b1e;
  background-size: cover; background-position: center;
  animation: hero-float 4.5s ease-in-out infinite;
}
body.page-home .hero-collage span:nth-child(1) {
  transform: rotate(-3deg) translateY(10px);
  box-shadow: 0 20px 40px rgba(0, 0, 0, .4);
}
body.page-home .hero-collage span:nth-child(2) {
  transform: rotate(2deg);
  box-shadow: 0 20px 40px rgba(0, 0, 0, .45);
  position: relative; z-index: 1;
  animation-delay: .6s;
}
body.page-home .hero-collage span:nth-child(3) {
  transform: rotate(-1deg) translateY(14px);
  animation-delay: 1.2s;
}
/* translateY 만 움직인다 — transform 을 통째로 쓰면 위 nth-child 의 rotate 가 지워져
   애니메이션이 도는 동안 세 장이 반듯하게 펴진다. */
@keyframes hero-float {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -10px; }
}

/* 핸드오프는 이 라벨을 13px + rgba(...,.45) 로 줄이는데, .45 는 #111214 위에서 대비 4.23 로
   본문 기준(4.5)에 못 미친다(.40 은 3.59). 크기만 따르고 투명도는 .5(4.95)에서 멈춘다 —
   이 표 위쪽에 같은 실수를 두 번 적어뒀다(브라스 글자, 공유 시트). 옅게 하는 건
   "덜 중요하다"는 표시지 "안 읽혀도 된다"는 뜻이 아니다. */
body.page-home .hero-stat p {
  margin: 0;
  font-size: 13px; font-weight: 600; letter-spacing: -.01em;
  color: rgba(248, 244, 236, .5);
}
body.page-home .hero-stat strong {
  display: block;
  margin: 6px 0;
  font-size: clamp(44px, 5vw, 64px);
  font-weight: 700; letter-spacing: -.04em;
  color: var(--brass);
  font-variant-numeric: tabular-nums;
  /* 히어로 텍스트가 다 들어온 뒤 숫자가 마지막으로 떠오른다(지연 .4s). */
  animation: hero-count-in 1.2s ease both .4s;
}
@keyframes hero-count-in { from { opacity: 0; } to { opacity: 1; } }

/* 수집이 살아 있다는 표시. 초록 점 + 문구.

   **"실시간"이라고 쓰지 않는다.** 수집은 매일 새벽 4시 배치다
   (PerformanceSyncScheduler, cron 0 0 4 * * *). 실시간이 아닌 걸 실시간이라 적으면
   그건 장식이 아니라 거짓말이고, 이 프로젝트가 표기에서 지켜온 기준과 어긋난다. */
body.page-home .hero-live {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 14px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .02em;
  color: rgba(248, 244, 236, .5);
}
body.page-home .hero-live::before {
  content: "";
  width: 5px; height: 5px; border-radius: 50%;
  background: #9ee6a8;
  animation: pulse-live 1.8s ease-out infinite;
}
/* 점 주위로 고리가 퍼져 나간다. box-shadow 만 움직이므로 레이아웃을 건드리지 않는다. */
@keyframes pulse-live {
  0%   { box-shadow: 0 0 0 0 rgba(158, 230, 168, .6); }
  100% { box-shadow: 0 0 0 8px rgba(158, 230, 168, 0); }
}
/* hero-sub 는 구 버전(알약형 통계 배지)의 이름을 이번 리디자인에서는 우측 큰 숫자로
   대체했다 — 마크업이 hero-stat 으로 바뀌었으므로 이 클래스는 더 안 쓰인다. */

/* 히어로 진입 애니메이션. prefers-reduced-motion 은 전역 규칙(* { animation: none !important })이
   이미 무력화하므로 여기서 따로 분기하지 않는다. */
@keyframes hero-fade-up {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
body.page-home .hero-kicker,
body.page-home .hero h2,
body.page-home .hero-search,
body.page-home .hero-stat {
  animation: hero-fade-up .7s ease both;
}
body.page-home .hero h2 { animation-delay: .08s; }
body.page-home .hero-search { animation-delay: .18s; }
body.page-home .hero-stat { animation-delay: .26s; }

/* 검색창은 히어로 안에서 유일하게 면을 가진 조작부다. 통계와 콜라주를 낮춘 만큼
   입력 영역의 폭·테두리·포커스 빛으로 다음 행동을 더 명확하게 만든다. */
body.page-home .hero-search {
  display: flex; align-items: center; gap: 14px;
  max-width: 600px; margin: 0;
  padding: 8px 8px 8px 16px;
  border: 1px solid rgba(248, 244, 236, .24);
  border-radius: 999px;
  background: rgba(248, 244, 236, .06);
  transition: border-color .25s, background .25s, box-shadow .25s;
}
body.page-home .hero-search:focus-within {
  border-color: var(--brass);
  background: rgba(248, 244, 236, .1);
  box-shadow: 0 0 0 4px rgba(233, 180, 76, .12);
}
body.page-home .hero-search svg { flex: none; }
body.page-home .hero-search input {
  flex: 1; min-width: 0;
  background: none; border: 0; outline: none;
  font: inherit; font-size: 17px; color: var(--paper);
}
body.page-home .hero-search input::placeholder { color: rgba(248, 244, 236, .5); }
body.page-home .hero-search button {
  flex: none;
  padding: 10px 22px;
  background: var(--brass); color: var(--ink);
  border: 0; border-radius: 999px;
  font: inherit; font-size: 14.5px; font-weight: 700;
  cursor: pointer;
  transition: transform .2s, background .2s;
}
body.page-home .hero-search button:hover { background: #f2c874; transform: scale(1.04); }
/* 눌린 순간 살짝 들어간다. 응답이 오기 전까지의 빈 시간을 메꿔서 눌린 게 맞는지
   다시 누르는 걸 줄인다. 로그인 링크와 회원가입 CTA는 모양은 달라도 같은 계정 진입
   묶음이므로, 눌린 순간의 피드백은 같이 제공한다. */
body.page-home .hero-search button:active { transform: scale(.94); }
.authbtns a:active { transform: scale(.94); }

/* 장르 바로가기. 라이트 배경 위 알약 — 아웃라인 기본, hover 시 브라스 라인 + 살짝 뜬다. */
body.page-home .genrenav {
  display: flex; flex-wrap: wrap; gap: 10px;
  max-width: 1240px; margin: 0 auto;
  padding: 26px 48px;
  overflow-x: auto;
}
body.page-home .genrenav a {
  display: inline-flex; align-items: center; gap: 8px;
  flex: none;
  padding: 10px 18px;
  border: 1px solid rgba(17, 18, 20, .16); border-radius: 999px;
  color: var(--ink); font-weight: 700; font-size: 14px;
  transition: border-color .2s, transform .2s;
}
body.page-home .genrenav a:hover { border-color: var(--brass); transform: translateY(-2px); }
/* 선택된 칩을 잉크로 채운다. 홈에서는 장르를 안 걸고 있으므로 항상 "전체"가 여기 해당한다
   — 목록 화면에서 장르를 고르고 돌아왔을 때와 같은 표시 방식이라 두 화면이 같은 말을 한다.
   :first-child 로 잡지 않는 건 그게 "선택됨"이 아니라 "맨 앞"을 칠하는 것이기 때문이다. */
body.page-home .genrenav a.active {
  background: var(--ink); border-color: var(--ink); color: var(--paper);
}
body.page-home .genrenav a.active:hover { border-color: var(--ink); }
body.page-home .genrenav a.active em { opacity: .6; }
body.page-home .genrenav em {
  font-style: normal; font-weight: 600; opacity: .5;
}

/* ---- 최근 본 공연 (장르 칩과 첫 섹션 사이) ----

   편집 섹션(.homesec)이 아니라 도구에 가깝다. 그래서 제목을 26px 디스플레이체로
   두지 않고 13px 라벨로 낮춘다 — 같은 크기로 두면 「곧 막을 내려요」와 나란히
   서면서 홈에 큰 제목이 넷이 되고, 정작 편집한 섹션이 묻힌다.

   포스터도 카드(240px)의 절반이 안 되는 92px 이다. 훑는 대상이 아니라 "아까 그거"를
   집어내는 자리라 알아볼 수 있을 만큼만 크면 된다. */
body.page-home .recentsec {
  max-width: 1240px; margin: 0 auto;
  padding: 6px 48px 0;
}
body.page-home .rs-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; margin-bottom: 14px;
}
body.page-home .rs-head h2 {
  margin: 0;
  font-size: 13px; font-weight: 800; letter-spacing: .08em;
  /* .55 는 아이보리 위 대비 4.04 로 본문 기준(4.5)에 못 미친다. 작은 글씨라 여기서
     한 단계 더 내리면 라벨이 배경으로 가라앉는다. */
  color: rgba(17, 18, 20, .6);
}
body.page-home .rs-clear {
  flex: none;
  background: none; border: 0; padding: 0;
  font: inherit; font-size: 13px; font-weight: 600;
  color: rgba(17, 18, 20, .6);
  cursor: pointer;
  transition: color .2s;
}
/* 호버는 --brass-dim(2.88) 대신 잉크로 올린다. 기록을 지우는 버튼이라 "눌리는 중"이
   흐려지면 안 되고, .hs-more 처럼 훑다가 지나치는 링크와는 성격이 다르다. */
body.page-home .rs-clear:hover { color: var(--ink); }

/* 카드형 섹션(.homesec .grid)과 같은 가로 스크롤·스냅. 스크롤바를 감추는 것도 같다 —
   두 띠가 같은 화면에 세로로 놓이므로 미는 느낌이 달라지면 어긋나 보인다. */
body.page-home .rs-rail {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: 4px;
  margin-bottom: -4px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
body.page-home .rs-rail::-webkit-scrollbar { width: 0; height: 0; display: none; }
body.page-home .rs-item {
  flex: none;
  width: 92px;
  scroll-snap-align: start;
  color: var(--ink);
  transition: transform .2s;
}
body.page-home .rs-item:hover { transform: translateY(-3px); }
body.page-home .rs-item:hover .rs-title { color: var(--brass-dim); }
body.page-home .rs-poster {
  position: relative;
  aspect-ratio: 3 / 4;
  margin-bottom: 8px;
  border-radius: var(--radius-poster);
  overflow: hidden;
  /* 라이트 화면의 카드 포스터와 같은 바탕(body.tone-light .card .poster.no-img).
     포스터가 뜨는 동안에도 이 색이 깔려 있어야 흰 칸이 깜빡이지 않는다. */
  background: var(--ink);
}
body.page-home .rs-poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 포스터가 없거나 주소가 죽었을 때만. 스크립트가 .no-img 를 붙인다. */
body.page-home .rs-fallback {
  display: none;
  position: absolute; inset: 0;
  align-items: center; justify-content: center;
  font-family: var(--display); font-size: 30px; font-weight: 800;
  color: rgba(248, 244, 236, .45);
}
body.page-home .rs-poster.no-img .rs-fallback { display: flex; }
body.page-home .rs-title {
  /* 두 줄까지만. 92px 폭이라 긴 공연명은 네댓 줄이 되는데, 그러면 항목마다 높이가
     달라져서 띠가 들쭉날쭉해진다. 말줄임으로 끊고 전체 제목은 상세에서 본다. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 12.5px; font-weight: 600; line-height: 1.35;
  transition: color .2s;
}

/* 섹션 공통(카드형 · 에디토리얼형 모두). 제목 줄과 카드 사이 간격이 섹션 사이
   간격보다 뚜렷하게 좁아야 "이 카드들이 이 제목에 속한다"가 읽힌다. */
body.page-home .homesec {
  max-width: 1240px; margin: 0 auto;
  padding: 36px 48px 20px;
}
body.page-home .hs-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; margin-bottom: 26px;
  padding-bottom: 18px;
  border-bottom: 1px solid rgba(17, 18, 20, .1);
}
body.page-home .hs-title h2 {
  font-family: var(--display);
  font-size: 26px; font-weight: 800; letter-spacing: -.02em;
  margin: 0 0 6px;
  color: var(--ink);
}
body.page-home .hs-title p { margin: 0; font-size: 14px; color: rgba(17, 18, 20, .55); }
body.page-home .hs-more {
  flex: none;
  font-size: 14px; font-weight: 700; color: var(--ink);
  border-bottom: 0;
  transition: color .2s;
}
body.page-home .hs-more:hover { color: var(--brass-dim); }

/* 카드형 섹션(곧 막을 내려요 / 새로 들어온 공연) — 가로 슬라이드. 그리드로 줄바꿈하는
   대신 카드를 옆으로 죽 늘어놓고 가로 스크롤한다 — 6장을 다 펼치면 세로로 길어져
   에디토리얼 밴드까지 가기 전에 스크롤이 지루해지므로, 한눈에 4장 정도 보여주고
   나머지는 옆으로 밀어서 보게 한다. 스냅으로 카드 경계에서 멈춘다. */
body.page-home .railwrap { position: relative; }
body.page-home .homesec .grid {
  display: flex;
  gap: 24px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* 아래위로 4px 을 빼 두는 것과 같은 수법을 왼쪽에도 쓴다. 회차 알약이 글자 열을 맞추려고
     왼쪽으로 10px 나가 있는데(.m-date margin-left 주석 참고), 여기가 overflow-x 컨테이너라
     그대로 두면 첫 카드의 알약만 잘린다. 안쪽으로 10px 밀고 상자를 같은 만큼 왼쪽으로
     늘려서, 보이는 위치는 그대로 두고 잘림만 없앤다. */
  padding: 0 0 4px 10px;
  margin: 0 0 -4px -10px;
  /* 스냅이 카드의 왼쪽 모서리를 스크롤 영역 시작에 붙이는데, 그 "시작"은 padding 을
     안 본다 — 그대로 두면 브라우저가 10px 만큼 스스로 스크롤해서(실측 scrollLeft: 10)
     방금 만든 자리를 도로 없애고 알약이 또 잘린다. 스냅 기준을 padding 만큼 밀어준다. */
  scroll-padding-left: 10px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
body.page-home .homesec .grid::-webkit-scrollbar { width: 0; height: 0; display: none; }
body.page-home .homesec .card {
  flex: none;
  width: 240px;
  scroll-snap-align: start;
  gap: 14px; transition: transform .25s;
}

/* 좌우 화살표. 포스터(3:4) 세로 중앙 즈음에 오도록 %로 근사한다 — 카드가 포스터+제목+
   메타로 이어져 실제 포스터 중앙은 전체 높이의 절반보다 위(37%쯤)다. */
body.page-home .railnav {
  position: absolute; top: 37%; transform: translateY(-50%);
  width: 40px; height: 40px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(17, 18, 20, .12); border-radius: 999px;
  background: rgba(248, 244, 236, .92);
  color: var(--ink); font-size: 20px; line-height: 1;
  box-shadow: 0 8px 20px rgba(17, 18, 20, .14);
  cursor: pointer; z-index: 2;
  transition: background .2s, opacity .15s, transform .2s;
}
body.page-home .railnav:hover { background: #fff; transform: translateY(-50%) scale(1.06); }
body.page-home .railnav--prev { left: -8px; }
body.page-home .railnav--next { right: -8px; }
/* 스크립트가 양 끝에서 disabled 를 걸어 "더 없다"를 알려준다. JS 가 없으면(느린 3자
   스크립트 차단 등) disabled 가 안 걸릴 뿐 버튼 자체는 항상 눌려서 스크롤은 된다. */
body.page-home .railnav:disabled { opacity: 0; pointer-events: none; }

@media (max-width: 860px) {
  body.page-home .railnav { display: none; }
}
/*
   위 규칙은 "좁으면 손가락으로 밀 테니 화살표는 자리만 차지한다"는 뜻이었는데, 가르는
   기준이 폭이라 <b>861px 미만의 마우스 화면</b>(작은 노트북, 창을 반만 쓴 데스크톱,
   태블릿 가로)까지 같이 걸렸다. 그 화면에는 손가락도 없고, 이 레일은 스크롤바도 영구히
   꺼 둔 상태다(.homesec .grid::-webkit-scrollbar) — 카드 10장 중 화면에 드는 3장 말고는
   shift+휠을 아는 사람만 볼 수 있었다. 실측: 뷰포트 792px, 레일 내용 2,026px, 보이는 폭
   747px, ontouchstart 없음, 화살표·스크롤바 둘 다 display:none.

   그래서 폭 대신 입력 장치로 되살린다. 마우스(hover 가능 + 정밀 포인터)면 좁아도 화살표를
   남기고, 터치는 종전대로 숨긴 채 밀어서 본다. 560px 아래로는 마우스라도 숨긴다 — 그
   폭에서는 섹션 머리(제목 + 「전체 보기」)가 이미 꽉 차서 40px 버튼 두 개가 들어갈 자리가
   없다.
*/
@media (max-width: 860px) and (min-width: 561px) and (hover: hover) and (pointer: fine) {
  body.page-home .railnav { display: flex; }
}
body.page-home .homesec .card:hover { transform: translateY(-6px); }
body.page-home .homesec .card .poster {
  border: 0; border-radius: var(--radius-poster);
  background: var(--ink);
}
body.page-home .homesec .card .poster::after { display: none; }
/* 목록(1.04 / .25s)보다 조금 크고 느리게. 홈은 훑는 화면이라 카드가 크고 수가 적어
   같은 값이면 거의 안 보인다 — 핸드오프 값(1.06 / .4s)을 그대로 쓴다. */
body.page-home .homesec .card .poster img { transition: transform .4s ease; }
body.page-home .homesec .card:hover .poster img { transform: scale(1.06); }
body.page-home .homesec .card h3 { color: var(--ink); font-size: 16px; letter-spacing: -.01em; }
body.page-home .homesec .card:hover h3 { color: var(--brass-dim); }
body.page-home .homesec .card .meta { color: rgba(17, 18, 20, .55); }
body.page-home .homesec .card .m-venue::before,
body.page-home .homesec .card .m-date::before { display: none; }
body.page-home .homesec .card-badge {
  background: rgba(17, 18, 20, .75); color: var(--brass);
  border: 0;
}
/* 마감 임박 배지. 장르 배지(좌상단·브라스)와 반대쪽 모서리에 로즈로 둔다 —
   같은 쪽에 두면 둘이 한 덩어리로 읽혀 "장르 뒤에 붙은 꼬리표"처럼 보인다.
   z-index 는 장르 배지와 같은 3 — 포스터 img(1)와 .poster::after(2) 위에 와야 한다
   (배지가 포스터 뒤로 숨던 결함을 겪은 자리다). */
body.page-home .homesec .card-dday {
  position: absolute; top: 10px; right: 10px; z-index: 3;
  padding: 4px 9px;
  border-radius: 999px;
  background: var(--rose); color: #fff;
  font-size: 11px; font-weight: 700;
}
/* 오늘 마감인 것만 고리가 퍼진다.

   핸드오프는 D-day 배지 전부에 이 맥동을 주는데, 목업에는 배지가 한 장뿐이라
   그렇게 보였을 뿐이다. 실데이터에서는 「곧 막을 내려요」가 마감순이라 열 장이
   한꺼번에 깜빡인다 — 전부 뛰면 아무것도 안 뛰는 것과 같아서 "임박"이라는 뜻이
   사라지고 화면만 소란해진다. 진짜 오늘 끝나는 것에만 남긴다. */
body.page-home .homesec .card-dday--today {
  animation: pulse-live 1.4s ease-out infinite;
}
/* 시작 시점을 어긋나게 준다. 배지가 전부 같은 순간에 켜지면 화면이 한 박자로 깜빡여
   "고장난 표시등"처럼 읽힌다 — 어긋나 있으면 같은 개수라도 잔잔하게 도는 것으로 보인다.
   주기가 1.4s 라 넷으로 나눠 0/.35/.7/1.05 를 돌려 쓴다. */
body.page-home .homesec .card:nth-child(4n+2) .card-dday--today { animation-delay: .35s; }
body.page-home .homesec .card:nth-child(4n+3) .card-dday--today { animation-delay: .7s; }
body.page-home .homesec .card:nth-child(4n+4) .card-dday--today { animation-delay: 1.05s; }

/* 에디토리얼 밴드(가운데 섹션 — "N월 공연") — 다크 풀블리드, 3열, 카드 사이 1px 구분선. */
body.page-home .homesec--editorial {
  max-width: none; padding: 0;
  background: var(--ink); color: var(--paper);
  margin: 56px 0 0;
}
body.page-home .homesec--editorial .hs-inner { max-width: 1240px; margin: 0 auto; padding: 56px 48px; }
body.page-home .homesec--editorial .hs-head { border-bottom: 0; margin-bottom: 30px; padding-bottom: 0; }
body.page-home .homesec--editorial .hs-title h2 { color: var(--paper); }
body.page-home .homesec--editorial .hs-title p { color: rgba(248, 244, 236, .5); }
body.page-home .homesec--editorial .hs-more { color: var(--brass); }
body.page-home .homesec--editorial .hs-more:hover { opacity: .7; color: var(--brass); }
body.page-home .editorial-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
  background: rgba(248, 244, 236, .14);
  border-radius: 10px; overflow: hidden;
}
body.page-home .editorial-card {
  display: flex; flex-direction: column; gap: 16px;
  background: var(--ink); padding: 26px;
  transition: background .2s;
}
body.page-home .editorial-card:hover { background: #1c1d20; }
/* 실제 포스터는 세로(3:4)가 대부분이라, 16:10 가로 프레임에 object-fit:cover 를 쓰면
   위아래(제목·얼굴 등)가 크게 잘려나간다. 포스터는 안 잘리게 통째로 보여주고(contain),
   남는 좌우 여백은 같은 이미지를 흐리게 확대한 배경으로 채운다 — 검은 여백만 남는
   것보다 완성도 있어 보인다(실서비스 포스터 배너의 흔한 처리). */
body.page-home .editorial-card .ec-image {
  position: relative;
  aspect-ratio: 16 / 10; border-radius: 4px; overflow: hidden;
  background: #1a1b1e;
}
body.page-home .editorial-card .ec-image .ec-backdrop {
  position: absolute; inset: -10%;
  background-size: cover; background-position: center;
  filter: blur(20px) brightness(.55); transform: scale(1.1);
}
body.page-home .editorial-card .ec-image img {
  position: relative; z-index: 1;
  width: 100%; height: 100%; object-fit: contain; display: block;
}
/* 포스터가 없거나(no-img) 깨진 주소라 img 가 onerror 로 빠진 경우의 대체 표시.
   목록 카드 .posterfallback(2153행)과 같은 발상 — 평소엔 숨겨 두고 no-img 일 때만
   가운데에 큼직한 제목 첫 글자를 띄운다. 16:10 프레임이 3:4 포스터 칸보다 넓어서
   글자도 그만큼(34→44px) 키운다. */
body.page-home .editorial-card .ec-fallback {
  display: none;
  font-family: var(--body); font-size: 44px; font-weight: 700; color: var(--mute);
}
body.page-home .editorial-card .ec-image.no-img {
  display: grid; place-items: center;
}
body.page-home .editorial-card .ec-image.no-img .ec-fallback { display: block; }
/* 예매 가능 여부. 카드형 목록의 .card-avail 과 같은 판단을 포스터 우상단에 얹는다 —
   여기는 이미지 프레임(.ec-image)이 카드 배경(var(--ink))과 달라서 배지가 이미지
   위에 뜨는 걸 z-index 로 보장해야 한다(장르 배지가 이미지 뒤로 숨던 결함과 같은 자리). */
body.page-home .editorial-card .ec-avail {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  padding: 4px 9px;
  border-radius: 999px;
  font-size: 11px; font-weight: 700;
}
body.page-home .editorial-card .ec-avail.avail-soldout { background: var(--rose); color: #fff; }
body.page-home .editorial-card .ec-avail.avail-low { background: var(--brass); color: var(--ink); }
body.page-home .editorial-card .ec-avail.avail-pending,
body.page-home .editorial-card .ec-avail.avail-unknown {
  background: rgba(17, 18, 20, .6); color: var(--paper);
  backdrop-filter: blur(4px);
  font-weight: 500;
  border: 1px solid rgba(248, 244, 236, .16);
}
body.page-home .editorial-card .ec-cat {
  font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--brass); margin: 0 0 8px;
}
/* 제목·메타의 줄 수를 못박는다. 밴드가 묶음을 돌리는데 묶음마다 제목이 한 줄이었다
   두 줄이었다 하면 밴드 높이가 바뀌면서 아래 화면 전체가 몇 초마다 위아래로 튄다.
   높이를 min-height 가 아니라 height 로 주는 이유가 그것이다 — 줄 수와 무관하게
   모든 카드가 정확히 같은 높이여야 묶음끼리도 같아진다.

   겸사겸사 종전부터 있던 문제도 같이 잡힌다: 수집분에는 「애기봉평화생태공원 6~8월
   토요상설공연 <애기봉 플레이리스트> Vol.3 그 여름 날, 애기봉」 같은 제목이 흔한데,
   그 한 장이 자기 줄 전체를 늘려 놓고 있었다(그리드라 같은 행이 같이 늘어난다). */
body.page-home .editorial-card h3 {
  font-size: 18px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 6px; color: var(--paper);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  line-height: 1.35; height: 2.7em;
}
/*
   공연장 · 회차 알약 · 기간 세 줄. 카드(.card .meta)와 같은 규칙인데, 여기는 다크 판
   위라 색을 따로 정한다.

   높이를 2.8em 로 못박던 것을 세 줄에 맞춰 다시 잡는다 — 밴드가 묶음을 돌리는데 묶음마다
   높이가 다르면 화면이 7초마다 위아래로 튄다(그래서 h3 도 두 줄로 고정해 뒀다).
   공연장 한 줄(말줄임) + 알약 한 줄 + 기간 한 줄이라 줄 수가 항상 같다.
*/
body.page-home .editorial-card .ec-meta {
  font-size: 13px; color: rgba(248, 244, 236, .5); margin: 0;
  display: grid; gap: 4px; justify-items: start;
  /*
     높이를 못박는다. 남은 회차가 없는 공연(그 달 회차가 이미 다 지난 경우)은 알약 줄이
     통째로 빠지는데, 실측하면 74px → 44px 로 30px 이 줄어든다. 밴드는 묶음을 7초마다
     돌리므로 그런 카드가 한 장만 섞여도 그 묶음에서만 아래 화면이 30px 튄다.
     align-content: start 라 줄이 모자라면 아래가 비고, 위 두 줄의 자리는 그대로다.
     (같은 이유로 h3 도 두 줄로 고정해 뒀다 — 그쪽 주석 참고)
  */
  height: 74px;
  align-content: start;
}
/* 아주 긴 공연장명(「플러스씨어터(구. 컬처스페이스 엔유 구. 쁘티첼 씨어터)」)이 두 줄로
   접히면 카드 높이가 달라진다. 한 줄로 자른다 — 카드 목록의 .m-venue 와 같은 처리다. */
body.page-home .editorial-card .ec-venue {
  max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 회차 알약. 채움은 오늘·내일뿐인 것도 카드와 같다. 다크 판 위라 글자는 브라스를 쓰고
   (아이보리 배경에서 잉크를 쓰던 것과 반대), 채웠을 때만 잉크로 뒤집는다. */
body.page-home .editorial-card .ec-next {
  display: inline-flex; align-items: center;
  /* 카드 목록과 같은 이유로 왼쪽 padding 만큼 당긴다 — 공연장·기간과 글자 열을 맞춘다.
     이쪽은 카드가 padding: 26px 을 갖고 있어서 10px 나가도 카드 안이다. */
  margin-left: -10px;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(233, 180, 76, .18);
  color: var(--brass);
  font-size: 12.5px; font-weight: 700; letter-spacing: -.01em;
  white-space: nowrap;
}
body.page-home .editorial-card .ec-next--soon {
  background: var(--brass);
  color: var(--ink);
}
body.page-home .editorial-card .ec-run { font-size: 12px; }

/* ---- 밴드 묶음 돌리기 ----

   여섯 장을 고정으로 걸어두면 한 달 내내 같은 여섯 개가 서 있다(HOME_BAND_PAGES 주석).
   네 묶음을 받아 두고 화면이 돌린다. 감춰진 묶음은 [hidden] 이라 자리를 차지하지 않고,
   나타날 때의 전환은 리빌(.reveal .editorial-card)이 이미 갖고 있는 것을 그대로 쓴다 —
   같은 화면에서 카드가 나타나는 방식이 두 가지면 그게 더 어색하다. */
body.page-home .editorial-grid[hidden] { display: none; }

body.page-home .banddots { display: flex; justify-content: center; gap: 8px; margin-top: 4px; }
/* 막대는 3px 이지만 누르는 영역은 44px 로 잡는다 — 보이는 만큼만 버튼으로 두면
   손가락으로 못 누른다(README 「터치 타깃 최소 크기」와 같은 기준). 남는 높이는
   투명해서 화면에는 얇은 막대만 보인다. */
body.page-home .banddots button {
  display: inline-flex; align-items: center;
  width: 40px; height: 44px; padding: 0;
  background: none; border: 0; cursor: pointer;
}
body.page-home .banddots button::before {
  content: ''; display: block;
  width: 100%; height: 3px; border-radius: 999px;
  background: rgba(248, 244, 236, .22);
  transition: background .2s;
}
body.page-home .banddots button:hover::before { background: rgba(248, 244, 236, .45); }
/* 지금 보고 있는 묶음. 색만 바꾸지 않고 폭도 같이 주는 건, 얇은 막대에서는 밝기 차이가
   생각보다 안 읽히기 때문이다. */
body.page-home .banddots button[aria-current]::before { background: var(--brass); }
body.page-home .banddots button[aria-current] { width: 56px; }
body.page-home .banddots button:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; border-radius: 4px; }

/* 카드형 섹션과 같은 IntersectionObserver 리빌을 쓴다(home.html 스크립트가
   .editorial-grid/.editorial-card 도 같이 관찰한다) — 클래스를 .grid/.card 와
   공유하지 않는 이유는 그 두 클래스가 이미 body.page-list 쪽 다른 grid-template-columns
   규칙을 갖고 있어(4열) 여기 3열+구분선 디자인과 충돌하기 때문이다. */
body.page-home .editorial-grid.reveal .editorial-card {
  opacity: 0; transform: translateY(14px);
  transition: opacity .45s ease, transform .45s ease;
}
body.page-home .editorial-grid.reveal .editorial-card.is-visible { opacity: 1; transform: none; }

body.page-home .allmore { text-align: center; max-width: 1240px; margin: 0 auto; padding: 44px 48px 60px; }
body.page-home .allmore a {
  display: inline-block;
  padding: 0;
  border: 0; border-bottom: 2px solid var(--brass);
  border-radius: 0;
  font-size: 15px; font-weight: 700; color: var(--ink);
  transition: letter-spacing .2s;
}
body.page-home .allmore a:hover { letter-spacing: .02em; background: none; }

@media (max-width: 860px) {
  body.page-home .masthead { padding: 16px 20px; }
  /* 데스크톱용 gap:20px/font-size:14.5px(위 body.page-home .masthead .authnav) 그대로면
     pill 5개가 한 줄에 다른 페이지보다 더 넓게 필요해 좁은 화면에서 줄바꿈이 더 자주 난다.
     다른 페이지와 같은 값으로 맞춘다 — 이 규칙이 화면을 짚고 있어(명시도가 높아) 파일
     끝의 「좁은 화면 헤더 압축」 블록을 이겨버리므로, 거기서 정한 8px 을 여기도 같이 쓴다.
     한 곳만 고치면 홈만 헤더가 세 줄로 남는다. */
  body.page-home .masthead .authnav { gap: 6px 8px; font-size: 13px; }
  body.page-home .hero { padding: 48px 20px 36px; }
  body.page-home .hero-inner { grid-template-columns: 1fr; gap: 28px; align-items: start; }
  body.page-home .hero-stat { text-align: left; border-left: 0; padding-left: 0; }
  /* 통계가 왼쪽으로 붙으므로 콜라주도 같이 왼쪽으로. 한 칸에 세로로 쌓이는 배치라
     포스터만 오른쪽에 남으면 숫자와 어긋나 보인다. 여백도 줄인다 — 좁은 화면에서
     36px 은 포스터와 숫자 사이가 끊긴 것처럼 벌어진다. */
  body.page-home .hero-collage { justify-content: flex-start; margin-bottom: 24px; }
  body.page-home .hero h2 { font-size: 32px; white-space: normal; }
  /* 장르 칩 10개가 줄바꿈되면 2열 x 5줄로 쌓여 히어로 바로 아래가 알약 벽이 된다
     (실측: 10개를 한 줄에 두려면 1,112px 필요 — 860px 아래에서는 애초에 줄바꿈 없이
     못 들어간다). 다른 가로 레일(.rs-rail, .homesec .grid)과 같은 처리로 한 줄
     가로 스크롤로 바꾼다 — 처음 들어온 화면에서 카테고리 선택에 쓰는 세로 공간을
     줄이고, 훑어보는 리듬도 아래 레일들과 맞춘다. */
  body.page-home .genrenav {
    padding: 20px;
    flex-wrap: nowrap;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  body.page-home .genrenav::-webkit-scrollbar { width: 0; height: 0; display: none; }
  body.page-home .genrenav a { scroll-snap-align: start; }
  /* 좌우 여백은 다른 띠와 같이 20px 로. 포스터는 그대로 92px 를 쓴다 — 카드(240→180px)와
     달리 이미 작아서 더 줄이면 어느 공연이었는지 알아볼 수가 없다. */
  body.page-home .recentsec { padding: 0 20px; }
  body.page-home .homesec { padding: 28px 20px 16px; }
  body.page-home .homesec .card { width: 180px; }
  body.page-home .homesec--editorial .hs-inner { padding: 40px 20px; }
  /*
     3열에서 바로 1열로 떨어뜨리면 카드 폭이 그대로 본문 폭이 되어(실측 697px) 카드 하나가
     627px 이 되고, 6장짜리 밴드가 4,038px — 홈 문서 전체(6,242px)의 65% 를 먹는다. 그
     아래 「새로 들어온 공연」은 y=5,571 에 묻힌다. 2열 단계를 하나 둔다.
  */
  body.page-home .editorial-grid { grid-template-columns: repeat(2, 1fr); }
}

/* 폰 폭에서는 2열도 카드가 너무 좁아 제목이 서너 줄로 접힌다. 여기서만 1열로 둔다. */
@media (max-width: 560px) {
  body.page-home .editorial-grid { grid-template-columns: 1fr; }
}

/* ==================================================================
   공연 상세 액션 (공유 · 찜)

   예매(회차 선택)와 성격이 다르다 — 결정을 미루거나 남에게 넘기는 동작이라
   brass 를 쓰지 않는다. 화면의 주요 액션은 어디까지나 예매 하나여야 한다.
   ================================================================== */
body.page-detail .pf-actions {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 28px 0 32px;
}
body.page-detail .pf-actions button {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 16px;
  background: none; color: var(--mute);
  border: 1px solid var(--line-strong); border-radius: 999px;
  font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
}
body.page-detail .pf-actions button:hover { border-color: var(--paper); color: var(--paper); }
body.page-detail .pf-actions button svg { flex: none; }

/* ---- 공유 시트 ----
   "공유하기" 를 누르면 뜨는 채널 선택 창(카카오톡 / 링크복사). 배경을 눌러도 뒤 화면을
   조작 못 하게 막는 게 맞는 화면이라 팝오버가 아니라 전체 화면 dialog 로 뒀다.
   기존 .fp-backdrop(모바일 필터 패널)과 같은 opacity/pointer-events 전환 방식이라
   숨김에 display:none 대신 이 방식을 쓴다 — 열고 닫을 때 애니메이션이 붙는다.

   body.page-detail 스코프를 뺐다(환불 규정을 결제 화면과 공용으로 만들 때와 같은 이유).
   마크업이 fragment/sharesheet.html 로 빠져 결제 완료 화면도 쓰는데, 스코프가 남아 있으면
   그 화면에서는 규칙이 하나도 안 걸려 **시트가 숨지 않고 본문 아래 그대로 펼쳐진다.**
   클래스 이름이 .sharesheet- 로 고유해서 스코프 없이도 다른 화면과 겹치지 않는다. */
.sharesheet-backdrop {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(0, 0, 0, .55);
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease;
}
.sharesheet-backdrop.open { opacity: 1; pointer-events: auto; }

.sharesheet {
  position: fixed; top: 50%; left: 50%; z-index: 71;
  width: min(320px, calc(100vw - 40px));
  transform: translate(-50%, -50%) scale(.96);
  background: var(--ink-soft);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 24px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
}
.sharesheet.open { opacity: 1; pointer-events: auto; transform: translate(-50%, -50%) scale(1); }

.sharesheet-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 24px;
}
.sharesheet-head h3 { margin: 0; font-size: 17px; font-weight: 700; color: var(--paper); }
.sharesheet-close {
  display: flex; padding: 4px;
  background: none; border: none; color: var(--mute);
  cursor: pointer; transition: color .12s;
}
.sharesheet-close:hover { color: var(--paper); }

.sharesheet-options { display: flex; gap: 28px; justify-content: center; }
.sharesheet-option {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  background: none; border: none; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--paper);
}
.sharesheet-icon {
  width: 56px; height: 56px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  transition: transform .12s;
}
.sharesheet-option:hover .sharesheet-icon { transform: scale(1.06); }
/* 카카오 브랜드 색(로그인 화면 .oauthbtn-kakao 와 같은 값). 링크복사는 브랜드가 없으니
   이 화면의 중립 표면색(--ink-raised)에 테두리만 준다. */
.sharesheet-icon-kakao { background: #FEE500; }
.sharesheet-icon-link {
  background: var(--ink-raised); border: 1px solid var(--line-strong); color: var(--paper);
}

/* 찜한 상태. 눌렸다는 걸 색 하나로만 알리면 색각 이상에서 구분이 안 되므로
   하트를 속이 빈 것에서 채운 것으로 바꾸고(아이콘 모양) 글자도 함께 바꾼다. */
body.page-detail .pf-wish[aria-pressed="true"] {
  color: var(--brass);
  border-color: var(--brass);
  background: rgba(255, 201, 64, .08);
}

/* 짧은 안내. alert 는 확인을 눌러야 사라져서, 알려주자고 흐름을 끊는 꼴이 된다. */
.toast {
  position: fixed; left: 50%; bottom: 32px;
  transform: translate(-50%, 12px);
  z-index: 60;
  max-width: min(420px, calc(100vw - 32px));
  padding: 12px 20px;
  background: var(--ink-soft); color: var(--paper);
  border: 1px solid var(--line-strong); border-radius: 999px;
  font-size: 13px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .4);
  opacity: 0; pointer-events: none;
  transition: opacity .16s ease, transform .16s ease;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/*
   좌석 선택 화면에는 화면 아래에 고정된 예매 바가 있다. 기본 위치(bottom: 32px)로 두면
   토스트가 가격표와 「예매하기」 위에 겹친다(z-index 60 이라 가려지진 않지만, 마침
   그때 눌러야 하는 버튼을 덮는다). 미니맵과 같은 방식으로 바 높이(--footer-h, JS 가 실측)
   만큼 띄운다.
*/
body.page-seatmap .toast { bottom: calc(var(--footer-h, 150px) + 20px); }

/* 움직임을 줄이도록 설정한 사용자에게는 위치 이동 없이 밝기만 바꾼다. */
@media (prefers-reduced-motion: reduce) {
  .toast { transition: opacity .16s ease; transform: translate(-50%, 0); }
}

/* 하트는 채움/외곽선 두 겹으로 그려 두고 상태에 따라 하나만 보인다.
   색만 바꾸면 색각 이상에서 구분이 안 되므로 모양 자체를 바꾼다. */
body.page-detail .pf-wish .wish-fill { display: none; }
body.page-detail .pf-wish[aria-pressed="true"] .wish-fill { display: inline; }
body.page-detail .pf-wish[aria-pressed="true"] .wish-outline { display: none; }
body.page-detail .pf-wish:disabled { opacity: .6; cursor: default; }

/* ---- 마이페이지 상단 탭 (예매 내역 · 찜한 공연) ---- */
.mypagetabs {
  display: flex; gap: 8px;
  margin: 0 0 24px;
}
.mypagetabs a {
  padding: 9px 18px;
  border: 1px solid var(--line-strong); border-radius: 999px;
  font-size: 13px; font-weight: 600; color: var(--mute);
  transition: border-color .12s, color .12s, background .12s;
}
.mypagetabs a:hover { border-color: var(--paper); color: var(--paper); }
.mypagetabs a.active {
  color: var(--brass); border-color: var(--brass);
  background: rgba(255, 201, 64, .08);
}

/* ==================================================================
   라이트 톤 공통 기반 — 극장 예매 UI 톤앤무드 리디자인(2026-08-06)

   홈이 먼저 라이트로 넘어갔지만(body.page-home), 그 규칙은 히어로·에디토리얼처럼
   홈에만 있는 것과 섞여 있어 그대로 못 쓴다. 목록·상세·좌석도·결제가 차례로
   넘어오면서 매번 복제하게 될 것들(배경·헤더·인증 버튼·카드)만 여기 모은다.

   왜 body 기본값을 뒤집지 않고 옵트인 클래스로 가는가: 로그인·회원가입·마이페이지·
   관리자·에러 화면은 이번 리디자인 범위가 아니라 다크로 남는다. 기본값을 라이트로
   뒤집으면 그 화면들이 전부 흰 배경에 흰 글씨가 된다(--paper 를 본문색으로 쓴다).
   범위 밖 화면을 안 건드리려면 켜는 쪽이 맞다.

   page-home 에는 이 클래스를 안 단다 — 이미 자기 블록에서 같은 값을 정하고 있어
   두 번 칠할 이유가 없고, 홈은 이번 작업에서 손대지 않는다.
   ================================================================== */
body.tone-light {
  background: var(--paper);
  color: var(--ink);
}

/* 헤더. 다크 화면의 .masthead 와 같은 sticky blur 바인데 명암만 뒤집는다. */
body.tone-light .masthead {
  background: rgba(248, 244, 236, .86);
  border-bottom: 1px solid rgba(17, 18, 20, .08);
  -webkit-backdrop-filter: saturate(160%) blur(10px);
  backdrop-filter: saturate(160%) blur(10px);
}
body.tone-light .masthead h1 { color: var(--ink); }
body.tone-light .masthead .logo-mark { color: var(--ink); }
/* 부제 태그는 감춘다. 홈이 이미 그렇게 두고 있어서, 남겨두면 홈에서 넘어오는 순간
   헤더에 없던 회색 문구가 하나 생긴다. 목업도 로고+내비만 둔다. */
body.tone-light .masthead .tag { display: none; }
body.tone-light .masthead .authnav { color: var(--ink); }
body.tone-light .masthead .authnav .who { color: var(--ink); }
/* 내비 링크 — 라이트 배경. 평소엔 한 단계 낮추고(.6 → 대비 4.75) 호버·현재 위치에서
   잉크로 올라온다. 현재 위치는 아주 옅은 판을 깔아 "여기"를 표시한다 — 진하게 채우면
   로그인·회원가입 알약보다 무거워져서 헤더의 무게중심이 왼쪽으로 쏠린다.

   홈(body.page-home)을 셀렉터에 같이 적는 이유: 홈은 tone-light 를 안 달고 자기 블록에서
   같은 값을 따로 정한다. 한쪽만 고치면 헤더가 홈에서만 달라지는데, 헤더는 화면을 옮겨도
   그대로여야 하는 부품이라 그 어긋남이 제일 눈에 띈다. */
body.tone-light .masthead .authnav .navlink,
body.page-home .masthead .authnav .navlink { color: rgba(17, 18, 20, .6); }
body.tone-light .masthead .authnav .navlink:hover,
body.page-home .masthead .authnav .navlink:hover { background: rgba(17, 18, 20, .06); color: var(--ink); }
body.tone-light .masthead .authnav .navlink.active,
body.page-home .masthead .authnav .navlink.active { background: rgba(17, 18, 20, .07); color: var(--ink); }
body.tone-light .masthead .authnav .who-avatar,
body.page-home .masthead .authnav .who-avatar { background: var(--ink); color: var(--paper); }
body.tone-light .masthead .authnav a { color: var(--ink); transition: color .2s; }
body.tone-light .masthead .authnav a:hover { color: var(--brass-ink); }
body.tone-light .masthead .linklike { color: var(--ink); }
body.tone-light .masthead .linklike:hover { color: var(--brass-ink); }
body.tone-light .masthead .navactions::before { background: rgba(17, 18, 20, .18); }

/* ---- 헤더 검색 — 라이트 배경 ----
   내비 링크와 같은 규칙을 그대로 따른다(평소 .6 → 호버·열림에서 잉크). 아이콘 하나가
   옆의 「공연 찾기」와 다른 밝기로 앉아 있으면 같은 줄에서 무게가 어긋나 보인다.
   홈을 셀렉터에 같이 적는 이유는 위 .navlink 규칙과 같다 — 홈은 tone-light 를 안 단다. */
body.tone-light .masthead .navsearch-toggle,
body.page-home .masthead .navsearch-toggle { color: rgba(17, 18, 20, .6); }
body.tone-light .masthead .navsearch-toggle:hover,
body.page-home .masthead .navsearch-toggle:hover { background: rgba(17, 18, 20, .06); color: var(--ink); }
body.tone-light .masthead.searchopen .navsearch-toggle,
body.page-home .masthead.searchopen .navsearch-toggle { background: rgba(17, 18, 20, .07); color: var(--ink); }

body.tone-light .masthead .navsearch,
body.page-home .masthead .navsearch { border-bottom-color: rgba(17, 18, 20, .18); }
/* 초점 밑줄은 --brass 원색이 아니라 --brass-dim. 원색은 아이보리 위 대비 1.73 이라
   초점이 갔다는 신호가 거의 안 보인다(같은 이유로 라이트 화면의 브라스 글자는
   --brass-ink 를 따로 둔다). */
body.tone-light .masthead .navsearch:focus-within,
body.page-home .masthead .navsearch:focus-within { border-bottom-color: var(--brass-dim); }
body.tone-light .masthead .navsearch svg,
body.page-home .masthead .navsearch svg { color: rgba(17, 18, 20, .4); }
body.tone-light .masthead .navsearch input[type="text"],
body.page-home .masthead .navsearch input[type="text"] { color: var(--ink); }
/* 자리표시자는 목록 필터·로그인 폼이 쓰는 값(.4)을 그대로 쓴다. 이 화면들에서 검색·입력
   칸의 흐린 안내문은 전부 같은 밝기여야 하고, 진짜 라벨은 .sr-only 로 따로 있다. */
body.tone-light .masthead .navsearch input::placeholder,
body.page-home .masthead .navsearch input::placeholder { color: rgba(17, 18, 20, .4); }

/* 라이트 화면에서는 로그인만 본문 톤으로 낮춘다. 회원가입은 공통 브라스 CTA를 유지해
   홈·목록·상세를 이동해도 행동 우선순위가 바뀌지 않게 한다. */
body.tone-light .authbtns .btn-login { color: rgba(17, 18, 20, .68) !important; }
body.tone-light .authbtns .btn-login:hover { color: var(--ink) !important; }

/* 라이트 배경 위 공통 카드. 목록·찜 목록이 같은 마크업(.card)을 쓰므로 여기 둔다.
   포스터는 테두리 대신 모서리만 각지게(--radius-poster: 6px) — 아이보리 배경에서는
   1px 테두리가 포스터 가장자리를 지저분하게 만든다. */
body.tone-light .card .poster {
  border: 0;
  border-radius: var(--radius-poster);
  background: var(--ink);
}
/* 다크용 shimmer 는 어두운 회색 그라디언트라 라이트 배경에서 눈에 띄게 튄다.
   포스터 자리는 어차피 --ink 로 채워져 있어 로딩 중에도 빈칸으로 안 보인다. */
body.tone-light .card .poster::before { animation: none; background: none; }
/* 가장자리를 눌러 액자처럼 보이게 하던 inset shadow 도 뺀다 — 다크 배경에서는
   포스터와 배경의 경계를 만들어 줬지만, 라이트 배경에서는 포스터 아래쪽만
   검게 그을린 것처럼 보인다. */
body.tone-light .card .poster::after { display: none; }
body.tone-light .card .poster.no-img { background: var(--ink); }
body.tone-light .card h3 { color: var(--ink); }
body.tone-light .card:hover h3 { color: var(--brass-ink); }
body.tone-light .card .meta { color: rgba(17, 18, 20, .55); }
/* 날짜만 한 단계 진하게(.62 → 대비 5.08). 「오늘 19:30」이 카드에서 제일 먼저 눈에
   걸려야 하는 줄인데 종전에는 제일 안 보였다 — 위 .m-date 주석의 실측값 참고. */
/*
   라이트 배경 위 알약. 글자는 두 경우 다 잉크를 쓴다 — 옅은 브라스 판 위에서 15.8,
   채운 브라스 위에서 9.9 로 둘 다 넉넉하다. --brass-ink 를 얹어 보면 판이 살짝 노래진
   만큼 대비가 4.18 로 떨어져서 본문 기준(4.5)을 못 넘긴다.

   채움은 오늘·내일뿐이다. 브라스는 이 화면에서 "지금 급한 것"을 뜻해야 하는데,
   카드마다 채우면 그냥 배경색이 된다.
*/
body.tone-light .card .m-date,
body.page-home .homesec .card .m-date {
  background: rgba(233, 180, 76, .16);
  color: var(--ink);
}
body.tone-light .card .m-date--soon,
body.page-home .homesec .card .m-date--soon {
  background: var(--brass);
  color: var(--ink);
}
/* 공연장·기간 앞 핀/달력 아이콘은 뺀다. data URI 안에 --mute(#B6B0A7)가 문자열로
   박혀 있어 라이트 배경에서는 거의 안 보이는데, 색을 맞추려면 아이콘마다 data URI 를
   한 벌 더 두어야 한다. 목업도 아이콘 없이 두 줄로 두고 있어 그쪽을 따른다. */
body.tone-light .card .m-venue::before,
body.tone-light .card .m-date::before { display: none; }
/* 주변 공연 찾기 전용. --brass 는 라이트 배경 위에서 대비 1.73(위 --brass-ink 주석
   참고)이라 안 보인다 — 글자로 쓸 때는 항상 --brass-ink 로 바꿔야 한다. */
body.tone-light .card .m-distance { color: var(--brass-ink); }
/* 배지는 포스터(다크) 위에 얹히므로 어두운 판 그대로 두되, 글자를 브라스로 올려
   포스터 이미지와 안 섞이게 한다. */
body.tone-light .card-badge {
  background: rgba(17, 18, 20, .75);
  color: var(--brass);
  border: 0;
  font-weight: 700;
}

body.tone-light .empty-state { color: rgba(17, 18, 20, .55); }
body.tone-light .empty-state code {
  background: rgba(17, 18, 20, .06);
  color: var(--ink);
}

/* ==================================================================
   공연 목록 — 라이트 톤

   위 공통 기반에 없는, 이 화면에만 있는 것(필터 패널·결과바·페이저)을 덮는다.
   마크업은 그대로다 — 목업과 구조가 이미 같아서 색·모양만 바꾸면 된다.
   ================================================================== */

/* 헤더를 화면 폭만큼 늘리기 위해 .wrap 에서 폭·여백을 걷어내고, 본문 폭은 안쪽
   .listinner 가 잡는다(홈이 쓰는 방식과 같다). 값은 목업 그대로. */
body.page-list.tone-light .wrap { max-width: none; margin: 0; padding: 0; }
body.page-list.tone-light .masthead {
  margin: 0 0 0;
  padding: 22px 48px;
}
body.page-list.tone-light .listinner {
  max-width: 1240px;
  margin: 0 auto;
  padding: 32px 48px 100px;
}

/* 필터 패널의 카드(어두운 면 + 테두리)를 없애고 배경에 그대로 얹는다. 다크에서는
   면을 하나 띄워야 필터 영역이 구분됐지만, 라이트에서는 아이보리 위에 밝은 카드가
   겹쳐 경계가 오히려 흐려진다. 구분은 아래 결과바의 가로선이 맡는다. */
body.page-list.tone-light .filterpanel {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin-bottom: 26px;
}
body.page-list.tone-light .fp-close { color: rgba(17, 18, 20, .45); }
body.page-list.tone-light .fp-keyword {
  background: #fff;
  border-color: rgba(17, 18, 20, .1);
  max-width: 520px;
  margin-bottom: 22px;
}
body.page-list.tone-light .fp-keyword:focus-within { border-color: var(--brass); }
body.page-list.tone-light .fp-keyword input { color: var(--ink); }
body.page-list.tone-light .fp-keyword input::placeholder { color: rgba(17, 18, 20, .4); }
body.page-list.tone-light .fp-label,
body.page-list.tone-light .fp-hint { color: rgba(17, 18, 20, .5); }
body.page-list.tone-light .fp-advanced { border-top-color: rgba(17, 18, 20, .1); }
body.page-list.tone-light .fp-advanced summary { color: rgba(17, 18, 20, .62); }
body.page-list.tone-light .fp-advanced summary em {
  background: rgba(233, 180, 76, .18);
  color: var(--brass-ink);
}

/* 필터 pill. 선택된 것은 브라스가 아니라 잉크로 채운다 — 이 화면에서 브라스는
   결과 건수와 예매 CTA 가 쓰는 색이고, 필터는 "지금 어디를 보고 있나" 표시라
   강조 단계가 한 칸 아래다. 라이트 배경에서 잉크 채움이 가장 또렷하기도 하다. */
body.page-list.tone-light .cattab {
  border-color: rgba(17, 18, 20, .16);
  color: rgba(17, 18, 20, .7);
  font-weight: 700;
}
body.page-list.tone-light .cattab:hover { border-color: var(--brass); color: var(--ink); }
body.page-list.tone-light .cattab.active {
  background: var(--ink); border-color: var(--ink);
  color: var(--paper);
}
body.page-list.tone-light .fp-dropdowns select,
body.page-list.tone-light .sortform select {
  color: var(--ink);
  border-color: rgba(17, 18, 20, .16);
  font-weight: 600;
  background-color: #fff;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23111214'/%3E%3C/svg%3E");
}
body.page-list.tone-light .fp-dropdowns select:hover,
body.page-list.tone-light .sortform select:hover { border-color: var(--brass); }
body.page-list.tone-light .fp-dropdowns select option,
body.page-list.tone-light .sortform select option { background: #fff; color: var(--ink); }

body.page-list.tone-light .mobile-filter-btn {
  background: #fff;
  border-color: rgba(17, 18, 20, .16);
  color: var(--ink);
}
body.page-list.tone-light .mobile-filter-btn em { color: rgba(17, 18, 20, .5); }

/* 결과바. 위아래 가로선으로 필터와 결과를 가른다(목업의 result bar). */
body.page-list.tone-light .resultbar {
  padding: 16px 0;
  border-top: 1px solid rgba(17, 18, 20, .1);
  border-bottom: 1px solid rgba(17, 18, 20, .1);
  margin-bottom: 28px;
}
body.page-list.tone-light .resultcount { color: var(--ink); font-size: 14.5px; font-weight: 700; }
/* 건수 숫자만 브라스. --brass 원색은 아이보리 배경에서 대비 1.73, --brass-dim 도 2.88 이라
   둘 다 본문 글자로는 못 읽는다. 라이트 배경용으로 따로 잡은 --brass-ink(4.51)를 쓴다. */
body.page-list.tone-light .resultcount strong { color: var(--brass-ink); }
body.page-list.tone-light .resultreset {
  color: rgba(17, 18, 20, .5);
  border-bottom-color: rgba(17, 18, 20, .25);
}
body.page-list.tone-light .resultreset:hover { color: var(--brass-ink); border-color: var(--brass-ink); }
body.page-list.tone-light .savebtn {
  color: var(--ink);
  border-color: rgba(17, 18, 20, .25);
}
body.page-list.tone-light .savebtn:hover { border-color: var(--brass-ink); color: var(--brass-ink); }
body.page-list.tone-light .nearby-prompt h2 { color: var(--ink); }
body.page-list.tone-light .nearby-prompt p { color: rgba(17, 18, 20, .55); }

/* 빈 화면의 출구 버튼. 다크 규칙은 body.page-list 안에만 있어서 목록·찜 목록에서만
   모양이 잡혔는데, 마이페이지 예매 내역의 빈 화면도 같은 버튼을 쓴다. 라이트 쪽은
   스코프 없이 tone-light 아래 두어 어느 화면에서든 같은 모양이 나오게 한다. */
body.tone-light .emptyreset {
  display: inline-block; margin-top: 16px;
  padding: 10px 22px;
  border: 1px solid rgba(17, 18, 20, .16); border-radius: 999px;
  font-size: 13px; font-weight: 600; color: var(--ink);
  transition: border-color .12s, background .12s;
}
body.tone-light .emptyreset:hover { border-color: var(--brass); background: rgba(233, 180, 76, .08); }

/* 페이저. 현재 페이지는 필터 pill 과 같은 언어(잉크 채움)로 둔다. */
body.page-list.tone-light .pager-nav,
body.page-list.tone-light .pager-num {
  border: 0;
  min-width: 36px; height: 36px;
  color: rgba(17, 18, 20, .65);
  font-weight: 700;
}
body.page-list.tone-light .pager-nav:hover,
body.page-list.tone-light .pager-num:hover {
  background: rgba(17, 18, 20, .06);
  color: var(--ink);
}
body.page-list.tone-light .pager-num.active {
  background: var(--ink); color: var(--paper);
}
body.page-list.tone-light .pager-num.active:hover { background: var(--ink); color: var(--paper); }

/* ==================================================================
   공연 상세 — 라이트 톤

   포스터·요약 카드가 있는 왼쪽 sticky 칸과 정보/회차/규정이 흐르는 오른쪽 본문.
   구조는 그대로 두고 명암만 뒤집는다. 이 화면에서 어두운 면은 딱 둘 —
   포스터와 요약 카드다. 예매로 가는 길(최저가 → 회차 선택하기)만 어둡게 묶어서
   아이보리 본문 위에서 그 덩어리가 먼저 눈에 들어오게 한다.
   ================================================================== */

/* 목록과 같은 이유로 헤더를 화면 폭까지 늘린다. 목록은 안쪽 컨테이너를 새로
   넣었지만 여기는 .detail 이 이미 유일한 본문 블록이라 그 역할을 겸한다. */
body.page-detail.tone-light .wrap { max-width: none; margin: 0; padding: 0; }
body.page-detail.tone-light .masthead { margin: 0; padding: 22px 48px; }
body.page-detail.tone-light .detail {
  max-width: 1160px;
  margin: 0 auto;
  padding: 44px 48px 100px;
  gap: 56px;
}
/* 목업은 좌측 칸을 top:44px 로 붙이는데, 그건 목업 헤더가 고정이 아니라서다.
   이 프로젝트 헤더는 sticky(높이 63px)라 44px 로 두면 포스터 윗부분이 헤더 밑으로
   말려 들어간다. 헤더 높이 + 목업의 44px 만큼 내린다. */
body.page-detail.tone-light .detail > div:first-child { top: 88px; }

/* 포스터. 다크에서는 배경과 안 붙게 테두리·바깥 그림자·가장자리 inset 을 겹쳐 줬는데,
   아이보리 위에서는 셋 다 때가 탄 것처럼 보인다. 모서리만 남긴다. */
body.page-detail.tone-light .poster {
  border: 0;
  border-radius: 10px;
  box-shadow: none;
  background: var(--ink);
}
body.page-detail.tone-light .poster::after { display: none; }
/* 배지는 어두운 포스터 위에 얹히므로 목록 카드와 같은 규칙(브라스 글자)으로 맞춘다.
   .poster .card-badge 는 선택자가 하나 더 길어 body.tone-light .card-badge 를 이기므로
   여기서 다시 적어야 한다. */
body.page-detail.tone-light .poster .card-badge {
  background: rgba(17, 18, 20, .75);
  color: var(--brass);
  border: 0;
  font-weight: 700;
}

/* 요약 카드. 다크 화면에서는 어두운 면 위 어두운 면이라 테두리로 구분했고 CTA 도
   외곽선이었다. 라이트에서는 카드 자체가 유일한 어두운 덩어리라 구분이 이미 되고,
   그 안에서는 브라스 채움 CTA 가 화면 전체의 단일 주요 액션으로 읽힌다. */
body.page-detail.tone-light .summarycard {
  border: 0;
  border-radius: 14px;
  padding: 22px;
  background: var(--ink);
  color: var(--paper);
}
body.page-detail.tone-light .sc-row + .sc-row { margin-top: 14px; }
body.page-detail.tone-light .sc-label { font-size: 12.5px; font-weight: 600; color: rgba(248, 244, 236, .55); }
body.page-detail.tone-light .sc-price { font-size: 20px; color: var(--brass); }
body.page-detail.tone-light .sc-next { font-size: 14px; font-weight: 700; color: var(--paper); }
body.page-detail.tone-light .sc-cta {
  margin-top: 18px;
  padding: 13px;
  border: 0; border-radius: 999px;
  background: var(--brass); color: var(--ink);
  font-size: 14.5px; font-weight: 800;
  transition: background .2s, transform .2s;
}
body.page-detail.tone-light .sc-cta:hover {
  background: #f2c874; color: var(--ink);
  transform: scale(1.02);
}

/* 찜·공유. 둘이 한 줄을 반씩 나눠 갖는다 — 성격이 같은 보조 액션이라 크기도 같아야
   하고, 폭이 고정되면 위 CTA 의 알약과 아래에서 한 덩어리로 읽힌다. */
body.page-detail.tone-light .pf-actions { gap: 10px; margin: 18px 0 0; flex-wrap: nowrap; }
body.page-detail.tone-light .pf-actions button {
  flex: 1;
  justify-content: center;
  padding: 11px;
  border-color: rgba(17, 18, 20, .16);
  color: var(--ink);
  font-size: 13.5px; font-weight: 700;
}
body.page-detail.tone-light .pf-actions button:hover {
  border-color: var(--brass-ink); color: var(--brass-ink);
}

body.page-detail.tone-light h2 {
  font-size: 34px;
  letter-spacing: -.02em;
  color: var(--ink);
  margin: 0 0 32px;
}

/* 섹션 머리("공연 정보"/"공연 소개"/"회차 선택"/"취소·환불 규정"). 본문보다 작고
   흐리되 자간을 벌려 라벨로 읽히게 한다 — 본문 제목이 아니라 구획 표시다. */
body.tone-light .detail-heading {
  font-size: 13px; font-weight: 700;
  letter-spacing: .06em;
  color: rgba(17, 18, 20, .45);
  margin: 0 0 16px;
}
/* 위 단축 margin 이 .refund-heading 의 margin-top(56px)을 0 으로 되돌린다 — 다크 톤에서
   같은 실수를 하고 남긴 경고(.detail-heading 뒤에 와야 한다)가 라이트 톤을 만들면서
   그대로 재현됐다. 같은 특이도라 순서로 결정되므로 여기서 다시 준다.
   안 주면 회차 목록 바로 밑에 "취소·환불 규정"이 붙어 같은 구획처럼 읽힌다. */
body.tone-light .refund-heading { margin-top: 56px; }

body.page-detail.tone-light .spec { border-top: 0; margin: 0 0 40px; }
body.page-detail.tone-light .spec div {
  padding: 13px 0;
  border-bottom: 1px solid rgba(17, 18, 20, .08);
}
/* 마지막 행의 밑줄은 뺀다. 규정 목록도 같은 처리를 하는데, 줄이 하나 남으면
   목록이 끝난 게 아니라 뒤가 잘린 것처럼 보인다. */
body.page-detail.tone-light .spec div:last-child { border-bottom: 0; }
body.page-detail.tone-light .spec dt {
  width: 100px;
  font-size: 14px; color: rgba(17, 18, 20, .5);
}
body.page-detail.tone-light .spec dd { font-size: 14.5px; font-weight: 700; color: var(--ink); }

body.tone-light .map-link { color: var(--brass-ink); border-bottom-color: var(--brass-ink); }
body.tone-light .venue-map { border-color: rgba(17, 18, 20, .12); }

body.page-detail.tone-light .description {
  font-size: 15px; line-height: 1.75;
  color: rgba(17, 18, 20, .75);
  margin: 0 0 44px;
}

/* 회차. 날짜 칸을 96px → 64px 로 줄인다 — "8/7 (금)" 이면 충분하고, 남은 폭은
   시각 알약이 한 줄에 더 들어가는 데 쓴다. */
body.page-detail.tone-light .scheduledays { border-top: 0; margin-bottom: 44px; }
body.page-detail.tone-light .scheduleday {
  align-items: flex-start;
  gap: 24px;
  border-bottom: 1px solid rgba(17, 18, 20, .08);
}
body.page-detail.tone-light .scheduleday:last-child { border-bottom: 0; }
/* 목업은 64px 인데 그건 예시가 "8/7" 한 자리 날짜뿐이라서다. 실제로는 8/20 처럼
   두 자리 날이 오면 "(목)" 이 다음 줄로 접혀 그 행만 높이가 커진다. 가장 긴 조합
   ("12/28 (월)")이 한 줄에 들어가는 폭으로 잡고 줄바꿈 자체를 막는다. */
body.page-detail.tone-light .sd-date { width: 78px; white-space: nowrap; }
body.page-detail.tone-light .sd-day { font-size: 16px; font-weight: 800; color: var(--ink); }
body.page-detail.tone-light .sd-dow { font-size: 13px; color: rgba(17, 18, 20, .45); }
body.page-detail.tone-light .scheduleday.soldout .sd-day,
body.page-detail.tone-light .scheduleday.soldout .sd-dow { color: rgba(17, 18, 20, .35); opacity: 1; }

body.page-detail.tone-light .sd-times { gap: 10px; }
body.page-detail.tone-light .sd-times .sd-chip {
  border-color: rgba(17, 18, 20, .16);
  border-radius: 999px;
  color: var(--ink);
  padding: 9px 16px;
  font-size: 13.5px; font-weight: 700;
}
body.page-detail.tone-light .sd-times a.sd-chip:hover {
  border-color: var(--brass); color: var(--ink);
  background: rgba(233, 180, 76, .08);
}
body.page-detail.tone-light .sd-remain { color: rgba(17, 18, 20, .4); font-weight: 600; }
body.page-detail.tone-light .sd-times a.sd-chip:hover .sd-remain { color: var(--brass-ink); }
/* 알림 구독 버튼. 매진임을 먼저 알리는 취소선은 유지하되(있었지만 지금은 못 사는 회차),
   완전한 비활성이 아니라 실제 액션이 있으므로 hover 로 반응을 보여준다. */
body.page-detail.tone-light .sd-times .restock-btn {
  border-color: rgba(17, 18, 20, .1);
  color: rgba(17, 18, 20, .35);
  text-decoration: line-through;
}
body.page-detail.tone-light .sd-times .restock-btn .sd-remain { color: inherit; }
body.page-detail.tone-light .sd-times .restock-btn:hover {
  border-color: var(--brass); color: rgba(17, 18, 20, .55);
}
/* 구독 중은 취소선을 뗀다 — "못 산다"보다 "알림을 신청해 뒀다"는 사실이 우선이다. */
body.page-detail.tone-light .sd-times .restock-btn[aria-pressed="true"] {
  border-color: var(--brass); color: var(--brass-ink);
  background: rgba(233, 180, 76, .1);
  text-decoration: none;
}

body.page-detail.tone-light .empty-inline { color: rgba(17, 18, 20, .5); }

/* 취소·환불 규정은 결제 화면과 공용(스코프 없는 .refundpolicy)이라 tone-light 에 둔다.
   결제가 라이트로 넘어올 때 그대로 따라온다. */
body.tone-light .refundpolicy { border-top: 0; }
body.tone-light .refundpolicy li {
  padding: 11px 0;
  border-bottom: 1px solid rgba(17, 18, 20, .08);
  font-size: 13.5px;
}
body.tone-light .refundpolicy li:last-child { border-bottom: 0; }
body.tone-light .rp-when { color: rgba(17, 18, 20, .6); }
body.tone-light .rp-fee { color: var(--ink); font-weight: 700; }
body.tone-light .refundpolicy .rp-deny .rp-fee { color: var(--rose); }
body.tone-light .refundnote { font-size: 13px; color: rgba(17, 18, 20, .5); }

/* ---- 관람평 ----
   색은 전부 아이보리(#F8F4EC) 위에서 실측해 골랐다. 보조 텍스트를 .55(4.04)로 두는
   앱 관행보다 한 단계 진한 .6(4.75)을 쓰는 곳이 있는데, 읽으라고 띄운 문장이라
   본문 기준(4.5)을 넘겨야 해서다. */
body.tone-light .sc-rating { display: flex; align-items: baseline; justify-content: space-between; }
body.tone-light .sc-rating .sc-star { color: var(--brass-ink); }
body.tone-light .sc-rating .sc-count { font-weight: 600; color: rgba(17, 18, 20, .6); }

body.tone-light .reviewerror {
  margin: 0 0 16px; padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: rgba(178, 58, 58, .07);
  font-size: 14px; color: #B23A3A;
}

body.tone-light .reviewform {
  margin: 0 0 28px; padding: 18px;
  border: 1px solid rgba(17, 18, 20, .12);
  border-radius: var(--radius);
  background: #fff;
}
body.tone-light .rf-head { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
body.tone-light .rf-rating select {
  padding: 8px 12px;
  border: 1px solid rgba(17, 18, 20, .16); border-radius: 999px;
  background: none; font: inherit; font-size: 14px; color: var(--ink);
  cursor: pointer;
}
body.tone-light .rf-note { font-size: 13px; color: rgba(17, 18, 20, .6); }
body.tone-light .reviewform textarea {
  width: 100%; box-sizing: border-box;
  padding: 12px 14px;
  border: 1px solid rgba(17, 18, 20, .16); border-radius: var(--radius-sm);
  font: inherit; font-size: 14.5px; line-height: 1.6; color: var(--ink);
  resize: vertical;
}
body.tone-light .reviewform textarea:focus { outline: 2px solid var(--brass-ink); outline-offset: 1px; }
body.tone-light .reviewform button {
  margin-top: 12px; padding: 10px 20px;
  border: 0; border-radius: 999px;
  background: var(--ink); color: var(--paper);
  font: inherit; font-size: 14px; font-weight: 700;
  cursor: pointer; transition: background .15s, transform .15s;
}
body.tone-light .reviewform button:hover { background: #292a2d; }
body.tone-light .reviewform button:active { transform: scale(.96); }

body.tone-light .reviewlist { list-style: none; margin: 0; padding: 0; }
body.tone-light .reviewlist li {
  padding: 18px 0;
  border-top: 1px solid rgba(17, 18, 20, .1);
}
body.tone-light .reviewlist li:first-child { border-top: 0; padding-top: 0; }
body.tone-light .rv-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
body.tone-light .rv-head form { margin: 0 0 0 auto; }
body.tone-light .rv-stars { color: var(--brass-ink); font-size: 14px; letter-spacing: 1px; }
body.tone-light .rv-writer { font-size: 14px; font-weight: 700; color: var(--ink); }
body.tone-light .rv-date { font-size: 13px; color: rgba(17, 18, 20, .5); }
body.tone-light .rv-delete {
  background: none; border: 0; padding: 0;
  font: inherit; font-size: 13px; color: rgba(17, 18, 20, .5);
  cursor: pointer; transition: color .15s;
}
body.tone-light .rv-delete:hover { color: #B23A3A; }
/* 줄바꿈을 살린다 — 관람평은 문단을 나눠 쓰는 사람이 많은데 그걸 한 덩어리로 흘리면
   쓴 사람 의도와 다르게 읽힌다. break-word 는 띄어쓰기 없는 긴 문자열 대비다. */
body.tone-light .rv-content {
  margin: 0;
  font-size: 14.5px; line-height: 1.7; color: rgba(17, 18, 20, .8);
  white-space: pre-wrap; overflow-wrap: break-word;
}
body.tone-light .reviewempty { margin: 0; font-size: 14px; color: rgba(17, 18, 20, .6); }

/* 공유 시트. 라이트 배경 위에 뜨는 창이라 면도 밝게 간다.

   **면만 밝히고 글자를 안 옮겨서 시트가 통째로 안 보이던 걸 고쳤다.** 기본 규칙은 어두운
   면을 전제로 글자를 --paper(아이보리)로 두는데, 여기서 배경까지 --paper 로 바꾸니
   아이보리 위 아이보리 — 대비 1.0 이 됐다. 열어도 제목("공유하기")도 채널 이름도 없고
   아이콘 두 개만 떠 있는 창이 나온다. 아이콘은 자기 배경색을 갖고 있어 멀쩡히 보이므로
   **화면이 깨진 게 아니라 그냥 비어 보여서** 이상하다는 생각조차 하기 어렵다.
   같은 리디자인에서 --brass 를 글자에 그대로 쓴 것과 같은 종류의 실수다(트러블슈팅 표 참고).

   page-detail 스코프도 뺐다 — 결제 완료 화면도 tone-light 이고 같은 시트를 쓴다. */
body.tone-light .sharesheet {
  background: var(--paper);
  border-color: rgba(17, 18, 20, .12);
  box-shadow: 0 24px 60px rgba(17, 18, 20, .22);
}
body.tone-light .sharesheet-head h3 { color: var(--ink); }
body.tone-light .sharesheet-close { color: rgba(17, 18, 20, .45); }
body.tone-light .sharesheet-close:hover { color: var(--ink); }
body.tone-light .sharesheet-option { color: var(--ink); }
/* 링크복사 아이콘도 어두운 면 전제였다(--ink-raised 채움 + 아이보리 선). 밝은 시트
   위에서는 검은 원이 되어 카카오 옵션보다 훨씬 무겁게 읽힌다 — 둘은 대등한 선택지다. */
body.tone-light .sharesheet-icon-link {
  background: rgba(17, 18, 20, .05);
  border-color: rgba(17, 18, 20, .14);
  color: var(--ink);
}

/* ==================================================================
   좌석 배치도 — 라이트 톤

   이 화면은 톤을 뒤집는 비용이 가장 크다. 다크 시절의 연출이 "어두운 객석 +
   조명 받는 무대" 라는 전제 위에 서 있어서 — 무대의 빛무리, 좌석 hover 의 흰 링,
   등급 팔레트까지 전부 배경이 어둡다는 가정을 깔고 있었다. 아이보리 위에서는
   빛무리가 안 보이고 흰 링이 사라지고 밝은 등급색이 배경에 묻는다.
   그래서 색만 바꾸는 게 아니라 그 셋을 각각 다시 정한다.
   ================================================================== */

/*
   등급 팔레트를 라이트 배경용으로 다시 잡는다.

   :root 의 원래 값은 어두운 배경(--ink) 위에서 3.0 을 넘도록 고른 것이라
   방향이 반대다 — 아이보리 위에서는 VIP 가 1.97 까지 떨어져 좌석이 있는지조차
   흐릿해진다. 색상(hue)은 그대로 두고(골드 → 시에나 → 청록 → 슬레이트) 명도만
   내려서, 배경 대비 3.0 과 "등급이 올라갈수록 밝다"는 단조 관계를 둘 다 지킨다.

   측정(배경 #F8F4EC 기준): VIP 3.34 / R 4.95 / S 6.11 / A 7.97.
   인접 등급 간 비는 1.48 / 1.23 / 1.30 으로 다크 팔레트(1.70 / 1.31 / 1.20)와
   같은 수준이고, 색상이 넷 다 달라서 흑백 출력·색각 이상에서도 순서가 남는다.

   예매 불가 좌석은 다크에서 "배경 쪽으로 어둡게" 물렸던 것을 그대로 뒤집어
   "배경 쪽으로 밝게" 물린다. 판매 완료가 결제 대기보다 더 물러나는 순서도 같다.
*/
body.tone-light {
  --grade-vip: #A97F16;
  --grade-r:   #9E5732;
  --grade-s:   #3D6076;
  --grade-a:   #4A4A55;
  --seat-held: rgba(17, 18, 20, .26);
  --seat-sold: rgba(17, 18, 20, .12);
}

body.page-seatmap.tone-light .masthead { padding: 20px 40px; margin-bottom: 0; }
/* 좌석도 헤더의 부제는 "공연명 · 8/10(월) 11:00" 이라 어느 회차를 고르는 중인지
   알려주는 유일한 표시다. 다른 화면과 달리 감추면 안 된다. */
body.page-seatmap.tone-light .masthead .tag {
  display: inline;
  color: var(--ink);
  font-size: 15px; font-weight: 700;
}

/* 인원수 + 연석 찾기 줄. 헤더 바로 밑에 붙는 두 번째 sticky 바라 헤더와 같은
   반투명 아이보리로 둔다 — 다른 색이면 두 바가 층져 보인다. */
body.page-seatmap.tone-light .partysize {
  background: rgba(248, 244, 236, .86);
  border-bottom: 1px solid rgba(17, 18, 20, .08);
  color: rgba(17, 18, 20, .55);
}
body.page-seatmap.tone-light .partysize label { color: var(--ink); }
body.page-seatmap.tone-light .partysize select {
  background: #fff; color: var(--ink);
  border-color: rgba(17, 18, 20, .16);
}
body.page-seatmap.tone-light .partysize .hint { color: rgba(17, 18, 20, .5); }

body.page-seatmap.tone-light .zoomtoggle {
  background: none; color: var(--ink);
  border-color: rgba(17, 18, 20, .18);
  font-weight: 700;
}
body.page-seatmap.tone-light .zoomtoggle:hover { border-color: var(--brass-ink); }
body.page-seatmap.tone-light .zoomtoggle[aria-pressed="true"] {
  background: var(--ink); color: var(--paper);
  border-color: var(--ink);
}
/* 좌석이 누르기엔 작은 홀에서 확대를 주요 액션으로 올리는 상태. 브라스 채움이라
   라이트에서도 그대로 읽히지만, 어두운 배경을 전제로 깔았던 글로우는 뺀다. */
body.page-seatmap.tone-light .zoomtoggle--suggest {
  background: var(--brass); border-color: var(--brass); color: var(--ink);
  box-shadow: none;
}

/*
   무대. 다크에서는 브라스 빛무리와 아래로 퍼지는 조명 빔으로 그렸는데, 빛은 어두운
   배경이 있어야 빛으로 보인다 — 아이보리 위에서는 누런 얼룩이 된다. 목업대로
   어두운 알약 한 줄로 바꾸고 빔(::after)은 없앤다. 이제 무대는 "빛나는 것" 이 아니라
   "객석이 향하는 기준선" 으로 읽힌다.
*/
body.page-seatmap.tone-light .stage {
  width: 70%;
  max-width: 520px;
  height: auto;
  margin: 8px auto 40px;
  padding: 12px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  box-shadow: none;
  color: var(--brass);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .14em;
  padding-left: calc(12px + .14em);
}
body.page-seatmap.tone-light .stage::after { display: none; }
/*
   위 라이트 톤 규칙이 margin 을 통째로 지정하는 바람에 `.zoneline + .stage` 의 위쪽 여백이
   묻힌다(선택자가 더 구체적이라 이긴다). 그래서 같은 값을 여기서 다시 준다 — 앞쪽에만
   적어두면 화면에서는 8px 그대로다. 오늘 줄번호 배경(.rowlabel)에서 똑같이 겪었다.

   무대 뒤 밴드가 있을 때만 키운다. 무대가 맨 앞에 오는 홀(뒤 밴드 없음)에서는 위가
   비어 있으므로 여백을 더 주면 지도 첫머리가 그만큼 빈다.
*/
body.page-seatmap.tone-light .zoneline + .stage { margin-top: calc(var(--seat-size) * 2); }

/* 층 머릿글 · 구역 이름 */
body.page-seatmap.tone-light .bandname { color: var(--ink); }
body.page-seatmap.tone-light .bandname em { color: rgba(17, 18, 20, .5); }
body.page-seatmap.tone-light .bandname em b { color: var(--brass-ink); }
body.page-seatmap.tone-light .floorname { color: rgba(17, 18, 20, .45); }

/* 층 바로가기 */
/* 배경색은 슬롯의 변수로 정한다(위 .floornav-slot 주석 참고) — 알약과 넘침
   그라데이션이 같은 값을 쓰게 하려는 것이다. */
body.page-seatmap.tone-light .floornav-slot { --floornav-bg: #fff; }
body.page-seatmap.tone-light .floornav {
  border-color: rgba(17, 18, 20, .12);
}
/* 옆으로 붙었을 때는 좌석 위에 뜨므로, 뒤가 비쳐 글자가 묻히지 않게 흰 배경을 유지하고
   그림자로 띄운다(다크 톤의 반투명 검정 대신). */
body.page-seatmap.tone-light .floornav.is-docked {
  background: rgba(255, 255, 255, .92);
  border-color: rgba(17, 18, 20, .16);
  box-shadow: 0 6px 24px rgba(17, 18, 20, .12);
}
body.page-seatmap.tone-light .floorchip { color: rgba(17, 18, 20, .7); }
body.page-seatmap.tone-light .floorchip:hover { background: rgba(17, 18, 20, .06); }
body.page-seatmap.tone-light .floorchip.active,
body.page-seatmap.tone-light .floorchip[aria-current="true"] {
  background: var(--ink); color: var(--paper);
}
body.page-seatmap.tone-light .floorchip b { color: var(--brass-ink); font-weight: 700; }
body.page-seatmap.tone-light .floorchip i { color: rgba(17, 18, 20, .45); }
body.page-seatmap.tone-light .floorchip.active b,
body.page-seatmap.tone-light .floorchip[aria-current="true"] b { color: var(--brass); }
body.page-seatmap.tone-light .floorchip.active i,
body.page-seatmap.tone-light .floorchip[aria-current="true"] i { color: rgba(248, 244, 236, .55); }

/* 행 번호. 가로 스크롤 때 좌석이 뒤로 지나가는 걸 가리는 배경이라 페이지 배경과
   같은 색이어야 한다 — --ink 로 두면 라이트 화면에 검은 띠가 세로로 남는다.
   글자색은 --paper(#F8F4EC) 위에서 대비 4.5:1(WCAG AA 본문 기준)을 넘도록 잡는다 —
   .4 는 실측 대비가 약 2.7:1 이라, 확대 지도 모드에서 몇 번째 줄인지 읽기 어려웠다. */
body.page-seatmap.tone-light .row .rowlabel {
  background: var(--paper);
  color: rgba(17, 18, 20, .65);
}
/* 라벨이 고정되지 않는 홀에서는 배경을 없앤다 — 이유는 `.housescroll.has-sides .rowlabel`
   주석 참고(부채꼴에서 옆 줄 좌석을 덮어 지우고 클릭도 막는다). 이 규칙은 바로 위
   라이트 톤 규칙보다 뒤에 있고 선택자도 한 단계 더 구체적이어야 이긴다 — 앞쪽 규칙에만
   적어두면 배경이 그대로 남는다(실제로 그렇게 놓쳤다가 실측에서 잡았다). */
body.page-seatmap.tone-light .housescroll.has-sides .row .rowlabel {
  background: none;
}

/*
   좌석 hover. 다크에서는 아이보리 링으로 들어올렸는데 배경이 아이보리가 되면서
   링이 배경에 그대로 묻는다. 잉크 링으로 뒤집고, 목업이 쓰는 확대(scale)를 같이 준다 —
   호 오프셋(--arc-y)을 유지한 채로 겹쳐야 hover 때 좌석이 평평한 자리로 안 튄다.
*/
body.page-seatmap.tone-light .seat:hover:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(var(--arc-y)) scale(1.18);
  box-shadow: 0 0 0 max(2px, calc(var(--seat-size) * 0.1)) rgba(17, 18, 20, .35);
}
/* 브라스 아웃라인은 아이보리 위에서 대비 1.73 이라 초점이 어디 있는지 안 보인다. */
body.page-seatmap.tone-light .seat:focus-visible { outline-color: var(--ink); }

/*
   고른 좌석. 다크에서는 아이보리 채움 + 브라스 링이었는데 라이트에서는 채움이
   배경과 같은 색이 되고 링마저 1.73 이라 고른 좌석이 통째로 사라진다.
   목업대로 아이보리 채움 + 잉크 안쪽 링으로 바꾼다 — 안쪽 링이라 좌석 자리를
   넘지 않아서, 좌석이 11px 까지 작아지는 홀에서도 옆 좌석과 안 겹친다.
*/
body.page-seatmap.tone-light .seat.selected {
  --seat-fill: var(--paper);
  box-shadow: inset 0 0 0 max(2px, calc(var(--seat-size) * 0.14)) var(--ink);
}
body.page-seatmap.tone-light .seat.selected:hover {
  box-shadow: inset 0 0 0 max(2px, calc(var(--seat-size) * 0.14)) var(--ink);
}

/* 툴팁은 어두운 판 그대로 둔다 — 라이트 배경 위에 뜨는 작은 말풍선은 어두워야
   본문과 안 섞인다. 테두리만 배경 대비가 나오는 색으로 바꾼다. */
body.page-seatmap.tone-light .seat::after { border-color: var(--ink); }
body.page-seatmap.tone-light .seat::before { border-top-color: var(--ink); }

/* 미니맵. 확대 모드에서 홀 전체를 보여주는 패널이라 본문 위에 떠 있다. */
body.page-seatmap.tone-light .minimap {
  background: rgba(248, 244, 236, .92);
  border-color: rgba(17, 18, 20, .16);
  box-shadow: 0 10px 30px rgba(17, 18, 20, .18);
}
/* 등급 블록 위 글자. 라이트 팔레트는 넷 다 어두운 쪽이라 R 도 아이보리 글자가 맞는다
   (잉크로 두면 3.47 로 4.5 미달). VIP 만 아직 밝아서 잉크가 낫다(5.16). */
body.page-seatmap.tone-light .minimap-zone.grade-R { color: var(--paper); }
/* 지금 보고 있는 범위 표시. 검은 테두리 글로우는 밝은 패널에서 때처럼 보인다. */
body.page-seatmap.tone-light .minimap-view {
  border-color: var(--ink);
  background: rgba(17, 18, 20, .12);
  box-shadow: 0 0 0 1px rgba(248, 244, 236, .8);
}

/* 좌석 상태 범례(본문 맨 아래). 예매 바 안의 가격표와 달리 이건 라이트 배경 위다. */
body.page-seatmap.tone-light .seatstatus .legend-row { color: rgba(17, 18, 20, .6); font-weight: 600; }

/*
   예매 바는 목업대로 어두운 채로 둔다.

   그래서 문제가 하나 생긴다 — 바 안의 가격표 견본(.legend-grade)은 좌석과 같은
   --grade-* 를 써야 하는데(견본과 좌석이 다른 규칙으로 그려지면 범례를 보고 지도를
   못 읽는다), 위에서 그 팔레트를 라이트 배경용으로 어둡게 내렸다. 어두운 바 위의
   어두운 견본은 S 2.82 / A 2.16 으로 잘 안 보인다.
   색을 갈라 두 벌로 만드는 대신 견본에 얇은 링을 둘러 어느 면 위에서든 윤곽이
   남게 한다 — 색 정보는 그대로 두고 가시성만 보탠다.
*/
body.page-seatmap.tone-light .bookbar .legend-grade {
  box-shadow: 0 0 0 1px rgba(248, 244, 236, .45);
}
/* 예매 CTA 는 목업의 알약 모양으로. 바가 어두우니 브라스 채움은 그대로 읽힌다. */
body.page-seatmap.tone-light .bookbar .inner button {
  border-radius: 999px;
  padding: 12px 26px;
  font-size: 14.5px; font-weight: 800;
  transition: transform .15s;
}
body.page-seatmap.tone-light .bookbar .inner button:not(:disabled):hover { transform: scale(1.03); }

@media (max-width: 860px) {
  /* 부제(.tag, "공연명 · 8/19(수) 19:00")가 길어서 좁은 화면에서는 로고("객석")까지
     밀려 h1 이 눌린다 — flex 아이템 기본 min-width:auto 때문에 두 글자가 "객"/"석"으로
     줄바꿈됐다. 로고는 한 줄로 지키고, 부제를 자기 줄로 내린다(다른 페이지의
     .masthead .authnav 줄바꿈과 같은 방식). */
  body.page-seatmap.tone-light .masthead { padding: 14px 20px; flex-wrap: wrap; row-gap: 4px; }
  body.page-seatmap.tone-light .masthead h1 { white-space: nowrap; }
  body.page-seatmap.tone-light .masthead .tag { flex-basis: 100%; }

  /*
     인원수 줄을 두 줄로 나눈다. 한 줄로 밀어넣으면 다섯 요소가 전부 내용보다 눌려서
     제각각 세로로 늘어나고(실측 375px: 라벨 62px, 안내 58px, 버튼 70·89px), 가장 큰
     요소가 바 높이를 정해 110px 를 먹는다. 이 바는 sticky 라 그만큼이 좌석도에서
     영구히 빠지는 높이다 — 좁은 화면에서 좌석이 안 보이던 원인의 하나였다.

     안내문만 order 로 맨 뒤로 보내 자기 줄을 통째로 쓰게 한다. 문장이라 한 줄로 읽혀야
     하고, 조작 요소(라벨·선택·버튼 둘)는 합쳐도 375px 안에 들어간다.
  */
  body.page-seatmap.tone-light .partysize { flex-wrap: wrap; row-gap: 8px; }
  body.page-seatmap.tone-light .partysize .hint { order: 9; flex-basis: 100%; text-align: center; }
}

/* ==================================================================
   마이페이지(예매 내역 · 찜한 공연) — 라이트 톤

   핸드오프는 예매 내역만 다루지만 찜한 공연도 같이 넘긴다. 둘은 같은 상단 탭으로
   오가는 한 화면의 두 면이라, 한쪽만 바꾸면 **탭을 누르는 순간 테마가 뒤집힌다** —
   앱 안에서 톤이 갈리는 자리 중 가장 나쁜 자리다. 찜한 공연은 목록과 같은
   .card/.grid 를 쓰므로 위 공통 기반이 대부분 처리하고, 여기서는 탭만 맞추면 된다.
   ================================================================== */
body.page-mypage.tone-light .wrap { max-width: none; margin: 0; padding: 0; }
/* 목록·상세와 같은 22px. 종전엔 20px 이라 마이페이지로 오갈 때 sticky 헤더가 4px 씩
   튀었다 — 내비를 끄고 부제만 두던 시절엔 안 보였지만, 헤더가 다른 화면과 같아진
   지금은 그 차이가 그대로 드러난다. */
body.page-mypage.tone-light .masthead { margin: 0; padding: 22px 48px; }
/* 부제를 아예 안 그린다. 종전에는 내비를 끈 자리에 "마이페이지"만 오른쪽에 조용히
   뒀는데, 내비를 켜면서 둘 다 오른쪽(margin-left: auto)을 잡으려 해 나란히 붙는다.
   게다가 바로 아래 탭 줄이 어느 면인지를 더 크게 말해주므로 남길 이유가 없다. */
body.page-mypage.tone-light .masthead .tag { display: none; }
body.page-mypage.tone-light .mypageinner {
  max-width: 880px;
  margin: 0 auto;
  padding: 32px 24px 80px;
}

/*
   상단 탭을 알약에서 밑줄로 바꾼다. 알약은 이 화면에서 필터 pill(목록)·상태 배지와
   같은 모양이라 "누르면 화면이 바뀌는 탭"으로 안 읽혔다. 밑줄 탭은 그 셋 어디와도
   안 겹치고, 활성 표시가 글자 바로 밑에 붙어 어느 면을 보고 있는지가 분명하다.
*/
body.page-mypage.tone-light .mypagetabs {
  gap: 28px;
  border-bottom: 1px solid rgba(17, 18, 20, .1);
  margin-bottom: 28px;
}
body.page-mypage.tone-light .mypagetabs a {
  padding: 12px 2px;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: none;
  font-size: 15px; font-weight: 700;
  color: rgba(17, 18, 20, .4);
  transition: color .15s, border-color .15s;
}
body.page-mypage.tone-light .mypagetabs a:hover { color: var(--ink); }
body.page-mypage.tone-light .mypagetabs a.active {
  color: var(--ink);
  font-weight: 800;
  border-bottom-color: var(--ink);
  background: none;
}

/* 안 읽은 알림 수. 브라스가 아니라 로즈를 쓰는 건 "확인할 게 남았다"를 말하는 자리라
   장식색(브라스)과 구분해야 해서다. #B23A3A 는 흰 배지 글씨 대비 5.9 로 읽힌다. */
.mypagetabs .tabbadge {
  display: inline-block;
  min-width: 18px;
  margin-left: 5px;
  padding: 0 5px;
  border-radius: 999px;
  background: #B23A3A;
  color: #fff;
  font-size: 11px; font-weight: 700; line-height: 18px;
  text-align: center;
  vertical-align: 1px;
}

/* ---- 알림 목록 ---- */
body.page-mypage.tone-light .notilist { display: flex; flex-direction: column; gap: 10px; }
body.page-mypage.tone-light .notirow {
  display: flex; align-items: center; gap: 14px;
  padding: 18px 20px;
  border: 1px solid rgba(17, 18, 20, .1);
  border-radius: 14px;
  background: #fff;
  transition: border-color .2s, transform .2s;
}
body.page-mypage.tone-light .notirow:hover {
  border-color: rgba(17, 18, 20, .28);
  transform: translateY(-1px);
}
/* 안 읽음은 왼쪽 굵은 선 + 점으로 표시한다. 배경색을 칠하는 방식은 카드가 흰색이라
   대비를 얻으려면 색을 꽤 넣어야 하고, 그러면 목록이 알록달록해진다. */
body.page-mypage.tone-light .notirow.unread {
  border-left: 3px solid #B23A3A;
  padding-left: 18px;
}
body.page-mypage.tone-light .notidot {
  flex: none;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #B23A3A;
}
body.page-mypage.tone-light .notibody { flex: 1; min-width: 0; }
body.page-mypage.tone-light .notitext {
  margin: 0 0 4px;
  font-size: 15px; color: var(--ink);
}
body.page-mypage.tone-light .notitext strong { font-weight: 800; }
body.page-mypage.tone-light .notimeta {
  margin: 0;
  font-size: 13px; color: rgba(17, 18, 20, .5);
}
body.page-mypage.tone-light .notidivider { margin: 0 6px; }
body.page-mypage.tone-light .notigo {
  flex: none;
  padding: 8px 16px;
  border: 1px solid var(--ink); border-radius: 999px;
  background: none;
  font-family: inherit;
  font-size: 13px; font-weight: 700; color: var(--ink);
  cursor: pointer;
  transition: background .15s, color .15s;
}
body.page-mypage.tone-light .notirow:hover .notigo,
body.page-mypage.tone-light .notigo:focus-visible { background: var(--ink); color: var(--paper); }

@media (max-width: 640px) {
  body.page-mypage.tone-light .notirow { flex-wrap: wrap; }
  body.page-mypage.tone-light .notigo { margin-left: auto; }
}

/* ---- 저장한 검색 목록 ---- 알림 목록(.notilist/.notirow)과 같은 행 모양을 쓴다 —
   둘 다 "조건 하나 + 행동 하나"인 목록이라 같은 리듬이 자연스럽다. 삭제 버튼은
   예매 내역의 .btn-delete 를 그대로 재사용한다(같은 뜻·같은 자리). */
body.page-mypage.tone-light .savedlist { display: flex; flex-direction: column; gap: 10px; }
body.page-mypage.tone-light .savedrow {
  display: flex; align-items: center; gap: 14px;
  padding: 18px 20px;
  border: 1px solid rgba(17, 18, 20, .1);
  border-radius: 14px;
  background: #fff;
  transition: border-color .2s, transform .2s;
}
body.page-mypage.tone-light .savedrow:hover { border-color: rgba(17, 18, 20, .28); }
body.page-mypage.tone-light .savedinfo { flex: 1; min-width: 0; }
body.page-mypage.tone-light .savedinfo:hover .savedlabel { color: var(--brass-ink); }
body.page-mypage.tone-light .savedlabel {
  margin: 0 0 4px; font-size: 15px; font-weight: 700; color: var(--ink);
  transition: color .12s;
}
body.page-mypage.tone-light .savedmeta { margin: 0; font-size: 13px; color: rgba(17, 18, 20, .5); }

/* ---- 예매 카드 ---- */
body.page-mypage.tone-light .reslist { gap: 14px; }
body.page-mypage.tone-light .resrow {
  gap: 20px;
  padding: 20px;
  border: 1px solid rgba(17, 18, 20, .1);
  border-radius: 14px;
  background: #fff;
  transition: border-color .2s, transform .2s;
}
/* 카드 전체가 눌리는 영역이라(.resinfo) hover 를 카드에 준다. 목록 카드와 같은
   언어(살짝 떠오름)를 쓰되, 여기는 카드에 테두리가 있으니 그 색으로도 알린다. */
body.page-mypage.tone-light .resrow:hover {
  border-color: var(--brass);
  transform: translateY(-2px);
}
body.page-mypage.tone-light .restitle { font-size: 16.5px; font-weight: 800; color: var(--ink); margin-bottom: 8px; }
body.page-mypage.tone-light .resinfo:hover .restitle { color: var(--ink); }
body.page-mypage.tone-light .resinfo:focus-visible { outline-color: var(--ink); }
body.page-mypage.tone-light .resmeta { font-size: 13.5px; color: rgba(17, 18, 20, .55); margin-bottom: 6px; }
body.page-mypage.tone-light .resno { font-size: 12.5px; color: rgba(17, 18, 20, .4); }
body.page-mypage.tone-light .reslink { color: rgba(17, 18, 20, .4); border-color: rgba(17, 18, 20, .16); }
body.page-mypage.tone-light .reslink:hover { color: var(--brass-ink); border-color: var(--brass-ink); }
/* 취소 시 얼마가 남는지. 다른 상태 문구(badge)와 헷갈리지 않게 배지보다 흐리고 작게 둔다 —
   이건 상태가 아니라 "지금 누르면 어떻게 되는지"에 대한 미리보기다. */
body.page-mypage.tone-light .refundhint { font-size: 12px; color: rgba(17, 18, 20, .5); margin: 0; text-align: right; }

/* 액션 줄. 다크에서는 세로로 쌓아 오른쪽 끝에 붙였는데, 카드가 밝아지면서 배지와
   버튼이 한 줄에 나란히 있어도 안 복잡해 보인다(목업도 한 줄이다). */
body.page-mypage.tone-light .resaction { flex-direction: row; align-items: center; gap: 10px; }

/*
   상태 배지. 다크에서는 테두리 + 글자색만 썼는데 라이트에서는 옅은 틴트 채움이
   더 또렷하다. 네 상태를 색으로 가르되 글자색은 전부 대비 4.5 를 넘긴 값을 쓴다 —
   틴트 배경이 거의 흰색에 가까워서 배경이 아니라 페이지 대비로 계산해야 한다.
*/
body.page-mypage.tone-light .badge {
  border: 0;
  padding: 6px 12px;
  font-size: 12px; font-weight: 700; letter-spacing: 0;
  background: rgba(17, 18, 20, .06);
  color: rgba(17, 18, 20, .45);
}
body.page-mypage.tone-light .badge-PENDING {
  background: rgba(233, 180, 76, .18);
  color: var(--brass-ink);
}
body.page-mypage.tone-light .badge-CONFIRMED {
  background: rgba(17, 18, 20, .08);
  color: var(--ink);
}
body.page-mypage.tone-light .badge-CANCELED {
  background: rgba(242, 109, 109, .14);
  color: #B23A3A;
  opacity: 1;
}
body.page-mypage.tone-light .badge-EXPIRED {
  background: rgba(17, 18, 20, .06);
  color: rgba(17, 18, 20, .45);
  opacity: 1;
}

body.page-mypage.tone-light .btn-pay,
body.page-mypage.tone-light .btn-cancel,
body.page-mypage.tone-light .btn-delete {
  border-radius: 999px;
  padding: 9px 16px;
  font-size: 13px;
  /* 핸드오프가 못 박아 둔 값. 없으면 "취소"·"삭제" 두 글자가 세로로 접힌다. */
  white-space: nowrap;
  flex: none;
}
body.page-mypage.tone-light .btn-pay { background: var(--brass); color: var(--ink); font-weight: 800; }
body.page-mypage.tone-light .btn-pay:hover { background: #f2c874; }
body.page-mypage.tone-light .btn-cancel,
body.page-mypage.tone-light .btn-delete {
  border: 1px solid rgba(17, 18, 20, .16);
  font-weight: 700;
}
body.page-mypage.tone-light .btn-cancel { color: var(--ink); }
body.page-mypage.tone-light .btn-cancel:hover { border-color: var(--rose); color: #B23A3A; }
body.page-mypage.tone-light .btn-delete { color: rgba(17, 18, 20, .5); }
body.page-mypage.tone-light .btn-delete:hover { border-color: rgba(17, 18, 20, .4); color: var(--ink); }

/* ---- 예매 요약 팝업 ---- */
body.page-mypage.tone-light .resmodal {
  background: var(--paper);
  border-color: rgba(17, 18, 20, .12);
  box-shadow: 0 24px 60px rgba(17, 18, 20, .22);
}
/*
   닫기 버튼은 포스터 오른쪽 위에 겹쳐 놓인다. 다크에서는 아이콘이 밝은색(--mute)이라
   어떤 포스터 위에서도 보였는데, 라이트에 맞춰 어둡게 바꾸면 어두운 포스터 위에서
   그대로 사라진다(실제로 사라졌다). 포스터 이미지는 공연마다 달라서 아이콘 색만으로는
   어느 쪽으로 골라도 절반은 안 보인다 — 아이콘 뒤에 판을 깔아 배경과 무관하게 만든다.
*/
body.page-mypage.tone-light .resmodal-close {
  width: 28px; height: 28px;
  padding: 0;
  align-items: center; justify-content: center;
  border-radius: 999px;
  background: rgba(248, 244, 236, .92);
  color: var(--ink);
  box-shadow: 0 2px 8px rgba(17, 18, 20, .25);
}
body.page-mypage.tone-light .resmodal-close:hover { background: #fff; color: var(--ink); }
/* 포스터 자리는 어두운 채로 둔다 — 포스터가 곧 이미지라 밝은 판을 깔면 흰 여백이
   포스터의 일부처럼 보인다(목록·상세 카드와 같은 처리). */
body.page-mypage.tone-light .resmodal-poster { border: 0; background: var(--ink); }
body.page-mypage.tone-light .resmodal-body h3 { color: var(--ink); }
body.page-mypage.tone-light .resmodal-spec div { border-top-color: rgba(17, 18, 20, .1); }
body.page-mypage.tone-light .resmodal-spec dt { color: rgba(17, 18, 20, .5); }
body.page-mypage.tone-light .resmodal-spec dd { color: var(--ink); }

/* 「공유」. 이 팝업은 예매 내용을 확인하는 자리라 공유는 부수적인 행동이다 —
   채운 버튼으로 두면 팝업을 여는 순간 눈이 그쪽으로 먼저 간다. 정보 목록과 선으로만
   나누고 폭을 채워서, 찾으면 바로 보이되 먼저 읽히지는 않게 뒀다. */
body.page-mypage.tone-light .resmodal-share {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%;
  margin-top: 4px;
  padding: 12px;
  border: 0;
  border-top: 1px solid rgba(17, 18, 20, .1);
  background: none;
  font-family: inherit;
  font-size: 14px; font-weight: 700;
  color: rgba(17, 18, 20, .55);
  cursor: pointer;
  transition: color .15s, background .15s;
}
body.page-mypage.tone-light .resmodal-share:hover,
body.page-mypage.tone-light .resmodal-share:focus-visible {
  background: rgba(17, 18, 20, .04);
  color: var(--ink);
}

@media (max-width: 720px) {
  /* 좁은 화면에서는 정보 블록 아래로 액션 줄이 내려간다(다크 규칙 그대로).
     그때 배지와 버튼이 양끝으로 갈리도록 폭을 채운다. */
  body.page-mypage.tone-light .resaction { width: 100%; justify-content: space-between; }
}

@media (max-width: 560px) {
  /* 목록·상세와 같은 문제(내비가 줄바꿈 없이 폭을 넘겨 낱말마다 두 줄로 접힘) —
     그 화면들은 .page-list 클래스를 공유해(홈과 카드 CSS 를 나누는 것과 같은 이유로)
     이미 고쳐져 있었는데 마이페이지만 빠져 있었다. 실기동(500px)으로 확인. */
  body.page-mypage.tone-light .masthead { padding: 16px 20px; flex-wrap: wrap; row-gap: 8px; }
  body.page-mypage.tone-light .masthead h1 { white-space: nowrap; }
  /* flex-start 인 이유는 목록·상세와 같다(위 body.page-list .masthead .authnav 주석 참고). */
  body.page-mypage.tone-light .masthead .authnav { margin-left: 0; flex-basis: 100%; justify-content: flex-start; }
  body.page-mypage.tone-light .mypageinner { padding: 24px 20px 60px; }
  body.page-mypage.tone-light .mypagetabs { gap: 20px; }
}

/* ==================================================================
   로그인 · 회원가입 · 비밀번호 재설정 — 라이트 톤

   핸드오프는 로그인·회원가입 둘만 다루지만 비밀번호 재설정 두 화면도 같이 넘긴다.
   같은 부품(.authwrap/.authcard/.authform)을 쓰는 데다 로그인 화면의
   「비밀번호를 잊으셨나요?」 바로 다음 화면이라, 여기만 두면 링크 한 번에 톤이 뒤집힌다.

   카드(면 + 테두리 + 그림자)를 걷어내고 아이보리 위에 폼만 남긴다. 다크에서는
   "폼만 덩그러니 떠 있으면 미완성처럼 보인다"는 이유로 카드를 씌웠는데, 그건
   어두운 배경에서 입력창과 배경이 잘 안 갈리기 때문이었다. 라이트에서는 흰 입력창이
   아이보리 배경에서 저절로 떠올라서 카드가 한 겹 더 있으면 오히려 답답하다.
   ================================================================== */
/* 다른 라이트 화면과 같이 헤더를 화면 폭까지 늘리고, 본문 폭은 .authwrap 이 잡는다.
   헤더만 .wrap(1080px)에 갇혀 있으면 목록·상세에서 넘어오는 순간 구분선 길이가 바뀐다. */
body.page-auth.tone-light .wrap { max-width: none; margin: 0; padding: 0; }
body.page-auth.tone-light .masthead { margin: 0; padding: 22px 48px; }
body.tone-light .authwrap {
  /* 헤더(63px)를 뺀 나머지 높이 안에서 세로 가운데. 종전 200px 은 헤더 + 아래 여백을
     어림한 값이었는데, 헤더가 이 화면에서 로고 한 줄로 얇아져 폼이 위로 붙어 보였다. */
  min-height: calc(100vh - 64px);
  padding: 48px 24px;
}
body.tone-light .authcard {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  box-shadow: none;
}
body.tone-light .authhead { margin-bottom: 32px; }
body.tone-light .authhead h2 { font-size: 28px; color: var(--ink); margin: 0 0 8px; }
body.tone-light .authhead p { font-size: 14px; color: rgba(17, 18, 20, .55); }

body.tone-light .authnotice {
  background: rgba(233, 180, 76, .14);
  border-color: rgba(233, 180, 76, .4);
  color: var(--brass-ink);
}
body.tone-light .autherror {
  background: rgba(242, 109, 109, .10);
  border-color: rgba(242, 109, 109, .35);
  /* --rose 원색은 아이보리 위에서 대비 2.9 라 안내문으로 못 읽는다. 같은 색상에서
     명도만 내려 4.6 을 맞춘 값이다(브라스에 --brass-ink 를 따로 둔 것과 같은 이유). */
  color: #B23A3A;
}

/* 라벨을 값보다 진하게. 다크에서는 라벨이 --mute 였는데, 카드가 없어진 라이트에서는
   라벨이 곧 각 필드의 경계 표시라 또렷해야 한다. */
body.tone-light .authform label { font-size: 13.5px; font-weight: 700; color: var(--ink); }
body.tone-light .authform .hint { font-size: 12.5px; font-weight: 500; color: rgba(17, 18, 20, .45); }
body.tone-light .authform input {
  background: #fff;
  color: var(--ink);
  border: 1px solid rgba(17, 18, 20, .16);
  border-radius: 10px;
  padding: 13px 14px;
}
body.tone-light .authform input::placeholder { color: rgba(17, 18, 20, .4); }
body.tone-light .authform input:hover { border-color: rgba(17, 18, 20, .32); }
body.tone-light .authform input:focus-visible {
  border-color: var(--brass);
  box-shadow: 0 0 0 3px rgba(233, 180, 76, .22);
}

/*
   제출 버튼을 브라스에서 잉크 채움으로 바꾼다(회원가입만 브라스로 남긴다 — 아래).

   이건 종전 문제를 다른 방식으로 푸는 것이기도 하다. 다크에서는 브라스(43°)와 카카오
   노랑(54°)이 색상차 11도에 명암비 1.2 라 색으로 안 갈려서, 크기·굵기로만 위계를
   세워야 했다. 아이보리 배경에서는 잉크가 대비 16.4, 카카오 노랑이 1.15 이라 —
   주요 액션이 색만으로 압도적으로 이긴다. 밝기 다툼이 아니라 배경과의 대비 싸움이
   되면서 그 제약 자체가 사라졌다.
*/
body.tone-light .authform button[type="submit"] {
  background: var(--ink); color: var(--paper);
  border-radius: 999px;
  padding: 15px;
  font-size: 15.5px; font-weight: 800;
  margin-top: 6px;
  transition: background .2s;
}
body.tone-light .authform button[type="submit"]:hover { background: #292a2d; filter: none; }
/* 회원가입만 브라스. 로그인과 같은 셸·같은 폼이라 화면만 봐서는 구별이 잘 안 되는데,
   유일하게 다른 CTA 색이 "여기는 가입하는 곳" 이라고 알려준다. */
body.page-signup.tone-light .authform button[type="submit"] {
  background: var(--brass); color: var(--ink);
}
body.page-signup.tone-light .authform button[type="submit"]:hover { background: #f2c874; }

body.tone-light .pwtoggle { color: rgba(17, 18, 20, .5); font-size: 13px; font-weight: 700; }
body.tone-light .pwtoggle:hover { color: var(--ink); }

body.tone-light .authlink { font-size: 13.5px; color: rgba(17, 18, 20, .55); margin-top: 28px; }
body.tone-light .authlink a { color: var(--brass-ink); }
/* 「비밀번호를 잊으셨나요?」. 가입 유도 링크와 달리 조용해야 하는 보조 출구라
   강조색을 안 쓴다. */
body.tone-light .authlink--tight { margin-top: 16px; }
body.tone-light .authlink--tight a { color: rgba(17, 18, 20, .5); font-weight: 500; }
body.tone-light .authlink--tight a:hover { color: var(--ink); }

body.tone-light .oauthdivider {
  margin: 28px 0;
  font-size: 12.5px; font-weight: 600; color: rgba(17, 18, 20, .4);
}
body.tone-light .oauthdivider::before,
body.tone-light .oauthdivider::after { background: rgba(17, 18, 20, .1); }

/*
   소셜 버튼을 가로 두 칸에서 세로 전폭으로 되돌린다.

   가로로 좁혀 뒀던 건 "면적×밝기" 로 카카오 노랑이 주요 액션을 이기는 걸 막기 위한
   보정이었다. 위 제출 버튼 주석대로 그 전제가 사라졌으므로(잉크 16.4 vs 노랑 1.15)
   보정도 같이 걷는다 — 세로로 두면 버튼마다 브랜드명이 잘리지 않고 다 들어간다.
*/
body.tone-light .oauthbuttons { flex-direction: column; gap: 10px; }
body.tone-light .oauthbtn {
  flex: none;
  gap: 8px;
  padding: 13px;
  font-size: 14.5px; font-weight: 700;
  line-height: 18px;
  /* 카카오 가이드는 모서리 12px 를 적어 두지만, 세로로 겹쳐 놓으면 네이버(10px)와
     2px 차이가 그대로 눈에 띈다. 가이드가 실제로 규정하는 건 배경색·심볼·라벨이고
     그 셋은 그대로 지킨다. */
  border-radius: 10px;
}
body.tone-light .oauthbtn:hover { filter: brightness(.95); }
body.tone-light .oauthbtn-kakao { border-radius: 10px; }
body.tone-light .oauthicon { width: 16px; height: 16px; }

@media (max-width: 560px) {
  body.tone-light .authwrap { padding: 32px 20px; }
}

/* ==================================================================
   결제 · 결제 결과 — 라이트 톤

   핸드오프는 결제 화면 하나만 다루지만 완료·실패·자동환불 세 화면이 같은
   page-payment 스코프와 같은 부품(.orderhead/.payseats/.paytotal/.payno)을 쓴다.
   결제만 라이트로 바꾸면 「결제하기」를 누른 다음 화면에서 톤이 뒤집힌다 —
   결제 직후는 "방금 그게 잘 됐나" 를 확인하는 순간이라 화면이 딴 데로 온 것처럼
   보이면 안 된다. 그래서 넷 다 같이 넘긴다.

   어두운 면은 결제 패널 하나로 몰았다. 상세의 요약 카드와 같은 역할 —
   이 화면에서 끝까지 눈에 있어야 하는 것(남은 시간·총액·결제 버튼)만 그 안에 있다.
   ================================================================== */
body.page-payment.tone-light .wrap { max-width: none; margin: 0; padding: 0; }
body.page-payment.tone-light .masthead { margin: 0; padding: 22px 48px; }
/* 이 화면의 헤더 부제는 "결제" 한 단어라 지금 어느 화면인지 알려준다. 목록·상세처럼
   감추지 않는다(목업도 남겨 둔다). */
body.page-payment.tone-light .masthead .tag {
  display: inline;
  color: rgba(17, 18, 20, .45);
  font-size: 13px; font-weight: 600;
}

/* 진행 단계. 가운데로 모으고, 현재 단계에만 브라스 점을 찍는다 — 글자 굵기만으로는
   세 단계 중 어디인지 한눈에 안 잡힌다. */
body.page-payment.tone-light .paysteps {
  justify-content: center;
  gap: 12px;
  margin: 0;
  padding: 22px 48px 8px;
  font-weight: 700;
  color: rgba(17, 18, 20, .35);
}
body.page-payment.tone-light .paysteps li + li::before { background: rgba(17, 18, 20, .2); }
body.page-payment.tone-light .paysteps .ps-done { color: rgba(17, 18, 20, .35); }
body.page-payment.tone-light .paysteps .ps-current { color: var(--ink); }
body.page-payment.tone-light .paysteps .ps-current::after {
  content: '';
  order: -1;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--brass);
}

body.page-payment.tone-light .paylayout {
  max-width: 1080px;
  margin: 0 auto;
  padding: 28px 48px 100px;
  grid-template-columns: 1.5fr 1fr;
  gap: 56px;
}

/* ---- 주문 정보 (좌측) ---- */
body.page-payment.tone-light .orderhead {
  gap: 22px;
  padding-bottom: 28px;
  border-bottom: 1px solid rgba(17, 18, 20, .1);
  margin-bottom: 28px;
}
body.page-payment.tone-light .poster {
  width: 110px;
  border: 0;
  border-radius: var(--radius-poster);
  background: var(--ink);
}
body.page-payment.tone-light .paytitle { font-size: 24px; color: var(--ink); }
body.page-payment.tone-light .payspec div { gap: 14px; padding: 4px 0; }
body.page-payment.tone-light .payspec dt { width: 44px; color: rgba(17, 18, 20, .5); font-weight: 600; }
body.page-payment.tone-light .payspec dd { color: var(--ink); font-weight: 600; }

body.page-payment.tone-light .payseats { border-top: 0; margin-bottom: 32px; }
body.page-payment.tone-light .payseats li {
  padding: 14px 0;
  border-bottom: 1px solid rgba(17, 18, 20, .08);
  font-size: 14.5px;
}
body.page-payment.tone-light .payseats li:last-child { border-bottom: 0; }
body.page-payment.tone-light .ps-seat { color: var(--ink); font-weight: 700; }
/* 목업은 등급을 알약 없이 브라스 글자로만 둔다. 알약을 걷으면 좌석·등급·가격 세 열이
   같은 무게로 읽혀 목록이 훨씬 조용해진다. 색은 --brass 가 아니라 --brass-ink 를 쓴다 —
   원색은 아이보리 위에서 대비 1.73 이라 등급이 안 읽힌다. */
body.page-payment.tone-light .ps-grade {
  border: 0; padding: 0;
  color: var(--brass-ink);
  font-size: 12.5px; font-weight: 700; letter-spacing: .04em;
}
body.page-payment.tone-light .ps-price { color: var(--ink); font-weight: 700; }
body.page-payment.tone-light .payseats-fallback {
  border-color: rgba(17, 18, 20, .08);
  color: var(--ink);
}

/* ---- 결제 패널 (우측, sticky) ---- */
body.page-payment.tone-light .paypanel {
  top: 100px;
  gap: 22px;
  padding: 28px;
  border: 0;
  border-radius: var(--radius-panel);
  background: var(--ink);
  color: var(--paper);
}
/* 타이머를 감싸던 테두리 상자를 걷는다. 다크 화면에서는 어두운 면 위 어두운 면이라
   상자로 구분해야 했지만, 여기서는 패널 자체가 유일한 어두운 덩어리라 이미 구분된다. */
body.page-payment.tone-light .paytimer {
  grid-template-columns: 1fr;
  grid-template-areas: "label" "clock" "note";
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
}
body.page-payment.tone-light .pt-label { font-size: 12.5px; font-weight: 700; color: rgba(248, 244, 236, .55); }
body.page-payment.tone-light .pt-clock { font-size: 38px; letter-spacing: -.02em; color: var(--brass); }
body.page-payment.tone-light .pt-note { font-size: 12.5px; color: rgba(248, 244, 236, .4); }

/*
   1분 미만.

   종전에는 "색만 바꾸고 깜빡이지는 않는다 — 결제 정보를 입력하는 중에 화면이 깜빡이면
   방해가 된다" 로 두었는데, 실제 결제 입력은 토스 결제창(별도 오버레이)에서 이뤄져서
   이 타이머가 입력을 방해할 자리에 있지 않다. 마지막 1분을 색 변화만으로 알리는 건
   화면을 안 보고 있던 사용자에게는 전달이 안 된다. 핸드오프대로 맥박을 넣는다.
   모션을 끈 사용자에게는 아래 prefers-reduced-motion 규칙이 색만 남긴다.
*/
@keyframes pay-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .45; }
}
body.page-payment.tone-light .paytimer--urgent { border: 0; background: none; }
body.page-payment.tone-light .paytimer--urgent .pt-clock {
  color: var(--rose);
  animation: pay-pulse 1s ease-in-out infinite;
}
body.page-payment.tone-light .paytimer--dead { border: 0; background: none; }
body.page-payment.tone-light .paytimer--dead .pt-clock { color: rgba(248, 244, 236, .55); font-size: 24px; }

body.page-payment.tone-light .paytotal {
  padding: 0;
  border-bottom: 0;
  border-top: 1px solid rgba(248, 244, 236, .12);
}
body.page-payment.tone-light .paytotal div { padding: 22px 0 0; }
body.page-payment.tone-light .paytotal dt { font-size: 14px; font-weight: 600; color: rgba(248, 244, 236, .6); }
/* 총액은 브라스가 아니라 아이보리로 둔다. 브라스는 이 패널에서 남은 시간과 결제
   버튼이 이미 쓰고 있어서, 총액까지 같은 색이면 셋이 서로 강조를 나눠 갖는다. */
body.page-payment.tone-light .paytotal dd { font-size: 26px; color: var(--paper); }

body.page-payment.tone-light .payno { font-size: 12px; color: rgba(248, 244, 236, .35); }

@media (prefers-reduced-motion: reduce) {
  body.page-payment.tone-light .paytimer--urgent .pt-clock { animation: none; }
}

/* ---- 결제 결과 화면(완료 · 실패 · 자동환불) ---- */
/* 결과 화면에서는 이 부품들이 어두운 패널이 아니라 밝은 카드 안에 놓인다 —
   위에서 패널용으로 잡은 어두운 배경 전제(총액 아이보리 등)를 되돌린다. */
body.page-payment.tone-light .paydone {
  background: #fff;
  border: 1px solid rgba(17, 18, 20, .1);
  border-radius: var(--radius-panel);
  box-shadow: 0 18px 44px rgba(17, 18, 20, .10);
}
body.page-payment.tone-light .paydone-icon {
  background: rgba(233, 180, 76, .16);
  border-color: var(--brass-ink);
  color: var(--brass-ink);
}
body.page-payment.tone-light .paydone-icon--fail {
  background: rgba(242, 109, 109, .12);
  border-color: var(--rose);
  color: var(--rose);
}
body.page-payment.tone-light .paydone-lead { color: var(--ink); }
body.page-payment.tone-light .payfail-code,
body.page-payment.tone-light .payfail-reason { color: rgba(17, 18, 20, .55); }
/* 결과 화면에서는 이 블록 위에 헤드라인·사유·오류코드가 먼저 오고, 그 마지막 줄의
   아래 여백이 6px 뿐이라 포스터가 글자에 바싹 붙는다. 여기서 위 간격을 낸다.
   아래쪽 구분선은 뺀다 — 바로 다음이 "좌석" 머리글이라 선이 두 겹으로 보인다. */
body.page-payment.tone-light .paydone .orderhead {
  border-bottom: 0;
  padding-bottom: 0;
  margin-top: 24px;
}
body.page-payment.tone-light .paydone .paytotal {
  border-top: 1px solid rgba(17, 18, 20, .1);
  border-bottom: 1px solid rgba(17, 18, 20, .1);
}
body.page-payment.tone-light .paydone .paytotal div { padding: 12px 0; }
body.page-payment.tone-light .paydone .paytotal dt { color: rgba(17, 18, 20, .5); }
body.page-payment.tone-light .paydone .paytotal dd { color: var(--ink); font-size: 22px; }
body.page-payment.tone-light .paydone .payno { color: rgba(17, 18, 20, .45); }

/* ---- 결제 버튼 ---- */
body.page-payment.tone-light .paybtn {
  border: 0; border-radius: 999px;
  background: var(--brass); color: var(--ink);
  padding: 16px; font-size: 16px; font-weight: 800;
  transition: background .2s, transform .15s;
}
body.page-payment.tone-light .paybtn:not(:disabled):hover {
  background: #f2c874;
  transform: scale(1.015);
}
body.page-payment.tone-light .paybtn:disabled {
  background: rgba(248, 244, 236, .12); color: rgba(248, 244, 236, .45);
}
/* 결제할 수 없는 상태의 이동 링크. 주요 액션이 아니라 테두리만 쓴다 — 어두운 패널
   안이므로 아이보리 계열 테두리다. */
body.page-payment.tone-light .paybtn--ghost {
  background: none; color: var(--paper);
  border: 1px solid rgba(248, 244, 236, .28);
}
body.page-payment.tone-light .paybtn--ghost:hover {
  background: rgba(248, 244, 236, .08);
  border-color: rgba(248, 244, 236, .45);
  transform: none;
}
/* 결과 화면의 버튼들은 밝은 카드 안에 있다. */
body.page-payment.tone-light .paydone .paybtn--ghost {
  color: var(--ink);
  border-color: rgba(17, 18, 20, .18);
}
body.page-payment.tone-light .paydone .paybtn--ghost:hover {
  background: rgba(17, 18, 20, .05);
  border-color: rgba(17, 18, 20, .35);
}

/* ---- 「공유」 ----
   위 두 버튼(예매 내역 · 공연 더 보기)과 같은 줄에 안 두고 아래에 따로 둔다. 저 둘은
   "이제 어디로 갈까"(이동)이고 이건 "누구에게 알릴까"(공유)라 성격이 다르다 — 셋을
   나란히 놓으면 방금 결제를 마친 사람이 세 개 중 뭘 눌러야 하는지 고르게 된다.
   그래서 알약 버튼이 아니라 글자 링크에 가까운 모양으로, 한 단계 낮춰 놓는다. */
body.page-payment.tone-light .paydone-share {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%;
  margin-top: 14px;
  padding: 12px;
  border: 0; border-radius: 999px;
  background: none;
  font-family: inherit;
  font-size: 14px; font-weight: 700;
  color: rgba(17, 18, 20, .5);
  cursor: pointer;
  transition: color .15s, background .15s;
}
body.page-payment.tone-light .paydone-share:hover,
body.page-payment.tone-light .paydone-share:focus-visible {
  background: rgba(17, 18, 20, .04);
  color: var(--ink);
}

@media (max-width: 860px) {
  body.page-payment.tone-light .masthead { padding: 16px 20px; }
  body.page-payment.tone-light .paysteps { padding: 18px 20px 8px; }
  /*
     2단 → 1단 접기와 sticky 해제를 여기서 다시 선언해야 한다. 위쪽 다크 규칙
     (`body.page-payment .paylayout`, `.paypanel`)에 이미 같은 내용이 있지만 **둘 다
     죽어 있었다** — 라이트 톤을 나중에 얹으면서 모바일 대응을 같이 안 옮긴 탓이다.
       · 열 접기: `body.page-payment .paylayout`(클래스 2개)이
         `body.page-payment.tone-light .paylayout`(클래스 3개)에 명시도로 진다.
         미디어 쿼리는 명시도를 올려주지 않으므로 폭과 무관하게 2단이 유지됐다.
       · sticky 해제: `position: static` 이 같은 명시도의 뒤쪽 규칙(2347행 sticky)에 밀렸다.
     그 결과 375px 에서 결제 패널이 x=329~503 으로 화면 밖에 놓여, 타이머·총액은 물론
     「결제하기」 버튼까지 못 누르는 상태였다. `.rowlabel` 배경에서 겪은 것과 같은 함정이다.
  */
  body.page-payment.tone-light .paylayout {
    padding: 24px 20px 72px; gap: 32px;
    grid-template-columns: 1fr;
  }
  body.page-payment.tone-light .paypanel { position: static; }
}

@media (max-width: 860px) {
  body.page-detail.tone-light .masthead { padding: 16px 20px; }
  /* 아래 여백 72 → 132px: 이 폭에서는 하단 고정 예매 바(.mobilebar, 실측 77px)가
     본문 위에 얹히므로, 그만큼 자리를 비워야 마지막 관람평이 바 뒤로 들어가지 않는다.
     바 높이가 내용에 따라 변하지 않아 상수로 둔다(좌석도 .bookbar 는 고른 좌석 수에
     따라 늘어나서 JS 가 --footer-h 로 실측해 넘긴다 — 여기는 그 장치가 필요 없다). */
  body.page-detail.tone-light .detail { padding: 28px 20px 132px; gap: 28px; }
  body.page-detail.tone-light h2 { font-size: 26px; }
  /* 좁은 화면에서는 요약 카드가 숨겨지므로(위 다크 규칙 그대로) 찜·공유가 포스터
     바로 밑에 오는데, 그때는 위 여백이 카드용으로 잡힌 18px 보다 넉넉해야 한다. */
  body.page-detail.tone-light .pf-actions { margin-top: 16px; }
}

@media (max-width: 860px) {
  body.page-list.tone-light .masthead { padding: 16px 20px; }
  body.page-list.tone-light .listinner { padding: 24px 20px 72px; }
  /* 좁은 화면의 필터는 오른쪽에서 밀려 나오는 시트라 배경 위에 떠 있어야 한다 —
     위에서 배경을 없앴으므로 시트로 쓸 때만 다시 면을 깔아준다. */
  body.page-list.tone-light .filterpanel {
    background: var(--paper);
    padding: 24px 20px;
  }
}

/* ==================================================================
   터치 기기 입력창 최소 글자 크기 (iOS Safari 자동 확대 방지)

   iOS Safari 는 글자 크기가 16px 미만인 입력창에 포커스가 가면 그 칸이 읽을 만해질
   때까지 페이지를 확대한다. 그리고 되돌리지 않는다 — 사용자가 두 손가락으로 직접
   축소해야 원래 배율로 돌아온다. `user-scalable=no` 로 막는 방법도 있지만 그건 확대
   자체를 뺏는 것이라(저시력 사용자에게 필요하다) 쓰지 않는다. 글자를 16px 로 올리면
   확대할 이유가 없어져 애초에 안 일어난다.

   전 화면 실측(390px)에서 걸린 것: 목록 검색 14px · 필터 드롭다운 5개와 정렬 13px ·
   로그인/회원가입/비밀번호 재설정 15px · 좌석도 인원수 13px · 관람평 별점 14px 와
   본문 14.5px · 관리자 기간 14px. 로그인 화면에서 아이디 칸을 누르는 순간 화면이
   튀는 상태였다.

   조건이 둘인 이유: 폭 조건은 좁은 화면에서 검증할 수 있게 하고(개발 중 확인),
   pointer: coarse 는 폭이 넓은 터치 기기(아이패드는 가로 1024px 까지 간다)를 잡는다.
   둘 중 하나만으로는 각각 아이패드와 개발 환경을 놓친다. 마우스 쓰는 좁은 창에도
   걸리지만 글자가 조금 커질 뿐이라 해가 없다.

   !important 를 쓰는 이유: 화면별 규칙(body.page-list .fp-dropdowns select 등)이
   명시도에서 이겨서, 같은 값을 페이지마다 하나씩 다시 적어야 이길 수 있다. 그러면
   나중에 새 입력창이 생길 때마다 여기에 한 줄씩 더해야 하고, 빠뜨리면 조용히 확대가
   돌아온다. 이건 디자인 선택이 아니라 플랫폼 동작을 막는 하한선이라 무조건 이겨야 한다.
   ================================================================== */
@media (max-width: 860px), (pointer: coarse) {
  input, select, textarea { font-size: 16px !important; }
  /*
     하한선으로 쓰는 규칙인데 CSS 에는 "최소 글자 크기"가 없어서, 위 한 줄은 이미 16px 를
     넘는 입력창까지 16px 로 끌어내린다. 실제로 홈 히어로 검색창이 17px → 16px 로 줄었다.
     16px 이상으로 의도한 입력창은 여기에 한 줄씩 되돌린다(!important 끼리는 명시도로
     갈리므로 화면을 짚은 이 선택자가 이긴다).

     되돌리기를 빠뜨리면 글자가 1px 작아질 뿐이지만, 반대로 하한선을 빠뜨리면 그 화면에서
     확대 버그가 되살아난다. 그래서 열거하는 쪽을 "예외"로 두고 기본을 하한선으로 잡았다.
  */
  body.page-home .hero-search input { font-size: 17px !important; }
}

/* ==================================================================
   좁은 화면 헤더 압축

   좁은 화면에서 헤더가 세 줄이었다 — ① 로고 ② 공연 찾기·마이페이지 ③ 로그인·회원가입.
   실측(390px) 160~166px 로, sticky 라 스크롤 내내 화면의 19~20% 를 붙잡고 있었다.
   좌석도만 93px 인데 그건 압축해서가 아니라 그 화면이 내비를 아예 안 쓰기 때문이다
   (showAuthNav=false) — 나머지 화면에는 해법이 없었던 셈이다.

   세 줄이 된 이유는 딱 19px 이었다. 내비 한 줄에 필요한 폭이 354px(76 + 84 + 174 +
   여백 20)인데 쓸 수 있는 건 335px 이라, 로그인 묶음만 다음 줄로 떨어졌다. 그래서
   글자나 항목을 빼지 않고 알약 여백만 조여 한 줄에 넣는다 — 정보는 그대로 두고
   줄 수만 셋에서 둘로 줄이는 게 목적이다.

   터치 목표가 작아지지 않도록 세로 여백은 거의 안 건드린다(가로만 줄인다). 알약
   자체가 작아 보이지 않게 gap 도 같이 좁혀, 항목 사이가 알약 안쪽 여백보다 넓어
   따로 노는 일이 없게 한다.
   ================================================================== */
@media (max-width: 860px) {
  .masthead .authnav { gap: 6px 8px; }
  /* 가로만 14 → 10px. 세로 8px 은 그대로 둬서 누를 높이를 지킨다. */
  .masthead .authnav .navlink { padding: 8px 10px; }
  .masthead .navactions { gap: 8px; }
  .masthead .authbtns { gap: 6px; }
  /* CTA만 가로 여백을 줄인다. 로그인은 이미 텍스트 링크라 더 줄일 공간이 작다. */
  .masthead .authbtns .btn-signup { padding: 9px 12px; }

  /*
     로그인 상태에서는 닉네임이 이 줄의 폭을 정한다 — 길이 제한이 서버에 있어도
     긴 닉네임 하나면 애써 한 줄로 만든 내비가 다시 두 줄이 된다. 폭을 잘라 말줄임으로
     막는다. 이니셜 원(24px)은 그대로 두므로 누구인지는 계속 보인다.
  */
  .masthead .authnav .who { min-width: 0; }
  .masthead .authnav .who > span:last-child {
    min-width: 0;
    /* 한 줄을 지키는 상한은 9ch(내비에 필요한 폭 323 vs 쓸 수 있는 335)로 실측됐다.
       여유 12px 은 기기·폰트에 따라 넘길 수 있어 8ch 로 한 칸 물러섰다. 한글 기준
       4~5자, 영문 8자쯤이라 흔한 닉네임은 잘리지 않고 그대로 다 보인다. */
    max-width: 8ch;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/*
   더 좁은 폭에서 한 번 더 조인다.

   위 값으로 390px 은 한 줄에 들어갔지만(필요 306 / 가용 335) 360px 에서는 가용이
   305px 라 1px 이 모자라 다시 세 줄이 됐다. 갤럭시 S 계열 기본 폭이 360px 이라
   그냥 두면 국내 사용자 상당수가 압축 효과를 전혀 못 본다.

   가로 여백만 더 줄인다 — gap 은 홈이 화면 스코프로 따로 갖고 있어(명시도가 높다)
   여기서 바꾸면 홈만 안 먹는다. 여백은 화면별로 덮는 규칙이 없어 여기 한 곳이면 된다.

   그런데 이 압축 이후에 헤더 검색 아이콘(.navsearch-toggle, 34px)이 추가되면서 이 예산이
   다시 깨졌다 — 아이콘 폭이 계산에 없었다. 360~375px 에서 다시 세 줄(내비가 한 번 더
   감겨 로그인·회원가입 줄만 오른쪽에 따로 떨어짐)이 된 것을 실측으로 확인했다. 아이콘을
   이 폭에서 통째로 뺀다 — 검색은 「공연 찾기」 너머 필터 패널에 그대로 있어 길이 막히지
   않는다(fragment/header 의 헤더 검색 기능 도입 시 판단과 같다).
*/
@media (max-width: 400px) {
  .masthead .navsearch-toggle { display: none; }
  .masthead .authnav .navlink { padding: 8px 8px; }
  .masthead .authbtns { gap: 4px; }
  .masthead .authbtns .btn-signup { padding: 9px 10px; }
}

/* ==================================================================
   좁은 화면 목록 2열

   기본 그리드가 `repeat(auto-fill, minmax(220px, 1fr))` 이라 좁은 화면에서는
   두 칸이 못 들어가 1열로 떨어졌다. 그러면 카드가 화면 폭을 다 쓰고 포스터가
   3:4 라 카드 하나가 529px 이 된다 — 실측(390px)으로 844px 화면에 **카드 한 장이
   다 안 들어왔고**, 6건을 보는 데 3,752px 을 스크롤해야 했다. 목록은 여러 개를
   훑어보고 고르는 화면인데 한 번에 한 장씩만 보이면 비교가 안 된다.

   인터파크·예스24·멜론티켓 모두 모바일에서 2열이다. auto-fill 의 최소 폭을 낮추는
   대신 칸 수를 못박는 이유는, 낮추면 폭에 따라 1열과 2열을 오가기 때문이다 —
   실제로 최소 150px 로 낮춰 보면 390px 은 2열인데 360px 은 다시 1열이 된다.

   홈은 제외한다. 같은 `.grid` 클래스를 쓰지만 그쪽은 `display: flex` 인 가로 레일이라
   칸 수라는 개념이 없다(body.page-home .homesec .grid). 찜한 공연은 목록과 같은
   카드·같은 그리드라 같이 2열이 된다 — body.page-list 를 공유하므로 자동으로 걸린다.
   ================================================================== */
@media (max-width: 560px) {
  body.page-list:not(.page-home) .grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* 열 간격은 24 → 12px. 카드가 절반 폭이 되면 24px 은 카드 사이가 카드 안쪽
       여백보다 넓어 보여 두 장이 한 벌로 안 읽힌다. 행 간격은 줄 구분이 필요하므로
       열보다 넉넉히 남긴다. */
    gap: 24px 12px;
  }
  /* 카드가 절반 폭이 되었으므로 글자도 같이 내린다 — 16px 제목은 160px 카드에서
     두 줄 제한에 금방 걸려 대부분의 공연명이 잘린다. */
  body.page-list:not(.page-home) .card h3 { font-size: 14px; }
  body.page-list:not(.page-home) .card .meta { font-size: 12px; }
  body.page-list:not(.page-home) .card .m-date { font-size: 11px; }
}

/* ==================================================================
   터치 타깃 최소 크기

   실측(390px)에서 손가락으로 누르기엔 작은 컨트롤이 여럿 있었다 — 공유 시트 닫기
   23×23, 필터 닫기 25×24, 비밀번호 보기 38×27, 그리고 본문 속 인라인 링크들이
   글자 높이 그대로 16~26px 이었다(「비밀번호를 잊으셨나요?」·「지도에서 찾기」·
   「전체 보기 →」). 손가락 접촉면은 대략 9mm 라 24px 짜리 ✕ 는 정확히 겨냥해야 눌린다.

   두 가지 방법을 쓴다. 어느 쪽인지는 **그 컨트롤이 눈에 보이는 상자를 갖는지**로 가른다.

   ① 상자를 실제로 키운다 — 아이콘 버튼과 알약처럼 배경·테두리로 "여기가 눌리는
      범위"를 이미 보여주는 것들. 이런 건 히트 영역만 넓히면 보이는 것보다 넓은 데가
      눌려서, 화면이 사용자에게 거짓말을 하게 된다.

   ② 히트 영역만 넓힌다(::after) — 본문 흐름 속 인라인 링크. 상자를 키우면 글줄 간격이
      밀려 문단이 헝클어진다. 보이는 건 그대로 두고 누를 수 있는 범위만 위아래로 넓힌다.
      투명한 판이라 옆 글자를 가리지도 않는다.

   조건은 3번(입력창 16px)과 같다 — 폭만으로는 아이패드를 놓치고, pointer 만으로는
   개발 중에 확인할 수가 없다.
   ================================================================== */
@media (max-width: 860px), (pointer: coarse) {
  /* ---- ① 상자를 키우는 쪽 ---- */
  body.page-list .fp-close,
  .sharesheet-close,
  .pwtoggle {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* 알약은 이미 37~42px 라 조금만 모자랐다. 세로만 채우면 되므로 가로 여백(4번에서
     한 줄에 넣으려고 조인 값)은 건드리지 않는다 — 건드리면 내비가 다시 두 줄이 된다. */
  .masthead .authnav .navlink,
  .masthead .authbtns a,
  body.page-detail .sd-times .sd-chip {
    min-height: 44px;
    display: inline-flex; align-items: center;
  }
  body.page-detail .sd-times .sd-chip { justify-content: center; }

  /* ---- ② 히트 영역만 넓히는 쪽 ---- */
  /*
     로고는 알약과 달리 눌리는 범위를 배경으로 보여주지 않는다(글자뿐이다). 그래서
     상자를 키우는 대신 이쪽에 둔다 — 키우면 헤더 첫 줄이 9px 높아지는데, 바로 앞
     작업에서 줄인 높이를 되돌릴 이유가 없다.
  */
  .masthead .brandlink,
  .authlink a,
  .map-link,
  body.page-home .hs-more,
  body.page-home .allmore a { position: relative; }
  .masthead .brandlink::after,
  .authlink a::after,
  .map-link::after,
  body.page-home .hs-more::after,
  body.page-home .allmore a::after {
    content: '';
    position: absolute;
    left: 0; right: 0;
    top: 50%; transform: translateY(-50%);
    height: 44px;
  }
}

/*
   비밀번호 입력창의 오른쪽 여백. 미디어 쿼리 밖에 두는 이유는 이게 크기 조정이 아니라
   **겹침 버그 수정**이기 때문이다 — 좁은 화면만의 문제가 아니다.

   .pwfield input 의 padding-right: 62px 가 뒤에 오는 body.tone-light .authform input 의
   padding 단축 속성(13px 14px)에 명시도로 져서 14px 로 덮여 있었다. 그래서 「보기」
   토글이 입력 글자 위에 30px 겹쳤다 — 긴 비밀번호를 치면 글자가 버튼 밑으로 들어간다.
   위에서 토글을 44px 로 키우면 더 겹치므로 같이 잡는다.
*/
body.tone-light .authform .pwfield input { padding-right: 56px; }

/* ==================================================================
   공연 비교 (비교함 트레이 + 비교표)

   공연은 후보를 여러 개 놓고 고르는 일이 많은데, 후보를 나란히 볼 자리가 없었다.
   찜(오래 두고 볼 것)과 달리 지금 이 자리에서 둘셋을 견주고 끝나는 동작이라,
   담아둔 목록은 서버가 아니라 브라우저에 있다(fragment/comparetray 주석).
   ================================================================== */

/* ---- 상세의 「비교」 토글 ----
   찜과 같은 규칙으로 눌림을 나타낸다 — 색 하나로만 알리면 색각 이상에서 구분이 안 되므로
   아이콘(빈 막대 → 채운 막대)과 글자(「비교」 → 「비교 중」)가 함께 바뀐다. */
body.page-detail .pf-compare[aria-pressed="true"] {
  color: var(--brass);
  border-color: var(--brass);
  background: rgba(255, 201, 64, .08);
}
body.page-detail.tone-light .pf-compare[aria-pressed="true"] {
  color: var(--brass-ink);
  border-color: var(--brass-ink);
  background: rgba(233, 180, 76, .12);
}
body.page-detail .pf-compare[aria-pressed="true"] rect:last-of-type { fill: currentColor; stroke: none; }

/* ---- 비교함 트레이 ----
   면은 상세의 모바일 예매 바(body.page-detail .mobilebar)와 같은 --ink 로 둔다. 트레이가
   서는 화면은 홈(자체 라이트)·목록·찜(tone-light)으로 배경 톤이 갈리는데, 배경을 따라가면
   화면마다 다른 것처럼 보인다. "어두운 띠 = 지금 담아둔 것" 하나로 통일한다.

   z-index 는 모바일 예매 바와 같은 20. 토스트(60)·공유 시트(70)보다 아래라 그 둘은
   트레이를 덮는다 — 트레이가 안내를 가리면 왜 못 넘어가는지 못 읽는다. */
.ctray {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: 20;
  background: var(--ink);
  color: var(--paper);
  border-top: 1px solid rgba(248, 244, 236, .12);
  /* 아이폰 홈 인디케이터에 버튼이 물리지 않게 한다(좌석도 .bookbar 와 같은 처리). */
  padding: 12px 24px calc(12px + env(safe-area-inset-bottom));
}
.ctray-inner {
  max-width: 1080px; margin: 0 auto;
  display: flex; align-items: center; gap: 16px;
}
.ctray-head {
  margin: 0; flex: none;
  font-size: 13px; font-weight: 700; color: var(--mute);
}
.ctray-head em {
  font-style: normal; color: var(--brass);
}
/* 담은 게 셋뿐이라 넘칠 일은 없지만, 제목이 긴 공연이 겹치면 이 칸만 줄어들게 둔다 —
   min-width: 0 이 없으면 flex 항목이 콘텐츠보다 작아지지 않아 버튼이 밖으로 밀린다. */
.ctray-items { display: flex; gap: 10px; flex: 1; min-width: 0; }
.ctray-item { position: relative; flex: none; }
.ctray-thumb {
  display: block;
  width: 40px; height: 53px;
  border-radius: var(--radius-poster);
  overflow: hidden;
  background: var(--ink-raised);
}
.ctray-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 포스터가 없거나 주소가 죽었을 때의 첫 글자 폴백. 카드들이 쓰는 처리와 같다. */
.ctray-fallback {
  display: none;
  width: 100%; height: 100%;
  align-items: center; justify-content: center;
  font-size: 16px; font-weight: 700; color: var(--mute);
}
.ctray-thumb.no-img .ctray-fallback { display: flex; }
/* 빼기 버튼. 썸네일이 40px 이라 그 안에 두면 누를 수 있는 크기가 안 나오므로 모서리에
   겹쳐 띄우고, 실제 누르는 영역은 22px 로 잡는다. */
.ctray-drop {
  position: absolute; top: -6px; right: -6px;
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: var(--ink-soft); color: var(--paper);
  font: inherit; font-size: 13px; line-height: 1;
  cursor: pointer;
}
.ctray-drop:hover { border-color: var(--brass); color: var(--brass); }
.ctray-actions { display: flex; align-items: center; gap: 10px; flex: none; }
.ctray-clear {
  padding: 8px 10px;
  background: none; border: none;
  color: var(--mute); font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
}
.ctray-clear:hover { color: var(--paper); }
.ctray-go {
  display: inline-flex; align-items: center;
  min-height: 40px; padding: 0 20px;
  border-radius: 999px;
  background: var(--brass); color: var(--ink) !important;
  font-size: 14px; font-weight: 700;
  transition: background .12s, opacity .12s;
}
.ctray-go:hover { background: #f2c874; }
/* 하나만 담았을 때. disabled 로 두면 포커스를 못 받아 키보드로는 이유를 알 방법이 없어서
   (누르면 토스트가 왜 막혔는지 말해 준다) 보기에만 물린다. */
.ctray-go.is-disabled { opacity: .45; }

/* 트레이가 뜬 만큼 본문 아래를 비운다. 실측값(--ctray-h)을 쓰는 이유는 담은 개수·제목
   길이에 따라 높이가 변하기 때문이다 — 상수로 두면 마지막 카드 줄이 띠 뒤로 들어간다.

   .wrap 이 아니라 body 에 준다. 처음엔 body.has-ctray .wrap 으로 뒀는데 목록·찜에서
   전혀 안 먹었다 — body.page-list.tone-light .wrap 이 padding: 0 을 명시도(0,3,1)로
   깔고 있어서 (0,2,1) 인 이 규칙이 진다(그 화면들은 .wrap 이 아니라 안쪽 .listinner 가
   본문 여백을 갖는다). 화면마다 본문 컨테이너가 다른 게 원인이라 스코프를 올리는 것으로
   풀면 화면 수만큼 규칙이 늘어난다. body 는 어느 화면에서도 padding 을 쓰지 않으므로
   여기 한 줄이면 전부 맞는다(가로가 아니라 아래만 주므로 풀블리드 헤더·히어로도 그대로).
   실측: 목록 78px 트레이에서 body padding-bottom 102px. */
body.has-ctray { padding-bottom: calc(24px + var(--ctray-h, 0px)); }
/* 토스트도 같이 올린다. 안 올리면 "두 개 이상 담아야 해요" 가 트레이 뒤에서 뜬다. */
body.has-ctray .toast { bottom: calc(32px + var(--ctray-h, 0px)); }

@media (max-width: 720px) {
  .ctray { padding: 10px 16px calc(10px + env(safe-area-inset-bottom)); }
  .ctray-inner { gap: 12px; }
  /* 좁은 화면에서는 개수 표시를 뺀다 — 썸네일이 곧 개수라 같은 말을 두 번 하는 자리에
     폭을 쓰느니 버튼에 준다. */
  .ctray-head { display: none; }
  .ctray-go { padding: 0 16px; font-size: 13px; }
}

/* ---- 비교표 ----
   행이 항목, 열이 공연인 진짜 table 이다. 그리드로 흉내 내면 스크린리더에서 "이 값이
   어느 공연의 무엇인지" 가 사라진다. */
body.page-compare .cmp-title {
  font-size: 28px; font-weight: 800; letter-spacing: -.02em;
  color: var(--ink); margin: 0 0 24px;
}
/* 좁은 화면에서는 표를 줄이는 대신 이 칸 안에서만 가로로 스크롤한다 — 본문이 통째로
   밀리는 것보다 낫다(좌석도 격자와 같은 처리). */
body.page-compare .cmp-scroll { overflow-x: auto; }
body.page-compare .cmp {
  width: 100%;
  border-collapse: collapse;
  /* 열 폭을 내용이 아니라 표가 정하게 한다. 안 그러면 주소가 긴 공연 한 칸이 다른 두
     칸을 눌러버려 "나란히 놓고 본다" 가 성립하지 않는다. */
  table-layout: fixed;
  min-width: 560px;
}
body.page-compare .cmp th,
body.page-compare .cmp td {
  padding: 14px 12px;
  border-bottom: 1px solid rgba(17, 18, 20, .1);
  text-align: left;
  vertical-align: top;
  font-size: 14px; color: var(--ink);
}
/* 항목 이름 열. 값보다 물러나야 눈이 값끼리 가로로 짝지어진다. */
body.page-compare .cmp tbody th[scope="row"] {
  width: 96px;
  color: rgba(17, 18, 20, .5);
  font-size: 13px; font-weight: 600;
  white-space: nowrap;
}
/*
   항목 이름 열은 가로 스크롤에서 붙여 둔다. 360px 에서 표는 560px 이라 반드시 옆으로
   밀어 보게 되는데(3열 비교표는 폰 폭에 물리적으로 안 들어간다), 그때 이름이 왼쪽으로
   빠져나가면 「9/1(화) 19:00」이 다음 회차인지 공연 기간인지 알 수 없다. 값만 남은 표는
   비교표가 아니다.

   배경을 깔지 않으면 스크롤된 값이 이름 밑으로 비쳐 지나간다 — body 와 같은 --paper 다.
*/
body.page-compare .cmp tbody th[scope="row"],
body.page-compare .cmp .cmp-corner {
  position: sticky; left: 0; z-index: 1;
  background: var(--paper);
}
body.page-compare .cmp-corner { border-bottom-color: transparent; }
body.page-compare .cmp thead th { border-bottom-width: 2px; }

body.page-compare .cmp-head { display: block; }
body.page-compare .cmp-poster {
  display: block;
  width: 100%; max-width: 132px; aspect-ratio: 3 / 4;
  border-radius: var(--radius-poster);
  overflow: hidden;
  background: rgba(17, 18, 20, .06);
  margin-bottom: 10px;
}
body.page-compare .cmp-poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
body.page-compare .cmp-fallback {
  display: none;
  width: 100%; height: 100%;
  align-items: center; justify-content: center;
  font-size: 28px; font-weight: 700; color: rgba(17, 18, 20, .3);
}
body.page-compare .cmp-poster.no-img .cmp-fallback { display: flex; }
body.page-compare .cmp-cat {
  display: block;
  font-size: 12px; font-weight: 600; color: rgba(17, 18, 20, .5);
  margin-bottom: 2px;
}
/* 제목은 두 줄로 고정한다. 길이가 제각각인 채로 두면 헤더 높이가 열마다 달라져
   아래 첫 행이 서로 다른 높이에서 시작한다(홈 밴드의 h3 과 같은 처리). */
body.page-compare .cmp-name {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  font-size: 15px; font-weight: 700; line-height: 1.35; height: 2.7em;
  color: var(--ink);
}
body.page-compare .cmp-head:hover .cmp-name { color: var(--brass-ink); }

/* 다음 회차. 카드의 회차 알약과 같은 언어 — 오늘·내일만 채운다. */
body.page-compare .cmp-next { font-weight: 600; }
body.page-compare .cmp-next--soon {
  display: inline-block;
  padding: 3px 9px; margin: -3px 0;
  border-radius: 999px;
  background: var(--brass); color: var(--ink);
  font-size: 13px; font-weight: 700;
}
body.page-compare .cmp-none { color: rgba(17, 18, 20, .4); }
body.page-compare .cmp-sub {
  display: block; margin-top: 3px;
  font-size: 12px; color: rgba(17, 18, 20, .5);
}
body.page-compare .cmp-ok { color: rgba(17, 18, 20, .55); font-size: 13px; }
/* 예매 상태. 목록·홈 카드의 배지와 같은 색 언어를 쓴다(매진 로즈 / 매진임박 브라스 /
   그 외 중립) — 같은 공연이 화면마다 다른 색으로 보이면 안 된다. */
body.page-compare .cmp-avail {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 12px; font-weight: 700;
}
body.page-compare .cmp-avail.avail-soldout { background: var(--rose); color: #fff; }
body.page-compare .cmp-avail.avail-low { background: var(--brass); color: var(--ink); }
body.page-compare .cmp-avail.avail-pending,
body.page-compare .cmp-avail.avail-unknown {
  background: rgba(17, 18, 20, .08); color: rgba(17, 18, 20, .6); font-weight: 500;
}

body.page-compare .cmp-ctarow td { border-bottom: none; padding-top: 18px; }
body.page-compare .cmp-cta {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; min-height: 44px; padding: 0 12px;
  border-radius: var(--radius-sm);
  background: var(--ink); color: var(--paper) !important;
  font-size: 14px; font-weight: 700;
}
body.page-compare .cmp-cta:hover { background: #26272b; }
/* 남은 회차가 없는 공연. 예매가 아니라 상세로 보내므로 주요 액션의 무게를 주지 않는다. */
body.page-compare .cmp-cta--muted {
  background: none;
  color: var(--ink) !important;
  border: 1px solid rgba(17, 18, 20, .18);
}
body.page-compare .cmp-cta--muted:hover { background: rgba(17, 18, 20, .04); }
body.page-compare .cmp-foot { margin: 24px 0 0; font-size: 14px; }
body.page-compare .cmp-foot a { color: var(--brass-ink); border-bottom: 1px solid currentColor; }

/* ── 테스트 결제 안내 · 푸터 · 개인정보 안내 ─────────────────────────── */
/* 결제 화면과 결제 완료 화면의 테스트 결제 안내 */
.test-payment-notice {
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid rgba(143, 106, 21, 0.35);
  border-radius: 10px;
  background: rgba(233, 180, 76, 0.16);
  color: #2A2925;
  font-size: 13.5px;
  line-height: 1.6;
}
/* 결제 패널은 어두운 배경이라 글자색을 밝게 바꾼다. 그대로 두면 글자가 보이지 않는다. */
.paypanel .test-payment-notice {
  border-color: rgba(233, 180, 76, 0.5);
  background: rgba(233, 180, 76, 0.12);
  color: var(--paper);
}

.site-footer {
  border-top: 1px solid rgba(17, 18, 20, 0.12);
  background: #F1EDE4;
  /* 내용이 짧은 화면에서 푸터 아래가 비어 떠 보이지 않게, 같은 색을 화면 끝까지 칠한다. 레이아웃은 그대로다. */
  box-shadow: 0 100vh 0 100vh #F1EDE4;
  color: #55524C;
  font-size: 13px;
  line-height: 1.6;
}
.site-footer-inner {
  display: flex;
  justify-content: space-between;
  gap: 32px;
  max-width: 1080px;
  margin: 0 auto;
  padding: 32px 24px 40px;
}
.site-footer-about strong { color: #111214; font-size: 15px; }
.site-footer-about p { margin: 6px 0 0; max-width: 620px; }
.site-footer-links { display: flex; flex: none; flex-direction: column; gap: 8px; }
.site-footer-links a { color: #111214; text-decoration: underline; text-underline-offset: 3px; }

/* 좁은 화면의 공연 상세는 예매 바가 하단에 고정되어, 그 높이만큼 비워야 푸터가 가려지지 않는다. */
@media (max-width: 860px) {
  .page-detail .site-footer { padding-bottom: 76px; }
}
@media (max-width: 640px) {
  .site-footer-inner { flex-direction: column; gap: 18px; padding: 28px 20px 32px; }
}

.signup-privacy { margin: 4px 0 14px; color: #55524C; font-size: 13px; line-height: 1.6; }
.signup-privacy a { color: var(--brass-ink); text-decoration: underline; }

.infodoc { max-width: 720px; margin: 0 auto; color: #2A2925; line-height: 1.75; }
.infodoc h2 { margin: 0 0 12px; color: #111214; font-size: 26px; }
.infodoc h3 { margin: 30px 0 8px; color: #111214; font-size: 16px; }
.infodoc p, .infodoc ul { margin: 0 0 8px; }
.infodoc ul { padding-left: 20px; }
.infodoc a { color: var(--brass-ink); text-decoration: underline; }
.infodoc-lead {
  padding: 14px 16px;
  border-radius: 10px;
  background: rgba(233, 180, 76, 0.16);
}

/* 404·개인정보 안내도 헤더를 화면 폭에 맞춘다. 화면을 옮겨 다닐 때 헤더 폭이 바뀌지 않게 한다. */
body.page-error.tone-light .wrap,
body.page-info.tone-light .wrap { max-width: none; margin: 0; padding: 0 0 72px; }
body.page-error.tone-light .masthead,
body.page-info.tone-light .masthead { margin: 0; padding: 22px 48px; }
body.page-info.tone-light .masthead .tag { display: none; }
body.page-error.tone-light .errorbody,
body.page-error.tone-light .errorlinks {
  max-width: 1080px;
  margin-left: auto;
  margin-right: auto;
  padding-left: 24px;
  padding-right: 24px;
}
body.page-info.tone-light .infodoc { padding: 40px 24px 0; }
@media (max-width: 860px) {
  body.page-error.tone-light .masthead,
  body.page-info.tone-light .masthead { padding: 16px 20px; flex-wrap: wrap; row-gap: 8px; }
  body.page-info.tone-light .masthead h1 { white-space: nowrap; }
  body.page-info.tone-light .masthead .authnav { margin-left: 0; flex-basis: 100%; justify-content: flex-start; }
}

/* ==================================================================
   좁은 화면에서 스크롤하는 동안 헤더 숨기기

   헤더가 두 줄(실측 390px 에서 120px)이라 sticky 로 늘 붙어 있으면 화면의 15% 를
   차지한다. 항목을 빼 한 줄로 만드는 대신, 아래로 읽는 동안만 위로 비켜 서고 위로
   스크롤하면 돌아오게 했다. 클래스는 fragment/header 의 스크립트가 붙인다.
   ================================================================== */
@media (max-width: 860px) {
  .masthead { transition: transform .25s ease; }
  .masthead.masthead--away { transform: translateY(-100%); }
}
@media (max-width: 860px) and (prefers-reduced-motion: reduce) {
  .masthead { transition: none; }
}

/* ==================================================================
   폰 폭 홈 첫 화면

   소개·통계·포스터 콜라주가 첫 화면을 다 채워, 장르 칩이 화면 맨 아래에 겨우 걸리고
   공연 카드는 스크롤해야 나왔다. 콜라주는 아래 카드에 같은 포스터가 다시 나오는
   장식이라 빼고, 통계는 한 덩어리 문장으로 줄인다.
   ================================================================== */
/*
   좁은 화면 홈 검색창 잘림. 한 칸으로 바꾼 grid 의 1fr 은 최소 폭이 내용 폭(auto)이라,
   입력칸의 기본 폭만큼 칸이 넓어져 검색창 오른쪽 끝이 화면 밖으로 나갔다.
   칸의 최소 폭을 0 으로 풀고, 입력칸도 남은 폭만 쓰게 한다.
*/
@media (max-width: 860px) {
  body.page-home .hero-inner { grid-template-columns: minmax(0, 1fr); }
  body.page-home .hero-search input { min-width: 0; width: 100%; }
}

@media (max-width: 560px) {
  body.page-home .hero { padding: 28px 20px 24px; }
  body.page-home .hero-kicker { margin-bottom: 12px; }
  body.page-home .hero h2 { font-size: 28px; margin-bottom: 18px; }
  body.page-home .hero-inner { gap: 16px; }
  body.page-home .hero-collage { display: none; }
  /* 360px 에서 「모았습니 / 다」처럼 글자 단위로 끊겼다. 한국어도 단어 단위로 줄을 바꾼다. */
  body.page-home .hero-stat { word-break: keep-all; }
  body.page-home .hero-stat p,
  body.page-home .hero-stat strong { display: inline; }
  body.page-home .hero-stat strong { margin: 0 2px; font-size: 22px; }
  body.page-home .hero-live { margin: 0 0 0 8px; vertical-align: middle; }
}
