/* 코드하루 대시보드 커스텀 스타일
   출처: unban-integration-agent/apps/web_dashboard 의 시각 효과(스켈레톤 로더, 라이브 펄스,
   카드 호버, 보더빔)를 이 프로젝트 맥락으로 이식. */

body, .navbar-brand, .nav-link-title, .page-title, .card-title, .form-label, .btn, input, textarea {
    font-family: 'Noto Sans KR', sans-serif !important;
}

/* --- Skeleton UI (로딩 중 표시용, 이후 비동기 위젯에서 재사용) --- */
.skeleton-loader {
    display: inline-block;
    height: 1em;
    width: 100%;
    background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
    background-size: 200% 100%;
    animation: shimmer 1.5s infinite;
    border-radius: 4px;
    vertical-align: middle;
}
@keyframes shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* --- Live Status Pulse --- */
.status-pulse {
    display: inline-block;
    flex-shrink: 0; /* 좁은 flex 헤더(사이드바 브랜드 줄)에서 짜부라져 0px가 되던 문제 방지 */
    width: 8px;
    height: 8px;
    background-color: #2fb344;
    border-radius: 50%;
    margin-right: 8px;
    vertical-align: middle;
    position: relative;
}
.status-pulse::after {
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: inherit;
    border-radius: 50%;
    animation: pulse-ring 1.5s cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite;
}
@keyframes pulse-ring {
    0% { transform: scale(0.7); opacity: 0.8; }
    80%, 100% { transform: scale(2.5); opacity: 0; }
}
.pulse-blue { background-color: #206bc4; }

/* --- Micro-interactions --- */
.card {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
    transform: translateY(-4px);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}
.card-header {
    transition: background-color 0.3s ease;
}
.card:hover .card-header {
    background-color: rgba(32, 107, 196, 0.1) !important;
}
/* 문서 뷰어처럼 세로로 아주 긴 카드는 hover 리프트/그림자 대상에서 제외한다.
   (큰 요소에 transform+box-shadow 트랜지션을 걸면 스크롤된 아래쪽에서 호버할 때
   헤더 쪽 다운로드 버튼 배경이 부분적으로 깨져 보이는 리페인트 현상이 있었음) */
.card.card-static,
.card.card-static:hover {
    transform: none;
    box-shadow: var(--tblr-card-box-shadow, none);
}
.card.card-static:hover .card-header {
    background-color: inherit !important;
}

/* --- Animated Border Beam --- */
.border-beam {
    position: relative;
    overflow: hidden;
}
.border-beam::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: -100%;
    width: 100%;
    height: 2px;
    background: linear-gradient(to right, transparent, #206bc4, transparent);
    animation: beam-move 3s linear infinite;
}
@keyframes beam-move {
    0% { left: -100%; }
    100% { left: 100%; }
}

/* --- 서브 탭 디자인 표준 (W3C ARIA APG Tabs 패턴 기반, CLAUDE.md "UI — 서브 탭" 참고) --- */
.subtabs.nav-tabs { border-bottom: none; gap: .25rem; }
.subtabs .nav-link {
    color: var(--tblr-secondary, #626976);
    background: transparent;
    border: none;
    border-radius: 8px;
    font-weight: 500;
    padding: .5rem 1rem;
    transition: background-color .15s ease, color .15s ease;
}
.subtabs .nav-link:hover {
    background: rgba(32, 107, 196, 0.1);
    color: #206bc4;
}
.subtabs .nav-link:focus-visible {
    outline: 2px solid #206bc4;
    outline-offset: 2px;
}
.subtabs .nav-link.active,
.subtabs .nav-link.active:hover {
    background: #206bc4;
    color: #ffffff;
}

/* --- Game shell (숫자 잇기 퍼즐 / 기억력 게임 / NumSlide 공용) --- */
.game-hero {
    border-radius: 16px;
    padding: 1.1rem 1.4rem;
    margin-bottom: 1rem;
    color: #fff;
    display: flex;
    align-items: center;
    gap: .9rem;
    box-shadow: 0 10px 24px -12px rgba(0, 0, 0, 0.35);
}
.game-hero .game-hero-icon {
    width: 44px;
    height: 44px;
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.18);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.game-hero .game-hero-icon svg { width: 26px; height: 26px; stroke: #fff; }
.game-hero h3 { color: #fff; margin-bottom: .15rem; }
.game-hero p { color: rgba(255, 255, 255, 0.85); margin-bottom: 0; font-size: .875rem; }
.game-hero--indigo { background: linear-gradient(135deg, #6366f1, #4338ca); }
.game-hero--teal { background: linear-gradient(135deg, #14b8a6, #0f766e); }
.game-hero--amber { background: linear-gradient(135deg, #f59e0b, #b45309); }
.game-hero--purple { background: linear-gradient(135deg, #a855f7, #6b21a8); }
.game-hero--rose { background: linear-gradient(135deg, #f43f5e, #9f1239); }

/* --- AI 순발력 퀴즈(specs/012) 실시간 연출 --- */
.quiz-rush-timerbar-track {
    height: 10px;
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.08);
    overflow: hidden;
    margin-bottom: 1rem;
}
.quiz-rush-timerbar-fill {
    height: 100%;
    border-radius: 6px;
    background: linear-gradient(90deg, #f43f5e, #fb923c);
    transition: width 1s linear;
}
.quiz-rush-choice-btn.correct { background: #dcfce7 !important; border-color: #15803d !important; color: #15803d !important; }
.quiz-rush-choice-btn.wrong { background: #fee2e2 !important; border-color: #b91c1c !important; color: #b91c1c !important; }
.quiz-rush-combo-pop {
    position: absolute;
    top: 8px;
    right: 8px;
    font-weight: 800;
    font-size: 1.1rem;
    color: #f43f5e;
    opacity: 0;
    pointer-events: none;
}
.quiz-rush-combo-pop.show {
    animation: quiz-rush-combo-pop .55s ease-out;
}
@keyframes quiz-rush-combo-pop {
    0% { opacity: 0; transform: translateY(6px) scale(.8); }
    30% { opacity: 1; transform: translateY(-4px) scale(1.15); }
    100% { opacity: 0; transform: translateY(-18px) scale(1); }
}
.quiz-rush-score-countup {
    font-size: 3rem;
    font-weight: 800;
    color: #f43f5e;
}
.quiz-rush-generating-spinner { width: 3rem; height: 3rem; }

.scene-progress-dots { display: flex; gap: .4rem; align-items: center; }
.scene-progress-dots .dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--tblr-border-color, #dee2e6);
    transition: background-color .15s ease, transform .15s ease;
}
.scene-progress-dots .dot.filled { background: #6b21a8; transform: scale(1.15); }

.story-panel {
    background: linear-gradient(180deg, rgba(168, 85, 247, 0.06), rgba(168, 85, 247, 0));
    border: 1px solid rgba(168, 85, 247, 0.18);
    border-radius: 14px;
    padding: 1.1rem 1.25rem;
    position: relative;
}
.story-panel::before {
    content: "\201C";
    position: absolute;
    top: -.4rem;
    left: .6rem;
    font-size: 2.5rem;
    color: rgba(168, 85, 247, 0.25);
    font-family: Georgia, serif;
    line-height: 1;
}

/* 90년대 PC통신(하이텔) 텍스트 어드벤처 감성의 터미널 박스 */
.terminal-box {
    background: #0a0f0a;
    border: 1px solid rgba(74, 222, 128, 0.3);
    border-radius: 8px;
    padding: 1.1rem 1.25rem;
    box-shadow: inset 0 0 24px rgba(74, 222, 128, 0.06), 0 8px 20px -10px rgba(0, 0, 0, 0.5);
}
.terminal-box .terminal-narration {
    font-family: "Consolas", "SFMono-Regular", "Courier New", monospace;
    color: #86efac;
    line-height: 1.75;
    white-space: pre-line;
}
.terminal-box .terminal-narration::before { content: "> "; color: #4ade80; opacity: .7; }
.terminal-choices { display: flex; flex-direction: column; gap: .5rem; margin-top: 1rem; }
.terminal-choice-btn {
    font-family: "Consolas", "SFMono-Regular", "Courier New", monospace;
    text-align: left;
    white-space: normal;
    background: #0a0f0a;
    border: 1px solid rgba(74, 222, 128, 0.35);
    color: #d1fae5;
    border-radius: 6px;
    padding: .6rem .9rem;
    transition: background-color .12s ease, border-color .12s ease;
}
.terminal-choice-btn:hover {
    background: #12241a;
    border-color: rgba(74, 222, 128, 0.7);
    color: #f0fdf4;
}
.terminal-choice-btn .choice-index { color: #4ade80; font-weight: 700; margin-right: .4rem; }

.hp-bar-track {
    width: 100%;
    height: 8px;
    border-radius: 4px;
    background: rgba(0, 0, 0, 0.08);
    overflow: hidden;
    margin-top: .3rem;
}
.hp-bar-fill { height: 100%; border-radius: 4px; transition: width .3s ease, background-color .3s ease; }

.inventory-tags { display: flex; flex-wrap: wrap; gap: .35rem; }
.inventory-tag {
    font-family: "Consolas", "SFMono-Regular", "Courier New", monospace;
    font-size: .75rem;
    background: rgba(168, 85, 247, 0.1);
    border: 1px solid rgba(168, 85, 247, 0.3);
    color: #6b21a8;
    border-radius: 999px;
    padding: .15rem .6rem;
}

.stat-pill-row { display: flex; gap: .6rem; flex-wrap: wrap; }
.stat-pill {
    flex: 1 1 90px;
    background: var(--tblr-card-bg, #fff);
    border: 1px solid rgba(0, 0, 0, 0.06);
    border-radius: 12px;
    padding: .55rem .75rem;
    text-align: center;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
.stat-pill .stat-pill-label {
    font-size: .68rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: #8a94a6;
    display: block;
    margin-bottom: .15rem;
}
.stat-pill .stat-pill-value { font-size: 1.25rem; font-weight: 700; line-height: 1; }

.game-commentary {
    margin-top: .75rem;
    padding: .55rem .9rem;
    border-radius: 12px;
    background: rgba(0, 0, 0, 0.06);
    border: 1px dashed rgba(0, 0, 0, 0.15);
    font-size: .85rem;
    font-style: italic;
}
.game-commentary--loading { font-style: normal; opacity: .75; }

.geeknews-commentary {
    margin-top: .4rem;
    padding: .45rem .7rem;
    border-radius: 10px;
    background: rgba(32, 107, 196, 0.08);
    border: 1px dashed rgba(32, 107, 196, 0.35);
    font-size: .8rem;
    font-style: italic;
}

/* --- 아바타 커스터마이징(specs/031) --- */
.avatar-swatch {
    width: 100%;
    background: var(--tblr-card-bg, #fff);
    border: 2px solid var(--tblr-border-color, #dee2e6);
    border-radius: 14px;
    padding: .6rem .4rem .5rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .35rem;
    cursor: pointer;
    transition: border-color .15s ease, transform .1s ease, box-shadow .15s ease;
}
.avatar-swatch:hover { border-color: rgba(32, 107, 196, 0.45); transform: translateY(-2px); }
.avatar-swatch canvas { display: block; }
.avatar-swatch .avatar-swatch-tag {
    font-size: .68rem;
    color: #8a94a6;
    letter-spacing: .02em;
    text-align: center;
}
.avatar-swatch.current { border-style: dashed; border-color: rgba(32, 107, 196, 0.5); }
.avatar-swatch.selected {
    border-style: solid;
    border-color: #206bc4;
    box-shadow: 0 0 0 3px rgba(32, 107, 196, 0.18);
}
.avatar-swatch.selected .avatar-swatch-tag { color: #206bc4; font-weight: 600; }

/* --- 사건수첩(specs/029) ---
   증언 5개를 **동시에** 볼 수 있어야 대조가 된다. 그래서 접는 UI를 쓰지 않고 그리드로
   전부 펼쳐 두되, 모바일(390px)에서도 가로 스크롤 없이 한 줄씩 쌓이게 한다(SC-007). */
.casebook-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: .75rem;
}
/* ⚠️ 이 화면의 색은 테마 변수에 맡기지 않는다. 사건수첩은 클래식 모드와 하루타운
   포탈(iframe, 다크 테마) 양쪽에서 열리는데, 변수에 맡겼더니 한쪽에서 밝은 배경 위에
   흰 글씨가 되어 증언이 통째로 안 보였다(경환님 지적 2026-08-16, 튜토리얼에 이어 두 번째).
   밝은 값을 기본으로 못 박고, 다크 테마일 때만 명시적으로 뒤집는다. */
.casebook-suspect {
    background: #ffffff;
    color: #1a2634;
    border: 2px solid #dee2e6;
    border-radius: 14px;
    padding: .85rem .9rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    transition: border-color .15s ease, box-shadow .15s ease;
}
.casebook-suspect.picked {
    border-color: #206bc4;
    box-shadow: 0 0 0 3px rgba(32, 107, 196, .18);
}
.casebook-suspect-head { display: flex; align-items: center; gap: .45rem; }
.casebook-emoji { font-size: 1.35rem; line-height: 1; }
.casebook-name { font-weight: 600; }
.casebook-testimony {
    margin: 0;
    flex: 1 1 auto;
    font-size: .89rem;
    line-height: 1.55;
    color: inherit;   /* 카드가 색을 들고 있으므로 그대로 물려받는다 */
}
.casebook-pick { align-self: flex-start; }
.casebook-explanation { font-size: .92rem; line-height: 1.7; }
.casebook-explanation p { margin-bottom: .6rem; }

.casebook-rank { margin: 0; padding-left: 1.4rem; }
.casebook-rank li {
    display: flex;
    justify-content: space-between;
    gap: .5rem;
    padding: .28rem 0;
    border-bottom: 1px solid #eef0f3;
}
.casebook-rank li:last-child { border-bottom: 0; }
.casebook-rank-name { font-weight: 600; }
.casebook-rank-time { font-variant-numeric: tabular-nums; color: #667382; }

/* 슬롯 배지 + 남은 시간 카운트다운 */
.casebook-slot-tag {
    display: inline-block;
    background: rgba(255, 255, 255, .22);
    border-radius: 6px;
    padding: .05rem .4rem;
    font-size: .72em;
    vertical-align: middle;
    margin-right: .3rem;
}
.casebook-timer {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: .5rem;
    margin: .75rem 0;
    padding: .6rem .9rem;
    border-radius: 12px;
    background: #ffffff;
    color: #1a2634;
    border: 2px solid #dee2e6;
}
.casebook-timer-label { font-size: .82rem; color: #667382; }
.casebook-timer-value {
    font-size: 1.35rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;  /* 숫자가 바뀔 때 폭이 흔들리지 않게 */
}
.casebook-timer--urgent { border-color: #d63939; }
.casebook-timer--urgent .casebook-timer-value { color: #d63939; }
.casebook-timer--over .casebook-timer-value { color: #667382; }

/* 하루타운 포탈(iframe)은 다크 테마다 — 여기서만 어두운 톤으로 뒤집되, 배경과 글자를
   **함께** 지정해 어떤 경우에도 대비가 깨지지 않게 한다. */
[data-bs-theme="dark"] .casebook-suspect {
    background: #1c2333;
    color: #e6edf7;
    border-color: #2b3448;
}
[data-bs-theme="dark"] .casebook-timer {
    background: #1c2333;
    color: #e6edf7;
    border-color: #2b3448;
}
[data-bs-theme="dark"] .casebook-timer-label,
[data-bs-theme="dark"] .casebook-rank-time { color: #9aa7bd; }
[data-bs-theme="dark"] .casebook-rank li { border-bottom-color: #2b3448; }

/* 최초 1회 튜토리얼 — 모바일 세로(390px)에서도 읽히도록 전체 화면 + 큰 버튼 */
.casebook-tutorial {
    position: fixed;
    inset: 0;
    z-index: 2000;
    background: rgba(16, 20, 28, .82);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}
.casebook-tutorial-card {
    /* ⚠️ 색을 테마 변수에 맡기지 않는다. 이 화면은 클래식 모드와 하루타운 포탈(iframe,
       다크 테마) 양쪽에서 열리는데, 변수로 두면 한쪽에서 흰 카드 위에 흰 글씨가 되어
       내용이 통째로 안 보였다(경환님 지적 2026-08-16). 모달은 자기 배경을 직접 들고
       있으므로 글자색도 함께 못 박는 게 맞다. */
    background: #ffffff;
    color: #1a2634;
    border-radius: 18px;
    padding: 1.6rem 1.3rem 1.2rem;
    width: 100%;
    max-width: 420px;
    max-height: 92vh;
    overflow-y: auto;
    text-align: center;
}
.casebook-tutorial-emoji { font-size: 2.6rem; line-height: 1; margin-bottom: .5rem; }
.casebook-tutorial-card h3 { margin-bottom: .7rem; color: #1a2634; }
.casebook-tutorial-card p { font-size: .93rem; line-height: 1.65; margin-bottom: .6rem; color: #1a2634; }
.casebook-tutorial-card p.text-secondary { color: #667382 !important; }
.casebook-tutorial-card strong { color: #1a2634; }
.casebook-tutorial-nav { margin-top: 1.2rem; }
.casebook-tutorial-dots {
    display: flex;
    justify-content: center;
    gap: .35rem;
    margin-bottom: .8rem;
}
.casebook-tutorial-dot {
    width: 7px; height: 7px;
    border-radius: 50%;
    background: var(--tblr-border-color, #dee2e6);
    transition: background-color .15s ease;
}
.casebook-tutorial-dot.on { background: #206bc4; }

/* --- 부위별 아바타 커스터마이징(specs/033) ---
   미리보기는 왼쪽에 고정하고 슬롯 목록만 스크롤되게 한다 — 슬롯이 7개라 세로로 길어지는데,
   고르는 동안 미리보기가 화면 밖으로 나가면 "바뀌는 걸 보면서 고르는" 경험이 사라진다. */
.avatar-editor {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    align-items: flex-start;
}
.avatar-preview-pane {
    flex: 0 0 220px;
    position: sticky;
    top: 1rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .5rem;
    padding: 1rem .5rem;
    border-radius: 16px;
    /* 하루타운 바닥과 비슷한 어두운 톤 — 밝은 카드 위에서 보면 실제보다 밝아 보여서
       고른 색이 게임 안에서 다르게 느껴진다. */
    background: linear-gradient(180deg, #3a332c, #2b2620);
}
.avatar-preview-pane canvas { display: block; }
.avatar-preview-caption {
    font-size: .72rem;
    color: rgba(255, 255, 255, .72);
    text-align: center;
    letter-spacing: .02em;
}
.avatar-slot-pane { flex: 1 1 340px; min-width: 0; }
.avatar-slot + .avatar-slot { margin-top: 1rem; }
.avatar-slot-title {
    font-size: .78rem;
    font-weight: 600;
    color: var(--tblr-secondary, #667382);
    margin-bottom: .4rem;
}
.avatar-slot-options { display: flex; flex-wrap: wrap; gap: .4rem; }
.avatar-option {
    border: 2px solid var(--tblr-border-color, #dee2e6);
    background: var(--tblr-card-bg, #fff);
    color: var(--tblr-body-color, #1a2634);
    border-radius: 10px;
    padding: .3rem .65rem;
    font-size: .8rem;
    cursor: pointer;
    transition: border-color .15s ease, color .15s ease, box-shadow .15s ease;
}
.avatar-option:hover { border-color: rgba(32, 107, 196, .45); }
.avatar-option:focus-visible { outline: 2px solid #206bc4; outline-offset: 2px; }
.avatar-option--swatch {
    width: 34px;
    height: 34px;
    padding: 0;
    border-radius: 50%;
}
.avatar-option.current {
    border-color: #206bc4;
    box-shadow: 0 0 0 3px rgba(32, 107, 196, .18);
    font-weight: 600;
}
.geeknews-commentary--loading { font-style: normal; opacity: .75; }
.geeknews-commentary--error { font-style: normal; color: var(--tblr-danger, #d63939); }

.persona-option {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: .2rem;
    height: auto;
    min-height: 4.25rem;
    padding: .6rem .75rem;
    text-align: left;
    white-space: normal;
}
.persona-option .persona-option-label { font-weight: 600; }
.persona-option .persona-option-desc { line-height: 1.3; }

/* 선택된 페르소나 표시 — 원래 Bootstrap 기본값(테두리 색만 살짝 바뀜)이 얇아서
   모바일(호버 상태 없음, 화면도 작음)에서는 선택 여부가 거의 안 보였다(2026-08-11 버그 리포트).
   배경을 꽉 채워서 어떤 화면/테마에서도 선택 상태가 명확히 보이게 한다. */
.btn-check:checked + .persona-option {
    background-color: #206bc4;
    border-color: #206bc4;
    color: #fff;
}
.btn-check:checked + .persona-option .persona-option-desc {
    color: rgba(255, 255, 255, .85) !important;
}

/* 미스터리 어드벤처 테마 선택도 같은 문제라 동일하게 고친다. */
.btn-check:checked + .theme-option {
    background-color: #206bc4;
    border-color: #206bc4;
    color: #fff;
}
.btn-check:checked + .theme-option .theme-option-desc {
    color: rgba(255, 255, 255, .85) !important;
}

/* --- AI 페르소나 상담 답변 카드 — 배경/글자색을 항상 함께 고정한다. 예전엔 Bootstrap
   유틸리티 bg-light(고정 밝은 배경) + 기본 텍스트 색(테마에 따라 반응)을 섞어 써서,
   가상공간 포탈(다크 테마) 안에서 열면 흰 배경에 흰 글씨처럼 안 보이는 버그가 있었다
   (2026-08-09 수정) — 밝은 카드 하나로 두 색을 항상 묶어 어느 테마에서도 읽힌다. */
.persona-reply-box {
    background: #f8f9fa;
    border: 1px solid rgba(0, 0, 0, 0.08);
    color: #1c2128;
}
.persona-reply-box .persona-reply-label { color: #6c757d; }

.game-board-frame {
    border-radius: 18px;
    box-shadow: 0 18px 36px -18px rgba(0, 0, 0, 0.35), inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}

.game-action-btn {
    border-radius: 10px;
    font-weight: 600;
    letter-spacing: .01em;
}

/* --- Sidebar polish --- */
.navbar-vertical .nav-link:hover,
.navbar-vertical .dropdown-menu .dropdown-item:hover {
    background-color: #ffffff;
    color: #206bc4;
}
.navbar-vertical .nav-link:hover .nav-link-icon svg,
.navbar-vertical .dropdown-menu .dropdown-item:hover .nav-link-icon svg {
    stroke: #206bc4 !important;
}
.navbar-vertical .nav-link-icon {
    margin-right: .5rem;
}
.navbar-vertical .nav-item-header {
    font-size: .85rem;
    font-weight: bold;
    text-transform: uppercase;
    letter-spacing: .05em;
}
.navbar-vertical .header-common { color: #206bc4; }
.navbar-vertical .header-games { color: #0ca692; }
.navbar-vertical .header-admin { color: #f76707; }
.navbar-vertical .header-ai { color: #ae3ec9; }
.navbar-vertical .sidebar-divider {
    height: 1px;
    background-color: rgba(255, 255, 255, 0.1);
    margin: .75rem 1rem 0;
    border: none;
}

/* --- 사이드바 브랜드 영역(layout_main.html h1.navbar-brand) ---
   접속자 수/가상공간 모드 버튼이 늘어나며 넓은 화면(lg+, 세로 사이드바 고정폭)에서 폭이
   모자라면 flex 기본 동작상 "코드하루" 링크가 밀려 잘렸다. 브랜드 링크는 줄지 않게 고정하고
   나머지 요소는 자리가 부족하면 다음 줄로 흘러가게 한다. */
.navbar-brand.navbar-brand-autodark {
    flex-wrap: wrap;
    row-gap: .25rem;
}
.navbar-brand.navbar-brand-autodark > a:first-child {
    flex-shrink: 0;
    white-space: nowrap;
}

/* --- 가상공간 모드 토글 (layout_main.html 브랜드 영역) --- */
.vspace-toggle-link {
    font-size: .75rem;
    padding: .15rem .6rem;
    border-radius: 999px;
    background-color: rgba(32, 107, 196, 0.15);
    color: #6ea8f7 !important;
    text-decoration: none;
    white-space: nowrap;
}
.vspace-toggle-link:hover {
    background-color: rgba(32, 107, 196, 0.28);
}

/* 브랜드 옆 상태 줄(온라인 인원 + 출석 스트릭) — specs/027 */
.sidebar-status {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    margin-left: .6rem;
    font-size: .68rem;
    white-space: nowrap;
}
.sidebar-status #online-count-text { font-size: .68rem; }
@media (min-width: 992px) {
    /* 세로 사이드바에서는 브랜드와 같은 줄에 절대 못 들어간다 — 통째로 아랫줄로 내린다.
       Tabler가 .navbar-brand에 justify-content:center를 더 구체적인 선택자로 걸어둬서,
       줄바꿈시키면 브랜드만 가운데로 튄다(상태 줄은 왼쪽이라 어긋나 보인다). 아래 메뉴 항목들과
       같은 왼쪽 정렬로 맞추려면 명시도를 Tabler보다 높여야 한다. */
    aside.navbar-vertical.navbar-expand-lg .navbar-brand {
        flex-wrap: wrap;
        justify-content: flex-start;
    }
    .navbar-vertical .sidebar-status {
        flex-basis: 100%;
        margin-left: 0;
        margin-top: .25rem;
    }
}

/* 클래식 모드 사이드바의 출석 스트릭 배지(specs/027) — 하루타운 배지와 같은 톤 */
.classic-streak-badge {
    font-size: .68rem;
    font-weight: 700;
    color: #ffd8a8;
    background: rgba(245, 159, 0, 0.18);
    padding: .1rem .38rem;
    border-radius: 999px;
    white-space: nowrap;
    flex-shrink: 0;
}

/* --- 가상공간 전체화면 페이지 (virtual_space.html) — ZEP/Gather류 UI를 참고한 크롬 --- */
.vspace-body {
    margin: 0;
    /* 하단 도크/리액션 바가 body 기준으로 붙게 한다(specs/026). position 지정이 없으면 그것들은
       초기 컨테이닝 블록(=항상 100vh, 주소창이 보일 때의 큰 뷰포트) 기준이라, 실제 폰에서는
       브라우저 하단 툴바 뒤로 숨어 채팅 입력창이 아예 안 보였다(경환님 실기기 확인 2026-08-15). */
    position: relative;
    background: radial-gradient(circle at 50% 0%, #163229 0%, #081512 70%);
    color: #fff;
    /* 아래 3줄은 순서가 곧 폴백 사슬이다:
       100vh  — dvh를 모르는 구형 브라우저용
       100dvh — 주소창/툴바를 뺀 "지금 실제로 보이는" 높이
       --vspace-vh — 키보드가 올라온 높이까지 JS(visualViewport)가 흘려보내는 값 */
    height: 100vh;
    height: 100dvh;
    height: var(--vspace-vh, 100dvh);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    font-family: 'Noto Sans KR', sans-serif;
}
.vspace-topbar {
    position: absolute;
    top: .9rem;
    /* left:50% + translateX(-50%)로 가운데 맞추면, 절대 배치 요소가 쓸 수 있는 폭이
       "컨테이너의 절반"으로 줄어든다 — 그래서 태블릿(820px)이나 561px쯤에서 내용이 다
       들어가는데도 억지로 2~3줄로 접히며 키가 자라 캔버스를 덮었다(specs/026).
       left/right를 0으로 펴고 margin auto로 가운데 두면 폭 제약이 사라진다. */
    left: 0;
    right: 0;
    margin: 0 auto;
    width: max-content;
    max-width: calc(100% - 1.8rem);
    z-index: 20;
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .5rem .7rem;
    background: rgba(15, 23, 32, 0.72);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.6);
}
.vspace-pill-btn {
    font-size: .8rem;
    font-weight: 600;
    padding: .4rem .9rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.08);
    color: #f1f5f9 !important;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color .15s ease;
}
.vspace-pill-btn:hover { background: rgba(255, 255, 255, 0.16); }
/* 기본(데스크톱)은 긴 라벨, 모바일 미디어쿼리에서 짧은 라벨로 바꿔 단다(specs/026) */
.vspace-classic-label-short { display: none; }
.vspace-room-switch {
    display: flex;
    gap: .25rem;
    background: rgba(0, 0, 0, 0.25);
    padding: .2rem;
    border-radius: 999px;
}
.vspace-room-btn {
    border: none;
    background: transparent;
    color: rgba(255, 255, 255, 0.65);
    font-size: .8rem;
    font-weight: 600;
    padding: .4rem .85rem;
    border-radius: 999px;
    transition: background-color .15s ease, color .15s ease;
}
.vspace-room-btn.active {
    background: #206bc4;
    color: #fff;
}
.vspace-online-badge {
    font-size: .72rem;
    color: #a9e6c4;
    background: rgba(64, 192, 87, 0.15);
    padding: .35rem .7rem;
    border-radius: 999px;
    white-space: nowrap;
}

/* --- 출석 스트릭 배지(specs/027) — "다녀갈수록 자라는 하루타운" 태그라인을 처음으로
       사실로 만드는 표시. 인원 배지와 달리 모바일에서도 숨기지 않는다. --- */
.vspace-streak-wrap { position: relative; flex-shrink: 0; }
.vspace-streak-badge {
    border: none;
    font-size: .72rem;
    font-weight: 700;
    color: #ffd8a8;
    background: rgba(245, 159, 0, 0.18);
    padding: .35rem .7rem;
    border-radius: 999px;
    white-space: nowrap;
    transition: background-color .15s ease;
}
.vspace-streak-badge:hover { background: rgba(245, 159, 0, 0.3); }
.vspace-streak-num { margin-left: .15rem; }
.vspace-streak-panel {
    position: absolute;
    top: calc(100% + .5rem);
    right: 0;
    z-index: 30;
    width: 200px;
    padding: .6rem .7rem;
    background: rgba(15, 23, 32, 0.96);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    box-shadow: 0 20px 40px -16px rgba(0, 0, 0, 0.7);
}
.vspace-streak-row {
    display: flex;
    justify-content: space-between;
    gap: .5rem;
    font-size: .78rem;
    padding: .18rem 0;
    color: #cbd5e1;
}
.vspace-streak-row strong { color: #f1f5f9; }
.vspace-streak-note {
    margin: .4rem 0 0;
    font-size: .68rem;
    color: #94a3b8;
    line-height: 1.4;
}
.vspace-streak-toast {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 4.2rem;
    z-index: 35;
    padding: .5rem 1rem;
    font-size: .82rem;
    font-weight: 700;
    color: #3b2f0a;
    background: linear-gradient(135deg, #ffd43b, #f59f00);
    border-radius: 999px;
    box-shadow: 0 10px 24px -8px rgba(245, 159, 0, 0.6);
    animation: vspace-approach-pop .25s ease;
    transition: opacity .5s ease, transform .5s ease;
}
.vspace-streak-toast-out { opacity: 0; transform: translateX(-50%) translateY(-6px); }

/* --- 상단 메뉴 드롭다운 — 클래식 모드 사이드바처럼, 위치 이동 없이 바로 포탈로 진입 --- */
.vspace-menu-wrap { position: relative; }
#vspace-menu-btn.vspace-menu-open { background: #206bc4; }
.vspace-menu-dropdown {
    position: absolute;
    top: calc(100% + .6rem);
    left: 50%;
    transform: translateX(-50%);
    width: max(220px, min(280px, 90vw));
    max-height: min(70vh, 26rem);
    overflow-y: auto;
    background: rgba(15, 23, 32, 0.96);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 14px;
    box-shadow: 0 20px 40px -16px rgba(0, 0, 0, 0.7);
    padding: .4rem;
    z-index: 30;
}
.vspace-menu-group-label {
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: #6ea8f7;
    padding: .5rem .6rem .25rem;
}
.vspace-menu-item {
    display: block;
    width: 100%;
    text-align: left;
    border: none;
    background: transparent;
    color: #f1f5f9;
    font-size: .85rem;
    padding: .5rem .6rem;
    border-radius: 8px;
    transition: background-color .15s ease;
}
.vspace-menu-item:hover, .vspace-menu-item:focus-visible {
    background: rgba(32, 107, 196, 0.22);
    outline: none;
}

/* --- 미니맵 — 현재 방의 내 위치/봇 위치를 축소해서 항상 보여준다(모바일에서도 좁지 않게 크기 축소) --- */
.vspace-minimap {
    position: absolute;
    top: .9rem;
    right: .9rem;
    z-index: 15;
    width: 132px;
    height: 88px;
    border-radius: 10px;
    background: rgba(10, 15, 20, 0.6);
    border: 1px solid rgba(255, 255, 255, 0.14);
    box-shadow: 0 10px 24px -12px rgba(0, 0, 0, 0.6);
}
@media (max-width: 560px) {
    .vspace-minimap { width: 92px; height: 62px; top: .6rem; right: .6rem; }
}
.vspace-stage {
    position: relative;
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    padding: 4.5rem 1rem 6.5rem;
}
/* 카메라가 캐릭터를 따라다니는 뷰포트 방식(ZEP/게더타운류) — 캔버스는 방 전체가 아니라
   이 컨테이너를 꽉 채우고, 타일 픽셀 크기는 화면 크기와 무관하게 고정으로 유지된다. */
.vspace-canvas-wrap {
    width: 100%;
    height: 100%;
}
#space-canvas {
    display: block;
    touch-action: manipulation;
    width: 100%;
    height: 100%;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 30px 60px -20px rgba(0, 0, 0, 0.7);
    border-radius: 16px;
}
.vspace-dock {
    position: absolute;
    left: 50%;
    /* viewport-fit=cover라 iOS 홈 인디케이터 영역까지 화면이 넓어진다 — 그만큼 띄운다 */
    bottom: calc(1.1rem + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    z-index: 20;
    display: flex;
    align-items: center;
    gap: .4rem;
    width: min(520px, calc(100% - 2rem));
    padding: .4rem .5rem .4rem 1rem;
    background: rgba(15, 23, 32, 0.75);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.6);
}
.vspace-dock input {
    flex: 1;
    border: none;
    background: transparent;
    color: #fff;
    font-size: .9rem;
    outline: none;
}
.vspace-dock input::placeholder { color: rgba(255, 255, 255, 0.45); }
.vspace-dock button {
    flex-shrink: 0;
    width: 2.2rem;
    height: 2.2rem;
    border: none;
    border-radius: 50%;
    background: #206bc4;
    color: #fff;
    font-size: 1rem;
    display: flex;
    align-items: center;
    justify-content: center;
}
.vspace-dock button:hover { background: #1a56a0; }

.vspace-reaction-bar {
    position: absolute;
    left: 50%;
    bottom: calc(4.3rem + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    z-index: 20;
    display: flex;
    gap: .3rem;
    padding: .35rem;
    background: rgba(15, 23, 32, 0.75);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.6);
}
.vspace-reaction-btn {
    flex-shrink: 0;
    width: 2.1rem;
    height: 2.1rem;
    border: none;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    font-size: 1.1rem;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color .15s ease, transform .1s ease;
}
.vspace-reaction-btn:hover { background: rgba(255, 255, 255, 0.18); }
.vspace-reaction-btn:active { transform: scale(0.9); }
@media (max-width: 560px), (max-height: 480px) {
    .vspace-reaction-bar { bottom: calc(4rem + env(safe-area-inset-bottom, 0px)); gap: .2rem; padding: .25rem; }
    .vspace-reaction-btn { width: 1.8rem; height: 1.8rem; font-size: .95rem; }
}

.vspace-approach-btn {
    position: absolute;
    bottom: calc(7.4rem + env(safe-area-inset-bottom, 0px));
    left: 50%;
    transform: translateX(-50%);
    z-index: 20;
    border: none;
    border-radius: 999px;
    padding: .55rem 1.1rem;
    font-weight: 700;
    font-size: .85rem;
    color: #3b2f0a;
    background: linear-gradient(135deg, #ffd43b, #f59f00);
    box-shadow: 0 10px 24px -8px rgba(245, 159, 0, 0.6);
    animation: vspace-approach-pop .25s ease;
}
@keyframes vspace-approach-pop {
    from { transform: translateX(-50%) scale(0.85); opacity: 0; }
    to { transform: translateX(-50%) scale(1); opacity: 1; }
}

/* 사건수첩 수사 액션(specs/037) — 자유 대화 버튼 **위에** 따로 뜬다.
   색을 달리한 이유: 같은 봇에게 두 버튼이 동시에 보일 때 "대화"와 "수사"가 다른 일이라는 게
   한눈에 읽혀야 한다(FR-017). */
.vspace-approach-btn--case {
    bottom: calc(10.6rem + env(safe-area-inset-bottom, 0px));
    color: #f8fafc;
    background: linear-gradient(135deg, #6366f1, #4338ca);
    box-shadow: 0 10px 24px -8px rgba(67, 56, 202, 0.65);
}

/* 수사 안내 토스트 — "조금 더 가까이" 같은 짧은 거절 사유. 실패를 조용히 삼키면
   사용자는 버튼이 고장난 줄 안다. */
.vspace-casebook-toast {
    position: absolute;
    bottom: calc(14rem + env(safe-area-inset-bottom, 0px));
    left: 50%;
    transform: translateX(-50%);
    z-index: 21;
    max-width: min(22rem, 90vw);
    padding: .5rem .95rem;
    border-radius: .6rem;
    font-size: .82rem;
    text-align: center;
    color: #f8fafc;
    background: rgba(15, 23, 42, 0.92);
    box-shadow: 0 10px 24px -10px rgba(0, 0, 0, 0.7);
}

.vspace-npc-panel {
    position: absolute;
    top: 4.7rem;
    right: 1rem;
    width: min(320px, calc(100% - 2rem));
    max-height: min(60%, 26rem);
    background: rgba(15, 23, 32, 0.92);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 16px;
    display: flex;
    flex-direction: column;
    box-shadow: 0 20px 40px -16px rgba(0, 0, 0, 0.7);
    z-index: 25;
}
.vspace-npc-panel-header {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .7rem .9rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.vspace-npc-panel-avatar {
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 50%;
    background: rgba(245, 159, 0, 0.2);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
    flex-shrink: 0;
}
.vspace-npc-panel-title { flex: 1; min-width: 0; }
.vspace-npc-panel-title #npc-panel-name {
    font-weight: 700;
    font-size: .9rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.vspace-npc-panel-sub { font-size: .7rem; color: #94e0ab; }
.vspace-npc-panel-log {
    flex: 1;
    overflow-y: auto;
    padding: .7rem .9rem;
    font-size: .85rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}
.vspace-npc-line { padding: .45rem .7rem; border-radius: 12px; max-width: 88%; line-height: 1.4; }
.vspace-npc-line-bot {
    background: rgba(245, 159, 0, 0.16);
    align-self: flex-start;
    border-bottom-left-radius: 4px;
}
.vspace-npc-line-user {
    background: #206bc4;
    color: #fff;
    align-self: flex-end;
    border-bottom-right-radius: 4px;
}
.vspace-npc-line-system { color: #adb5bd; font-style: italic; font-size: .78rem; align-self: center; }

/* --- 게시판(방명록) 메모 목록 (specs/022) — vspace-npc-line-bot 말풍선을 재사용하되 작성자/시간 메타를 덧붙인다 --- */
.vspace-note-line { align-self: stretch; max-width: 100%; }
.vspace-note-meta { font-size: .68rem; opacity: .65; margin-bottom: .15rem; }

/* --- NPC가 claude -p 응답을 기다리는 동안 보여주는 "입력 중" 점 3개 애니메이션 --- */
.vspace-npc-typing-dots { display: inline-flex; align-items: center; gap: .22rem; padding: .1rem 0; }
.vspace-npc-typing-dots span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(241, 245, 249, 0.75);
    animation: vspace-npc-typing-bounce 1.1s infinite ease-in-out;
}
.vspace-npc-typing-dots span:nth-child(2) { animation-delay: .15s; }
.vspace-npc-typing-dots span:nth-child(3) { animation-delay: .3s; }
@keyframes vspace-npc-typing-bounce {
    0%, 60%, 100% { transform: translateY(0); opacity: .5; }
    30% { transform: translateY(-3px); opacity: 1; }
}
.vspace-npc-panel-form {
    display: flex;
    align-items: center;
    gap: .4rem;
    padding: .6rem .7rem;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.vspace-npc-panel-form input {
    flex: 1;
    border: none;
    background: rgba(255, 255, 255, 0.08);
    color: #fff;
    font-size: .85rem;
    padding: .5rem .8rem;
    border-radius: 999px;
    outline: none;
}
.vspace-npc-panel-form button {
    flex-shrink: 0;
    width: 2rem;
    height: 2rem;
    border: none;
    border-radius: 50%;
    background: #f59f00;
    color: #3b2f0a;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* --- 모바일 크롬 (specs/026) --------------------------------------------------
   데스크톱에서 상단바는 무대 위에 떠 있는 알약이고, 무대는 고정 패딩(4.5rem)으로 그 아래를
   피한다. 좁은 화면에서는 알약이 여러 줄로 접히며 키가 자라는데 패딩은 그대로라 캔버스를
   덮어버렸다(390×844에서 51px 겹침). 패딩 숫자를 키워 막으면 항목이 하나 늘 때마다 같은
   문제가 재발하므로, 모바일에서는 상단바를 **일반 문서 흐름**으로 되돌린다 — .vspace-body가
   이미 flex column이라 상단바가 몇 줄이 되든 무대(flex:1)와 구조적으로 겹칠 수 없다.

   가로 폰(844×390)은 폭이 560px을 넘어 폭 기준 쿼리에서 빠지므로 높이 기준도 함께 건다. */
@media (max-width: 560px), (max-height: 480px) {
    .vspace-topbar {
        position: static;
        transform: none;
        width: 100%;
        max-width: none;
        flex-wrap: nowrap;
        justify-content: flex-start;
        gap: .35rem;
        /* 노치/펀치홀 아래로 내려오도록 안전영역만큼 더 띄운다(viewport-fit=cover) */
        padding: calc(.4rem + env(safe-area-inset-top, 0px)) calc(.55rem + env(safe-area-inset-right, 0px)) .4rem calc(.55rem + env(safe-area-inset-left, 0px));
        border-radius: 0;
        border-width: 0 0 1px;
        box-shadow: none;
    }
    .vspace-pill-btn { font-size: .74rem; padding: .35rem .65rem; white-space: nowrap; }
    /* 방이 늘어나도 줄바꿈 대신 가로 스크롤로 흡수한다(스크롤바는 숨김). 스크롤은 방 전환
       묶음에만 건다 — 상단바 전체에 걸면 그 안의 메뉴 드롭다운까지 잘려버린다. */
    .vspace-room-switch { min-width: 0; overflow-x: auto; scrollbar-width: none; }
    .vspace-room-switch::-webkit-scrollbar { display: none; }
    .vspace-room-btn { font-size: .74rem; padding: .35rem .6rem; white-space: nowrap; }
    /* 드롭다운을 버튼 기준(left:50%)으로 두면 폭 280px가 화면 왼쪽으로 삐져나간다 —
       모바일에서는 화면 양끝에 물려 고정한다. */
    .vspace-menu-dropdown {
        position: fixed;
        top: calc(3.1rem + env(safe-area-inset-top, 0px));
        left: .5rem;
        right: .5rem;
        width: auto;
        /* 가로 폰에서는 화면이 넓어 끝까지 늘리면 항목이 허허벌판에 뜬다 — 폭을 묶어둔다 */
        max-width: 340px;
        transform: none;
    }
    .vspace-online-badge { display: none; }
    /* 스트릭은 숨기지 않고 줄이기만 한다 — 이게 이번 기능의 핵심 노출이다(specs/027 FR-004) */
    .vspace-streak-badge { font-size: .7rem; padding: .3rem .5rem; }
    .vspace-streak-suffix { display: none; }
    .vspace-streak-toast { top: 3.6rem; font-size: .76rem; padding: .4rem .8rem; }
    /* iOS 사파리는 폰트가 16px보다 작은 입력창에 포커스하면 화면을 확대해버린다 — 확대되면
       레이아웃이 통째로 틀어지므로 모바일에서만 16px로 올린다(확대 자체를 막는 것보다 안전). */
    .vspace-dock input,
    .vspace-npc-panel-form input { font-size: 16px; }
    /* 상단바를 더 이상 피할 필요가 없다 — 위쪽은 최소 여백만 둔다 */
    .vspace-stage { padding: .5rem .5rem calc(7rem + env(safe-area-inset-bottom, 0px)); }
    /* 좁은 화면에서는 "← 클래식 모드"를 "← 클래식"으로 줄여 한 줄에 들어가게 한다 */
    .vspace-classic-label-long { display: none; }
    .vspace-classic-label-short { display: inline; }
}
@media (max-width: 560px) {
    .vspace-npc-panel { left: 1rem; right: 1rem; width: auto; top: auto; bottom: calc(5.5rem + env(safe-area-inset-bottom, 0px)); max-height: 50vh; }
}
/* 가로 폰 — 무대 높이가 300px 남짓이라 하단 도크/리액션 바와 미니맵이 캔버스를 크게 잡아먹는다.
   도크를 납작하게 만들어야 위의 리액션 바(bottom: 4rem)와 겹치지 않는다. */
@media (max-height: 480px) {
    .vspace-stage { padding: .4rem .5rem calc(6rem + env(safe-area-inset-bottom, 0px)); }
    .vspace-minimap { width: 96px; height: 64px; top: .5rem; right: .5rem; }
    .vspace-dock { padding: .25rem .35rem .25rem .9rem; }
    .vspace-dock button { width: 2rem; height: 2rem; }
    .vspace-npc-panel { top: .5rem; bottom: 3.6rem; max-height: none; }
}

/* --- 포탈(클래식 모드 메뉴) 오버레이 — 페이지를 떠나지 않고 가상공간 위에 프레임으로 띄운다 --- */
.vspace-portal-overlay {
    position: fixed;
    inset: 0;
    z-index: 40;
    background: rgba(5, 10, 8, 0.72);
    backdrop-filter: blur(6px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2.2rem;
}
.vspace-portal-frame {
    width: min(1200px, 100%);
    height: 100%;
    max-height: 900px;
    background: #12181f;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 16px;
    box-shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.8);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.vspace-portal-header {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .7rem .9rem;
    background: rgba(15, 23, 32, 0.9);
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    flex-shrink: 0;
}
.vspace-portal-icon { font-size: 1.2rem; }
.vspace-portal-title { flex: 1; min-width: 0; font-weight: 700; color: #f1f5f9; }
.vspace-portal-newtab {
    font-size: .78rem;
    padding: .3rem .7rem;
}
.vspace-portal-iframe {
    flex: 1;
    width: 100%;
    border: none;
    background: #12181f;
}
@media (max-width: 560px) {
    .vspace-portal-overlay { padding: 0; }
    .vspace-portal-frame { border-radius: 0; max-height: none; }
}

/* --- 포탈 iframe 안에서 렌더링되는 클래식 페이지 콘텐츠 (layout_main.html의 is_portal 분기) ---
   사이드바/헤더 없이 콘텐츠만 보여주고, <html data-bs-theme="dark">로 Tabler 컴포넌트
   (card/badge/input/table 등)를 자동으로 다크 스킨 적용시켜 가상공간 톤과 맞춘다. */
.vspace-portal-body {
    background: #12181f;
}
.vspace-portal-page {
    max-width: 1140px;
    margin: 0 auto;
    padding: 1.25rem 1.25rem 2.5rem;
}

/* --- 오늘의 픽셀펫(specs/016) --- */
.pet-stage {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 1.5rem 1rem 1rem;
}
.pet-sprite {
    width: 140px;
    height: 140px;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    filter: drop-shadow(0 10px 14px rgba(0, 0, 0, 0.18));
}
.pet-sprite--happy { animation: pet-wiggle 1.4s ease-in-out infinite; }
.pet-sprite--hyper { animation: pet-bounce .55s ease-in-out infinite; }
.pet-sprite--sleepy { animation: pet-sway 3.2s ease-in-out infinite; }
.pet-sprite--curious { animation: pet-float 2.4s ease-in-out infinite; }
.pet-sprite--moody { animation: pet-droop 2.6s ease-in-out infinite; }
@keyframes pet-wiggle {
    0%, 100% { transform: rotate(-4deg); }
    50% { transform: rotate(4deg); }
}
@keyframes pet-bounce {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-14px); }
}
@keyframes pet-sway {
    0%, 100% { transform: rotate(-2deg) translateY(0); }
    50% { transform: rotate(2deg) translateY(2px); }
}
@keyframes pet-float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-6px); }
}
@keyframes pet-droop {
    0%, 100% { transform: translateY(0) rotate(0deg); }
    50% { transform: translateY(3px) rotate(-1deg); }
}

.pet-name {
    font-size: 1.15rem;
    font-weight: 700;
    margin-top: .75rem;
}

/* 가상공간 포탈(다크 테마)에서도 항상 읽히도록 배경/글자색을 함께 고정한다
   (persona-reply-box와 동일한 이유, 2026-08-09 교훈). */
.pet-speech-bubble {
    position: relative;
    background: #f8f9fa;
    color: #1c2128;
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-radius: 14px;
    padding: .7rem 1rem;
    margin-top: 1rem;
    max-width: 320px;
    text-align: center;
    font-size: .95rem;
}
.pet-speech-bubble::before {
    content: "";
    position: absolute;
    top: -8px;
    left: 50%;
    transform: translateX(-50%);
    border-width: 0 8px 8px 8px;
    border-style: solid;
    border-color: transparent transparent #f8f9fa transparent;
}
.pet-mood-badge {
    margin-top: .6rem;
}
.pet-species-caption {
    color: #8a94a6;
    font-size: .8rem;
    margin-top: .3rem;
}

/* --- 픽셀펫 갤러리(specs/021) --- */
.pet-gallery-card .pet-sprite {
    width: 84px;
    height: 84px;
}
.pet-gallery-date {
    color: #8a94a6;
    font-size: .78rem;
}
.pet-gallery-empty {
    padding: 2.5rem 1rem;
    text-align: center;
    color: #8a94a6;
}

/* 하루타운 상단바 사건수첩 배지 — 마감이 다가오는 걸 맵 위에서도 알 수 있게 한다.
   상단바는 항상 어두운 톤이라 색을 직접 지정한다(테마 변수에 맡기면 대비가 깨진다). */
.vspace-casebook-badge {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    background: rgba(255, 255, 255, .1);
    border: 1px solid rgba(255, 255, 255, .18);
    color: #e6edf7;
    border-radius: 999px;
    padding: .25rem .7rem;
    font-size: .82rem;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: background-color .15s ease, border-color .15s ease;
}
.vspace-casebook-badge:hover { background: rgba(255, 255, 255, .18); }
.vspace-casebook-badge .vspace-casebook-time { font-variant-numeric: tabular-nums; }
.vspace-casebook-badge.urgent {
    background: rgba(214, 57, 57, .22);
    border-color: rgba(214, 57, 57, .55);
    color: #ffb4b4;
}
.vspace-casebook-badge.over { opacity: .55; }

/* ⚠️ 상단바는 한 줄을 유지해야 한다 — 두 줄이 되면 키가 자라 캔버스를 덮는다(specs/026에서
   같은 문제를 겪고 잡아둔 규칙). 배지를 하나 더 넣었더니 561px 근처에서 다시 접혔다.
   좁은 폭에서는 배지를 압축해 한 줄 안에 들어가게 한다. */
@media (max-width: 900px) {
    /* ⚠️ 예전엔 좁은 화면에서 배지를 통째로 감췄다("잃는 게 크지 않다 — 남은 시간은 수첩
       안에도 있다"). specs/038부터는 그 판단이 뒤집혔다: 이 배지가 **진행 현황과 남은 대상
       목록으로 들어가는 유일한 입구**가 됐고, 그게 없어지면 모바일 사용자는 다시
       "메뉴 > 사건수첩 > 탭"이라는 세 단계로 돌아간다 — 이번 작업이 없애려던 바로 그것이다.

       그래서 감추는 대신 **남은 시간을 접는다.** 시간은 수첩 화면 안에도 크게 있지만,
       진행 현황은 여기에만 있다. 둘 중 하나를 버려야 한다면 버릴 것은 시간 쪽이다. */
    .vspace-casebook-badge .vspace-casebook-time { display: none; }
    .vspace-casebook-badge .vspace-casebook-progress { margin-left: .15rem; font-size: .68rem; }
    /* 마감이 임박했을 때만 시간을 되살린다 — 그때는 시간이 진행 현황보다 급하다. */
    .vspace-casebook-badge.urgent .vspace-casebook-time,
    .vspace-casebook-badge.over .vspace-casebook-time { display: inline; }
}
@media (max-width: 560px) {
    /* 여기서 상단바가 다시 접혔던 폭이다(specs/026 이후 실측). 글자를 한 단 더 줄여
       한 줄을 지킨다 — 감추는 것보다 작게 보이는 편이 낫다. */
    .vspace-casebook-badge .vspace-casebook-progress { font-size: .62rem; }
    .vspace-casebook-badge { padding-left: .45rem; padding-right: .45rem; }
}

/* ── 기여 비율 지표 (specs/039) ───────────────────────────────────────────────
   ⚠️ 색 규칙: **색은 오직 "작업 주체"만 뜻한다.** 작업 단계는 색이 아니라 막대 길이로
   나타낸다. 단계가 8개라 색을 주면 한 화면에 색 언어가 둘이 되어 서로 잡아먹고, 단계용
   8단 서수 램프는 인접 명도 차가 모자라 접근성 검증에서 떨어졌다(ΔL 0.047, 기준 미달).

   아래 3색은 색각이상 분리도·명도대역·채도를 검증기로 통과한 조합이다.
   라이트에서 aqua(#1baf7a)가 흰 배경 대비 2.82:1로 3:1에 못 미치는데, 그래서 범례에
   **수치를 눈에 보이게 함께 적는다**(색만으로 뜻을 전달하지 않는다). */
.contrib {
    --contrib-a: #eb6834;   /* 주체 슬롯 A */
    --contrib-b: #1baf7a;   /* 주체 슬롯 B */
    --contrib-c: #4a3aa7;   /* 주체 슬롯 C */
    --contrib-d: #eda100;   /* 예비 슬롯 */
    --contrib-muted: #adb5bd;          /* 표식 없음 — 모르는 것은 시리즈가 아니라 회색 */
    --contrib-track: rgba(11, 11, 11, 0.07);
    --contrib-gap: #ffffff;            /* 세그먼트 사이 2px 틈의 색 = 배경색 */
}
[data-bs-theme="dark"] .contrib {
    /* 같은 여덟 색을 어두운 배경용으로 다시 고른 값이다 — 밝기만 뒤집은 게 아니다. */
    --contrib-a: #d95926;
    --contrib-b: #199e70;
    --contrib-c: #9085e9;
    --contrib-d: #c98500;
    --contrib-muted: #6c7a91;
    --contrib-track: rgba(255, 255, 255, 0.08);
    --contrib-gap: #182433;   /* Tabler 다크 카드 배경과 같은 값이라야 틈이 배경으로 읽힌다 */
}

.contrib-slot-a { background: var(--contrib-a); }
.contrib-slot-b { background: var(--contrib-b); }
.contrib-slot-c { background: var(--contrib-c); }
.contrib-slot-d { background: var(--contrib-d); }
.contrib-slot-muted { background: var(--contrib-muted); }

/* 주체 100% 막대 — 이 화면의 머리 그림 */
.contrib-bar {
    display: flex;
    height: 14px;
    border-radius: 4px;      /* 데이터 끝을 4px로 둥글린다 */
    overflow: hidden;
    background: var(--contrib-track);
    gap: 2px;                /* 세그먼트 사이 2px 틈 — 인접 색이 서로 번지지 않게 */
}
.contrib-bar > span { display: block; min-width: 2px; }

/* 단계 행 — 길이가 곧 비율이다. 트랙 전체 폭이 100%. */
.contrib-stage { display: grid; grid-template-columns: 6.5rem 1fr auto; gap: .5rem; align-items: center; }
.contrib-stage + .contrib-stage { margin-top: .375rem; }
.contrib-stage-name { font-size: .8125rem; color: var(--tblr-secondary, #626976); }
.contrib-stage-track { background: var(--contrib-track); border-radius: 4px; height: 10px; overflow: hidden; }
.contrib-stage-fill { display: flex; height: 100%; border-radius: 4px; overflow: hidden; gap: 2px; }
.contrib-stage-fill > span { display: block; min-width: 2px; }
.contrib-stage-value { font-size: .8125rem; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* 좁은 화면 — 이름·수치·막대를 한 줄 그리드에 욱여넣으면 이름(6.5rem 고정) + 줄바꿈 없는
   수치(auto)가 폭을 요구해 카드 밖으로 넘친다(피드백 id=34). 막대를 아래 줄로 내린다. */
@media (max-width: 560px) {
    .contrib-stage {
        grid-template-columns: 1fr auto;
        grid-template-areas: "name value" "track track";
        row-gap: .25rem;
    }
    .contrib-stage-name { grid-area: name; }
    .contrib-stage-value { grid-area: value; text-align: right; }
    .contrib-stage-track { grid-area: track; }
}

/* 범례 — 색만으로 뜻을 전달하지 않기 위해 항상 수치를 함께 적는다 */
.contrib-legend { display: flex; flex-wrap: wrap; gap: .25rem 1rem; margin-top: .5rem; }
.contrib-legend-item { display: inline-flex; align-items: center; gap: .375rem; font-size: .8125rem; }
.contrib-legend-swatch { width: 10px; height: 10px; border-radius: 2px; flex: 0 0 auto; }
.contrib-legend-value { font-variant-numeric: tabular-nums; color: var(--tblr-secondary, #626976); }

.contrib-note { font-size: .75rem; color: var(--tblr-secondary, #626976); }
.contrib-note summary { cursor: pointer; }
.contrib-table { font-size: .8125rem; font-variant-numeric: tabular-nums; }

/* ── 게임 연출 (specs/038) ────────────────────────────────────────────────────
   원칙: **강조점에만 쓴다.** 평범한 이동이나 일상 대화까지 흔들리고 번쩍이면 사용자는
   피로해져서 정작 중요한 순간을 못 알아본다(FR-030). 그래서 흔들림은 단서를 얻는
   순간에만, 플래시는 판정에만 쓴다.

   그리고 전부 "동작 줄이기"를 존중한다(FR-029) — 멀미를 유발하는 움직임이라
   운영체제에서 꺼둔 사람에게 강요하면 안 된다. */

/* 탄성 등장 — 버튼과 패널이 "띡" 뜨지 않고 고무공처럼 자리를 잡는다.
   0.3초를 넘기지 않는다. 길면 고급스러운 게 아니라 답답해진다(FR-027). */
.vspace-approach-btn { animation: vspace-pop-bounce .26s cubic-bezier(.175, .885, .32, 1.275); }
@keyframes vspace-pop-bounce {
    0%   { transform: translateX(-50%) scale(.7) translateY(10px); opacity: 0; }
    60%  { transform: translateX(-50%) scale(1.06) translateY(0); opacity: 1; }
    100% { transform: translateX(-50%) scale(1); }
}
.vspace-npc-panel:not(.d-none) { animation: vspace-panel-bounce .26s cubic-bezier(.175, .885, .32, 1.275); }
@keyframes vspace-panel-bounce {
    0%   { transform: scale(.9) translateY(14px); opacity: 0; }
    65%  { transform: scale(1.02) translateY(0); opacity: 1; }
    100% { transform: scale(1); }
}

/* 화면 흔들림 — 단서를 얻은 순간. 캔버스를 직접 흔들지 않고 감싼 요소를 흔든다.
   캔버스 좌표를 건드리면 카메라 계산과 엉켜 캐릭터 위치가 어긋나 보인다(FR-025). */
.vspace-canvas-wrap.shake { animation: vspace-shake .18s linear; }
@keyframes vspace-shake {
    0%, 100% { transform: translate(0, 0); }
    20% { transform: translate(-5px, 3px); }
    40% { transform: translate(4px, -4px); }
    60% { transform: translate(-3px, -2px); }
    80% { transform: translate(3px, 2px); }
}

/* 비네팅 — 패널이 열려 있는 동안만. 게임 밖 배경과 지금 읽어야 할 것을 갈라준다. */
.vspace-vignette {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 15;
    opacity: 0;
    transition: opacity .22s ease;
    background: radial-gradient(ellipse at center,
                rgba(0, 0, 0, 0) 38%, rgba(0, 0, 0, .34) 78%, rgba(0, 0, 0, .58) 100%);
}
.vspace-vignette.on { opacity: 1; }

/* 판정 플래시 — 정답은 하얗게 터지고, 오답은 붉게 내려앉는다. */
.casebook-flash {
    position: fixed;
    inset: 0;
    z-index: 2000;
    pointer-events: none;
    opacity: 0;
}
.casebook-flash.correct { animation: casebook-flash-correct .9s ease-out; background: #ffffff; }
.casebook-flash.wrong { animation: casebook-flash-wrong .7s ease-out; background: #b91c1c; }
@keyframes casebook-flash-correct {
    0% { opacity: 0; } 8% { opacity: .92; } 100% { opacity: 0; }
}
@keyframes casebook-flash-wrong {
    0% { opacity: 0; } 10% { opacity: .55; } 45% { opacity: .12; } 60% { opacity: .4; } 100% { opacity: 0; }
}

/* 지목 직후의 정적 — 결과를 곧바로 보여주지 않고 1초 남짓 숨을 고른다.
   ⚠️ 이 구간에는 결과가 어떤 형태로도 드러나면 안 된다(FR-022). */
.casebook-suspense {
    position: fixed;
    inset: 0;
    z-index: 1999;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(2, 6, 23, .82);
    backdrop-filter: blur(3px);
    animation: casebook-suspense-in .35s ease;
}
@keyframes casebook-suspense-in { from { opacity: 0; } to { opacity: 1; } }
.casebook-suspense-dots { display: flex; gap: .6rem; }
.casebook-suspense-dots span {
    width: 10px; height: 10px; border-radius: 50%;
    background: #e2e8f0;
    animation: casebook-suspense-pulse 1.1s ease-in-out infinite;
}
.casebook-suspense-dots span:nth-child(2) { animation-delay: .18s; }
.casebook-suspense-dots span:nth-child(3) { animation-delay: .36s; }
@keyframes casebook-suspense-pulse {
    0%, 100% { opacity: .25; transform: scale(.8); }
    50% { opacity: 1; transform: scale(1.15); }
}

/* 판정 결과 카드 — 정답은 튀어오르고 오답은 무겁게 내려앉는다(FR-023). */
.casebook-verdict-in-correct { animation: casebook-verdict-up .5s cubic-bezier(.175, .885, .32, 1.275); }
.casebook-verdict-in-wrong { animation: casebook-verdict-down .5s cubic-bezier(.3, .7, .4, 1); }
@keyframes casebook-verdict-up {
    0% { transform: scale(.82) translateY(26px); opacity: 0; }
    70% { transform: scale(1.04) translateY(0); opacity: 1; }
    100% { transform: scale(1); }
}
@keyframes casebook-verdict-down {
    0% { transform: translateY(-18px); opacity: 0; }
    100% { transform: translateY(0); opacity: 1; }
}

/* 상단 진행 현황 패널 — 배지를 누르면 그 자리에서 펼쳐진다.
   포탈을 열고 탭을 바꾸는 세 단계를 없애는 게 목적이다(경환님 피드백). */
.vspace-progress-panel {
    position: absolute;
    top: calc(100% + .4rem);
    right: 0;
    z-index: 60;
    width: min(19rem, calc(100vw - 1.5rem));
    max-height: min(60vh, 22rem);
    overflow-y: auto;
    padding: .6rem;
    border-radius: 12px;
    background: rgba(15, 23, 42, .96);
    border: 1px solid rgba(255, 255, 255, .1);
    box-shadow: 0 16px 34px -12px rgba(0, 0, 0, .8);
    animation: vspace-panel-bounce .22s cubic-bezier(.175, .885, .32, 1.275);
}
.vspace-progress-title { font-size: .72rem; color: #94a3b8; margin: .25rem .25rem .4rem; }
.vspace-progress-item {
    display: flex; align-items: center; gap: .5rem;
    width: 100%; padding: .42rem .5rem;
    border: none; border-radius: 8px;
    background: transparent; color: #e2e8f0;
    font-size: .8rem; text-align: left;
}
.vspace-progress-item:hover { background: rgba(255, 255, 255, .09); }
.vspace-progress-item .room { margin-left: auto; font-size: .68rem; color: #94a3b8; white-space: nowrap; }
.vspace-progress-done { padding: .5rem; font-size: .8rem; color: #cbd5e1; text-align: center; }
.vspace-progress-cta {
    display: block; width: 100%; margin-top: .4rem;
    padding: .45rem; border: none; border-radius: 8px;
    background: linear-gradient(135deg, #6366f1, #4338ca);
    color: #f8fafc; font-size: .8rem; font-weight: 700;
}
.vspace-casebook-badge .vspace-casebook-progress {
    margin-left: .35rem; font-size: .72rem; opacity: .92; font-variant-numeric: tabular-nums;
}

/* 음소거 토글 — 소리가 나는 이상 끌 방법이 항상 보여야 한다(FR-015). */
.vspace-mute-btn { min-width: 2.1rem; text-align: center; }

@media (prefers-reduced-motion: reduce) {
    /* 큰 움직임만 걷어낸다. 페이드는 남겨야 무엇이 바뀌었는지 알 수 있다. */
    .vspace-approach-btn,
    .vspace-npc-panel:not(.d-none),
    .vspace-progress-panel,
    .casebook-verdict-in-correct,
    .casebook-verdict-in-wrong { animation: none; }
    .vspace-canvas-wrap.shake { animation: none; }
    .casebook-flash.correct, .casebook-flash.wrong { animation-duration: .3s; }
}

/* 배경음악 토글 — 꺼진 상태를 이모지만으로 구분하기 어려워(🎵 vs 🔇) 밝기로도 표시한다. */
.vspace-mute-btn.off { opacity: .5; }

/* 음악이 켜져 있는데 브라우저가 아직 막고 있는 상태 — 화면을 한 번 누르면 시작된다.
   ⚠️ 이건 우리 버그가 아니라 브라우저 자동재생 정책이라 우회할 수 없다. 대신 **왜
   조용한지 모르는 상태**를 없앤다: 버튼이 스스로 "나를 눌러라"라고 말하게 한다.
   (경환님: "새로고침하고 마우스로 한번 화면을 찍어야 소리가 나네?") */
.vspace-mute-btn.pending {
    animation: vspace-audio-pending 1.6s ease-in-out infinite;
    box-shadow: 0 0 0 0 rgba(255, 212, 59, .55);
}
@keyframes vspace-audio-pending {
    0%, 100% { box-shadow: 0 0 0 0 rgba(255, 212, 59, .5); }
    50% { box-shadow: 0 0 0 6px rgba(255, 212, 59, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .vspace-mute-btn.pending { animation: none; outline: 2px solid rgba(255, 212, 59, .6); }
}

/* 배경음악 음량 슬라이더 — 상단바에 들어가야 하므로 아주 납작하게 만든다. */
.vspace-volume {
    -webkit-appearance: none; appearance: none;
    width: 68px; height: 4px; margin: 0 .1rem;
    border-radius: 999px; background: rgba(255, 255, 255, .25);
    cursor: pointer; flex: 0 0 auto;
}
.vspace-volume::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 12px; height: 12px; border-radius: 50%;
    background: #ffd43b; border: none; cursor: pointer;
}
.vspace-volume::-moz-range-thumb {
    width: 12px; height: 12px; border-radius: 50%;
    background: #ffd43b; border: none; cursor: pointer;
}
.vspace-volume:focus-visible { outline: 2px solid #ffd43b; outline-offset: 3px; }
@media (max-width: 700px) {
    /* 좁은 화면에서는 상단바 한 줄을 지키는 게 먼저다 — 켜고 끄는 버튼은 남는다. */
    .vspace-volume { display: none; }
}

/* --- 아침 컷씬 (specs/042) ------------------------------------------------
 *
 * 이 화면의 규칙은 하나다: **한 번에 읽는 글은 컷당 한 줄.** 같은 사건을 글로 냈다가
 * 네 번 연속 "읽어도 무슨 일인지 모르겠다"는 판정을 받았다. 그림이 먼저 말하고,
 * 글은 화면 아래 한 줄로만 거든다.
 *
 * 클래식 모드(밝은 배경)와 하루타운 포탈(다크) 양쪽에서 열린다. 무대 안쪽은 어느 쪽이든
 * 어두운 톤을 유지한다 — 컷 자체가 밤에서 아침으로 넘어온 장면이라 배경까지 밝으면
 * 화면이 두 겹으로 갈린다.
 */
.cutscene-page { max-width: 44rem; margin: 0 auto; }
.cutscene-head h1 { font-size: 1.15rem; margin: 0 0 .15rem; letter-spacing: -.01em; }
.cutscene-sub { margin: 0 0 1rem; font-size: .82rem; color: var(--tblr-secondary, #79828e); }
.cutscene-stage {
    position: relative; background: #000; border-radius: 6px; overflow: hidden;
    border: 1px solid rgba(255, 255, 255, .08); cursor: pointer;
}
.cutscene-stage:focus-visible { outline: 2px solid #206bc4; outline-offset: 2px; }
.cutscene-stage canvas { display: block; width: 100%; height: auto; }
.cutscene-cap {
    position: absolute; left: 0; right: 0; bottom: 0; padding: 1.6rem 1rem .9rem;
    background: linear-gradient(transparent, rgba(0, 0, 0, .85) 55%);
    color: #e6e9ee; font-size: 1.05rem; text-align: center; letter-spacing: -.01em;
    opacity: 0; transition: opacity .3s ease; pointer-events: none;
}
.cutscene-cap.on { opacity: 1; }
.cutscene-hint {
    margin: .55rem 0 0; font-size: .78rem; text-align: right; height: 1.1em;
    color: var(--tblr-secondary, #79828e);
}
.cutscene-dots { display: flex; gap: .3rem; margin: .8rem 0 0; }
.cutscene-dot {
    height: 3px; flex: 1; border-radius: 2px; transition: background-color .2s ease;
    background: rgba(128, 138, 152, .3);
}
.cutscene-dot.on { background: #dfa347; }

/* 선택지 — 컷이 끝난 뒤에만 나타난다. 컷씬은 전달이고 여기가 결정이다.
 * (연출만 늘리고 결정을 안 만든 게 specs/038의 실패였다.) */
.cutscene-choices { display: none; flex-direction: column; gap: .5rem; margin: 1.4rem 0 0; }
.cutscene-choices.on { display: flex; }
.cutscene-choices h2 {
    font-size: .68rem; letter-spacing: .14em; text-transform: uppercase;
    color: var(--tblr-secondary, #79828e); font-weight: 700; margin: 0 0 .1rem;
}
.cutscene-choice {
    background: transparent; color: inherit; text-align: left; font: inherit;
    border: 1px solid rgba(128, 138, 152, .35); border-radius: 5px;
    padding: .75rem .9rem; cursor: pointer;
    transition: border-color .15s ease, background-color .15s ease;
}
.cutscene-choice:hover:not(:disabled) { border-color: #dfa347; background: rgba(223, 163, 71, .08); }
.cutscene-choice:focus-visible { outline: 2px solid #dfa347; outline-offset: 2px; }
.cutscene-choice:disabled { cursor: default; opacity: .45; }
.cutscene-choice.picked { opacity: 1; border-color: #dfa347; background: rgba(223, 163, 71, .12); }
.cutscene-after {
    display: none; margin: 1.2rem 0 0; padding: .9rem 1rem;
    border-left: 2px solid #dfa347; background: rgba(128, 138, 152, .08); font-size: .97rem;
}
.cutscene-after.on { display: block; }
.cutscene-after small {
    display: block; margin-top: .5rem; font-size: .78rem;
    color: var(--tblr-secondary, #79828e);
}
@media (prefers-reduced-motion: reduce) {
    .cutscene-cap, .cutscene-dot, .cutscene-choice { transition: none; }
}

/* --- 게시판 서브 탭: 방명록 / 오늘의 뉴스 (specs/043) ------------------------------
   클래식 대시보드 홈에 있던 GeekNews 카드를 로비 게시판 안으로 옮기면서 생겼다.
   .subtabs 표준을 그대로 쓰되, 하루타운 패널은 어두운 반투명 배경이라 기본 회색 글자가
   묻힌다 — 색만 다시 잡는다(활성 탭 배경 #206bc4는 표준 그대로). */
.vspace-board-tabs {
    padding: .45rem .6rem 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    flex: 0 0 auto;
}
.vspace-board-tabs .nav-link {
    color: rgba(255, 255, 255, 0.62);
    font-size: .82rem;
    padding: .35rem .7rem;
}
.vspace-board-tabs .nav-link:hover {
    background: rgba(77, 171, 247, 0.16);
    color: #74c0fc;
}
.vspace-board-tabs .nav-link.active,
.vspace-board-tabs .nav-link.active:hover {
    background: #206bc4;
    color: #ffffff;
}
/* 패널 본체는 세로로 꽉 채운다 — 안쪽 로그가 스크롤을 갖는 구조라 min-height:0이 필요하다. */
.vspace-board-panes { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.vspace-board-panes > [role="tabpanel"] { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* display:flex가 hidden의 기본 display:none을 이기므로 명시적으로 다시 숨긴다. */
.vspace-board-panes > [role="tabpanel"][hidden] { display: none; }
.vspace-news-link { color: #74c0fc; text-decoration: none; }
.vspace-news-link:hover { text-decoration: underline; color: #a5d8ff; }

/* --- 상단 HUD 알림 토글 (specs/043) ------------------------------------------------
   클래식 홈 카드의 스위치를 하루타운 상단바로 옮긴 것. 좁은 화면에서는 글자를 숨기고
   아이콘만 남긴다 — 상단바는 이미 메뉴·방 전환·스트릭·소리로 붐빈다. */
.vspace-push-btn { display: inline-flex; align-items: center; gap: .3rem; }
.vspace-push-btn[aria-pressed="true"],
.vspace-push-btn.vspace-push-on {
    background: rgba(32, 107, 196, 0.35);
    border-color: rgba(116, 192, 252, 0.55);
    color: #d0ebff;
}
.vspace-push-btn:disabled { opacity: .55; cursor: default; }
@media (max-width: 720px) {
    .vspace-push-label { display: none; }
}
/* "왜 중요한가?" AI 코멘터리 — 클래식 홈 뉴스 카드에 있던 것을 게시판 탭으로 옮겼다. */
.vspace-news-why { margin-top: .35rem; }
.vspace-news-why-btn {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.14);
    color: rgba(255, 255, 255, 0.72);
    border-radius: 999px;
    font-size: .74rem;
    padding: .15rem .6rem;
    cursor: pointer;
    transition: background-color .15s ease, color .15s ease;
}
.vspace-news-why-btn:hover:not(:disabled) { background: rgba(32, 107, 196, 0.35); color: #d0ebff; }
.vspace-news-why-btn:disabled { opacity: .6; cursor: default; }
.vspace-news-why-text { display: none; }
.vspace-news-why-text.vspace-news-why-shown {
    display: block;
    margin-top: .3rem;
    padding: .4rem .55rem;
    border-left: 2px solid #206bc4;
    background: rgba(32, 107, 196, 0.12);
    border-radius: 0 6px 6px 0;
    font-size: .78rem;
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.82);
}
