/* ==========================================================================
   glass.css — 재질·색·모션 foundation (UI 오버홀 3기)
   기준: docs/_temp/ui-overhaul-3.md, 용어는 docs/with-ai/CONTEXT.md

   3기 구조 (ADR 0001): 실시간 유리(backdrop-filter)는 크롬 — 내비바·사이드
   버튼·모달 뒤 베일·스크롤 베일 — 에만 쓴다. 표·카드·폼 본문을 담는
   콘텐츠 패널(.content-panel)은 불투명하고, 재질감은 프로스트(사진을 미리
   흐려 구운 그림, frost-baking.js)가 준다.

   이 파일이 담당하는 것:
     1) 디자인 토큰(:root, dark 는 html[data-theme="dark"])과 웹폰트(SUITE 가변) 선언
     2) 콘텐츠 패널(.content-panel)과 Bootstrap 컴포넌트(.btn, .form-control, .modal 등)의
        시각 오버라이드 — 동작(JS)은 Bootstrap 그대로 두고 겉만 바꾼다
     3) 배경 연출: 빛 웅덩이(body::before), 사진 배경(.photo-bg), 햇살(.sky/.ray), 보케(.bokeh)
     4) 모션 어휘: 로드 시퀀스(.veil), 스크롤 리빌([data-lift]), 스크롤 베일(.scroll-veil)
     5) 접근성: reduced-motion / reduced-transparency / contrast 대응

   레이아웃과 페이지 구성은 다루지 않는다 (containers.css, page/*.css 담당).
   Bootstrap CSS 뒤, 다른 커스텀 CSS 앞에 로드해야 한다.
   ========================================================================== */

/* ---------- 웹폰트 ----------
   본문 글꼴 SUITE (sun.fo, SIL OFL): 가변 한 벌로 300~900 을 모두 그린다.
   NanumSquareRound 는 굵기 얼굴이 넷(300/400/700/800)뿐이라 600 처럼 얼굴 없는
   값이 700 으로 그려졌고, 그래서 굵기 위계를 두 단으로 줄여 쓰고 있었다.
   파일은 /static/fonts/ 에 self-host — 추적 방지가 CDN 을 막아도 본문 글꼴은 떠야 한다.

   안전망: SUITE 는 한글 2,668자(자주 쓰는 음절)만 담는다. 나머지는 font-fallback.css
   가 Wanted Sans 로 받는다 — 한글 구간에서만, 글자 단위로.
   가나·한자는 두 글꼴 모두 담지 않아 기기 글꼴이 그린다. */
@font-face {
    font-family: 'SUITE';
    font-weight: 300 900;
    font-display: swap;
    src: url(/fonts/SUITE-Variable-eeb2ee40db024e704d37bb786532e92c.woff2) format("woff2-variations");
}

/* ---------- 디자인 토큰 ---------- */
:root {
    /* 잉크(글자) 4단계: 진한 · 보조 · 옅은 · 흐린 */
    --ink: #1b232b;
    --ink-2: #45515c;
    --ink-3: #66717e;
    --ink-4: #8792a0;

    /* 바탕 */
    --bg: #eaf0f4;

    /* 콘텐츠 패널: 불투명 바탕 + 패널 그림자. 프로스트 굽기(frost-baking.js)가
       background-image 로 흐린 사진을 깔고, 그 위를 --panel-frost-cover 색으로
       한 번 덮어 글자 대비를 지킨다 (시안 FROST_L/FROST_D 값) */
    --panel-bg: #fff;
    --panel-frost-cover: rgb(255 255 255 / .8);
    --panel-shadow:
        0 1px 0 rgb(255 255 255 / .9) inset,
        0 0 0 1px rgb(30 42 56 / .06),
        0 18px 48px -12px rgb(30 42 56 / .18);

    /* 버튼 사다리 (D20, iOS HIG 3단 + xsmall): 높이(줄 높이)·글자 크기 쌍.
       large 는 단독 카드 버튼·모달 발 버튼(터치 44px 충족), medium 은 입력
       그룹 옆·헤더 컨트롤, small 은 표 안 보조 컨트롤, xsmall 은 패널 헤더
       옆 순서변경·삭제. 패딩·글자를 감으로 정하지 않고 이 중 하나를 고른다 */
    --btn-h-lg: 50px;
    --btn-h-md: 36px;
    --btn-h-sm: 28px;
    --btn-h-xs: 24px;
    --btn-font-lg: 17px;
    --btn-font-md: 15px;
    --btn-font-sm: 13px;
    --btn-font-xs: 12px;

    /* 햇살 색 (rgb 채널값 — rgb(var(--sun) / a) 형태로 사용) */
    --sun: 255 240 205;

    /* 서비스 브랜드: TJ·금영 식별색을 유리에 맞춘 tint + 어두운 잉크 */
    --tj-tint: rgb(249 80 1 / .14);
    --tj-ink: #933a05;
    --ky-tint: rgb(100 149 237 / .18);
    --ky-ink: #2f5495;

    /* 추가일 badge·최신곡 highlight 공용 */
    --badge-tint: rgb(255 200 120 / .30);
    --badge-ink: #7a5210;

    /* 위험 동작(구독취소·탈퇴) */
    --danger-tint: rgb(255 122 96 / .18);
    --danger-ink: #a03015;

    /* 스프링 (P11): 위치·크기·코너의 움직임은 살짝 튀는 spatial 곡선 하나,
       색·투명도는 튀지 않는 effects 곡선. spatial 은 언더댐핑 스텝 응답을
       linear() 로 샘플링한 값 — 정본 계산기는
       docs/decisions/references/_ux/_material-expressive/_raw/13-spring-linear.js.
       reduced-motion 에서는 spatial 을 effects 로 바꾼다 (파일 끝 접근성 절) */
    --spring-spatial: linear(0, 0.013 0.6%, 0.05 1.2%, 0.2 2.5%, 0.55 6%, 0.86 10%, 1.05 14%, 1.12 18%, 1.1 22%, 1.03 30%, 0.99 38%, 1);
    --ease-effects: ease;
    /* 떠오름(로드 시퀀스·스크롤 리빌)용 감속 곡선 */
    --ease-float: cubic-bezier(.22, .8, .3, 1);

    /* 글자 사다리 (P21, 4기 7턴): iOS Dynamic Type 을 px 로 옮긴 8단. 글자
       브레이크포인트는 768·1200 둘뿐이고 아래 미디어 블록에서 토큰 값만 바뀐다 —
       쓰는 쪽은 var(--fs-*) 만 적고 vw·미디어 쿼리를 두지 않는다. 버튼 글자는
       위의 --btn-font-* (D20) 가 따로 정한다. 표: docs/_temp/ui-overhaul-4.md 7턴 */
    --fs-xs: 12px;    /* caption: 배지 */
    --fs-sm: 13px;    /* footnote: 푸터·아이콘 라벨·동의 문구·결과 수 */
    --fs-md: 14px;    /* subheadline: 표 머리글·번호·보조줄, 안내문, placeholder */
    --fs-body: 15px;  /* body: 본문·표 제목 셀·입력창·모달 항목 */
    --fs-lg: 18px;    /* title3: 패널 제목 h3·상단바 브랜드·h4 */
    --fs-xl: 20px;    /* title2: 모달 제목 */
    --fs-2xl: 24px;   /* title1: 섹션 제목 h2, 에러 카드 제목 */
    --fs-title-large: 28px; /* large title: 페이지 제목 h1 (홈은 히어로가 대신) */
    --fs-hero: 26px;  /* index 히어로 헤드라인 */

    /* 굵기 사다리 (4기 9턴 후속): SUITE 가변이 300~900 을 모두 그리므로 얼굴 없는 값이
       한 단 굵게 그려지던 문제가 없다. 다섯 단만 쓰고, 고를 때는 크기를 함께 본다 —
       작은 글자일수록 같은 강조에 더 굵어야 하므로 13px 이하의 강조는 strong(600),
       18px 이상의 제목은 title(700) 이다. body 아래(300 이하)는 쓰지 않는다:
       18px 아래에서 흐려진다. 글자 크기는 위의 --fs-* 가 정한다 */
    --fw-body: 400;     /* 본문, 보조줄, 대표값, 입력창 */
    --fw-mid: 500;      /* 본문보다 한 단: 아티스트, 노래 번호, 표 머리글 */
    --fw-strong: 600;   /* 작은 글자의 강조: 버튼, 배지, 링크, 루비(11px) */
    --fw-title: 700;    /* 제목: 노래 제목, h1~h6, 모달 제목 */
    --fw-display: 800;  /* 한 화면에 하나뿐인 표시 (지금은 쓰는 곳 없음) */

    /* 반경 사다리. 반경을 가진 컨테이너는 --r-parent 를 함께 내려주고, 가장자리에
       맞닿는 자식은 calc(var(--r-parent) - 간격) 으로 받는다. 홀로 놓이면 --r-control */
    --r-panel: 24px;     /* 콘텐츠 패널 */
    --r-sheet: 28px;     /* 모달, 에러 카드 */
    --r-control: 14px;   /* 입력, 보조 버튼, 경고, lift 판, 그룹 */
    --r-capsule: 999px;  /* 상단의 두 캡슐, 주요 액션 버튼, 배지 */
}

@media (min-width: 768px) {
    :root {
        --fs-md: 15px;
        --fs-body: 17px;
        --fs-lg: 20px;
        --fs-xl: 22px;
        --fs-2xl: 28px;
        --fs-title-large: 34px;
        --fs-hero: 32px;
    }
}

@media (min-width: 1200px) {
    :root {
        --fs-hero: 34px;
    }
}

