@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.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; gap: 10px;
  font-size: 13px; color: var(--mute);
}
.masthead .authnav a { color: var(--brass); }
.masthead .navdivider { 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;
}

/* ---------- 페이지네이션 ---------- */
.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; }

/* ---------- 좌석 배치도 (시그니처) ---------- */
/*
   인원수 선택과 확대 버튼은 상단 헤더(.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; }
.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).
*/
#housecontent { transform-origin: 0 0; }
.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; }
/* 무대 뒤 밴드와 무대 사이는 좁게 — 무대 뒤 좌석은 무대에 붙어 있다. */
.zoneline + .stage { margin-top: 14px; }

/*
   층 머릿글.

   종전에는 층 경계를 알려주는 게 구역 이름뿐이었는데, 층 사이 간격과 같은 열 안 구역 사이
   간격이 **둘 다 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 {
  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(--ink-soft);
}
.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); margin-inline: auto; }
.row .rowlabel {
  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)보다는 아래에 둔다. */
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--ink);
}

/*
   무대를 감싸는 홀(정면에 좌·우 구역이 있는 홀)에서는 행 라벨과 구역 이름을 고정하지 않는다.
   sticky 는 스크롤 컨테이너 왼쪽을 기준으로 잡히므로 열이 여러 개면 오른쪽 구역의 라벨까지
   전부 왼쪽 끝으로 몰려 다른 구역 좌석 위에 겹쳐 뜬다. 열이 하나인 홀에서는 종전대로 붙어 있다.
*/
.housescroll.has-sides .rowlabel { position: static; }
/*
   구역 이름도 같은 이유로 고정을 풀고, 그 구역의 좌석 폭 가운데로 옮긴다.

   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; }
.housescroll.has-sides .floorname {
  position: absolute;
  top: 0;
  left: 50%;
  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 가 걸렸다(핀치가 사실상 멈춤).
*/
.house { --arc-strength: -0.24%; }
/* 무대 뒤 구역은 무대가 아래쪽에 있으니 반대로 휜다. */
.zoneline-rear .house { --arc-strength: 0.24%; }

.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::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;
}

@media (max-width: 720px) {
  /* 좁은 화면에서는 지도 자체가 좁으니 미니맵도 자리를 덜 차지하게 줄인다. */
  .minimap { right: 12px; bottom: calc(var(--footer-h, 150px) + 10px); }
}

/* 가격표. 예매 바 안에 고정으로 들어간다. 바깥 여백은 바가 갖고 있다. */
.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;
}
.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; }
.chartbox { border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; }
.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); }
.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; }
.resno { font-size: 12px; color: var(--mute); margin: 0; }

/* ---- 예매 요약 팝업 ----
   공유 시트(공연 상세)와 같은 배경+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; }

.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; }
  .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; }

body.page-list .authbtns, body.page-detail .authbtns { display: flex; align-items: center; gap: 8px; }
body.page-list .authbtns a, body.page-detail .authbtns a {
  display: inline-flex; align-items: center;
  padding: 7px 16px;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  font-size: 13px; font-weight: 600; color: var(--paper) !important;
  transition: border-color .12s, background .12s;
}
body.page-list .authbtns a:hover, body.page-detail .authbtns a:hover {
  border-color: var(--brass);
  background: rgba(255, 201, 64, 0.08);
}

/* ---- 모바일 필터 토글 버튼 (기본 숨김, 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; }

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); }
/* 선택값 텍스트 길이에 따라 각 드롭다운 폭이 들쭉날쭉해지지 않도록 고정폭을 준다. */
/* 드롭다운이 5개(지역·공연장·월·요일·시간대)라 920px 컨테이너에 한 줄로 들어가려면
   합이 빠듯하다. 요일을 추가하면서 공연장 240→200, 그룹 라벨 여백 32→20 으로 줄여
   맞췄다 — 안 줄이면 시간대만 다음 줄로 밀려 "월 · 요일 · 시간대" 라벨과 떨어진다.
   (더 좁은 화면에서는 flex-wrap 이 자연스럽게 접는다.) */
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-grouplabel { font-size: 13px; font-weight: 600; color: var(--mute); margin-left: 20px; }
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);
}
/*
   제목이 포스터 바로 아래에 오므로 같은 행의 제목은 자동으로 같은 높이에서 시작한다.
   (종전에는 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;
  gap: 2px;
  color: var(--mute);
  line-height: 1.5;
}
/* 아주 긴 공연장명("대학로 자유극장 (자유문화발전소)")은 말줄임 — 줄바꿈시키면
   다시 카드 높이가 갈린다. */
