/* ═══════════════════════════════════════════════════════════════
   app.css
   사이트 내부 화면 — 대시보드 위젯, 사이드바(레일/드로어), 카드·팝업·토스트·입력, 로고, 시작 가이드 색, 테마 전환.

   가장 큰 파일이다. 화면별로 찾을 때는 구간 머리말(── 로 시작하는 줄)을 검색하는 편이 빠르다.

   ⚠️ 이 파일들은 index.html의 <style> 하나를 순서 그대로 자른 것이다.
      선택자가 파일을 넘나들며 겹치므로 <link> 순서를 바꾸면 화면이 깨진다.
      순서: tokens → base → app → landing → overrides
   ═══════════════════════════════════════════════════════════════ */

/* 대시보드 위젯의 가로 칸 수. 예전엔 Tailwind col-span 클래스를 마크업에 박아 뒀는데,
   관리자가 크기를 바꿀 수 있게 되면서 data-span 하나로 옮겼다(JS가 이 값만 갈아끼운다). */
.dash-widget { position: relative; }
@media (min-width: 640px) {
  .dash-widget[data-span="2"], .dash-widget[data-span="4"] { grid-column: span 2 / span 2; }
}
@media (min-width: 1024px) {
  .dash-widget[data-span="1"] { grid-column: span 1 / span 1; }
  .dash-widget[data-span="2"] { grid-column: span 2 / span 2; }
  .dash-widget[data-span="3"] { grid-column: span 3 / span 3; }
  .dash-widget[data-span="4"] { grid-column: span 4 / span 4; }
}
/* 편집 모드: 위젯 내용은 흐리게 + 클릭 차단하고 조작 버튼만 살린다.
   (편집 중에 '더보기' 링크를 눌러 다른 섹션으로 튀어 나가는 사고를 막는다) */
.dash-widget-tools { display: none; }
#dashboard-widgets.editing .dash-widget { outline: 2px dashed rgb(var(--md-primary-rgb) / 0.55); outline-offset: 3px; cursor: grab; }
#dashboard-widgets.editing .dash-widget > :not(.dash-widget-tools) { pointer-events: none; opacity: 0.5; }
#dashboard-widgets.editing .dash-widget-tools { display: flex; }
#dashboard-widgets.editing .dash-widget.dragging { opacity: 0.35; cursor: grabbing; }
#dashboard-widgets.editing .dash-widget.drop-target { outline-style: solid; outline-width: 3px; }

/* 크기 조절 손잡이 — 편집 모드에서 위젯 오른쪽 가장자리에 붙는 점자 모양 그립.
   좌우로 끌면 가로 칸(1~4)이 실시간으로 바뀐다. 터치에서도 동작하도록 포인터 이벤트를 쓴다. */
.dash-resize-grip { display: none; }
#dashboard-widgets.editing .dash-resize-grip {
  display: flex; align-items: center; justify-content: center;
  position: absolute; top: 50%; right: -0.5rem; transform: translateY(-50%);
  width: 1.25rem; height: 3rem; z-index: 11;
  border-radius: 9999px; cursor: ew-resize; touch-action: none;
  background-color: rgb(var(--md-surface-container-high-rgb));
  border: 1px solid rgb(var(--md-outline-rgb) / 0.45);
  color: rgb(var(--md-on-surface-variant-rgb));
  transition: background-color 0.2s ease-out, transform 0.2s cubic-bezier(0.2,0,0,1);
}
#dashboard-widgets.editing .dash-resize-grip:hover,
#dashboard-widgets.editing .dash-widget.resizing .dash-resize-grip {
  background-color: rgb(var(--md-primary-rgb));
  color: rgb(var(--md-on-primary-rgb));
  transform: translateY(-50%) scale(1.12);
}
/* 끌고 있는 위젯은 테두리를 실선으로 굵게 해서 "지금 이걸 조절 중"임을 드러낸다. */
#dashboard-widgets.editing .dash-widget.resizing { outline-style: solid; outline-width: 3px; }
/* 끄는 동안에는 칸 수가 매 프레임 바뀌므로 폭 전환을 잠깐 끈다(따라오는 잔상 방지). */
#dashboard-widgets.resizing-any .dash-widget { transition: none; }

/* 학습&공지사항의 분류 구역은 접었다 펼 수 있다(기본은 펼침).
   0fr→1fr 그리드 전환이라 내용 높이를 몰라도 부드럽게 접힌다. */
.academic-group-body { display: grid; grid-template-rows: 1fr;
  transition: grid-template-rows 0.3s cubic-bezier(0.2,0,0,1); }
.academic-group.collapsed .academic-group-body { grid-template-rows: 0fr; }
.academic-group-body > div { overflow: hidden; min-height: 0; }
.academic-group-chevron { transition: transform 0.3s cubic-bezier(0.2,0,0,1); }
.academic-group.collapsed .academic-group-chevron { transform: rotate(-90deg); }

/* 사용자 지정 팔레트 편집기 — '+' 를 누르면 스와치 줄 아래로 "펼쳐진다"(팝업/모달 아님).
   0fr→1fr 그리드 전환을 쓰면 내용 높이를 몰라도 부드럽게 열리므로 max-height 매직넘버가 필요 없다. */