/* dark (D7, D22): 색 토큰이 통째로 바뀌고 사진은 어둡게 눌린다.
   스위치는 html[data-theme="dark"] — 초기값은 theme-init.js, 토글은
   common-handlers.js. Bootstrap 기본색을 위해 data-bs-theme 도 함께 세팅된다 */
html[data-theme="dark"] {
    --ink: #eef2f6;
    --ink-2: #c3ccd6;
    --ink-3: #a7b1bc;
    --ink-4: #8b96a3;
    --bg: #121a24;
    --tj-ink: #ffb088;
    --ky-ink: #9fc0ff;
    --badge-tint: rgb(255 200 120 / .18);
    --badge-ink: #ffd18a;
    --danger-tint: rgb(255 69 58 / .26);
    --danger-ink: #ff5a50;
    --panel-bg: #1b2430;
    --panel-frost-cover: rgb(27 36 48 / .72);
    --panel-shadow:
        0 1px 0 rgb(255 255 255 / .06) inset,
        0 0 0 1px rgb(255 255 255 / .06),
        0 24px 60px -16px rgb(0 0 0 / .6);
}

/* ---------- 전역 베이스 ---------- */
/* overflow-x 는 html 에도 걸어야 한다. body 에만 걸면 절대배치 요소(보케·햇살)가
   뷰포트 가로 스크롤을 만든다 (시안 모바일 검증에서 확인된 결함) */
html {
    overflow-x: clip;
    /* body 그라디언트가 끝나는 지점(짧은 페이지·overscroll)에서도 바탕색이 이어지도록 */
    background: #e4ebf1;
    /* 스크롤바 자리를 상시 확보 — 모달이 스크롤을 잠가도 레이아웃이 밀리지 않는다 */
    scrollbar-gutter: stable;
}

html[data-theme="dark"] {
    background: #0f151d;
}

body {
    color: var(--ink);
    /* 배경은 칠하지 않는다 — body 에 불투명 배경을 주면 음수 z 로 깔리는
       빛 웅덩이 필드(body::before)와 보케가 그 밑에 묻혀 안 보인다.
       바탕색은 html(#e4ebf1)과 필드의 마지막 linear-gradient 층이 맡는다 */
    background: transparent;
    min-height: 100vh;
    /* html 의 clip 만으로는 부족하다 — 루트는 세로축이 스크롤 컨테이너라
       가로 clip 이 hidden 처럼 동작해, 스크롤바만 없을 뿐 프로그램적
       스크롤(scrollIntoView·포커스 이동·페이지 내 찾기)로는 여전히 옆으로
       밀린다. 보케 상자의 좌우 600px 여백이 만드는 가로 오버플로를 body
       에서 clip 으로 잘라야 문서의 가로 스크롤 여지 자체가 사라진다.
       (clip 은 hidden 과 달리 스크롤 컨테이너를 만들지 않아 이중 스크롤바
       없음 — 실측 확인. 모달은 fixed 라 조상 clip 의 영향을 받지 않는다) */
    overflow-x: clip;
}

/* 오전 10시의 빛: 좌상단 해 + 곳곳의 빛 웅덩이. 시안 값 그대로 —
   단, body 가 아니라 뷰포트 고정 레이어에 칠한다. body 에 직접 칠하면
   그라디언트 기하가 문서 높이에 묶여, collapse 등 height 전환이 매 프레임
   문서 전체 재도장을 일으킨다. background-attachment: fixed 는 iOS Safari
   미지원이라 fixed 의사요소를 쓴다 */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -2;   /* 보케(z-index: -1)보다 아래 */
    pointer-events: none;
    background:
        radial-gradient(900px 600px at 12% -4%, rgb(var(--sun) / .55), transparent 62%),
        radial-gradient(1100px 800px at 88% 12%, rgb(205 226 244 / .6), transparent 60%),
        radial-gradient(800px 620px at 76% 38%, rgb(186 212 240 / .5), transparent 62%),
        radial-gradient(700px 560px at 10% 52%, rgb(var(--sun) / .34), transparent 60%),
        radial-gradient(900px 700px at 30% 68%, rgb(255 255 255 / .75), transparent 65%),
        radial-gradient(850px 680px at 88% 82%, rgb(196 220 238 / .5), transparent 62%),
        radial-gradient(700px 550px at 22% 94%, rgb(var(--sun) / .26), transparent 60%),
        linear-gradient(180deg, #edf2f6 0%, var(--bg) 45%, #e4ebf1 100%);
}