body.page-list .card .m-venue {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 기간은 공연장보다 한 단계 뒤로 — 색을 새로 만들지 않고 같은 --mute 를 흐리게 쓴다. */
body.page-list .card .m-date { font-size: 12px; opacity: .75; }

/*
   공연장·기간 앞에 작은 아이콘. 카드 수가 많아 <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); }

/* 정렬 드롭다운. 결과바 오른쪽 끝에 붙인다 — 목록을 훑다가 "다르게 정렬해볼까" 할 때
   눈이 가는 자리이고, 왼쪽의 건수/초기화와 역할이 달라서 붙여두면 셋이 한 덩어리로 읽힌다.
   모양은 필터 패널의 드롭다운(.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("로그인 · 회원가입")는 폭이 좁으면
     제 몫의 공간을 못 받아 두 글자짜리 로고까지 세로로 쪼개지므로 아래 줄로 내린다. */
  body.page-list .masthead, body.page-detail .masthead { flex-wrap: wrap; row-gap: 8px; }
  body.page-list .masthead h1, body.page-detail .masthead h1 { white-space: nowrap; }
  body.page-list .masthead .tag, body.page-detail .masthead .tag { white-space: nowrap; }
  body.page-list .masthead .authnav, body.page-detail .masthead .authnav { margin-left: 0; flex-basis: 100%; justify-content: flex-end; }

  body.page-list .mobile-filter-btn { display: inline-flex; }
  /* 좁은 화면에서는 건수가 필터 버튼에 이미 붙어 있으므로 중복을 피한다. */
  body.page-list .resultcount { display: none; }
  /* 종전엔 초기화 링크가 없으면 결과바를 통째로 숨겼다(건수만 들어 있어 빈 줄이 됐으므로).
     이제 정렬 드롭다운이 항상 이 안에 있어서 숨기면 모바일에서 정렬할 방법이 사라진다.
     좁은 화면일수록 스크롤이 길어 정렬이 더 필요하므로 그대로 둔다. */
  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; }
}

/* ==================================================================
   공연 상세 화면 — 레이아웃은 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);
}
/* 헤더가 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;
}

/* 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; }
body.page-detail .sd-times a {
  display: inline-flex; align-items: baseline; gap: 8px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--paper);
  padding: 9px 14px;
  font-size: 14px; font-weight: 600;
  transition: border-color .12s, color .12s, background .12s;
}
body.page-detail .sd-times a: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:hover .sd-remain { color: var(--brass-dim); }
body.page-detail .sd-times a.soldout {
  border-color: var(--line); color: var(--mute); opacity: .5; pointer-events: none;
}

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; }
  body.page-detail .detail > div:first-child { position: static; max-width: 320px; }
  /* 데스크톱 40px 는 좁은 화면에서 긴 공연명이 한 글자씩 좁게 줄바꿈된다. */
  body.page-detail h2 { font-size: 28px; }
  /* 요약 카드는 sticky 칸의 남는 자리를 채우려고 있는 것인데, 여기서는 칸이 sticky 가 아니라
     제목 위로 올라간다 — 공연명보다 최저가가 먼저 읽히는 이상한 순서가 되고, 같은 정보가
     바로 아래 공연 정보 표에 다시 나온다. 그래서 숨긴다. */
  body.page-detail .summarycard { display: none; }
  /* 좁은 화면에서는 날짜를 시각 위로 올린다 — 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); }
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 .navdivider { background: rgba(17, 18, 20, .18); }
/* .authbtns a 는 body.page-list .authbtns a { color: var(--paper) !important; ... }
   (다크 화면용, 알약 아웃라인) 규칙을 !important 로 깔고 있어 page-home 스코프여도
   그냥 덮어쓰지 못한다 — 로그인 링크가 라이트 배경에 흰 글자로 거의 안 보이던 버그가
   여기서 났다. 홈 전용으로 색·테두리를 다시 !important 로 눌러 이긴다. */
body.page-home .authbtns a {
  color: var(--ink) !important;
  border-color: rgba(17, 18, 20, .16) !important;
}
body.page-home .authbtns a:hover {
  border-color: var(--brass) !important;
  background: rgba(233, 180, 76, .08);
}
body.page-home .masthead .btn-signup {
  background: var(--ink); color: var(--paper) !important;
  padding: 9px 18px; border-radius: 999px; font-weight: 700;
  border-color: var(--ink) !important;
  transition: transform .2s, background .2s;
}
body.page-home .masthead .btn-signup:hover {
  background: #292a2d !important; border-color: #292a2d !important;
  transform: translateY(-1px);
}