.palette-editor { display: grid; grid-template-rows: 0fr; opacity: 0; visibility: hidden;
  transition: grid-template-rows 0.34s cubic-bezier(0.2,0,0,1), opacity 0.22s ease-out, visibility 0s linear 0.34s; }
.palette-editor.open { grid-template-rows: 1fr; opacity: 1; visibility: visible; transition-delay: 0s; }
.palette-editor > div { overflow: hidden; min-height: 0; }

/* 색 하나를 고르는 줄. 네이티브 색상 선택기를 동그란 스와치처럼 보이게 감싼다. */
.pal-swatch { -webkit-appearance: none; appearance: none; border: none; padding: 0;
  width: 2.5rem; height: 2.5rem; border-radius: 9999px; cursor: pointer; background: transparent; }
.pal-swatch::-webkit-color-swatch-wrapper { padding: 0; }
.pal-swatch::-webkit-color-swatch { border: 1px solid rgb(var(--md-outline-rgb) / 0.45); border-radius: 9999px; }
.pal-swatch::-moz-color-swatch { border: 1px solid rgb(var(--md-outline-rgb) / 0.45); border-radius: 9999px; }
.pal-hex { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; }

/* 미리보기 — 실제 대시보드를 축소해 옮겨 놓은 화면.
   바깥 래퍼에 후보 팔레트의 --md-*-rgb 를 직접 얹으면 안쪽 md-* 클래스들이 그 값을 쓴다.
   (그래서 사이트에 적용하기 전에도 결과를 그대로 볼 수 있다) */
#palette-preview { background-color: rgb(var(--md-background-rgb)); }
#palette-preview .pv-rail { background-color: rgb(var(--md-surface-container-low-rgb)); }

/* ⚠️ 라이트 모드 글자 보정 (2026-08-20 기준).
   포인트 컬러 8종은 M3 TonalSpot으로 생성한 값이라 대부분 보정 없이도 AA를 넘긴다.
   단 '레드'만 라이트를 손봐서(밝은 레드 + 어두운 베이지 배경) 보정 없이는 링크가 4.24로
   미달한다. 검정을 6% 섞으면(94%) 8종 전부 4.68 이상이 된다.
   ⚠️ 채움 배경(bg-md-primary)에는 이 보정이 걸리지 않는다 — 글자·아이콘일 때만 적용된다.
   ⚠️ 포인트 컬러 값은 js/theme.js의 ACCENTS 스와치 HEX와 1:1로 대응한다 — 한쪽만 바꾸지 말 것.
   다크 모드에서는 M3 다크 primary가 이미 밝은 톤이라 이 보정이 필요 없다. */
:root:not([data-theme="dark"]) .text-md-primary {
  color: color-mix(in srgb, rgb(var(--md-primary-rgb)) 94%, black);
}

/* 대시보드 히어로 — 라이트에서는 포인트 컬러로 채운 큰 면이 보기 좋지만, 다크에서 그대로 두면
   M3 다크 primary가 밝은 톤이라 화면에서 큰 밝은 덩어리로 튄다. 다크에서는 M3 권장대로
   톤 서피스(secondary-container)로 낮춰 배경과 어울리게 한다. */
[data-theme="dark"] #dashboard-welcome-banner {
  background-color: rgb(var(--md-secondary-container-rgb));
  color: rgb(var(--md-on-secondary-container-rgb));
}
/* 히어로 안의 배지/칩은 "히어로 글자색"을 기준으로 섞어서, 히어로 배경이 무엇이든 항상 읽힌다. */
.hero-badge { background: color-mix(in srgb, currentColor 18%, transparent); }
/* 요약 칩은 면을 확실히 분리해야 눌리는 느낌이 난다 — 두 테마 모두에서 대비가 큰 조합. */
.hero-chip { background-color: rgb(var(--md-surface-rgb)); color: rgb(var(--md-primary-rgb)); }
.hero-chip:hover { filter: brightness(0.97); }

/* 일정 태그 칩 — 색을 사용자가 자유롭게 고르므로(HEX) Tailwind 클래스가 아니라 CSS 변수로 받는다.
   라이트에서는 원색 그대로, 다크에서는 배경을 조금 더 올리고 글자를 흰쪽으로 섞어(color-mix)
   어떤 색을 골라도 두 테마 모두에서 읽히게 만든다. */
.tag-chip {
  background: color-mix(in srgb, var(--tag-color) 14%, transparent);
  color: color-mix(in srgb, var(--tag-color) 82%, black);
  border: 1px solid color-mix(in srgb, var(--tag-color) 28%, transparent);
}
.tag-dot { background: var(--tag-color); }
[data-theme="dark"] .tag-chip {
  background: color-mix(in srgb, var(--tag-color) 24%, transparent);
  color: color-mix(in srgb, var(--tag-color) 55%, white);
  border-color: color-mix(in srgb, var(--tag-color) 38%, transparent);
}

