/* ============================================================================
   한경국립대학교 포털 — 레이아웃 & 컴포넌트
   (TF 사본 — 포털 전용 화면(학생증·포털 달력·식단·포털 홈·포털 AI·부팅 화면 등)의 규칙은 TF 가 쓰지 않아 뺐다, 2026-10-21.
    포털에서 다시 가져와 맞출 때는 TF 에서 쓰는 클래스만 남긴다)

   반응형 규칙은 세 구간이다.
     ~767px    스마트폰   : 하단 5탭 네비, 본문 전체 폭
     768~1199  태블릿     : 왼쪽 아이콘 레일, 본문 가운데 정렬
     1200px~   데스크톱   : 왼쪽 사이드바(라벨 포함) + 넓은 화면에선 2단 구성

   입력 장치 대응은 <html data-pointer="touch|mouse"> 로 갈린다 (theme.css 참고).
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    overscroll-behavior-y: none;   /* 당겨서 새로고침 막기 — 앱처럼 보이게 */
}

body {
    font-family: var(--font);
    font-size: var(--font-base);
    line-height: 1.55;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    -webkit-tap-highlight-color: transparent;
}

h1, h2, h3, h4, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }
img { max-width: 100%; display: block; }

/* 인라인 SVG 는 크기를 안 주면 부모 폭을 다 먹는다. 기본값을 여기서 한 번 정하고
   버튼·네비 등에서만 따로 키운다. */
svg { width: 20px; height: 20px; flex: none; }
.chev { width: 18px; height: 18px; }

/* 포커스는 키보드 사용자에게만 — 마우스/터치 클릭에는 링이 안 뜬다 */
:focus { outline: none; }
:focus-visible {
    outline: 3px solid var(--hknu-blue-400);
    outline-offset: 2px;
    border-radius: 4px;
}

/* 모션 최소화 설정 존중 */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── 부팅 화면 (Blazor 로딩 전) ─────────────────────────────────────────── */
/* 진한 파란 그라디언트 위라 흰색 단색 로고를 쓴다 (원래 색은 묻힌다) */
@keyframes bootslide { 0% { transform: translateX(-100%); } 100% { transform: translateX(350%); } }

#blazor-error-ui {
    display: none;
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 2000;
    padding: 12px 16px;
    background: #ffefc2; color: #4a3a00;
    box-shadow: 0 -2px 12px rgba(0,0,0,.2);
}
#blazor-error-ui .dismiss { position: absolute; right: 12px; top: 10px; cursor: pointer; }
#blazor-error-ui .reload { text-decoration: underline; font-weight: 700; }

/* ============================================================================
   앱 셸
   ========================================================================= */
/* 사이드 네비는 position:fixed 라 흐름에서 빠진다.
   그래서 그리드 칸을 잡아주는 대신 셸에 왼쪽 여백을 줘서 자리를 비운다.
   (그리드로 하면 fixed 요소가 grid item 이 되지 못해 본문이 1번 칸으로 밀려 들어간다.) */
.shell {
    min-height: 100dvh;
    background: var(--bg);
}

.shell-main {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 100dvh;
}

/* 본문 — 상하 고정 바만큼 자리를 비워 둔다 */
.page {
    flex: 1;
    width: 100%;
    max-width: var(--content-max);
    margin: 0 auto;
    padding: calc(var(--topbar-h) + var(--safe-top) + 8px) 0
             calc(var(--bottomnav-h) + var(--safe-bottom) + 16px);
}

/* 좌우 스와이프 페이지(공지·학사)는 자기가 패딩을 관리한다 */
.page.is-flush { padding-left: 0; padding-right: 0; }

/* 안쪽이 따로 스크롤되는 화면(AI 대화)은 페이지 자체 높이를 화면에 고정한다.
   그래야 대화 목록이 늘어나는 대신 그 안에서 스크롤된다. */
.page.is-fixed { height: 100dvh; overflow: hidden; flex: none; min-height: 0; }
/* ★ flex: none · min-height: 0 이 없으면 셸(.shell-main, flex 세로)의 flex:1 이 height 를 덮고,
   min-height:auto 때문에 내용만큼 늘어난다. 그러면 대화 목록이 안에서 스크롤되지 않고 페이지째 길어져
   답이 써지는 동안 입력창이 화면 밖으로 밀려 내려갔다(2026-10-12 사용자 지적, 실측 800px 화면에서 1,028px). */

.page-pad { padding-inline: 16px; }

/* ── 상단바 ────────────────────────────────────────────────────────────── */
.topbar {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 60;
    height: calc(var(--topbar-h) + var(--safe-top));
    padding-top: var(--safe-top);
    display: flex; align-items: center; gap: 4px;
    padding-inline: 8px 6px;
    background: color-mix(in srgb, var(--surface) 86%, transparent);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid var(--line);
}

/* 이름 영역 (누르면 프로필 시트) */