/* 히어로. 다크 풀블리드 + 미세한 그레인 텍스처(무대 조명 아래 객석 질감). 좌측 헤드라인/검색,
   우측 큰 숫자 통계를 2단으로 놓는다 — 숫자를 오른쪽에 크게 두면 "많이 모았다"가 카피 없이
   바로 읽힌다. */
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%); }
}
body.page-home .hero-inner {
  position: relative;
  max-width: 1240px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.3fr .9fr;
  gap: 64px;
  align-items: end;
}
body.page-home .hero-kicker {
  font-size: 14px; font-weight: 700; letter-spacing: .14em;
  color: var(--brass); text-transform: uppercase;
  margin: 0 0 22px;
}
body.page-home .hero h2 {
  font-family: var(--display);
  max-width: none;
  font-size: clamp(40px, 6vw, 76px);
  line-height: 1.02;
  font-weight: 800;
  letter-spacing: -.03em;
  margin: 0 0 28px;
  white-space: normal;
}
body.page-home .hero-stat {
  text-align: right;
  border-left: 1px solid rgba(248, 244, 236, .18);
  padding-left: 40px;
}
body.page-home .hero-stat p { margin: 0; font-size: 14px; font-weight: 600; color: rgba(248, 244, 236, .55); }
body.page-home .hero-stat strong {
  display: block;
  margin: 6px 0;
  font-size: clamp(48px, 7vw, 88px);
  font-weight: 800; letter-spacing: -.03em;
  color: var(--brass);
  font-variant-numeric: tabular-nums;
}
/* 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: 520px; margin: 0;
  padding-bottom: 14px;
  border-bottom: 2px solid rgba(248, 244, 236, .35);
  transition: border-color .25s;
}
body.page-home .hero-search:focus-within { border-color: var(--brass); }
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); }

/* 장르 바로가기. 라이트 배경 위 알약 — 아웃라인 기본, 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); }
body.page-home .genrenav em {
  font-style: normal; font-weight: 600; opacity: .5;
}

/* 섹션 공통(카드형 · 에디토리얼형 모두). 제목 줄과 카드 사이 간격이 섹션 사이
   간격보다 뚜렷하게 좁아야 "이 카드들이 이 제목에 속한다"가 읽힌다. */
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;
  padding-bottom: 4px;
  margin-bottom: -4px;
  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; }
}
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; }
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;
}

/* 에디토리얼 밴드(가운데 섹션 — "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;
}
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;
}
body.page-home .editorial-card h3 { font-size: 18px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 6px; color: var(--paper); }
body.page-home .editorial-card .ec-meta { font-size: 13px; color: rgba(248, 244, 236, .5); margin: 0; }

/* 카드형 섹션과 같은 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; }
  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; }
  body.page-home .hero h2 { font-size: 32px; white-space: normal; }
  body.page-home .genrenav { padding: 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; }
  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 .sharesheet-backdrop {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(0, 0, 0, .55);
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease;
}
body.page-detail .sharesheet-backdrop.open { opacity: 1; pointer-events: auto; }

body.page-detail .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;
}
body.page-detail .sharesheet.open { opacity: 1; pointer-events: auto; transform: translate(-50%, -50%) scale(1); }

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

body.page-detail .sharesheet-options { display: flex; gap: 28px; justify-content: center; }
body.page-detail .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);
}
body.page-detail .sharesheet-icon {
  width: 56px; height: 56px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  transition: transform .12s;
}
body.page-detail .sharesheet-option:hover .sharesheet-icon { transform: scale(1.06); }
/* 카카오 브랜드 색(로그인 화면 .oauthbtn-kakao 와 같은 값). 링크복사는 브랜드가 없으니
   이 화면의 중립 표면색(--ink-raised)에 테두리만 준다. */
body.page-detail .sharesheet-icon-kakao { background: #FEE500; }
body.page-detail .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); }
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 .navdivider { background: rgba(17, 18, 20, .18); }

/* .authbtns a 는 body.page-list/.page-detail 쪽 다크용 규칙이 color 를 !important 로
   박고 있어(--paper) 스코프를 하나 더 얹는 것만으로는 못 이긴다. 홈에서 로그인 링크가
   라이트 배경에 흰 글씨로 사라졌던 것과 같은 자리다. 여기서도 !important 로 되받는다. */
body.tone-light .authbtns a {
  color: var(--ink) !important;
  border-color: rgba(17, 18, 20, .16) !important;
}
body.tone-light .authbtns a:hover {
  border-color: var(--brass) !important;
  background: rgba(233, 180, 76, .08);
}
body.tone-light .masthead .btn-signup {
  background: var(--ink); color: var(--paper) !important;
  border-color: var(--ink) !important;
  font-weight: 700;
  transition: transform .2s, background .2s;
}
body.tone-light .masthead .btn-signup:hover {
  background: #292a2d !important; border-color: #292a2d !important;
  transform: translateY(-1px);
}