/* 동적으로 새로 렌더링되는 리스트 아이템(일정/공지/채팅/댓글 카드 등)에 공용으로 붙이는 페이드인 */
.enter-fade { animation: enterFade 0.35s cubic-bezier(0.2,0,0,1) both; }
@keyframes enterFade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
/* 클릭 가능한 카드형 요소 공용 호버/프레스 피드백 (전체가 하나의 클릭 대상인 경우에만 사용) */
.card-tap { transition: transform 0.18s cubic-bezier(0.2,0,0,1), box-shadow 0.18s cubic-bezier(0.2,0,0,1); }
.card-tap:hover { transform: translateY(-3px); box-shadow: 0 10px 24px rgba(0,0,0,0.12); }
.card-tap:active { transform: translateY(-1px) scale(0.98); }
/* 내부에 버튼이 여러 개 섞여 있어 "전체 클릭"은 아니지만, 살짝 떠 보이는 정도로만 생동감을 주는 카드 */
.card-lift { transition: transform 0.18s cubic-bezier(0.2,0,0,1), box-shadow 0.18s cubic-bezier(0.2,0,0,1); }
.card-lift:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(0,0,0,0.10); }
/* 버튼 탭 피드백을 공용 마커 클래스/속성 선택자로 한 번에 표준화한다(사이트 전체 ⋮ 메뉴, 모달 열기/닫기
   버튼 등 개별적으로 일일이 클래스를 추가하지 않아도 자동 적용됨). 터치에서도 :active가 그대로 먹힌다. */
.btn-dropdown, [data-open-modal], [data-close-modal] { transition: transform 0.15s cubic-bezier(0.2,0,0,1); }
.btn-dropdown:active { transform: scale(0.82); }
[data-open-modal]:active, [data-close-modal]:active { transform: scale(0.95); }
/* 채팅 말풍선: 길게 눌러 더보기 메뉴를 여는 동작에 눌림 피드백을 살짝 얹어 "반응하고 있다"는 걸 알려준다 */
.chat-bubble-trigger { transition: transform 0.15s ease, filter 0.15s ease; }
.chat-bubble-trigger:active { transform: scale(0.97); filter: brightness(0.96); }
/* 하단 토스트: 라이트/다크·포인트 컬러 테마와 무관하게 항상 같은 회색조로(시스템 알림 느낌을 주려는
   의도적 선택) 아래에서 살짝 올라오며 나타났다가 다시 내려가며 사라진다. */
#sys-toast {
  /* 첫 화면(100)·시작 가이드(95) 위에 떠야 한다. 다만 조작을 막는 시스템
     대화상자(110)보다는 아래에 둔다. */
  position: fixed; left: 50%; bottom: 1.5rem; z-index: 105;
  transform: translateX(-50%) translateY(150%); opacity: 0; pointer-events: none;
  transition: transform 0.3s cubic-bezier(0.2,0,0,1), opacity 0.25s ease-out;
}
#sys-toast.sys-toast-show { transform: translateX(-50%) translateY(0); opacity: 1; }
/* 모바일 하단은 Navigation Bar(80dp)가 화면 폭 전체를 덮으므로 토스트를 그 위로 띄운다.
   FAB(56 + 바닥에서 96px)까지 피하려면 그보다 더 위여야 한다. */
@media (max-width: 767.98px) {
  #sys-toast { bottom: calc(10rem + env(safe-area-inset-bottom, 0px)); }
  /* 하단 Navigation Bar가 스크롤 영역의 마지막 메뉴("설정")를 가리지 않도록 그만큼 패딩을 준다
     (Nav Bar 80dp = 5rem + 여유). Tailwind CDN은 JIT 스타일을
     런타임에 동적으로 주입하는데, 그 시점이 이 인라인 <style> 블록보다 나중이라 같은 우선순위의
     선택자끼리는 나중에 삽입된 Tailwind의 p-6(padding-bottom: 1.5rem)이 이겨버린다 — .hidden의
     !important와 같은 이유로 여기도 !important가 필요하다. */
  .sidebar-nav-scroll { padding-bottom: 7rem !important; }
  /* iOS Safari는 포커스된 입력의 font-size가 16px 미만이면 화면을 자동으로 확대한다 — 사이트
     전체 레이아웃이 커진 것처럼 보여 불편하다는 피드백이 있어, 모바일에서는 모든 입력 요소의
     글자 크기를 16px 이상으로 고정해 확대 자체가 트리거되지 않게 한다(레이아웃은 그대로 두고
     입력창 안 텍스트만 살짝 커 보일 수 있음 — 확대되는 것보다는 훨씬 낫다). */
  input, select, textarea { font-size: 16px !important; }
}
.gallery-img-wrap { aspect-ratio: 1 / 1; overflow: hidden; border-radius: 24px; }
.gallery-img { width: 100%; height: 100%; object-fit: cover; cursor: pointer; transition: transform 0.3s ease; }
.gallery-img:hover { transform: scale(1.05); }
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-thumb { background: #CAC4D0; border-radius: 10px; }
.m3-switch { appearance: none; -webkit-appearance: none; width: 44px; height: 26px; background: rgb(var(--md-surface-variant-rgb)); border-radius: 999px; position: relative; cursor: pointer; transition: background 0.3s cubic-bezier(0.2, 0, 0, 1); flex-shrink: 0; }
.m3-switch::before { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.2); transition: transform 0.3s cubic-bezier(0.2, 0, 0, 1); }
.m3-switch:checked { background: rgb(var(--md-error-rgb)); }
.m3-switch:checked::before { transform: translateX(18px); }
/* 다크모드/일반 토글처럼 위험 신호가 아닌 스위치는 포인트 색상으로 켜진다 */
.m3-switch-primary:checked { background: rgb(var(--md-primary-rgb)); }
/* 파일 선택 입력 — 네이티브 컨트롤은 브라우저마다 제각각이라 MD3와 어긋난다.
   입력 자체는 면(surface)으로 두고, 안의 "파일 선택" 버튼만 M3 Tonal 버튼 모양으로 덧입힌다. */