/* dark 의 빛 웅덩이: 같은 자리에 어두운 색 얼룩 (시안 값 그대로) */
html[data-theme="dark"] body::before {
    background:
        radial-gradient(900px 600px at 12% -4%, rgb(255 190 120 / .22), transparent 62%),
        radial-gradient(1100px 800px at 88% 12%, rgb(90 140 220 / .22), transparent 60%),
        radial-gradient(800px 620px at 76% 38%, rgb(120 90 200 / .16), transparent 62%),
        radial-gradient(700px 560px at 10% 52%, rgb(255 160 110 / .12), transparent 60%),
        radial-gradient(850px 680px at 88% 82%, rgb(80 130 200 / .18), transparent 62%),
        linear-gradient(180deg, #131b26 0%, #0f151d 45%, #0c1118 100%);
}

/* 키보드 포커스는 유리 위에서도 명확하게 */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible {
    outline: 3px solid rgb(100 149 237 / .55);
    outline-offset: 2px;
}

/* ---------- 콘텐츠 패널 ----------
   불투명한 패널 (ADR 0001). 실시간 유리가 없으므로 스크롤·펼침에 재래스터
   비용이 들지 않는다. 재질감은 frost-baking.js 가 background-image 로 깔아
   주는 프로스트가 맡고, 그 전(사진 로드 전·실패 시)에는 바탕색만 보인다.
   border 0 + padding-box 클립: 테두리가 있으면 굽힌 그림이 테두리 밑으로
   비쳐 1px 띠가 생긴다 (시안 5턴 교훈). 그림자 안쪽 1px 흰 선이 테두리 역할.
   프로스트 그림은 frost-baking.js 가 패널마다 inline 변수(--panel-frost-image,
   --panel-frost-size)로 준다. 크기는 구운 자연 크기 — 높이 전환 중 그림이
   늘어나지 않고 잘리기만 한다. 변수가 없으면(사진 로드 전·실패·오류 페이지)
   덮개색만 바탕색 위에 얹혀 바탕색 패널이 된다 */
.content-panel {
    position: relative;
    background-color: var(--panel-bg);
    background-image:
        linear-gradient(var(--panel-frost-cover), var(--panel-frost-cover)),
        var(--panel-frost-image, none);
    background-repeat: no-repeat;
    background-size: auto, var(--panel-frost-size, 100% 100%);
    background-position: 0 0;
    background-clip: padding-box;
    border: 0;
    --r-parent: var(--r-panel);
    border-radius: var(--r-parent);
    box-shadow: var(--panel-shadow);
    /* 프로스트 교체 페이드(::before)를 패널 배경과 내용 사이에 끼우려면 패널이
       스태킹 컨텍스트여야 한다. 모달은 모두 패널 밖(body 직속)에 있어 갇히지
       않는다 — 패널 안에 모달을 넣지 않는다 (page/search.css #container 주석) */
    isolation: isolate;
}

/* 프로스트 교체 페이드 (4기 2턴): 본 배경은 새 그림으로 바로 바뀌고,
   frost-baking.js 가 옛 그림을 ::before 에 옛 크기만큼 얹어 0.4s 동안 걷는다.
   background-image 는 전환이 안 되므로 두 장을 겹쳐 opacity 로 바꾼다
   (컴포지터 처리). 덮개도 옛 그림 크기로만 깔아 그 밖은 투명 — 높이 전환
   중 패널이 옛 그림보다 커진 자리엔 본 배경의 새 그림이 바로 비친다(바탕색
   띠 없음). top 1px 은 패널의 inset 흰 선(--panel-shadow) 자리라 비워 두고
   그림을 1px 올려 맞춘다. 층 높이는 옛 그림 높이(패널보다 길면 패널까지).
   옛 그림이 새 그림보다 짧을 때(패널이 자랄 때, .frost-crossfade-feather)만
   아래 96px 을 마스크로 흐린다 — 층이 그대로 끝나면 그 아랫변이 전환 중
   가는 가로선으로 비친다(4기 3턴). 같은 크기 교체(테마·창 크기)에 마스크를
   두면 패널이 위아래로 갈라져 보인다(4기 4턴) */
.content-panel.frost-crossfade::before {
    content: '';
    position: absolute;
    inset: 1px 0 auto 0;
    height: calc(var(--panel-frost-height-prev, 100%) - 1px);
    max-height: calc(100% - 1px);
    z-index: -1;
    border-radius: inherit;
    pointer-events: none;
    background-image:
        linear-gradient(var(--panel-frost-cover), var(--panel-frost-cover)),
        var(--panel-frost-image-prev, none);
    background-repeat: no-repeat;
    background-size: var(--panel-frost-size-prev, 100% 100%), var(--panel-frost-size-prev, 100% 100%);
    background-position: 0 -1px;
    animation: frost-crossfade-out .4s ease forwards;
}
@keyframes frost-crossfade-out { to { opacity: 0; } }
.content-panel.frost-crossfade.frost-crossfade-feather::before {
    -webkit-mask-image: linear-gradient(#000 calc(100% - 96px), transparent);
    mask-image: linear-gradient(#000 calc(100% - 96px), transparent);
}

/* 테마 전환 (4기 4턴): 토글(common-handlers.js)이 View Transition 으로 감싼다 —
   옛 화면과 새 화면 스냅샷이 .3s 크로스페이드. 새 스냅샷에는 콜백 안에서 즉시
   구운 프로스트가 들어간다(frost-baking.js). 미지원·모션 최소화는 즉시 전환 */
::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: .3s;
}

/* ---------- 버튼: 알파 버튼 (5기 3번) ----------
   Bootstrap .btn 전체를 하나의 재질로 통일한다. 몸체는 알파 한 겹 — 패널 위에
   얹힌 얇은 덮개. 링·안쪽 하이라이트·그림자는 없다 (rim 버튼은 커밋 eec7743
   까지의 glass.css). 실시간 유리 없음 — 비용 0.
   variant(btn-outline-* 등)는 구분하지 않고 위험 동작(danger)과 주된
   동작(primary)만 색을 달리한다. 모양은 컨트롤 반경이 기본이고 주된 동작만
   캡슐, 크기는 사다리 4단.
   변형(danger·primary·dark)은 아래 --btn-* 변수만 덮어쓴다.

   오류 페이지는 Bootstrap 없이 glass.css 만 로드하므로, .btn 이 단독으로도
   버튼이 되도록 font-family·cursor 까지 여기서 준다. */
.btn {
    /* 재질 변수 — 변형은 이것만 바꾼다 */
    --btn-ink: var(--ink);
    --btn-bg: rgb(30 42 56 / .07);
    --btn-bg-hover: rgb(30 42 56 / .11);
    --btn-bg-active: rgb(30 42 56 / .14);

    /* Bootstrap 의 :active 규칙(.btn:first-child:active 등)은 우리보다
       특정도가 높아 background-color·color 를 덮는다 — 그 값을 투명·같은
       잉크로 맞춰 우리 그라디언트가 그대로 보이게 한다 */
    --bs-btn-active-bg: transparent;
    --bs-btn-active-color: var(--btn-ink);
    --bs-btn-active-border-color: transparent;

    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    white-space: nowrap;
    /* 사다리 기본 단 = medium. 높이는 line-height 로 잡는다 — jQuery
       show() 가 display 를 inline-block 으로 되돌려도 글자가 가운데 온다 */
    line-height: var(--btn-h-md);
    padding: 0 18px;
    min-width: 96px;
    font-family: inherit;
    font-size: var(--btn-font-md);
    font-weight: var(--fw-strong);
    color: var(--btn-ink);
    text-decoration: none;
    text-align: center;
    cursor: pointer;
    user-select: none;
    border: 0;
    border-radius: var(--r-control);
    background: var(--btn-bg);
    /* 위치·크기는 스프링, 색은 effects 곡선 (P11) */
    transition: transform .5s var(--spring-spatial), background .2s var(--ease-effects);
    will-change: transform;
}

/* hover 는 행 lift 와 같은 스프링으로 살짝 커지며 뜬다 */
.btn:hover {
    color: var(--btn-ink);
    background: var(--btn-bg-hover);
    transform: translateY(-1px) scale(1.06);
}

/* 누르면 즉시(.1s) 가라앉고, 떼면 스프링으로 복귀 */
.btn:active {
    color: var(--btn-ink);
    background: var(--btn-bg-active);
    transform: translateY(0) scale(.94);
    transition-duration: .1s;
}

/* Bootstrap 은 focus-visible 에 box-shadow 링을 쓰지만 우리는 outline 으로
   표시한다 (전역 포커스 규칙과 같은 색) */
.btn:focus-visible {
    box-shadow: none;
    outline: 3px solid rgb(100 149 237 / .55);
    outline-offset: 2px;
}

.btn:disabled,
.btn.disabled {
    color: var(--ink-4);
    background: rgb(30 42 56 / .04);
    transform: none;
}

/* 위험 동작(구독취소·탈퇴·삭제): 붉은 틴트 몸체.
   hover 도 붉은 계열 안에서만 진해진다 */
.btn-danger,
.btn-outline-danger {
    --btn-ink: var(--danger-ink);
    --btn-bg: var(--danger-tint);
    --btn-bg-hover: rgb(255 122 96 / .28);
    --btn-bg-active: rgb(255 122 96 / .36);
}

/* 주된 동작(primary): 잉크 캡슐 — 색은 글자가 아니라 바탕에 (P18).
   Bootstrap .btn-primary 와 페이지별 버튼: 구독하기(index), 구글 로그인·
   수집 동의 확인(모달), 애창곡 추가·설정 변경(애창곡),
   순서변경·삭제 완료(편집 모드 마무리) */
.btn-primary,
#submitButton,
.favorite-login-button,
#agreementModal .modal-footer .btn,
#addSongButton,
#submitChangeSettingButton,
.switch-order-complete-button,
.delete-favorite-song-complete-button {
    border-radius: var(--r-capsule);
    --btn-ink: #fff;
    --btn-bg: var(--ink);
    --btn-bg-hover: #2b3642;
    --btn-bg-active: #17212c;
}

/* dark: 흰 알파 몸체. 특정도가 위 변형들보다 높으므로
   danger·primary 의 dark 값을 이 뒤에 다시 적는다 */
html[data-theme="dark"] .btn {
    --btn-bg: rgb(255 255 255 / .09);
    --btn-bg-hover: rgb(255 255 255 / .14);
    --btn-bg-active: rgb(255 255 255 / .06);
}

html[data-theme="dark"] .btn:disabled,
html[data-theme="dark"] .btn.disabled {
    background: rgb(255 255 255 / .05);
}

/* dark 위험색은 iOS 시스템 빨강(#FF453A) 계열 채움 + 흰 글자 (시안 12턴) */
html[data-theme="dark"] .btn-danger,
html[data-theme="dark"] .btn-outline-danger {
    --btn-ink: #fff;
    --btn-bg: rgb(255 69 58 / .65);
    --btn-bg-hover: rgb(255 69 58 / .76);
    --btn-bg-active: rgb(255 69 58 / .53);
}

/* dark primary: 흰 캡슐에 어두운 글자 */
html[data-theme="dark"] .btn-primary,
html[data-theme="dark"] #submitButton,
html[data-theme="dark"] .favorite-login-button,
html[data-theme="dark"] #agreementModal .modal-footer .btn,
html[data-theme="dark"] #addSongButton,
html[data-theme="dark"] #submitChangeSettingButton,
html[data-theme="dark"] .switch-order-complete-button,
html[data-theme="dark"] .delete-favorite-song-complete-button {
    --btn-ink: #121a24;
    --btn-bg: #f0f3f7;
    --btn-bg-hover: #fff;
    --btn-bg-active: #dfe4ea;
}

/* ---------- 버튼 사다리 (D20) ----------
   기본(.btn)이 medium. btn-lg/btn-sm 은 Bootstrap 클래스 이름을 그대로 쓰되
   값은 사다리 토큰이고, btn-xsm 은 우리 확장이다 */
.btn-lg {
    line-height: var(--btn-h-lg);
    padding: 0 28px;
    min-width: 120px;
    font-size: var(--btn-font-lg);
}

.btn-sm {
    line-height: var(--btn-h-sm);
    padding: 0 12px;
    min-width: 72px;
    font-size: var(--btn-font-sm);
}

.btn-xsm {
    line-height: var(--btn-h-xs);
    padding: 0 10px;
    min-width: 56px;
    font-size: var(--btn-font-xs);
}

/* xsmall 은 터치 44px 을 못 채우므로, 터치 기기에서는 보이지 않는 hit 영역을
   위아래 10px 씩 넓힌다 (형제 버튼과 좌우로 붙어 있어 옆은 넓히지 않음) */
@media (pointer: coarse) {
    .btn-xsm::before {
        content: '';
        position: absolute;
        left: 0;
        right: 0;
        top: -10px;
        bottom: -10px;
    }
}

/* ---------- 입력 + 제출 (5기 8번) ----------
   글자 버튼으로 제출하는 쌍(.field-row): 입력창과 캡슐 버튼을 떼어 한 줄에 둔다.
   둘의 키는 large(50px). 버튼 색은 역할 변형(primary·danger 등)이 정한다.
   검색(.search-field): 글자 버튼 없이 입력창 안 앞쪽의 돋보기가 제출한다.
   입력 글자는 휴대폰에서도 16px 이상 — iOS Safari 가 입력창을 확대하지 않게 */
.field-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: .55rem;
}

.field-row > .form-control,
.search-field > .form-control {
    min-width: 0;
    max-width: none;
    height: var(--btn-h-lg);
    padding: 0 1.1rem;
    font-size: max(16px, var(--fs-body));
    border-radius: var(--r-control);
}

.field-row > .btn {
    min-height: var(--btn-h-lg);
    padding: 0 24px;
    font-size: var(--btn-font-lg);
    border-radius: var(--r-capsule);
    white-space: nowrap;
}

/* 여러 줄 입력(애창곡 추가 내용): 세 줄 높이. 버튼은 폼 전체를 제출하므로 입력칸 아래 가운데에 둔다 */
.field-row > textarea.form-control {
    height: calc(3lh + 1.6rem);
    padding-block: .8rem;
    resize: none;
}

.field-row:has(> textarea) {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 12px;
}

.field-row:has(> textarea) > .btn {
    justify-self: center;
    min-width: 160px;
}

/* 768px 미만: 사다리 한 칸 아래(medium) */
@media (max-width: 767.98px) {
    .field-row:has(> textarea) > .btn {
        min-height: var(--btn-h-md);
        padding: 0 18px;
        min-width: 128px;
        font-size: var(--btn-font-md);
    }
}

.search-field {
    position: relative;
    max-width: 500px;
    margin-inline: auto;
}

.search-field > .form-control {
    padding-inline-start: 3rem;
}

/* 돋보기 제출 버튼: 입력창 안 앞쪽, 입력창 모서리와 동심 (14px - 간격 4px) */
.search-field-submit {
    position: absolute;
    inset-block: 4px;
    inset-inline-start: 4px;
    width: 42px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: calc(var(--r-control) - 4px);
    background: transparent;
    color: var(--ink-2);
    font-size: 18px;
    transition: background-color .15s ease, color .15s ease;
}

.search-field-submit:hover {
    background: rgb(30 42 56 / .07);
    color: var(--ink);
}

html[data-theme="dark"] .search-field-submit:hover {
    background: rgb(255 255 255 / .09);
}

.search-field-submit:disabled {
    opacity: .4;
}

/* ---------- 입력: 패널 위의 움푹한 입력창 (3기, 유리 없음) ----------
   콘텐츠 패널이 불투명이므로 입력창도 실시간 유리 대신 잉크 알파의 얕은 홈.
   포커스는 흰 바탕 + 파란 링 */
.form-control {
    color: var(--ink);
    background: rgb(30 42 56 / .05);
    border: 1px solid rgb(30 42 56 / .10);
    border-radius: var(--r-control);
    box-shadow: none;
}

.form-control:focus {
    color: var(--ink);
    background: #fff;
    border-color: rgb(30 42 56 / .25);
    box-shadow: 0 0 0 3px rgb(100 149 237 / .25);
}

.form-control::placeholder {
    color: var(--ink-2);
}

html[data-theme="dark"] .form-control {
    background: rgb(255 255 255 / .06);
    border-color: rgb(255 255 255 / .12);
}

html[data-theme="dark"] .form-control:focus {
    background: rgb(255 255 255 / .10);
    border-color: rgb(255 255 255 / .3);
}

.form-check-input {
    accent-color: #6d7d8d;
}

.form-check-input:checked {
    background-color: #6d7d8d;
    border-color: #6d7d8d;
}

html[data-theme="dark"] .form-check-input {
    background-color: rgb(255 255 255 / .16);
    border-color: rgb(255 255 255 / .6);
}

html[data-theme="dark"] .form-check-input:checked {
    background-color: #f0f3f7;
    border-color: #f0f3f7;
}

/* ---------- 모달: iOS 시트 문법 (D19) ----------
   불투명 패널(28px, 구분선 없음) + 원형 닫기 + 캡슐 발 버튼. 뒤에는 실시간
   블러 베일 한 장(.modal-backdrop — 크롬이므로 허용). 발 버튼은 768px 미만
   전폭 나란히, 768px 부터 오른쪽 정렬·128px 이상으로 크게, primary 는
   Apple 관례대로 오른쪽 끝. 버튼 크기는 사다리 large(btn-lg)로 통일한다 —
   D20 이 D19 의 임시 치수(.7rem 1rem / 1.05rem)보다 뒤에 정해졌다.
   primary 색은 사이트 rim primary(잉크)이고 iOS 파랑은 쓰지 않는다 */
.modal {
    --bs-modal-width: 440px;
}

.modal-dialog {
    font-size: var(--fs-body);
}

.modal-content {
    background: var(--panel-bg);
    color: var(--ink);
    border: 0;
    --r-parent: var(--r-sheet);
    border-radius: var(--r-parent);
    box-shadow: var(--panel-shadow), 0 30px 80px -20px rgb(30 42 56 / .35);
}

.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    border: 0;
    padding: 22px 24px 6px;
}