/* 라이트 배경 위 공통 카드. 목록·찜 목록이 같은 마크업(.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); }
/* 공연장·기간 앞 핀/달력 아이콘은 뺀다. data URI 안에 --mute(#B6B0A7)가 문자열로
   박혀 있어 라이트 배경에서는 거의 안 보이는데, 색을 맞추려면 아이콘마다 data URI 를
   한 벌 더 두어야 한다. 목업도 아이콘 없이 두 줄로 두고 있어 그쪽을 따른다. */
body.tone-light .card .m-venue::before,
body.tone-light .card .m-date::before { display: none; }
/* 배지는 포스터(다크) 위에 얹히므로 어두운 판 그대로 두되, 글자를 브라스로 올려
   포스터 이미지와 안 섞이게 한다. */
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-grouplabel,
body.page-list.tone-light .fp-hint { color: rgba(17, 18, 20, .5); }

/* 필터 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 아래 두어 어느 화면에서든 같은 모양이 나오게 한다. */
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;
}

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 a {
  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: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:hover .sd-remain { color: var(--brass-ink); }
/* 매진 회차는 취소선으로 "있었지만 지금은 못 산다"를 그대로 보여준다. 흐리기만
   하면 비활성인지 매진인지 구분이 안 된다. */
body.page-detail.tone-light .sd-times a.soldout {
  border-color: rgba(17, 18, 20, .1);
  color: rgba(17, 18, 20, .35);
  opacity: 1;
  text-decoration: line-through;
}
body.page-detail.tone-light .sd-times a.soldout .sd-remain { color: inherit; }

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); }

/* 공유 시트. 라이트 배경 위에 뜨는 창이라 면도 밝게 간다. */
body.page-detail.tone-light .sharesheet {
  background: var(--paper);
  border-color: rgba(17, 18, 20, .12);
  box-shadow: 0 24px 60px rgba(17, 18, 20, .22);
}

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

   이 화면은 톤을 뒤집는 비용이 가장 크다. 다크 시절의 연출이 "어두운 객석 +
   조명 받는 무대" 라는 전제 위에 서 있어서 — 무대의 빛무리, 좌석 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; }

/* 층 머릿글 · 구역 이름 */
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); }

/* 층 바로가기 */
body.page-seatmap.tone-light .floornav {
  background: #fff;
  border-color: 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 로 두면 라이트 화면에 검은 띠가 세로로 남는다. */
body.page-seatmap.tone-light .row .rowlabel {
  background: var(--paper);
  color: rgba(17, 18, 20, .4);
}

/*
   좌석 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) {
  body.page-seatmap.tone-light .masthead { padding: 14px 20px; }
}

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

   핸드오프는 예매 내역만 다루지만 찜한 공연도 같이 넘긴다. 둘은 같은 상단 탭으로
   오가는 한 화면의 두 면이라, 한쪽만 바꾸면 **탭을 누르는 순간 테마가 뒤집힌다** —
   앱 안에서 톤이 갈리는 자리 중 가장 나쁜 자리다. 찜한 공연은 목록과 같은
   .card/.grid 를 쓰므로 위 공통 기반이 대부분 처리하고, 여기서는 탭만 맞추면 된다.
   ================================================================== */
body.page-mypage.tone-light .wrap { max-width: none; margin: 0; padding: 0; }
body.page-mypage.tone-light .masthead { margin: 0; padding: 20px 48px; }
/* 이 헤더의 부제("마이페이지 · 예매 내역")는 어느 탭인지까지 말해 주는데, 바로 아래
   탭 줄이 같은 걸 더 크게 보여준다. 목업대로 "마이페이지"만 오른쪽에 조용히 둔다. */
body.page-mypage.tone-light .masthead .tag {
  display: inline;
  margin-left: auto;
  font-size: 14px; font-weight: 600; color: rgba(17, 18, 20, .5);
}
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;
}

/* ---- 예매 카드 ---- */
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 .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); }

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

@media (max-width: 560px) {
  body.page-mypage.tone-light .masthead { padding: 16px 20px; }
  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);
}

@media (max-width: 860px) {
  body.page-payment.tone-light .masthead { padding: 16px 20px; }
  body.page-payment.tone-light .paysteps { padding: 18px 20px 8px; }
  body.page-payment.tone-light .paylayout { padding: 24px 20px 72px; gap: 32px; }
}

@media (max-width: 860px) {
  body.page-detail.tone-light .masthead { padding: 16px 20px; }
  body.page-detail.tone-light .detail { padding: 28px 20px 72px; 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;
  }
}