.m3-file {
  background-color: rgb(var(--md-surface-container-low-rgb)); color: rgb(var(--md-on-surface-rgb));
  border: 1px solid rgb(var(--md-outline-rgb) / 0.45); border-radius: 16px;
  padding: 10px 12px; width: 100%; font-size: 14px; transition: 0.2s;
}
.m3-file:hover { background-color: rgb(var(--md-surface-container-rgb)); }
.m3-file::file-selector-button {
  background-color: rgb(var(--md-secondary-container-rgb)); color: rgb(var(--md-on-secondary-container-rgb));
  border: 0; border-radius: 9999px; padding: 8px 18px; margin-right: 12px;
  font-weight: 700; font-size: 14px; cursor: pointer; transition: filter 0.2s;
}
.m3-file::file-selector-button:hover { filter: brightness(0.95); }

.m3-input-pill { border-radius: 9999px !important; border-bottom: none !important; background-color: rgb(var(--md-surface-rgb)); padding: 12px 20px; }
.m3-input-pill:focus { background-color: rgb(var(--md-surface-container-low-rgb)); }
/* 공용 "더보기" 팝업 애니메이션 — 채팅 입력창의 (+) 더보기와 게시글/파일의 (⋮) 더보기가 모두 이 규칙을 공유한다. */
.popup-menu { transform-origin: top right; transform: scale(0.85) translateY(-6px); opacity: 0; pointer-events: none; transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s ease-out, background-color 0.35s cubic-bezier(0.2, 0, 0, 1), border-color 0.35s cubic-bezier(0.2, 0, 0, 1), color 0.35s cubic-bezier(0.2, 0, 0, 1); }
.popup-menu.open { transform: scale(1) translateY(0); opacity: 1; pointer-events: auto; }
/* 채팅 더보기처럼 버튼 아래쪽에서 위로 펼쳐지는 경우 */
.popup-menu.popup-menu-up { transform-origin: bottom left; transform: scale(0.85) translateY(10px); }
.popup-menu.popup-menu-up.open { transform: scale(1) translateY(0); }
/* 임의 좌표(우클릭/롱프레스)에서 뜨는 컨텍스트 메뉴 */
.popup-menu.popup-menu-context { transform-origin: top left; }
/* 채팅 말풍선: 롱프레스 시 iOS 콜아웃/텍스트 선택이 뜨지 않도록 */
.chat-bubble-trigger { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; cursor: pointer; }
/* 전역 .hidden{display:none!important} 규칙이 Tailwind의 md:flex를 항상 이겨버리므로,
   "모바일 숨김 + 데스크톱 노출"이 필요한 요소는 이 유틸리티를 대신 사용한다. */
.md-flex-only { display: none; }
@media (min-width: 768px) {
  .md-flex-only { display: flex; }
}
/* 모바일에서는 섹션 제목(아이콘+이름)을 상단바로 옮겨 보여주므로 본문 안의 원래 제목은 숨기고,
   데스크톱에서는 다시 블록으로 노출한다(사이드바 상단은 학급 이름이 아니라 ClassSpace 로고다). */
.md-block-only { display: none; }
@media (min-width: 768px) {
  .md-block-only { display: block; }
}
/* 사이드바 — 모바일: 평소엔 화면 밖으로 완전히 슬라이드 아웃 + 투명 상태이고, 하단 FAB를
   누르면 body.sidebar-open이 켜지면서 풀 폭 오버레이로 슬라이드+페이드 인 한다(닫히면 완전히
   사라짐 — 중간 상태 없음). 데스크톱은 아래 @media 블록에서 이 기본값을 아이콘 레일 방식으로
   덮어쓴다. */
#sidebar {
  width: 18rem; transform: translateX(-100%); opacity: 0; pointer-events: none;
  transition: transform 0.45s cubic-bezier(0.2,0,0,1), opacity 0.4s ease-out;
}
body.sidebar-open #sidebar { transform: translateX(0); opacity: 1; pointer-events: auto; }
#main-content { margin-left: 0; transition: margin-left 0.25s ease-out; }

/* 사이드바의 상단(브랜드·검색)·하단(알림·설정·내 정보) 공통 기본형.
   ⚠️ 반드시 미디어쿼리 **밖**에 둘 것 — 데스크톱 블록 안에만 두었다가 모바일 드로어에서
      이 요소들이 display:block에 padding 0으로 뭉개졌던 이력이 있다(알림 버튼 높이 22px).
   가로 여백(1.5rem)은 nav 항목(px-6)과 같게 맞춰 목록과 한 줄로 정렬된다. */