.avatar {
    flex: none;
    width: 34px; height: 34px;
    border-radius: 12px;
    display: grid; place-items: center;
    font-weight: 800; font-size: 14px;
    color: #fff;
    background: linear-gradient(140deg, var(--hknu-blue), var(--hknu-navy));
    letter-spacing: -.5px;
}
/* 사진을 넣으면 배경 그라디언트는 가려지고 사진이 칸을 꽉 채운다 */
.avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.avatar.sm { width: 28px; height: 28px; border-radius: 10px; font-size: 12px; }
.avatar.lg { width: 56px; height: 56px; border-radius: 18px; font-size: 21px; }
.avatar.xl { width: 76px; height: 76px; border-radius: 24px; font-size: 28px; }
.avatar.prof   { background: linear-gradient(140deg, var(--hknu-teal), #045f5a); }
.avatar.admin  { background: linear-gradient(140deg, #6b46c1, #3b2377); }

/* 아이콘 버튼 */
.iconbtn {
    position: relative;
    flex: none;
    width: var(--tap); height: var(--tap);
    min-width: 40px; min-height: 40px;
    display: grid; place-items: center;
    border: 0; background: none;
    border-radius: 50%;
    cursor: pointer;
    color: var(--text);
    transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.iconbtn svg { width: 21px; height: 21px; }
.iconbtn:active { transform: scale(.92); }

/* hover 는 마우스일 때만 — 터치에서 hover 가 남는 문제를 피한다 */
@media (hover: hover) and (pointer: fine) {
    .iconbtn:hover { background: var(--surface-sunken); }
}

/* 아이콘 오른쪽 위 모서리에 살짝 걸치게. 더 안쪽으로 오면 아이콘을 덮어버린다. */
.badge-dot {
    position: absolute; top: 3px; right: 2px;
    min-width: 15px; height: 15px; padding: 0 4px;
    border-radius: var(--r-pill);
    background: var(--hknu-red); color: #fff;
    font-size: 9.5px; font-weight: 700; line-height: 15px;
    text-align: center;
    border: 2px solid var(--surface);
    box-sizing: content-box;
}
.badge-dot.dot { min-width: 8px; width: 8px; height: 8px; padding: 0; }

/* ── 하단 탭 네비 (모바일) ──────────────────────────────────────────────── */
.bottomnav {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    z-index: 60;
    height: calc(var(--bottomnav-h) + var(--safe-bottom));
    padding-bottom: var(--safe-bottom);
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-top: 1px solid var(--line);
    box-shadow: var(--sh-up);
}

.navitem {
    position: relative;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px;
    border: 0; background: none;
    color: var(--text-3);
    cursor: pointer;
    font-size: 10.5px; font-weight: 600;
    transition: color var(--dur) var(--ease);
    -webkit-tap-highlight-color: transparent;
}
.navitem svg { width: 23px; height: 23px; }
.navitem.active { color: var(--hknu-blue); }
.navitem.active svg { stroke-width: 2.4; }
.navitem:active { transform: scale(.94); }

.navitem .badge-dot { top: 4px; right: calc(50% - 20px); border-color: var(--surface); }

/* ── 사이드 네비 (태블릿·데스크톱) ─────────────────────────────────────── */
.sidenav { display: none; }

/* ============================================================================
   공통 컴포넌트
   ========================================================================= */

.card {
    background: var(--surface);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-1);
    overflow: hidden;
}
.card + .card { margin-top: var(--gap); }

.card-head {
    display: flex; align-items: center; gap: 8px;
    padding: 14px 16px 8px;
}
.card-head h2 { font-size: 15px; font-weight: 700; }
.card-head .more {
    margin-left: auto; font-size: 12.5px; color: var(--text-2);
    display: inline-flex; align-items: center; gap: 2px;
    background: none; border: 0; cursor: pointer; padding: 4px;
}

.section-title {
    padding: 20px 16px 8px;
    font-size: 13px; font-weight: 700; color: var(--text-2);
    letter-spacing: -.2px;
}

/* 목록 행 */
.row {
    display: flex; align-items: center; gap: 12px;
    width: 100%;
    padding: var(--row-py) 16px;
    min-height: var(--tap);
    border: 0; background: none; text-align: left;
    cursor: pointer;
    transition: background var(--dur) var(--ease);
}
.row + .row { border-top: 1px solid var(--line); }
.row:active { background: var(--surface-sunken); }
@media (hover: hover) and (pointer: fine) {
    .row:hover { background: var(--surface-2); }
}
.row-main { flex: 1; min-width: 0; }
.row-title {
    font-size: 14.5px; font-weight: 600;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
}
.row-sub {
    font-size: 12px; color: var(--text-2);
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    margin-top: 2px;
}
.row-sub .sep { color: var(--line-strong); }
.row-aside { flex: none; font-size: 11.5px; color: var(--text-3); text-align: right; }
.chev { flex: none; color: var(--text-3); }

/* 칩(pill) 탭 — 참고 이미지의 상단 필터와 같은 형태 */
.chips {
    display: flex; gap: 8px;
    padding: 10px 16px;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
}
.chips::-webkit-scrollbar { display: none; }

.chip {
    flex: none;
    scroll-snap-align: center;
    display: inline-flex; align-items: center; justify-content: center; gap: 5px;
    padding: 8px 16px;
    min-height: 38px;
    border-radius: var(--r-pill);
    border: 1px solid var(--line-strong);
    background: var(--surface);
    color: var(--text-2);
    font-size: 13.5px; font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: all var(--dur) var(--ease);
}
.chip.active {
    background: var(--hknu-blue);
    border-color: var(--hknu-blue);
    color: #fff;
}
.chip:active { transform: scale(.96); }
@media (hover: hover) and (pointer: fine) {
    .chip:not(.active):hover { border-color: var(--hknu-blue-400); color: var(--hknu-blue); }
}
.chip.sm { padding: 5px 12px; min-height: 30px; font-size: 12.5px; }
/* 못 고르는 칩 — 답하는 중이거나, 서비스가 그 주제를 아직 준비하지 못했을 때 */
.chip:disabled { cursor: not-allowed; transform: none; }
.chip:disabled:not(.active) { opacity: .45; }

/* 칩 안에 붙는 안 읽은 건수 */

/* 서브탭 바 — 공지/학사 상단. 아래 인디케이터가 활성 탭을 따라간다 */
.subtabs {
    position: sticky;
    top: calc(var(--topbar-h) + var(--safe-top));
    z-index: 40;
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--line);
}
.subtabs-scroll {
    display: flex;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}
.subtabs-scroll::-webkit-scrollbar { display: none; }

.subtab {
    position: relative;
    flex: none;
    padding: 13px 16px 12px;
    border: 0; background: none;
    font-size: 14px; font-weight: 600;
    color: var(--text-3);
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--dur) var(--ease);
}
.subtab.active { color: var(--hknu-blue); font-weight: 700; }
.subtab.active::after {
    content: '';
    position: absolute; left: 12px; right: 12px; bottom: 0;
    height: 3px; border-radius: 3px 3px 0 0;
    background: var(--hknu-blue);
}
@media (hover: hover) and (pointer: fine) {
    .subtab:not(.active):hover { color: var(--text); }
}

/* 좌우 스와이프 컨테이너 — CSS scroll-snap 으로 구현했다.
   JS 제스처 라이브러리 없이도 터치에서 자연스럽고, 마우스에선 화살표 버튼을 쓴다. */
.swipe {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    overscroll-behavior-x: contain;

    /* 손가락 가로 스와이프는 js/app.js 의 swipeInit 이 직접 처리한다.
       pan-y 로 브라우저의 가로 팬을 꺼 두어야 우리가 넣는 scrollLeft 와 싸우지 않는다.
       세로 팬은 그대로 브라우저가 가져가므로 페이지 스크롤은 평소처럼 된다.
       (마우스 휠·트랙패드 가로 스크롤은 touch-action 과 무관하게 계속 동작한다.) */
    touch-action: pan-y;
}
.swipe::-webkit-scrollbar { display: none; }
.swipe > .pane {
    flex: none;
    width: 100%;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    min-height: 40vh;
}

/* 스와이프 안내 — 터치일 때만 보인다 */

/* 페이지 인디케이터 (점) */

/* 뱃지 / 태그 */
.tag {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 2px 8px;
    border-radius: var(--r-pill);
    font-size: 11px; font-weight: 700;
    background: var(--surface-sunken); color: var(--text-2);
    white-space: nowrap;
}
.tag.brand  { background: var(--brand-tint); color: var(--hknu-blue); }
.tag.ok     { background: #e3f5ea; color: var(--ok); }
.tag.warn   { background: #fdf1d9; color: var(--warn); }
.tag.danger { background: #fdeaea; color: var(--danger); }
:root[data-theme="dark"] .tag.ok     { background: #12301f; }
:root[data-theme="dark"] .tag.warn   { background: #33280d; }
:root[data-theme="dark"] .tag.danger { background: #33191a; }

/* 버튼 */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    min-height: var(--tap);
    padding: 0 18px;
    border-radius: var(--r-md);
    border: 1px solid transparent;
    background: var(--surface-sunken);
    color: var(--text);
    font-size: 14px; font-weight: 700;
    cursor: pointer;
    transition: all var(--dur) var(--ease);
}
.btn:active { transform: scale(.97); }
.btn.primary { background: var(--hknu-blue); color: #fff; }
.btn.ghost   { background: none; border-color: var(--line-strong); }
.btn.danger  { background: var(--danger); color: #fff; }
.btn.block   { width: 100%; }
.btn:disabled { opacity: .45; cursor: default; transform: none; }
@media (hover: hover) and (pointer: fine) {
    .btn.primary:hover { background: var(--hknu-blue-600); }
    .btn.ghost:hover { background: var(--surface-2); }
}

/* 버튼 세 개를 한 줄에 — 기본 .btn 여백으로는 좁은 폰에서 넘친다 */

/* 입력 */
.field {
    display: flex; align-items: center; gap: 8px;
    padding: 0 14px;
    min-height: var(--tap);
    background: var(--surface-sunken);
    border: 1px solid transparent;
    border-radius: var(--r-md);
}
.field:focus-within { border-color: var(--hknu-blue-400); background: var(--surface); }
.field input, .field textarea {
    flex: 1; min-width: 0;
    border: 0; background: none; outline: none;
    font: inherit; color: var(--text);
    padding: 10px 0;
    resize: none;
}
.field input::placeholder, .field textarea::placeholder { color: var(--text-3); }
.field svg { flex: none; color: var(--text-3); width: 18px; height: 18px; }

/* 바로가기 편집 — 분류 머리줄, 아이콘 고르기 */

/* 행 안에 여러 개 들어가는 작은 아이콘 버튼 */
.iconbtn.sm { width: 32px; height: 32px; min-width: 32px; min-height: 32px; }
.iconbtn.sm svg { width: 17px; height: 17px; }
.iconbtn:disabled { opacity: .25; cursor: default; }
.iconbtn:disabled:active { transform: none; }

/* 토글 스위치 */
.switch {
    position: relative; flex: none;
    width: 48px; height: 28px;
    border-radius: var(--r-pill);
    background: var(--line-strong);
    border: 0; cursor: pointer; padding: 0;
    transition: background var(--dur) var(--ease);
}
.switch::after {
    content: '';
    position: absolute; top: 3px; left: 3px;
    width: 22px; height: 22px; border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,.25);
    transition: transform var(--dur) var(--ease);
}
.switch.on { background: var(--hknu-blue); }
.switch.on::after { transform: translateX(20px); }

/* 빈 상태 */
.empty {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 8px;
    padding: 56px 24px;
    color: var(--text-3);
    text-align: center;
}
.empty .ico { font-size: 34px; opacity: .55; }
.empty b { font-size: 14.5px; color: var(--text-2); font-weight: 600; }
.empty p { font-size: 12.5px; }

/* 스켈레톤 */
.sk {
    background: linear-gradient(90deg, var(--surface-sunken) 25%, var(--surface-2) 37%, var(--surface-sunken) 63%);
    background-size: 400% 100%;
    animation: skshine 1.3s ease infinite;
    border-radius: var(--r-sm);
}
@keyframes skshine { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* ============================================================================
   시트 (바텀시트 / 모달)
   ========================================================================= */
.sheet-scrim {
    position: fixed; inset: 0; z-index: 100;
    background: rgba(10, 20, 40, .45);
    backdrop-filter: blur(2px);
    animation: fadein var(--dur) var(--ease);
}
@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }

.sheet {
    position: fixed; z-index: 101;
    left: 0; right: 0; bottom: 0;
    max-height: 88dvh;
    display: flex; flex-direction: column;
    background: var(--surface);
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    box-shadow: var(--sh-3);
    padding-bottom: var(--safe-bottom);
    animation: sheetup 280ms var(--ease);
}
@keyframes sheetup { from { transform: translateY(100%); } to { transform: translateY(0); } }

.sheet-grab {
    flex: none;
    width: 40px; height: 4px; border-radius: 4px;
    background: var(--line-strong);
    margin: 10px auto 4px;
}
.sheet-head {
    flex: none;
    display: flex; align-items: center; gap: 8px;
    padding: 8px 16px 12px;
    border-bottom: 1px solid var(--line);
}
.sheet-head h2 { font-size: 16.5px; font-weight: 700; flex: 1; }
.sheet-body { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }
/* 채팅방은 시트가 스크롤하는 게 아니라 대화 목록이 스크롤한다 */
.sheet-foot {
    flex: none;
    display: flex; gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--line);
    background: var(--surface);
}

/* 전체화면 시트 (채팅방, 공지 상세, 바코드) */
.sheet.full {
    top: 0; border-radius: 0; max-height: none;
    padding-top: var(--safe-top);
    animation: slidein 260ms var(--ease);
}
@keyframes slidein { from { transform: translateX(100%); } to { transform: translateX(0); } }
.sheet.full .sheet-grab { display: none; }

/* 토스트 */
.toast {
    position: fixed;
    left: 50%; transform: translateX(-50%);
    bottom: calc(var(--bottomnav-h) + var(--safe-bottom) + 16px);
    z-index: 200;
    max-width: min(90vw, 420px);
    padding: 12px 18px;
    border-radius: var(--r-pill);
    background: rgba(20, 26, 36, .94);
    color: #fff;
    font-size: 13.5px; font-weight: 500;
    box-shadow: var(--sh-3);
    animation: toastin 240ms var(--ease);
    text-align: center;
}
@keyframes toastin { from { opacity: 0; transform: translate(-50%, 12px); } to { opacity: 1; transform: translate(-50%, 0); } }

/* ============================================================================
   태블릿 (768px~)  — 왼쪽 아이콘 레일
   ========================================================================= */
@media (min-width: 768px) {
    .bottomnav { display: none; }

    .shell { padding-left: var(--rail-w); }

    .sidenav {
        display: flex; flex-direction: column;
        position: fixed; top: 0; bottom: 0; left: 0;
        width: var(--rail-w);
        z-index: 70;
        padding: 12px 8px calc(12px + var(--safe-bottom));
        background: var(--surface);
        border-right: 1px solid var(--line);
    }

    .sidenav-brand {
        display: flex; flex-direction: column; align-items: center; gap: 6px;
        padding: 8px 0 16px;
        margin-bottom: 4px;
        border-bottom: 1px solid var(--line);
    }
    /* 파비콘·앱 아이콘과 같은 심볼마크. 배경이 투명이라 어느 테마에도 얹힌다. */
    .sidenav-mark {
        width: 40px; height: 40px;
        flex: none;
        object-fit: contain;
    }

    /* 로고의 왼쪽 절반이 딥네이비라 어두운 바탕에서는 묻힌다.
       색을 바꾸지 않고 밝기만 올려 형태가 보이게 한다. */
    :root[data-theme="dark"] .sidenav-mark { filter: brightness(1.5) saturate(1.05); }

    @media (prefers-color-scheme: dark) {
        :root:not([data-theme="light"]):not([data-theme="dark"]) .sidenav-mark {
            filter: brightness(1.5) saturate(1.05);
        }
    }
    .sidenav-wordmark { display: none; }

    .sidenav .navitem {
        flex-direction: column;
        height: 62px;
        border-radius: var(--r-md);
        margin-top: 4px;
        font-size: 11px;
    }
    .sidenav .navitem.active { background: var(--brand-tint); }
    .sidenav .navitem .badge-dot { top: 8px; right: calc(50% - 20px); border-color: var(--surface); }
    @media (hover: hover) and (pointer: fine) {
        .sidenav .navitem:not(.active):hover { background: var(--surface-2); color: var(--text); }
    }

    .topbar { left: var(--rail-w); }
    .page { padding-top: calc(var(--topbar-h) + var(--safe-top) + 12px); padding-bottom: 32px; }
    .subtabs { top: calc(var(--topbar-h) + var(--safe-top)); }
    .toast { bottom: 28px; left: calc(50% + var(--rail-w) / 2); }

    /* 시트는 큰 화면에서 가운데 모달로 뜬다 */
    .sheet {
        left: 50%; right: auto; bottom: auto;
        top: 50%;
        transform: translate(-50%, -50%);
        width: min(560px, 92vw);
        max-height: 84dvh;
        border-radius: var(--r-xl);
        animation: popin 220ms var(--ease);
    }
    @keyframes popin {
        from { opacity: 0; transform: translate(-50%, -46%) scale(.97); }
        to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    }
    .sheet .sheet-grab { display: none; }
    .sheet.full {
        top: 50%; width: min(680px, 94vw); height: min(860px, 88dvh);
        border-radius: var(--r-xl);
        padding-top: 0;
        animation: popin 220ms var(--ease);
    }
}

/* ============================================================================
   데스크톱 (1200px~) — 라벨 있는 사이드바 + 넓은 본문
   ========================================================================= */
@media (min-width: 1200px) {
    :root { --content-max: 860px; }

    .shell { padding-left: var(--sidebar-w); }

    .sidenav {
        width: var(--sidebar-w);
        padding: 16px 12px;
    }
    .sidenav-brand { flex-direction: row; align-items: center; gap: 10px; padding: 6px 8px 16px; }
    .sidenav-wordmark { display: block; line-height: 1.15; }
    .sidenav-wordmark b { display: block; font-size: 14.5px; font-weight: 800; }
    .sidenav-wordmark small { font-size: 11px; color: var(--text-2); }

    .sidenav .navitem {
        flex-direction: row;
        justify-content: flex-start;
        gap: 12px;
        height: 46px;
        padding: 0 14px;
        font-size: 14px;
    }
    .sidenav .navitem .badge-dot { position: static; margin-left: auto; border: 0; }

    .topbar { left: var(--sidebar-w); }
    .toast { left: calc(50% + var(--sidebar-w) / 2); }

    /* 데스크톱 홈은 2단. DOM 순서(신분증 → 캘린더 → 요약 → 공지 → 링크)는 그대로 두고
       grid 배치만 바꾼다 — 캘린더가 왼쪽 큰 칸을 세로로 다 쓰고, 나머지가 오른쪽에 쌓인다. */

    /* 행은 지정하지 않는다 — 자동 배치라 신분증이 숨겨지면 바로가기가 알아서 위로 올라온다. */

    /* 바코드는 리더기에 대는 용도라 손에 드는 기기에서만 쓸모가 있다.
       데스크톱에서는 숨긴다. (아래에서 터치 기기는 예외로 되살린다) */

    /* 2단이 되면 각 조각이 스스로 갖고 있던 좌우 여백은 필요 없다 */
}

/* 큰 화면이라도 손가락을 쓰는 기기(가로로 놓은 태블릿 등)에서는 신분증을 그대로 둔다.
   폭이 아니라 "이 기기를 들고 다니느냐"가 기준이기 때문이다.
   data-pointer 는 실제 입력에 따라 js/app.js 가 바꾼다. */

/* 아주 넓은 화면 — 본문을 더 넓게 쓰되 줄 길이는 제한 */
@media (min-width: 1600px) {
    :root { --content-max: 1120px; }
}

/* ============================================================================
   화면별 스타일
   ========================================================================= */

/* ── 홈 ──────────────────────────────────────────────────────────────── */
/* 1200px 미만에서는 한 줄로 쌓인다. 조각 사이 간격만 준다. */

/* ── 홈: 모바일 신분증 카드 ─────────────────────────────────────────────── */

.barcode {
    background: #fff;
    border-radius: var(--r-md);
    padding: 10px 12px 6px;
    position: relative;
}

/* 전체화면 신분증 */
/* 전체화면 신분증의 사진. 어두운 배경 위라 흰 테두리로 카드에서 띄운다. */

/* ── 캘린더 ────────────────────────────────────────────────────────────── */

/* 주간 / 일간 타임그리드 — 헤더 한 줄 + 본문(시각 눈금 열 + 날짜 열들) */

/* 눈금 칸과 슬롯의 높이가 정확히 같아야 이벤트 위치가 맞는다 (SlotPx = 46) */

/* 선택한 날의 일정 목록 */

.ev {
    display: flex; gap: 10px; align-items: flex-start;
    padding: 9px 0;
}
.ev + .ev { border-top: 1px dashed var(--line); }
/* 내가 만든 일정만 눌러서 고칠 수 있다 */

/* 일정 종류 고르기 칩 앞의 색 점 */

/* ── 링크 그리드 ────────────────────────────────────────────────────────── */

/* ── 채팅 ──────────────────────────────────────────────────────────────── */
/* 메시지가 몇 개 없어도 아래쪽에 붙어 있게 (카카오톡과 같은 느낌).
   justify-content:flex-end 는 넘칠 때 위쪽이 잘리므로 이 방법을 쓴다. */
.msg { display: flex; gap: 8px; align-items: flex-end; max-width: 84%; }
.msg.me { align-self: flex-end; flex-direction: row-reverse; }

/* 채팅방 목록 행 */

/* ── AI ───────────────────────────────────────────────────────────────── */
@keyframes blink { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }

/* ── 검색 / 사이트맵 ────────────────────────────────────────────────────── */

/* ── 진행 막대 ─────────────────────────────────────────────────────────── */
.meter {
    height: 7px; border-radius: 7px;
    background: var(--surface-sunken);
    overflow: hidden;
}
.meter i { display: block; height: 100%; border-radius: 7px; background: var(--hknu-blue); }
.meter i.warn { background: var(--warn); }
.meter i.danger { background: var(--danger); }
.meter i.ok { background: var(--ok); }

/* ── 표 ───────────────────────────────────────────────────────────────── */
table.data {
    width: 100%; border-collapse: collapse;
    font-size: 13px;
    min-width: 460px;
}
table.data th, table.data td {
    padding: 10px 12px;
    text-align: left;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}
table.data th {
    font-size: 11.5px; font-weight: 700; color: var(--text-2);
    background: var(--surface-2);
    position: sticky; top: 0;
}
table.data td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── 통계 타일 ─────────────────────────────────────────────────────────── */
.stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
    gap: 1px;
    background: var(--line);
    border-radius: var(--r-md);
    overflow: hidden;
    margin: 0 16px;
}
.stat { background: var(--surface); padding: 14px 10px; text-align: center; }
.stat b { display: block; font-size: 20px; font-weight: 800; letter-spacing: -.5px; }
.stat small { font-size: 11px; color: var(--text-2); }

/* 유틸 */
.muted { color: var(--text-2); }
.center { text-align: center; }
.grow { flex: 1; }
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── 식단조회 ──────────────────────────────────────────────────────
   날짜 띠는 다섯 칸(월~금)을 균등 분할한다. 캘린더의 7칸과 달리
   주말이 없으므로 grid 로 고정해 폭이 흔들리지 않게 했다. */

/* 반찬은 칩처럼 죽 늘어놓는다 — 줄 수가 날마다 달라 목록보다 이 편이 눕는다 */
/* 첫 번째가 그날의 주 메뉴다. 눈이 먼저 가게 색을 준다. */

/* ── 규정 Q&A 근거 조문 ────────────────────────────────────────────
   답 밑에 접어 둔다. 가장 좋은 모델도 틀릴 때가 있어서, 학생이 조문을
   직접 보고 거를 수 있어야 한다. 그래서 펼치면 본문과 원문 PDF 가 나온다.
   펼치는 줄은 아래 .ai-foot(버튼과 한 줄), 목록은 .ai-src-list. */

.ai-source { padding: 8px 0; border-bottom: 1px solid var(--line); }
.ai-source:last-child { border-bottom: 0; }
.ai-source-head {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    font-size: 12.5px;
}
.ai-source p {
    margin: 5px 0 6px;
    font-size: 12.5px; line-height: 1.65; color: var(--text-2);
    white-space: pre-wrap;
}
.ai-source a {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 11.5px; font-weight: 600; color: var(--hknu-blue);
}

/* 서비스에 못 닿았거나 속도 제한에 걸린 답 */

/* ══ 첫 화면 (/) ═══════════════════════════════════════════════════════
   들어가는 길을 둘로 나눈다 — 실제로 쓰는 챗봇과, 포털 화면 구성 미리보기.
   챗봇 쪽을 진하게 칠한 것은 그쪽이 실물이라서다. */
.start {
    min-height: 100dvh;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    padding: 32px 20px calc(32px + env(safe-area-inset-bottom));
    background: var(--surface-2);
}

/* 카드 셋 — 챗봇·교직원 찾기·포털(2026-10-10). 넓으면 한 줄에 셋, 중간이면 둘, 폰이면 하나. */

/* 교직원 찾기 — 진한 남색(AI 챗봇)과 흰색(포털) 사이의 중간 톤(사용자 요청 2026-10-10).
   바탕색과 섞어 만들어 어두운 화면에서도 같은 느낌으로 보인다. 잠긴 곳이라는 표시만 붉은 계열로. */

/* ══ 챗봇 전체화면 (/chat) ═════════════════════════════════════════════
   포털 셸 없이 대화만. 글씨를 포털 AI 탭(14.2px)보다 크게 — 학생이 폰으로
   규정을 읽는 화면이라 읽기 편한 쪽을 택했다. 여기 규칙은 전부 .chatfull 아래로
   묶어서 포털의 AI 탭에는 닿지 않는다. */

/* 폰 — '한경대 교직원 찾기' 가 두 줄로 꺾이지 않게 조금 줄인다 */
/* 제목이 가운데 오도록 오른쪽 버튼 폭을 왼쪽과 맞춘다 */

/* 주제 — [규정 문답] [학교 안내]. 제목 바로 아래, 가운데. 아래 안내와 한 덩어리로 보이게 같은 바탕. */

/* 맨 위 안내 — 주제 바로 아래, 작게. 대화 중에도 늘 보인다. */
/* 돈이 드는 버튼의 이름만 붉게 — 챗봇 전체화면과 포털 AI 탭의 맨 위 안내가 같이 쓴다 */
/* 포털 AI 탭 — 학습·취업·인성 상담이 아직 예시 답변이라는 안내. 같은 붉은색으로 */

/* 글씨 크게 */

/* 한 줄 가로 스크롤이었는데 PC 에서 마지막 칩이 잘려 보이고 넘길 방법도 마땅치 않았다.
   줄을 바꿔 전부 보이게 한다. 칩 하나가 폰 폭보다 길면 그 안에서도 줄을 바꾼다. */
/* 폭이 모자라는 화면(폰)에서는 칩 안에서도 줄을 바꿔 잘리지 않게 */

/* 사이드바 로고가 첫 화면으로 가는 링크가 되면서 <a> 로 바뀌었다.
   링크 색·밑줄이 붙지 않게 예전 모양 그대로 둔다. */
a.sidenav-brand { color: inherit; text-decoration: none; }
a.sidenav-brand:focus-visible { outline: 2px solid var(--hknu-blue); outline-offset: 2px; border-radius: var(--r-md); }

/* 폰 상단바의 첫 화면 버튼. 사이드바가 보이는 768px 이상에서는 로고가 대신한다. */

/* [더 자세한 답 보기] 앞의 안내. 돈이 드는 버튼이라 누르기 전에 눈에 띄게 붉은 계열로. */

/* 답 아래 버튼 줄 — [복사] [더 자세한 답 보기] */
/* 답 아래 한 줄 — 왼쪽에 근거 펼치기, 오른쪽 끝에 [더 자세한 답 보기] [복사] (2026-10-12).
   폭이 모자라면(폰) 버튼 묶음이 다음 줄 오른쪽으로 내려간다. */
.ai-foot {
    display: flex; flex-wrap: nowrap; align-items: center; gap: 6px 10px;
    margin-top: 10px; padding-top: 8px;
    border-top: 1px solid var(--line);
}
/* 버튼은 늘 오른쪽 끝 같은 줄. 폭이 모자라면 버튼이 내려가는 대신 왼쪽 근거 줄이 안에서 두 줄로 접힌다
   (포털 AI 탭 말풍선에서 2px 모자라 버튼이 통째로 다음 줄로 내려갔다, 2026-10-12). */
.ai-foot-links { flex: 1 1 0; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 2px 14px; }
/* 폰처럼 아주 좁으면 그때만 버튼 묶음이 다음 줄 오른쪽으로 */
@media (max-width: 560px) {
    .ai-foot { flex-wrap: wrap; }
    .ai-foot-links { flex-basis: auto; }
}
.ai-foot-actions { flex: none; margin-left: auto; display: flex; gap: 6px; }
.ai-foot-actions .btn.sm { min-height: 32px; }
.ai-src-toggle {
    padding: 4px 0; border: 0; background: none; cursor: pointer;
    font-size: 12px; font-weight: 700; color: var(--hknu-blue); text-align: left;
}
.ai-src-toggle::before { content: "▸ "; }
.ai-src-toggle.is-open::before { content: "▾ "; }
.ai-src-toggle.is-rest { color: var(--text-3); font-weight: 600; }
.ai-src-list { margin-top: 4px; }
.ai-src-list.is-rest { margin-top: 8px; padding-top: 6px; border-top: 1px dashed var(--line); }
.ai-src-list-head { font-size: 11.5px; font-weight: 700; color: var(--text-3); }

/* ── [더 자세한 답 보기] 앞 숫자 4자리 잠금 창 ─────────────────────────── */
@keyframes pin-shake {
    20%, 60% { transform: translateX(-6px); }
    40%, 80% { transform: translateX(6px); }
}
/* 비밀번호 입력(PinForm) — 대화상자와 교직원 찾기 입구가 같이 쓴다 */

/* 교직원 찾기 입구 */
/* 비밀번호 문의처 — 설명 바로 아래, 한 단계 작게 */

/* 휴대폰(768px 미만)에서 대화가 시작되면 위 안내 문구를 접는다 — 대화 칸이 너무 좁았다(2026-10-12).
   /chat·/people 은 주제 단추·제목만, 포털 AI 탭은 상담 종류 칩만 남는다. 대화를 지우면 다시 보인다. */

/* 답 속 전화번호·이메일 링크 — 누르면 전화·메일 앱이 열린다(2026-10-15). 링크인 줄 알아보게 색과 밑줄을 둔다. */

/* ── 교직원 찾기 음성 (2026-10-15) ── */
/* 마이크 단추 — 입력칸 왼쪽. 운전 중에도 누르기 쉽게 보내기 단추와 같은 크기 */
@keyframes mic-pulse {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--hknu-red) 55%, transparent); }
    100% { box-shadow: 0 0 0 12px transparent; }
}

/* /chat·/ai 의 마이크(MicButton) — 듣는 중·못 들음 안내를 단추 위 말풍선으로. 입력줄 높이를 바꾸지 않게 띄운다. */
.mic-wrap { position: relative; flex: none; display: flex; }
.mic-status {
    position: absolute; left: 0; bottom: calc(100% + 10px); z-index: 5;
    display: flex; align-items: center; gap: 8px;
    max-width: min(320px, calc(100vw - 32px)); width: max-content;
    padding: 8px 12px; border-radius: var(--r-md);
    background: var(--surface); border: 1px solid var(--line); box-shadow: var(--sh-1);
    font-size: 13px; font-weight: 600; line-height: 1.45; color: var(--text-2);
}
.mic-status.is-on { color: var(--hknu-red); }

@keyframes mic-blink { 50% { opacity: .25; } }

/* 큰 전화 단추 — 말소리만으로는 브라우저가 전화 앱을 못 열게 막을 수 있어 같이 띄운다 */
.people-call {
    flex: none; display: flex; flex-direction: column; gap: 8px;
    padding: 10px 14px; background: var(--surface); border-top: 1px solid var(--line);
}
.people-call-who { font-size: 13.5px; font-weight: 700; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.people-call-btns { display: flex; gap: 8px; }
.people-call-btn {
    flex: 1; min-width: 0; min-height: 52px;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    padding: 8px 10px; border-radius: var(--r-md);
    background: var(--hknu-blue); color: #fff; text-decoration: none;
    font-size: 15px; line-height: 1.3;
}
.people-call-btn.is-mobile { background: #1f8a4c; }
.people-call-btn svg { flex: none; width: 18px; height: 18px; }
.people-call-btn span { display: flex; flex-direction: column; align-items: flex-start; }
.people-call-btn b { font-size: 15px; font-weight: 800; white-space: nowrap; }
.people-call-btn:active { transform: scale(.98); }