.modal-title {
    color: var(--ink);
    font-size: var(--fs-xl);
    font-weight: var(--fw-title);
    letter-spacing: -.01em;
}

.modal-body {
    padding: 10px 24px 6px;
    color: var(--ink);
}

.modal-footer {
    border: 0;
    padding: 14px 24px 24px;
    gap: 10px;
}

.modal-footer > * {
    margin: 0;
}

.modal-footer .btn {
    flex: 1 1 0;
}

@media (min-width: 768px) {
    .modal {
        --bs-modal-width: 560px;
    }

    .modal-header {
        padding: 26px 28px 8px;
    }

    .modal-body {
        padding-left: 28px;
        padding-right: 28px;
    }

    .modal-footer {
        padding: 18px 28px 26px;
        justify-content: flex-end;
    }

    .modal-footer .btn {
        flex: 0 0 auto;
        min-width: 128px;
    }

    /* primary 는 오른쪽 끝 — DOM 은 변경·닫기 순이라 CSS 로 뒤집는다.
       기존 순서가 낫다면 이 규칙만 뺀다 */
    .modal-footer .favorite-login-button,
    .modal-footer #submitChangeSettingButton {
        order: 2;
    }
}

/* 원형 닫기. Bootstrap 의 .modal-header .btn-close 가 음수 margin 을 주므로
   같은 특정도로 되돌린다 */
.btn-close {
    width: 30px;
    height: 30px;
    padding: 0;
    margin: 0;
    border-radius: 50%;
    background-color: rgb(30 42 56 / .08);
    background-size: .7em;
    opacity: 1;
    transition: background-color .2s var(--ease-effects), transform .38s var(--spring-spatial);
}

.modal-header .btn-close {
    padding: 0;
    margin: 0 0 0 auto;
}

.btn-close:hover {
    background-color: rgb(30 42 56 / .14);
    opacity: 1;
    transform: scale(1.08);
}

.btn-close:focus {
    box-shadow: none;
}

/* 모달 뒤 페이지를 어둡게만 하지 않고 실제로 흐린다 (현행 8px 유지) */
.modal-backdrop {
    --bs-backdrop-bg: #2a3947;
    --bs-backdrop-opacity: .35;
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
}

/* 로그인 안내: 힌트 이미지는 패널 안의 둥근 사진, 안내문은 회색,
   도움말 링크는 잉크색 (index·search 공용) */
.modal-image-wrapper {
    position: relative;
    margin: 4px 0 16px;
}

.modal-image-wrapper > img {
    width: 100%;
    border-radius: var(--r-control);
    box-shadow: 0 0 0 1px rgb(30 42 56 / .08), 0 8px 24px -8px rgb(30 42 56 / .25);
}

.login-modal-info {
    padding: 0 4px;
    color: var(--ink-2);
    font-size: var(--fs-md);
    line-height: 1.6;
}

.login-modal-info a {
    color: var(--ink);
    font-weight: var(--fw-strong);
    white-space: nowrap;
}

/* 이메일 수집 동의: 본문 회색, 소제목만 잉크 */
#agreementModal .modal-body-content {
    color: var(--ink-2);
    font-size: var(--fs-md);
    line-height: 1.7;
}

#agreementModal .modal-body-content h4 {
    color: var(--ink);
    font-size: var(--fs-body);
    font-weight: var(--fw-title);
    margin: 0 0 8px;
}

/* 구독 취소·회원 탈퇴: 안내문 회색 */
#unsubscribeModal .modal-body p,
#deleteUserModal .modal-body p {
    color: var(--ink-2);
    font-size: var(--fs-md);
    margin-bottom: 14px;
}

#unsubscribeModal .field-row,
#deleteUserModal .field-row {
    margin-bottom: 10px;
}

/* 인셋 그룹 목록 (애창곡 설정 모달): iOS 설정 앱의 그룹 표.
   markup: p.inset-group-title + div.inset-group > div.form-check × n
   라디오 input 은 숨기고 행 전체가 선택 영역, 선택된 행 오른쪽에 ✓ */
.inset-group-title {
    margin: 16px 0 8px;
    padding-left: 14px;
    color: var(--ink-2);
    font-size: var(--fs-body);
    font-weight: var(--fw-strong);
}

.inset-group-title:first-child {
    margin-top: 2px;
}

.inset-group {
    overflow: hidden; /* 행 배경을 그룹 모서리로 자른다 */
    border-radius: var(--r-control);
    background: rgb(30 42 56 / .05);
}

.inset-group .form-check {
    position: relative;
    display: flex;
    align-items: center;
    margin: 0;
    padding: 0;
    min-height: 0;
}

.inset-group .form-check + .form-check {
    border-top: 1px solid rgb(30 42 56 / .08);
}

.inset-group .form-check-input {
    position: absolute;
    width: 0;
    height: 0;
    margin: 0;
    opacity: 0;
}

.inset-group .form-check-label {
    flex: 1;
    padding: 12px 16px;
    cursor: pointer;
    color: var(--ink);
    font-size: var(--fs-body);
}

/* ✓: 잉크색 꺾쇠 — 선택 시 스프링으로 튀어나온다 */
.inset-group .form-check::after {
    content: '';
    position: absolute;
    right: 18px;
    top: 50%;
    width: 7px;
    height: 12px;
    margin-top: -8px;
    border: solid var(--ink);
    border-width: 0 2.2px 2.2px 0;
    transform: rotate(45deg) scale(0);
    opacity: 0;
    transition: transform .38s var(--spring-spatial), opacity .15s var(--ease-effects);
}

.inset-group .form-check:has(input:checked)::after {
    transform: rotate(45deg) scale(1);
    opacity: 1;
}

/* 숨긴 라디오의 키보드 포커스는 행에 표시한다 */
.inset-group .form-check:has(input:focus-visible) {
    box-shadow: inset 0 0 0 3px rgb(100 149 237 / .55);
}

@media (hover: hover) {
    .inset-group .form-check:hover {
        background: rgb(30 42 56 / .08);
    }
}

.inset-group .form-check:active {
    background: rgb(30 42 56 / .12);
}

#changeSettingModal .modal-body {
    padding-bottom: 2px;
}