.sidebar-head { padding: 0.25rem 0 0.5rem; }
.sidebar-foot {
  padding: 0.5rem 0 0; margin-top: 0.5rem;
  border-top: 1px solid rgb(var(--md-outline-rgb) / 0.18);
}
.sidebar-action {
  display: flex; align-items: center; gap: 1rem;
  min-height: 3rem; padding: 0 1.5rem; border-radius: 9999px; width: 100%;
  transition: background-color 0.2s ease-out;
}
.sidebar-search-input { padding: 0.5rem 0.875rem; }

/* 사이드바 — 데스크톱(768px 이상): M3 사이트와 같은 **네비게이션 레일**이다(2026-08-20 개편).
   아이콘 위 / 라벨 아래로 쌓고, 라벨은 항상 보인다(호버로 펼치지 않는다 — M3 레일에는 확장이
   없다). 활성 항목은 아이콘 **뒤에만** 알약이 깔린다(항목 전체가 칠해지지 않는다).
   위쪽은 둥근 사각형 검색 버튼, 아래쪽은 원형 외곽선 버튼 두 개(알림·설정).
   ⚠️ 라벨이 늘 보이므로 짧은 이름(.rail-label)을 쓴다 — 긴 이름(.sidebar-label)은 모바일
      드로어 전용이다. 새 섹션을 추가하면 두 라벨을 모두 넣을 것. */