/* 추가 순서 설정: 표 패널은 시트 위라 투명, 안내문 회색.
   선택 행(흰 판 + ✓)은 ③ 표·행 단계의 행 lift 와 함께 옮긴다 */
.modal .song-table-border {
    background: transparent;
    border: 0;
    box-shadow: none;
}

#presentOrderModal .modal-body .text-center {
    color: var(--ink-2);
    font-size: var(--fs-md);
}

/* dark 시트 */
html[data-theme="dark"] .modal-content {
    box-shadow: var(--panel-shadow), 0 30px 80px -20px rgb(0 0 0 / .7);
}

html[data-theme="dark"] .modal-backdrop {
    --bs-backdrop-bg: #05080c;
    --bs-backdrop-opacity: .55;
}

html[data-theme="dark"] .btn-close {
    filter: invert(1) grayscale(1) brightness(1.4);
    background-color: rgb(0 0 0 / .10);
}

html[data-theme="dark"] .btn-close:hover {
    background-color: rgb(0 0 0 / .16);
}

html[data-theme="dark"] .modal-image-wrapper > img {
    box-shadow: 0 0 0 1px rgb(255 255 255 / .10), 0 8px 24px -8px rgb(0 0 0 / .6);
}

html[data-theme="dark"] .inset-group {
    background: rgb(255 255 255 / .06);
}

html[data-theme="dark"] .inset-group .form-check + .form-check {
    border-top-color: rgb(255 255 255 / .08);
}

@media (hover: hover) {
    html[data-theme="dark"] .inset-group .form-check:hover {
        background: rgb(255 255 255 / .10);
    }
}

html[data-theme="dark"] .inset-group .form-check:active {
    background: rgb(255 255 255 / .14);
}

html[data-theme="dark"] .modal .song-table th {
    color: var(--ink-2);
    border-color: rgb(255 255 255 / .12);
}

/* ---------- 모션: scroll edge (ui-rnd 5턴) ----------
   상단바 유리 밑으로 콘텐츠가 지나가면 화면 상단에 점진 블러 베일이
   떠올라, 콘텐츠가 가장자리에서 디졸브된다. 베일 요소는 glass-motion.js 가
   상단바 있는 페이지에만 심고, 표시는 .navbar.scroll-veil-on(scrollY > 8)을
   형제 선택자로 따라간다. 상단바의 backdrop 을 먼저 뭉개지 않도록 짧게 둔다:
   뷰포트와 상단바 사이 틈(0~14px)은 완전 블러, 상단바 중간쯤에서 0 */