@media (min-width: 768px) {
  /* 화면 가장자리에 붙이지 않고 **여백을 두고 떠 있는 섬**으로 만든다(M3 결).
     네 모서리 모두 둥글고 그림자로 살짝 들어 올린다. */
  #sidebar {
    top: 0.75rem; left: 0.75rem; bottom: 0.75rem; height: auto;
    transform: none; opacity: 1; pointer-events: auto;
    width: 5.5rem;
    background-color: rgb(var(--md-surface-container-rgb));
    border-radius: 28px;
    /* M3 elevation 2 — 배경에서 한 겹 떠 있는 정도. 너무 세면 카드가 아니라 팝업처럼 보인다. */
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.28), 0 2px 6px 2px rgb(0 0 0 / 0.14);
    align-items: center; justify-content: flex-start;
    padding: 0.75rem 0;
    transition: width 0.35s cubic-bezier(0.2,0,0,1);
  }
  /* '더보기'로 펼친 상태 — 레일이 넓어지면서 레일에 없던 섹션까지 전부 보여준다.
     #sidebar는 z-50, 스크림은 z-40이라 화면의 다른 요소들 위로 올라온다.
     아래 "접힌 레일 전용" 규칙들이 전부 body:not(.sidebar-open)으로 잠겨 있어서,
     펼치면 미디어쿼리 밖의 기본형(= 모바일 드로어와 같은 모습)이 그대로 살아난다. */
  body.sidebar-open #sidebar { width: 20rem; align-items: stretch; padding: 0.75rem; }
  body.sidebar-open #sidebar .rail-label { display: none; }
  /* 프로필은 .sidebar-action이 아니라 아바타 원 자체라 좌우 패딩이 없다.
     펼친 목록에서 아이콘 열이 어긋나 보이므로 다른 항목의 아이콘 중심에 맞춰 들여쓴다. */
  body.sidebar-open #sidebar #desktop-profile-btn { margin-left: 0.75rem; }
  /* 펼친 사이드바에서만 이름·이메일·학급을 보여준다(접힌 레일에는 들어갈 자리가 없다). */
  body.sidebar-open #sidebar .sidebar-profile-info { display: flex; }
  body:not(.sidebar-open) #sidebar .sidebar-profile { justify-content: center; }
  /* 펼친 목록은 섹션 10개 + 분류 제목 2개 + 상·하단까지 담아야 한다. 본문 기본 여백(py-3.5,
     분류 pt-7) 그대로면 1000px 가까이 되어 노트북 화면에서 갤러리·신고함이 잘린다.
     데스크톱 펼침에서만 세로 여백을 조여 한 화면에 들어오게 한다(모바일 드로어는 그대로). */
  body.sidebar-open #sidebar #nav-menu .nav-link { padding-top: 0.625rem; padding-bottom: 0.625rem; }
  body.sidebar-open #sidebar .sidebar-category-label { padding-top: 1rem; padding-bottom: 0.25rem; }

  /* ══ 여기부터 접힌 레일 전용 ══ */
  /* 긴 라벨·분류 제목·브랜드는 레일에 두지 않는다(M3 사이트도 없다). */
  body:not(.sidebar-open) #sidebar .sidebar-label,
  body:not(.sidebar-open) #sidebar .sidebar-title,
  body:not(.sidebar-open) #sidebar .sidebar-category-label { display: none; }
  body:not(.sidebar-open) #sidebar .rail-label {
    display: block; font-size: 11px; line-height: 1.3; text-align: center;
    max-width: 4.5rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  body:not(.sidebar-open) #sidebar > * { width: 100%; flex-shrink: 0; }
  /* 상단은 더보기 → 검색 두 개를 세로로 쌓는다. */
  body:not(.sidebar-open) #sidebar .sidebar-head {
    padding: 0 0 0.5rem; display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
  }
  /* 목적지 목록은 레일의 **세로 가운데**에 온다(상단 = 더보기·검색, 하단 = 프로필·알림·설정). */
  body:not(.sidebar-open) #sidebar .sidebar-nav-scroll {
    padding: 0; width: 100%; display: flex; align-items: center;
  }
  body:not(.sidebar-open) #sidebar #nav-menu { display: flex; flex-direction: column; width: 100%; }
  /* 레일에는 주요 목적지 5개(data-rail)만 남기고 나머지는 '더보기'로 펼쳤을 때 보여준다.
     마크업 순서와 무관하게 order로 홈 → 공지사항 → 시간표 → 급식 → 채팅으로 세운다. */
  body:not(.sidebar-open) #sidebar #nav-menu .nav-link:not([data-rail]) { display: none; }
  body:not(.sidebar-open) #sidebar #nav-menu .nav-link[data-rail="1"] { order: 1; }
  body:not(.sidebar-open) #sidebar #nav-menu .nav-link[data-rail="2"] { order: 2; }
  body:not(.sidebar-open) #sidebar #nav-menu .nav-link[data-rail="3"] { order: 3; }
  body:not(.sidebar-open) #sidebar #nav-menu .nav-link[data-rail="4"] { order: 4; }
  body:not(.sidebar-open) #sidebar #nav-menu .nav-link[data-rail="5"] { order: 5; }
  body:not(.sidebar-open) #sidebar .sidebar-foot {
    padding: 0.75rem 0 0; margin-top: 0.5rem; border-top: none;
    display: flex; flex-direction: column; align-items: center; gap: 0.75rem;
  }

  /* 검색 — 둥근 사각형 채움 버튼(레일에서 유일하게 색이 채워진 포인트) */
  body:not(.sidebar-open) #sidebar .rail-search {
    width: 3.5rem; height: 3.5rem; min-height: 0; padding: 0; margin: 0;
    border-radius: 1rem; gap: 0;
    background-color: rgb(var(--md-secondary-container-rgb));
    color: rgb(var(--md-on-secondary-container-rgb));
    display: flex; align-items: center; justify-content: center;
  }
  body:not(.sidebar-open) #sidebar .rail-search:hover { filter: brightness(0.96); }
  body:not(.sidebar-open) #sidebar .rail-search .rail-icon { width: auto; height: auto; background: none; }

  /* 더보기 — 배경 없는 아이콘 버튼(검색만 채움으로 강조하기 위해 여기는 비워 둔다) */
  body:not(.sidebar-open) #sidebar .rail-plain {
    width: 3rem; height: 3rem; min-height: 0; padding: 0; margin: 0; gap: 0;
    border-radius: 9999px; display: flex; align-items: center; justify-content: center;
    color: rgb(var(--md-on-surface-variant-rgb)); background-color: transparent;
  }
  body:not(.sidebar-open) #sidebar .rail-plain:hover { background-color: rgb(var(--md-surface-container-high-rgb)); }
  body:not(.sidebar-open) #sidebar .rail-plain .rail-icon { width: auto; height: auto; background: none; }

  /* nav 항목 — 아이콘 위, 라벨 아래 */
  body:not(.sidebar-open) #sidebar .nav-link {
    flex-direction: column; gap: 0.25rem; width: 100%; height: auto; min-height: 0;
    margin: 0; padding: 0.375rem 0.25rem; border-radius: 0;
    justify-content: center; align-items: center;
    background-color: transparent;   /* 항목 전체 채움(Tailwind 활성 클래스)을 무력화 */
    color: rgb(var(--md-on-surface-variant-rgb));
  }
  body:not(.sidebar-open) #sidebar .nav-link:hover { background-color: transparent; }
  body:not(.sidebar-open) #sidebar .nav-link:hover .rail-icon { background-color: rgb(var(--md-surface-container-high-rgb)); }
  /* 활성 표시: 아이콘 뒤 알약만 칠한다 */
  body:not(.sidebar-open) #sidebar .rail-icon {
    width: 3.5rem; height: 2rem; border-radius: 1rem;
    display: flex; align-items: center; justify-content: center;
    transition: background-color 0.2s cubic-bezier(0.2,0,0,1);
  }
  body:not(.sidebar-open) #sidebar .nav-link.nav-link-active { color: rgb(var(--md-on-surface-rgb)); font-weight: 700; }
  body:not(.sidebar-open) #sidebar .nav-link.nav-link-active .rail-icon {
    background-color: rgb(var(--md-secondary-container-rgb));
    color: rgb(var(--md-on-secondary-container-rgb));
  }

  /* 하단 알림·설정 — 원형 외곽선 버튼(M3 사이트의 정지·밝기 버튼과 같은 모양) */
  body:not(.sidebar-open) #sidebar .rail-circle {
    width: 3rem; height: 3rem; min-height: 0; padding: 0; margin: 0; gap: 0;
    border-radius: 9999px; border: 1px solid rgb(var(--md-outline-rgb) / 0.6);
    display: flex; align-items: center; justify-content: center;
    color: rgb(var(--md-on-surface-variant-rgb));
    background-color: transparent;
  }
  body:not(.sidebar-open) #sidebar .rail-circle:hover { background-color: rgb(var(--md-surface-container-high-rgb)); }
  body:not(.sidebar-open) #sidebar .rail-circle .rail-icon { width: auto; height: auto; background: none; }
  body:not(.sidebar-open) #sidebar .rail-circle .rail-label { display: none; }
  body:not(.sidebar-open) #sidebar .rail-circle.nav-link-active {
    background-color: rgb(var(--md-secondary-container-rgb));
    color: rgb(var(--md-on-secondary-container-rgb));
    border-color: transparent;
  }
  body:not(.sidebar-open) #sidebar .notif-unread-dot { top: 0.5rem; right: 0.75rem; }
  body:not(.sidebar-open) #sidebar #chat-unread-dot { position: absolute; top: 0.375rem; right: 1rem; margin: 0; }
  #sidebar .nav-link { position: relative; }

  /* 사이드바가 섬이 된 만큼 본문도 그 폭 + 좌우 여백만큼 비켜 준다.
     (2026-08-20 개편 전에는 우측 상단 유틸리티 섬을 피하려고 padding-top: 4rem을 줬는데,
      그 섬을 없애고 프로필·알림을 사이드바 하단으로 옮겼으므로 더 이상 필요 없다.) */
  #main-content { margin-left: calc(5.5rem + 1.5rem); }
  #search-bar { margin-left: calc(5.5rem + 1.5rem); margin-right: 1.5rem; }
}
/* 모바일 드로어에서는 짧은 라벨을 숨기고 긴 이름만 쓴다(가로 폭이 넉넉하다). */
@media (max-width: 767.98px) {
  #sidebar .rail-label { display: none; }
  /* 프로필·알림은 모바일 상단 앱바에 이미 있고, '더보기'는 이 드로어를 연 버튼 자신이다.
     ⚠️ auth.js/notifications.js가 "화면에 보이는" 트리거 하나만 골라 쓰므로 여기서 반드시
        display:none으로 감춰야 상단 앱바 쪽 버튼이 선택된다(visibility로는 안 된다). */
  #sidebar .sidebar-profile,
  #sidebar #desktop-profile-btn,
  #sidebar #btn-notif-bell-desktop,
  #sidebar #btn-rail-more { display: none; }
}
/* 알림 배지(.notif-unread-dot)는 absolute라 버튼이 기준점이 되어야 한다 —
   예전에는 #desktop-utility 안에서 Tailwind `relative`가 이 역할을 했다. */
#sidebar .btn-notif-bell { position: relative; }
/* 사이드바 스크림: display:none 기반 .hidden 토글 대신 opacity로만 열고 닫아서, 나타나고
   사라질 때 모두 부드럽게 페이드된다(예전엔 사라질 때만 즉시 사라져 뚝 끊겨 보였음). */
/* 사진 뷰어 — 두 손가락 확대/축소는 viewer.js가 직접 처리한다.
   `pan-x pan-y`로 두면 한 손가락 스크롤(패닝)은 브라우저가 그대로 맡고, 핀치만 우리에게 온다.
   `none`으로 막으면 확대한 사진을 손가락으로 밀어 볼 수 없게 된다. */
#viewer-content-outer { touch-action: pan-x pan-y; }

/* 채팅 "최근 메시지" 버튼 — 바닥 근처에서는 숨고, 위로 올려 볼 때만 떠오른다. */
#btn-chat-scroll-bottom {
  opacity: 0; pointer-events: none; transform: translateY(8px) scale(0.92);
  transition: opacity 0.2s ease-out, transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}
#btn-chat-scroll-bottom.show { opacity: 1; pointer-events: auto; transform: translateY(0) scale(1); }

/* ── 모바일 상단바 아바타 ──
   버튼 자체가 아바타라 크기를 키우면 원이 커진다. 손가락 타겟은 .m3-tap과 같은 방식으로
   가상 요소가 대신 채운다(레이아웃은 36px 원 그대로). */
.topbar-avatar { position: relative; }
.topbar-avatar::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%); width: 48px; height: 48px;
}

/* ── [9] 좌우로 넘겨 보는 탭 줄(시간표 반 목록 등) ──
   항목이 많아도 줄바꿈으로 화면을 먹지 않고 옆으로 스크롤한다. */
.tab-scroller { scrollbar-width: none; -ms-overflow-style: none; scroll-behavior: smooth; }
.tab-scroller::-webkit-scrollbar { display: none; }
.tab-scroller > * { flex-shrink: 0; }

/* ── ClassSpace 로고 ─────────────────────────────────────────────────
   랜딩·사이드바·푸터 세 곳이 같은 마크업을 쓰고 크기만 다르다(cs-logo-lg / -md / -sm).
   ⚠️ 파일이 아직 없어도 화면이 비지 않도록 **글자 로고를 기본으로 두고**, 이미지가
      실제로 로드됐을 때만(.ok) 이미지로 바꾼다. 반대로 만들면 파일이 없을 때
      깨진 이미지 아이콘이 그대로 보인다. */
.cs-logo { display: inline-flex; align-items: center; line-height: 1; }
.cs-logo-img { display: none; width: auto; object-fit: contain; max-width: 100%; }
:root[data-theme="dark"] .cs-logo-dark.ok { display: block; }
:root:not([data-theme="dark"]) .cs-logo-light.ok { display: block; }
.cs-logo.has-logo .cs-logo-text { display: none; }