.scroll-veil {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 56px;
    z-index: 39; /* 상단바(40) 바로 아래 */
    pointer-events: none;
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    -webkit-mask-image: linear-gradient(to bottom, #000 25%, transparent);
    mask-image: linear-gradient(to bottom, #000 25%, transparent);
    opacity: 0;
    transition: opacity .45s ease;
}

/* 페이지 상단 흐려짐(scroll edge effect) 켜짐 */
.navbar.scroll-veil-on ~ .scroll-veil {
    opacity: 1;
}

/* dark 에서는 베일이 살짝 어둡게도 눌러 글자가 내비바 뒤로 잠기게 한다 */
html[data-theme="dark"] .scroll-veil {
    -webkit-backdrop-filter: blur(12px) brightness(.8);
    backdrop-filter: blur(12px) brightness(.8);
}

/* 모달이 열릴 때 화면이 옆으로 밀리는 것 방지.
   Bootstrap 은 스크롤바를 없애며 body 에 padding-right 를 인라인으로 넣어
   보정하는데, 위의 scrollbar-gutter: stable 이 이미 자리를 확보하므로
   그 보정이 오히려 콘텐츠를 왼쪽으로 민다 — 무효화한다 (인라인보다 우선하도록 !important) */
body.modal-open {
    padding-right: 0 !important;
}

/* 배경 스크롤 잠금은 body 대신 viewport(html)에서 — gutter 가 유지되어 밀림이 없다 */
html:has(body.modal-open) {
    overflow-y: hidden;
}

/* ---------- 그 밖의 Bootstrap 표면 ---------- */
/* 검색 결과를 감싸는 .card: 콘텐츠 패널 안에 또 패널을 두지 않는다 (패널 속 패널 금지) */
.card {
    background: transparent;
    border: 0;
    box-shadow: none;
}

.alert-info {
    color: var(--ky-ink);
    background: var(--ky-tint);
    border: 1px solid rgb(255 255 255 / .6);
    border-radius: var(--r-control);
}

/* 추가일 badge·최신곡 highlight 공용 표식 */
mark,
.mark {
    color: var(--badge-ink);
    background: var(--badge-tint);
    border-radius: 8px;
}

/* ---------- 사진 배경: 씻어낸 노래방 사진 ----------
   사이트 공통으로 가장 뒤에 까는 사진 (D15 — index·검색·애창곡).
   위로 갈수록 선명하고 아래로 갈수록 바탕색에 녹는다. 스크롤하면 함께
   밀려 올라간다. 내비바(유리)가 이 사진을 실제로 블러하고, 콘텐츠 패널은
   frost-baking.js 가 같은 사진을 패널 자리에 맞춰 흐려 구운 그림을 받는다 —
   아래 마스크·페이드·필터 값을 바꾸면 굽기 코드의 값도 함께 맞춘다 (ADR 0002) */
.photo-bg {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 105vh;
    opacity: .55;
    pointer-events: none;
    z-index: 0;
}

/* 사진·마스크·필터는 자식(::before)에 둔다 — 요소 자체에 두면 합성 레이어로
   올라갈 때 필터·마스크가 프레임마다 GPU 패스로 얹힌다. 자식에 두면
   레이어 타일에 한 번 구워진다 (2기 3턴) */
.photo-bg::before {
    content: "";
    position: absolute;
    inset: 0;
    background: url("/assets/img/karaoke-bb8db7072d9433d1f593245adae46a79.jpg") center / cover;
    /* 가장자리를 타원형으로 씻어내 바탕과 잇는다 (세로 72%: 3기 9턴에
       90% 에서 조여 사진이 첫 화면 안에서 끝나게 함) */
    -webkit-mask-image: radial-gradient(115% 72% at 50% 30%, #000 40%, transparent 88%);
    mask-image: radial-gradient(115% 72% at 50% 30%, #000 40%, transparent 88%);
    filter: saturate(.88) brightness(1.05);
}

/* 아래로 갈수록 바탕색에 녹아 사라진다. 페이드 층 자체도 바닥(105vh)
   전에 마스크로 소멸시킨다 — 그러지 않으면 사진이 끝나는 경계에서 거의
   불투명한 회색 띠가 뒤의 빛 웅덩이를 덮는다 (2기 19턴 검색 페이지 결함) */
.photo-bg::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg,
        rgb(255 255 255 / .18) 0%,
        rgb(255 255 255 / .06) 35%,
        rgb(236 241 245 / .55) 70%,
        rgb(234 240 244 / .95) 96%);
    -webkit-mask-image: linear-gradient(#000 75%, transparent 100%);
    mask-image: linear-gradient(#000 75%, transparent 100%);
}

/* dark: 사진을 눌러 어둡게, 페이드는 어두운 바탕색으로 */
html[data-theme="dark"] .photo-bg {
    opacity: .7;
}

html[data-theme="dark"] .photo-bg::before {
    filter: saturate(.75) brightness(.55) contrast(1.05);
}

html[data-theme="dark"] .photo-bg::after {
    background: linear-gradient(180deg,
        rgb(15 21 29 / .1) 0%,
        rgb(15 21 29 / .05) 35%,
        rgb(15 21 29 / .6) 70%,
        rgb(15 21 29 / .96) 96%);
}

/* ---------- 햇살 막대 ----------
   페이지 최상단에 고정된 자리를 차지하고, 스크롤하면 함께 밀려 올라간다.
   개별 막대의 위치·폭·불투명도·블러·딜레이는 markup 의 inline 변수로 지정.
   dark 에는 햇살이 없다 (밤의 노래방) */

.sky {
    position: absolute;
    inset: 0 0 auto 0;
    height: 130vh;
    overflow: hidden;
    pointer-events: none;
    z-index: 0;
}

html[data-theme="dark"] .sky {
    display: none;
}

.ray {
    position: absolute;
    top: -12vh;
    left: var(--x);
    width: var(--w);
    height: 120vh;
    transform-origin: top center;
    transform: rotate(32deg) scaleY(0);
    background: linear-gradient(180deg,
        rgb(var(--sun) / 0) 0%,
        rgb(var(--sun) / var(--a)) 22%,
        rgb(var(--sun) / calc(var(--a) * .55)) 55%,
        rgb(var(--sun) / 0) 88%);
    filter: blur(var(--b));
    opacity: 0;
    animation: ray-in 1.4s var(--ease-float) forwards;
    animation-delay: var(--d);
    /* 떠오르는 동안만 합성 레이어 — 끝나면 JS 가 .settled 를 붙여 해제한다.
       will-change 가 남아 있으면 blur 가 걸린 대형 레이어 8장이 매 프레임
       GPU 에서 다시 블러되어 스크롤·접힘 프레임을 깎는다 (2기 2턴 트레이스) */
    will-change: transform, opacity;
}

.ray.settled {
    will-change: auto; /* 정적 래스터로 강등 — blur 는 한 번만 그려진다 */
}

@keyframes ray-in {
    0% { transform: rotate(32deg) scaleY(0); opacity: 0; }
    55% { opacity: 1; }
    100% { transform: rotate(32deg) scaleY(1); opacity: 1; }
}

/* ---------- 보케 ----------
   히어로 사진의 조명 방울을 CSS 로 추상화한 잔상. 콘텐츠 패널 바깥의
   배경에서 떠다니고, 패널 안에는 굽기 시점의 모습이 프로스트에 정지 상태로
   들어간다 (D11). 위치·크기는 glass-motion.js 가 로드마다 랜덤으로 흩뿌린다 */
.bokeh {
    position: absolute;
    /* 점이 영역 밖으로 튀어나와도 문서 스크롤 높이에 관여하지 못하게
       overflow 로 자른다 — 없으면 컬랩스로 영역이 줄 때 하단 점(특히
       페이드아웃 중인 추가 점)이 사라질 때까지 스크롤이 길게 남는다.
       좌우는 600px 씩 넓혀 두는데, 기준 영역이 뷰포트보다 좁은 페이지
       (index·애창곡 섹션)에서 경계에 걸친 점이 세로 직선으로 잘리는 것을
       화면 밖으로 밀어내기 위함이다. JS 산포는 이 여백을 빼고 계산한다 */
    inset: -60px -600px;
    z-index: -1;
    pointer-events: none;
    overflow: hidden;
}

.bokeh i {
    position: absolute;
    left: var(--x);
    top: var(--y);
    width: var(--s);
    height: var(--s);
    border-radius: 50%;
    background: radial-gradient(circle, rgb(var(--c) / var(--o)) 0%, rgb(var(--c) / 0) 68%);
    filter: blur(6px);
}

html[data-theme="dark"] .bokeh {
    opacity: .6;
}

/* ---------- 로드 시퀀스 (딜레이 기반) ----------
   햇살(CSS 애니메이션) → 상단바 → 히어로 순서.
   .veil 이 붙은 요소는 숨은 채 시작하고, glass-motion.js 가 .on 을 붙이면
   inline 변수 --vd 만큼 기다렸다가 떠오른다 */
.veil {
    opacity: 0;
    transform: translateY(22px);
    filter: blur(14px);
    transition: opacity 1s var(--ease-float), transform 1s var(--ease-float), filter 1s var(--ease-float);
    transition-delay: var(--vd, 0s);
    will-change: transform, opacity, filter;
}

.veil.on {
    opacity: 1;
    transform: none;
    /* none 이어야 한다 — blur(0) 은 0px 짜리 필터 패스라 렌더 서피스가
       상시 남는다. none 으로의 전이는 같은 함수 목록의 항등(blur(0))으로
       보간되므로 떠오름 모션은 그대로다. will-change 도 도착 후엔 걷는다
       (전환 중 승격은 브라우저가 알아서 한다) (2기 2턴) */
    filter: none;
    will-change: auto;
}

/* ---------- 스크롤 리빌: 희미한 유리 → 선명 ----------
   [data-lift] 요소가 30% 이상 보이면 glass-motion.js 가 .is-lit 을 붙인다 */
[data-lift] {
    opacity: .14;
    transform: translateY(34px) scale(.985);
    filter: blur(12px);
    transition: opacity 1.1s var(--ease-float), transform 1.1s var(--ease-float), filter 1.1s var(--ease-float);
    will-change: transform, opacity, filter;
}

[data-lift].is-lit {
    opacity: 1;
    transform: none;
    filter: none; /* .veil.on 과 같은 취지 — 도착 후 필터 패스·레이어를 남기지 않는다 */
    will-change: auto;
}

/* 표의 행은 패널이 떠오른 뒤 위에서부터 순서대로 */
[data-lift] tbody tr {
    opacity: 0;
    transform: translateY(14px);
}

[data-lift].is-lit tbody tr {
    animation: row-in .7s var(--ease-float) both;
}

[data-lift].is-lit tbody tr:nth-child(1) { animation-delay: .25s; }
[data-lift].is-lit tbody tr:nth-child(2) { animation-delay: .35s; }
[data-lift].is-lit tbody tr:nth-child(3) { animation-delay: .45s; }
[data-lift].is-lit tbody tr:nth-child(4) { animation-delay: .55s; }
[data-lift].is-lit tbody tr:nth-child(5) { animation-delay: .65s; }
[data-lift].is-lit tbody tr:nth-child(6) { animation-delay: .75s; }

@keyframes row-in {
    to { opacity: 1; transform: none; }
}

/* 더보기(컬랩스)로 여는 표는 행 스태거를 끄고 표 전체가 한 번에 떠오른다.
   행마다 딜레이를 주면 높이 애니메이션(0.5s)이 끝난 뒤에도 1초 넘게
   행 단위 재페인트가 이어져 버벅임의 원인이 된다 (3턴 피드백).
   시작 시점은 펼침 시작과 동시 — 컬랩스는 위에서부터 드러나므로
   먼저 보이는 윗부분이 이미 차올라 있어야 빈 유리 패널이 보이지 않는다.
   (뒤 선언이 위의 nth-child 딜레이를 덮도록 이 블록은 스태거 뒤에 둘 것) */
[data-lift].is-lit .collapse tbody tr,
[data-lift].is-lit .collapsing tbody tr {
    animation: none;
    opacity: 1;
    transform: none;
}

[data-lift] .collapsing .song-table,
[data-lift] .collapse.show .song-table {
    /* 딜레이 .45s: 패널이 거의 다 펼쳐진 다음 내용이 떠오르는 2박자 연출.
       동시 시작(3턴)은 펼침 클리핑에 모션이 묻혀 보이지 않아 4턴에서 조정 */
    animation: table-in .5s var(--ease-float) .45s both;
}

@keyframes table-in {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: none; }
}

/* ---------- 더보기 행 리빌: 표 일부 행(d-none) 펼침 ----------
   통합검색 더보기(song-search.js)가 쓴다. 래퍼 높이 전환과 함께,
   드러나는 행이 index 리빌과 같은 어휘(row-in)로 하나씩 떠오른다.
   딜레이는 JS 가 --rd 로 주는 계단 — index 지난달 컬랩스와 같은 타이밍
   (첫 행 50ms + 행당 70ms)에, 최대 45행의 꼬리가 늘어지지 않게 상한
   1030ms(index 의 n+15 꼬리와 동일)를 둔다 (21턴에 index 와 통일).
   높이 전환과 행 스태거의 동시 진행은 3턴에서 버벅임으로 껐던
   조합이지만, 버벅임의 진범(문서 높이에 묶인 body 그라디언트)이 제거되어
   되살린다. 두 번째 선택자는 위 컬랩스 스태거 해제 블록(0-3-2)보다
   특정도를 높여 이긴다 */
tbody tr.more-reveal,
[data-lift].is-lit .collapse tbody tr.more-reveal {
    opacity: 0;
    transform: translateY(14px);
    animation: row-in .7s var(--ease-float) var(--rd, 0s) both;
}

/* 접는 동안은 리빌을 멈춘다 — 접힘 측정과 종료의 d-none 토글이 display
   복귀마다 애니메이션을 재시작시키는 것을 막는다 */
tbody tr.more-reveal.more-settled,
[data-lift].is-lit .collapse tbody tr.more-reveal.more-settled {
    animation: none;
    opacity: 1;
    transform: none;
    transition: opacity .3s ease;
}

/* 접힘과 동시에 내용을 지운다 — 스태거 없이 행 다발이 한 번에 사라지고
   (.3s, 접힘 1s 보다 빠르게), 비워진 패널이 뒤따라 접힌다 (2기 4턴) */
tbody tr.more-reveal.more-settled.more-hide,
[data-lift].is-lit .collapse tbody tr.more-reveal.more-settled.more-hide {
    opacity: 0;
}

/* 컬랩스 안에 나중에 삽입되는 표(추가검색 placeholder·갱신 결과)는 위
   table-in(2박자 통짜 떠오름)을 끈다 — placeholder 는 즉시 보여야 하고,
   갱신 결과는 행 스태거(.more-reveal)가 이중 fade 없이 단독으로 맡는다 */
[data-lift] .collapsing .song-table.no-table-in,
[data-lift] .collapse.show .song-table.no-table-in {
    animation: none;
    opacity: 1;
}

/* ---------- index 지난달 컬랩스: 통짜 → 행 스태거 (21턴) ----------
   table-in 통짜 떠오름 대신 행이 하나씩 떠오른다. 3턴에 뺐던 행 단위
   리빌의 복원 — 당시 버벅임의 진범(셀 border 페인트)이 11턴에서 제거되어
   족쇄가 풀렸다. 펼침 시작과 동시에 위 행부터 차오르고(검색 더보기와 같은
   호흡), 접힘의 .collapsing 에서도 같은 선언이 유지되므로 애니메이션이
   재시작되지 않고 끝 상태(보임) 그대로 접힌다 */
[data-lift] .song-table-collapse.collapsing .song-table,
[data-lift] .song-table-collapse.collapse.show .song-table {
    animation: none;
    opacity: 1;
}

[data-lift].is-lit .song-table-collapse.collapsing tbody tr,
[data-lift].is-lit .song-table-collapse.collapse.show tbody tr {
    /* 기점(0)을 함께 선언해야 한다 — 없으면 위 스태거 해제 블록의
       opacity 1 이 기점이 되어 row-in 이 1→1 로 무효가 된다 */
    opacity: 0;
    transform: translateY(14px);
    animation: row-in .7s var(--ease-float) var(--rd, 0s) both;
}

/* 접힘: hide 시작에 glass-motion.js 가 .closing 을 붙인다 — 접힘 측정의
   display 토글이 애니메이션을 재시작시키므로, 접히는 동안은 리빌을 멈추고
   보인 채로 접는다 (검색 more-settled 와 같은 취지). 스태거 블록 뒤에 둘 것 */
[data-lift].is-lit .song-table-collapse.closing tbody tr {
    animation: none;
    opacity: 1;
    transform: none;
}

/* 접힘 높이 전환을 1s 감속 곡선으로 — 22턴 track 팔로우는 화면이 접힘
   속도를 그대로 타므로, 관성 활강의 느낌은 접힘 전환 자체가 만든다.
   .closing 은 접힘 중에만 붙으므로 펼침은 부트스트랩 기본(.35s ease) 유지.
   부트스트랩 JS 는 전환 시간을 computed style 에서 읽어 hidden 발화를
   맞추므로 CSS 로 늘려도 안전하다 */
.song-table-collapse.collapsing.closing {
    transition-duration: 1s;
    transition-timing-function: cubic-bezier(.16, 1, .3, 1);
}

/* 행별 딜레이 — 70ms 계단 (21턴: 100→80→70 으로 조임, 검색 더보기도 동일).
   정의 밖의 행이 딜레이 0 으로 첫 행보다 먼저 튀지 않게 꼬리(n+15)를
   마지막 값으로 받는다 */
.song-table-collapse tbody tr:nth-child(1) { --rd: .05s; }
.song-table-collapse tbody tr:nth-child(2) { --rd: .12s; }
.song-table-collapse tbody tr:nth-child(3) { --rd: .19s; }
.song-table-collapse tbody tr:nth-child(4) { --rd: .26s; }
.song-table-collapse tbody tr:nth-child(5) { --rd: .33s; }
.song-table-collapse tbody tr:nth-child(6) { --rd: .4s; }
.song-table-collapse tbody tr:nth-child(7) { --rd: .47s; }
.song-table-collapse tbody tr:nth-child(8) { --rd: .54s; }
.song-table-collapse tbody tr:nth-child(9) { --rd: .61s; }
.song-table-collapse tbody tr:nth-child(10) { --rd: .68s; }
.song-table-collapse tbody tr:nth-child(11) { --rd: .75s; }
.song-table-collapse tbody tr:nth-child(12) { --rd: .82s; }
.song-table-collapse tbody tr:nth-child(13) { --rd: .89s; }
.song-table-collapse tbody tr:nth-child(14) { --rd: .96s; }
.song-table-collapse tbody tr:nth-child(n+15) { --rd: 1.03s; }

/* ---------- 크롬 유리 재질: 층 토큰과 세 상태 (5기 4번) ----------
   크롬 유리(.glass-capsule — 브랜드 캡슐, 메뉴 캡슐)의 재질은 여기 한 곳에 있다.
   층마다 토큰: 렌징(--glass-blur, --glass-saturate, --glass-refraction),
   틴트(--glass-tint-a 좌상, --glass-tint-b 우하, --glass-tint-ink 흰색에 섞는 잉크),
   그림자(--glass-shadow-alpha). 상태는 셋이다.
     사진 위           — 맑은 유리: 블러·틴트를 최소로
     콘텐츠 위         — 보통 유리: 뒤 글자의 잔무늬를 블러로 지우고, 흰 패널 위에서 묻히지 않게
                         틴트에 잉크를 섞는다. 그림자는 진해진다
     busy 콘텐츠 위    — 두꺼운 유리: 보통 유리에서 틴트를 훨씬 불투명하게 해 글자 대비를 받친다
   glass-motion.js 가 캡슐마다 판정해, 콘텐츠 패널(.content-panel)이 밑에 들어오면
   .over-content 를, 글자 밑에 busy 패널(.content-panel-busy)이 있으면 대신
   .over-content-busy 를 붙인다. 내려가 있는 메뉴 캡슐은 늘 콘텐츠 위다.
   busy 패널은 실측에서 캡슐 글자 대비가 깨지는 패널에 손으로 단다. 숫자 토큰을 @property 로 등록해 상태
   사이를 CSS 전환으로 섞는다(등록을 모르는 브라우저는 전환 없이 바뀐다).
   --glass-refraction 은 glass-material.js 가 url(#필터) 로 채운다.
   하이라이트 층은 glass-material.js 가 곡면의 법선으로 구운 이미지(--glass-specular)
   이고, .has-baked-specular 가 붙으면 conic 링과 상하 림 글린트를 대신한다 */
@property --glass-blur { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --glass-saturate { syntax: "<number>"; inherits: false; initial-value: 1; }
@property --glass-tint-a { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@property --glass-tint-b { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@property --glass-tint-ink { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@property --glass-shadow-alpha { syntax: "<number>"; inherits: false; initial-value: 0; }

.glass-capsule {
    --glass-blur: .8px;
    --glass-saturate: 1.4;
    --glass-tint-a: 5%;
    --glass-tint-b: 2%;
    --glass-tint-ink: 0%;
    --glass-shadow-alpha: .17;
    --glass-shadow-rgb: 30 42 56;
    --glass-tint-color: color-mix(in srgb, rgb(30 42 56) var(--glass-tint-ink), #fff);
    --glass-state-transition:
        --glass-blur .35s ease-in-out, --glass-saturate .35s ease-in-out,
        --glass-tint-a .35s ease-in-out, --glass-tint-b .35s ease-in-out,
        --glass-tint-ink .35s ease-in-out, --glass-shadow-alpha .35s ease-in-out;
    background: linear-gradient(135deg, rgb(255 255 255 / .12), rgb(255 255 255 / .04));
    background: linear-gradient(135deg,
        color-mix(in srgb, var(--glass-tint-color) var(--glass-tint-a), transparent),
        color-mix(in srgb, var(--glass-tint-color) var(--glass-tint-b), transparent));
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)) var(--glass-refraction,);
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)) var(--glass-refraction,);
    box-shadow:
        0 4px 12px rgb(var(--glass-shadow-rgb) / var(--glass-shadow-alpha)),
        inset 0 1px 1px rgb(255 255 255 / .65),
        inset 0 -1px 1px rgb(255 255 255 / .18);
}

.glass-capsule:is(.over-content, .over-content-busy) {
    --glass-blur: 4px;
    --glass-saturate: 1.9;
    --glass-tint-a: 12%;
    --glass-tint-b: 4%;
    --glass-tint-ink: 21%;
    --glass-shadow-alpha: .24;
}

/* 구운 스페큘러: ::after 의 conic 링(padding·mask 로 두께만 남긴 것)을 걷고 이미지
   한 장을 요소 크기에 맞춰 얹는다. 림은 이미지에 들어 있으므로 안쪽 그림자는 뺀다.
   dark 의 conic 규칙(containers·common.css)을 이기도록 같은 선택자를 나란히 둔다.
   그림자 규칙은 :where 로 특정도를 재질 절과 같게 두어 고대비 절의 테두리가 이긴다 */
:where(.glass-capsule).has-baked-specular {
    box-shadow: 0 4px 12px rgb(var(--glass-shadow-rgb) / var(--glass-shadow-alpha));
}

.glass-capsule.has-baked-specular::after,
html[data-theme="dark"] .glass-capsule.has-baked-specular::after {
    padding: 0;
    -webkit-mask: none;
    mask: none;
    background: var(--glass-specular) center / 100% 100% no-repeat;
}

html[data-theme="dark"] .glass-capsule {
    --glass-tint-a: 3.5%;
    --glass-tint-b: 1.2%;
    --glass-shadow-rgb: 0 0 0;
}

html[data-theme="dark"] .glass-capsule:is(.over-content, .over-content-busy) {
    --glass-tint-a: 8.4%;
    --glass-tint-b: 2.4%;
}

/* busy 콘텐츠 위 (5기 마무리, P16): 틴트를 테마 쪽으로 짙게 해 글자 대비를 4.5:1 위로
   올린다. 라이트는 흰 틴트, 다크는 잉크를 가득 섞은 어두운 틴트다 */
.glass-capsule.over-content-busy {
    --glass-tint-a: 58%;
    --glass-tint-b: 50%;
    --glass-tint-ink: 0%;
}

html[data-theme="dark"] .glass-capsule.over-content-busy {
    --glass-tint-a: 70%;
    --glass-tint-b: 62%;
    --glass-tint-ink: 100%;
}

/* 캡슐별 동적 다크모드 (5기 5번): 라이트 테마에서 어두운 영상·이미지 위에 있는 캡슐은
   어두운 유리가 된다. 틴트에 잉크를 가득 섞어 짙게 하고 글자를 밝게 한다. 판정은
   glass-motion.js 가 캡슐 밑의 픽셀 밝기를 읽어 .is-over-dark 를 붙인다.
   다크 테마의 유리는 원래 어두우므로 해당하지 않는다 */
html:not([data-theme="dark"]) .glass-capsule.is-over-dark {
    --glass-tint-ink: 100%;
    --glass-tint-a: 42%;
    --glass-tint-b: 26%;
    --glass-shadow-alpha: .3;
}

.brand-capsule .navbar-brand,
.menu-capsule .icon-link-label {
    transition: color .35s ease-in-out;
}

html:not([data-theme="dark"]) .glass-capsule.is-over-dark .navbar-brand,
html:not([data-theme="dark"]) .glass-capsule.is-over-dark .icon-link,
html:not([data-theme="dark"]) .glass-capsule.is-over-dark .icon-link-label {
    color: #f2f5f8;
}

html:not([data-theme="dark"]) .glass-capsule.is-over-dark .selection-lens {
    background: rgb(255 255 255 / .16);
    box-shadow: inset 0 1px 1px rgb(255 255 255 / .3), 0 2px 8px rgb(0 0 0 / .3);
}

/* ---------- 크롬 유리의 접점 반응 (5기 4번) ----------
   세 가지 모두 glass-motion.js 가 상태만 붙이고 모습은 여기서 정한다.
   1) 접점 발광: 누르는 동안 포인터 자리에서 빛이 번진다 (::before, 내용 아래).
      backdrop-filter 와 무관한 그라디언트 한 장
   2) 선택 렌즈: 그룹 안의 버튼마다 hover 배경을 켜는 대신, 밝은 렌즈 한 장이
      포인터가 올라간 버튼으로 미끄러진다. 넘침은 5.5%(사이트 스프링의 절반쯤)
   3) 젤 프레스: 눌린 버튼이 납작하게 퍼졌다가 스프링으로 돌아온다 */
@property --glass-glow-radius { syntax: "<length>"; inherits: false; initial-value: 30px; }

.glass-capsule::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    pointer-events: none;
    background: radial-gradient(circle var(--glass-glow-radius) at var(--glass-glow-x, 50%) var(--glass-glow-y, 50%),
        rgb(255 255 255 / .45), transparent 72%);
    mix-blend-mode: plus-lighter;
    opacity: 0;
    transition: opacity .7s ease, --glass-glow-radius .7s ease;
}

.glass-capsule.is-glowing::before {
    --glass-glow-radius: 130px;
    opacity: 1;
    transition: opacity .12s ease, --glass-glow-radius .55s cubic-bezier(.2, .7, .2, 1);
}

.has-selection-lens {
    position: relative;
    --selection-lens-spring: linear(0, .06 2%, .25 6%, .6 12%, .88 18%, 1.03 25%, 1.055 31%, 1.04 38%, 1.01 48%, .997 60%, 1);
}

.has-selection-lens .icon-link {
    position: relative;
    z-index: 1;
}

.selection-lens {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 0;
    border-radius: var(--r-capsule);
    background: rgb(255 255 255 / .62);
    box-shadow: inset 0 1px 1px rgb(255 255 255 / .8), 0 2px 6px rgb(30 42 56 / .09);
    pointer-events: none;
    opacity: 0;
    /* 위치는 translate 속성으로 옮긴다 — transform 에 두면 scale 이 이동 거리까지 곱한다 */
    transition:
        translate .5s var(--selection-lens-spring), width .5s var(--selection-lens-spring),
        height .5s var(--selection-lens-spring), scale .45s var(--selection-lens-spring), opacity .25s ease;
}

.selection-lens.is-on { opacity: 1; }
.selection-lens.is-placing { transition: none; }

html[data-theme="dark"] .selection-lens {
    background: rgb(255 255 255 / .16);
    box-shadow: inset 0 1px 1px rgb(255 255 255 / .3), 0 2px 8px rgb(0 0 0 / .3);
}

/* 렌즈가 hover 배경을 대신한다. 버튼의 hover 규칙은 뒤에 로드되는 common·containers.css
   에 있어 같은 특정도로는 지므로 클래스를 겹쳐 한 단계 올린다 */
.has-selection-lens.has-selection-lens .icon-link:hover,
html[data-theme="dark"] .has-selection-lens.has-selection-lens .icon-link:hover {
    background: transparent;
}

.has-selection-lens .icon-link {
    transition: scale .6s var(--spring-spatial), background .3s, color .3s;
}

.has-selection-lens.has-selection-lens .icon-link:active {
    transform: none;
    scale: 1.1 .88;
    transition: scale .09s ease-out;
}

/* ---------- 크롬 유리 폴백 사다리 (ADR 0003, P15) ----------
   위에서 아래로, 감지로만 내려간다:
   1) 굴절 + 블러 — Chromium: glass-material.js 가 .glass-capsule
      의 --glass-refraction 에 SVG 변위 맵 url() 을 채운다
   2) 블러 — backdrop-filter 를 지원하는 나머지 브라우저: 위 재질 절 그대로.
      이 상태가 완성된 디자인이다
   3) 색만 — backdrop-filter 없음: 얇은 틴트로는 뒤 글자가 그대로 비치므로
      알파를 올린다 (아래). 링·그림자는 그대로
   4) 불투명 — 투명도 최소화 설정: 접근성 절
   재질 절과 특정도가 같으므로 태그를 붙여 한 단계 올린다 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    div.glass-capsule {
        background: rgb(255 255 255 / .82);
    }
    html[data-theme="dark"] div.glass-capsule {
        background: rgb(27 36 48 / .86);
    }
    html:not([data-theme="dark"]) div.glass-capsule.is-over-dark {
        background: rgb(27 36 48 / .86);
    }
}

/* ---------- 접근성: 모션·투명도·대비 각각 대응 (P14) ----------
   3기 구조에서 손댈 것: 스프링 토큰(탄성)·로드 시퀀스·리빌·행 lift 의 모션,
   크롬 유리(브랜드 캡슐·메뉴 캡슐·모달 베일·스크롤 베일)의 투명도, 패널·버튼·
   입력창의 테두리 대비. 콘텐츠 패널은 불투명 프로스트라 투명도 설정과 무관하고,
   굴절(glass-material.js)은 투명도 최소화에서 스스로 물러선다 */
/* 모션 최소화: 탄성만 끄고(spatial → effects) 큰 전환은 짧은 크로스페이드.
   편집 모드 컨트롤·집은 행·옮겨진 행(favorite-song.css)과 행 lift 는 같은
   토큰을 쓰므로 토큰 교체만으로 탄성이 빠진다 */
@media (prefers-reduced-motion: reduce) {
    :root {
        --spring-spatial: var(--ease-effects);
    }
    .ray { animation-duration: .01s; animation-delay: 0s; }
    .veil, [data-lift] { transition: opacity .2s ease; }
    .veil { opacity: 1; transform: none; filter: none; }
    [data-lift] { opacity: 1; transform: none; filter: none; }
    [data-lift] tbody tr { animation: none; opacity: 1; transform: none; }
    [data-lift] .collapsing .song-table, [data-lift] .collapse.show .song-table { animation: none; }
    tbody tr.more-reveal, [data-lift].is-lit .collapse tbody tr.more-reveal { animation: none; opacity: 1; transform: none; }
    [data-lift].is-lit .song-table-collapse.collapsing tbody tr, [data-lift].is-lit .song-table-collapse.collapse.show tbody tr { animation: none; opacity: 1; transform: none; }
    /* 선택 렌즈는 미끄러지지 않고 자리만 바뀌고, 젤 프레스는 끈다 */
    .selection-lens { transition: opacity .2s ease; }
    .has-selection-lens.has-selection-lens .icon-link:active { scale: none; }
    /* 브랜드 캡슐 축소(containers.css)는 위치 이동이라 끈다. containers.css 가 이 파일보다 뒤에
       불러와지므로 html 을 앞에 붙여 이긴다 */
    html .navbar.is-past-top-zone .brand-capsule { scale: none; translate: none; }
    /* 메뉴 캡슐은 날아가지 않고 짧게 사라졌다가 새 자리에 나타난다 (glass-motion.js) */
    div.menu-capsule { transition: opacity .15s ease; }
    .btn:hover, .btn-close:hover { transform: none; }
}

/* 투명도 최소화: 폴백 사다리의 맨 아래 단 — 크롬 유리를 불투명하게.
   콘텐츠 패널은 원래 불투명이라 손댈 것이 없다.
   두 캡슐은 재질 선언이 뒤에 로드되는 파일(containers·
   common.css)에 있어, 같은 특정도로는 지므로 태그를 붙여 한 단계 올린다 */
@media (prefers-reduced-transparency: reduce) {
    div.glass-capsule {
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
    div.glass-capsule {
        background: rgb(255 255 255 / .95);
    }
    html[data-theme="dark"] div.glass-capsule {
        background: rgb(27 36 48 / .96);
    }
    html:not([data-theme="dark"]) div.glass-capsule.is-over-dark {
        background: rgb(27 36 48 / .96);
    }
    div.glass-capsule::after { display: none; }
    .modal-backdrop { -webkit-backdrop-filter: none; backdrop-filter: none; }
    .scroll-veil { display: none; }
}

/* 고대비: 잉크를 진하게, 크롬 유리를 거의 불투명하게, 장식 빛은 제거.
   림 보더가 기본 transparent(에지 스택)이므로 여기서 직접 되살린다 —
   상태 선택자까지 나란히 선언해 hover/active 의 transparent 를 이긴다 */
@media (prefers-contrast: more) {
    :root {
        --ink-2: #333f4a;
        --edge-contrast: rgb(70 85 100 / .55);
    }
    .content-panel, .modal-content, div.glass-capsule {
        box-shadow: 0 0 0 1px var(--edge-contrast);
    }
    .form-control {
        background: #fff;
        border-color: var(--edge-contrast);
    }
    /* 알파 몸체만으로는 경계가 약하므로 또렷한 테두리 하나를 더한다 */
    .btn, .btn:hover, .btn:active {
        box-shadow: 0 0 0 1.5px var(--edge-contrast);
    }
    /* 프로스트(구운 사진)는 덮개 아래로 비쳐 글자 대비를 깎는다 — 바탕색 패널만 */
    .content-panel { background-image: none; }
    .content-panel.frost-crossfade::before { display: none; }
    /* 맑은 유리는 몸체가 얇은 틴트라 대비가 부족하다 — 거의 불투명으로
       올리고 장식 테두리(conic ::after)는 제거해 림 보더만 남긴다 */
    div.glass-capsule {
        background: rgb(255 255 255 / .9);
    }
    html[data-theme="dark"] div.glass-capsule {
        background: rgb(27 36 48 / .94);
    }
    html:not([data-theme="dark"]) div.glass-capsule.is-over-dark {
        background: rgb(27 36 48 / .94);
    }
    div.glass-capsule::after { display: none; }
    .bokeh { display: none; }
    .ray { display: none; }
}