.cs-logo-text {
  font-weight: 800; letter-spacing: -0.032em; white-space: nowrap;
  color: rgb(var(--md-on-surface-rgb));
}
/* [M3 토큰 예외 — 브랜드 로고] 로고의 빨간 점은 서비스의 고정 상징색이라
   포인트 컬러(8종)나 라이트/다크에 따라 바뀌면 안 된다. */
.brand-dot { color: #FF2A24; }

/* 랜딩 — 화면 한가운데 크게 */
.cs-logo-lg { justify-content: center; min-height: 60px; }
.cs-logo-lg .cs-logo-img  { height: 58px; max-width: min(78vw, 340px); }
.cs-logo-lg .cs-logo-text { font-size: 38px; }
/* 사이드바 상단 */
.cs-logo-md .cs-logo-img  { height: 26px; }
.cs-logo-md .cs-logo-text { font-size: 19px; }
/* 푸터 */
.cs-logo-sm .cs-logo-img  { height: 17px; }
.cs-logo-sm .cs-logo-text { font-size: 13px; }
/* 모바일 상단바 — 홈 화면에서 학급 이름 대신 놓이는 로고. 앱바 높이(64dp)에 맞춘 크기다. */
.cs-logo-topbar .cs-logo-img  { height: 22px; }
.cs-logo-topbar .cs-logo-text { font-size: 18px; }
/* 대시보드 홈 상단 — 히어로 박스 **바깥**에 놓이므로 배경색과 부딪히지 않는다.
   그래서 예전의 surface 칩 없이 로고 본래 색 그대로, 크게 보여준다. */
.cs-logo-lead { padding: 0.25rem 0.25rem 0.5rem; }
.cs-logo-xl .cs-logo-img  { height: 40px; max-width: min(70vw, 260px); }
.cs-logo-xl .cs-logo-text { font-size: 30px; }
@media (min-width: 768px) {
  .cs-logo-lead { padding: 0.5rem 0.25rem 0.75rem; }
  .cs-logo-xl .cs-logo-img  { height: 52px; max-width: 340px; }
  .cs-logo-xl .cs-logo-text { font-size: 40px; }
}

/* 부팅 스플래시 — 로고가 천천히 숨 쉬듯 흐려졌다 진해진다. 걷힐 땐 페이드 아웃. */
.boot-pulse { animation: bootPulse 1.4s ease-in-out infinite; }
@keyframes bootPulse { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }
#boot-splash { transition: opacity 0.28s ease; }
#boot-splash.leaving { opacity: 0; pointer-events: none; }

/* ── 시작 가이드 색 ──────────────────────────────────────────────────
   메인 홈페이지 바로 다음에 오는 화면이라 같은 레드 정체성을 쓴다(사용자 결정).
   ⚠️ 안쪽 마크업은 전부 md-* 토큰을 쓰므로, 컨테이너에서 변수만 덮어쓰면
      버튼·배지·진행바까지 한 번에 따라온다. 사이트 본문의 포인트 컬러는 건드리지 않는다. */
#onboarding-view {
  --md-primary-rgb: 200 40 32;  --md-on-primary-rgb: 255 255 255;
  --md-secondary-container-rgb: 255 218 212; --md-on-secondary-container-rgb: 42 23 19;
  --md-tertiary-rgb: 113 91 46;
  --md-background-rgb: 255 248 246; --md-surface-rgb: 255 248 246; --md-surface-variant-rgb: 247 225 216;
  --md-surface-container-low-rgb: 255 244 240; --md-surface-container-rgb: 255 237 233; --md-surface-container-high-rgb: 249 232 224;
  --md-outline-rgb: 133 116 108; --md-on-surface-rgb: 34 26 22; --md-on-surface-variant-rgb: 83 68 62;
  background-color: rgb(var(--md-background-rgb));
  color: rgb(var(--md-on-surface-rgb));
}
:root[data-theme="dark"] #onboarding-view {
  --md-primary-rgb: 255 180 170; --md-on-primary-rgb: 105 0 2;
  --md-secondary-container-rgb: 90 65 61; --md-on-secondary-container-rgb: 255 218 212;
  --md-tertiary-rgb: 225 195 141;
  --md-background-rgb: 26 18 14; --md-surface-rgb: 26 18 14; --md-surface-variant-rgb: 83 68 62;
  --md-surface-container-low-rgb: 34 26 22; --md-surface-container-rgb: 39 30 26; --md-surface-container-high-rgb: 49 40 36;
  --md-outline-rgb: 160 141 134; --md-on-surface-rgb: 240 223 216; --md-on-surface-variant-rgb: 215 194 185;
}

/* ── 테마 전환 슬라이드 ───────────────────────────────────────────────
   바뀌는 순간 화면을 한 번 쓸고 지나간다. 쓸고 가는 면이 **다음 테마의 배경색**이라
   "새 테마가 덮어 온다"는 느낌이 된다. 색이 순간이동하는 것보다 무슨 일이 일어났는지 읽힌다. */
#theme-sweep {
  position: fixed; inset: 0; z-index: 109; pointer-events: none;
  transform: translateX(-100%);
}
#theme-sweep.sweeping-in  { transition: transform 0.26s cubic-bezier(0.4, 0, 1, 1); transform: translateX(0); }
#theme-sweep.sweeping-out { transition: transform 0.34s cubic-bezier(0, 0, 0.2, 1); transform: translateX(100%); }

