:root {
  --deep: #0b2616; --deep2: #123a22; --pine: #1c5433;
  --lime: #7ec31a; --lime2: #a6ce3c; --lime-t: #eef7e2; --lime-hi: #c9e89a;
  --font-display: 800;
  --navy: #1d3f6e; --navy-t: #e9eff8;
  --gold: #e0a800; --gold-t: #faf3dc;
  --red: #b03a2e; --red-t: #faeae8;
  --teal: #0f6e56; --teal-t: #e3f1ec;
  --ink: #1d2b22; --mut: #64715f; --line: #e2e8df;
  --bg: #f5f7f3; --white: #fff;
  --shadow: 0 1px 2px rgba(12, 38, 22, .05), 0 4px 16px rgba(12, 38, 22, .06);
  --shadow-up: 0 2px 4px rgba(12, 38, 22, .08), 0 10px 28px rgba(12, 38, 22, .12);
  /* ★강제 다크모드 방어 이중 선언(디자인 마감 2026-07-28) — <meta name="color-scheme"> 를 무시하는
     브라우저(안드로이드 크롬 '웹사이트 자동 다크 모드' 등)를 위해 CSS 로도 밝은 테마를 못박는다.
     포털은 다크 CSS 가 0건이라 임의 반전이 곧 색 붕괴다. only = UA 의 강제 전환 자체를 거부. */
  color-scheme: only light;
}
/* ★폼 컨트롤 브랜드색(디자인 마감 2026-07-28) — 체크박스·라디오·셀렉트가 UA 기본 하늘색으로 칠해졌다.
   개별 지정된 곳(.consent-row input:453행, index.html 인라인 style)과 값이 동일(#00843D)해 시각 변화는
   없고, 지정이 빠져 있던 회원표·건의 셀렉트 등만 브랜드색으로 수렴한다.
   특이도 검산: input[type=...] = (0,1,1) 로 `.consent-row input`(0,1,1)과 동급이며 이 규칙이 앞서므로
   개별 지정이 계속 이긴다(값이 같아 결과 동일). 인라인 style 은 언제나 우선 — 충돌 없음. */
input[type="checkbox"], input[type="radio"], select { accent-color: var(--skku-green, #00843D); }
* { box-sizing: border-box; margin: 0; }
html, body { height: 100%; }
body {
  font-family: "Pretendard Variable", Pretendard, "Malgun Gothic", "Apple SD Gothic Neo", sans-serif;
  background: var(--bg); color: var(--ink); font-size: 14px; line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* 스킵 링크(A11y N-C6) — 평소 화면 밖, 키보드 포커스 시에만 노출 → 본문(#main)으로 바로 이동 */
.skip-link { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; z-index: 9999; }
.skip-link:focus { position: fixed; left: 1rem; top: 1rem; width: auto; height: auto; padding: .6rem 1rem;
  background: var(--skku-green, #00843D); color: #fff; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,.2);
  font-weight: 600; text-decoration: none; }

/* ★가로 노치(safe-area) 규칙은 파일 **맨 끝**으로 옮겼다 — 이유는 그 자리 주석 참고.
   여기(파일 앞머리)에 두면 뒤쪽 단축 padding 에 통째로 눌려 무효가 된다. */

/* ============ 로그인 히어로 ============ */
/* ★모바일 100vh (모바일 점검 2026-07-31) — iOS·안드로이드 브라우저의 100vh 는 **주소창을
   포함한 높이**라 실제 보이는 영역보다 크다. 그래서 아래쪽이 잘리거나 접힘 밑으로 내려간다.
   haja 가 [HJ-11] 로 같은 문제를 이미 겪고 dvh 로 고쳤는데 다른 앱에는 안 퍼져 있었다.
   두 줄로 적는 이유: dvh 를 모르는 옛 브라우저는 뒷줄을 무시하고 앞줄(vh)을 그대로 쓴다.
   패널·모달은 지금 보이는 높이에 맞춰야 하므로 dvh, 전체화면 히어로는 주소창이 보일 때
   기준이라야 안 잘리므로 svh 를 쓴다. */
.hero {
  /* ⚠ svh 를 쓰면 안 된다. svh 는 **주소창이 보일 때**의 작은 높이라, 주소창이 접혀 화면이
     커지면 히어로 아래로 body 배경(#f5f7f3)이 띠처럼 드러난다. 자기 배경을 가진 전면
     요소에는 dvh(지금 보이는 높이)가 맞다. body 자체에 거는 reset/ada 는 배경이 캔버스로
     전파되므로 svh 로 두어도 안전하다 — 그래서 거기만 svh 다. */
  min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; position: relative;
  background:
    radial-gradient(1100px 640px at 12% 6%, rgba(28, 84, 51, .5), transparent 60%),
    radial-gradient(950px 580px at 92% 104%, rgba(18, 58, 34, .6), transparent 62%),
    linear-gradient(158deg, #081d10 0%, var(--deep) 38%, var(--deep2) 72%, var(--pine) 100%);
  color: #fff; padding: 0 20px;
}
/* 아주 미묘한 대각선 패턴 — 절제(마크업 무손상, 콘텐츠는 z-index 로 위) */
.hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background: repeating-linear-gradient(45deg, rgba(255,255,255,.015) 0 2px, transparent 2px 8px);
}
.hero-inner, .hero-foot { position: relative; z-index: 1; }
.hero-inner { width: 100%; max-width: 980px; margin: auto; padding: 64px 0 40px;
  display: grid; grid-template-columns: 1.15fr .85fr; gap: 56px; align-items: center; }
@media (max-width: 860px) { .hero-inner { grid-template-columns: 1fr; gap: 36px; padding-top: 48px; } }

.brand-kicker { display: flex; align-items: center; gap: 10px; font-size: 12px; letter-spacing: .22em;
  color: rgba(255,255,255,.55); text-transform: uppercase; margin-bottom: 26px; }
.brand-kicker .bar { width: 34px; height: 1px; background: var(--lime); display: inline-block; }
.hero h1 { font-size: 42px; line-height: 1.18; font-weight: 800; letter-spacing: -.02em; }
.hero h1 .lime { color: var(--lime2); }
@media (max-width: 560px) { .hero h1 { font-size: 30px; } }
.slogan { margin-top: 22px; font-size: 17px; font-style: italic; font-weight: 600;
  color: rgba(255,255,255,.92); letter-spacing: .01em; }
.slogan-ko { margin-top: 6px; font-size: 13px; color: rgba(255,255,255,.55); }
.hero-meta { margin-top: 42px; display: flex; align-items: center; flex-wrap: wrap; }
.hero-meta span { display: inline-flex; align-items: baseline; gap: 8px; padding: 2px 22px;
  font-size: 12px; color: rgba(255,255,255,.55); }
.hero-meta span:first-child { padding-left: 0; }
.hero-meta span + span { border-left: 1px solid rgba(255,255,255,.15); }
.hero-meta b { font-size: 28px; font-weight: var(--font-display); color: #fff; letter-spacing: -.02em; line-height: 1; }
.hero-meta .txt { font-size: 16px; font-weight: 800; color: #fff; }

.glass { background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.14);
  border-radius: 20px; padding: 34px 30px; backdrop-filter: blur(14px); }
/* 로그인 카드 — 유리감·깊이 강화 (deniedCard 와 분리) */
#loginCard { width: 100%; max-width: 440px; margin-left: auto; padding: 32px 28px; border-radius: 20px;
  background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.14);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 24px 64px rgba(0,0,0,.35); }
.glass h2 { font-size: 17px; font-weight: 700; margin-bottom: 4px; }
.glass .sub { font-size: 12px; color: rgba(255,255,255,.55); margin-bottom: 24px; }
.btn-google { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%;
  height: 48px; border: 0; border-radius: 12px; background: #fff; color: var(--ink);
  font-size: 14px; font-weight: 700; cursor: pointer; transition: transform .12s, box-shadow .12s; }
.btn-google:hover { transform: translateY(-1px); box-shadow: 0 8px 22px rgba(0,0,0,.25); }
.glass .note { margin-top: 12px; font-size: 12px; color: rgba(255,255,255,.62); text-align: center; }
.glass .err { margin-top: 12px; font-size: 12px; color: #ffb3a7; text-align: center; }
.sysstat { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin-top: 20px; }
.sysstat span { font-size: 11px; padding: 4px 10px; border-radius: 999px;
  background: rgba(255,255,255,.08); color: rgba(255,255,255,.55); border: 1px solid rgba(255,255,255,.1); }
.sysstat span.ok { background: rgba(126,195,26,.18); color: var(--lime-hi); border-color: rgba(126,195,26,.3); }
.sysstat span.err { background: rgba(255,120,100,.14); color: #ffb3a7; border-color: rgba(255,120,100,.25); }

.hero-foot { padding: 18px 0 26px; display: flex; justify-content: center; gap: 22px; flex-wrap: wrap;
  font-size: 11px; color: rgba(255,255,255,.55); letter-spacing: .04em; }

/* [PT-01] 모바일 로그인 우선 배치 — ?next= 왕복 착지 화면에서 히어로 문구(≈373px)가 첫 화면을 선점해
   #btnLoginStaff 가 y≈645(375×667 무스크롤 밖)로 밀리던 결함. ≤640px 에서만 ①카드를 문구 위로(order)
   ②보조 카피(.slogan-ko/.hero-meta) 숨김 ③패딩 축소. >640px 데스크톱 그리드는 무변경.
   ※ #loginCard 는 ID 특이도라 위 정의(67행)보다 반드시 뒤에 와야 승리 — 위치 이동 금지. */
@media (max-width: 640px) {
  .hero-inner { padding: 18px 0 26px; gap: 22px; }
  .hero-inner > div:last-child { order: -1; }   /* 로그인 카드 = 그리드 첫 항목 */
  .slogan-ko, .hero-meta { display: none; }     /* PT-29(랩 시 border-left 잔존)도 동반 해소 */
  .brand-kicker { margin-bottom: 14px; }
  .slogan { margin-top: 10px; font-size: 15px; }
  #loginCard { padding: 22px 18px; }
}

/* 접근 거부 */
.denied-box { text-align: center; }
.denied-box i { font-size: 34px; color: #ffb3a7; }
.denied-box p { margin: 12px 0 18px; font-size: 13px; color: rgba(255,255,255,.7); }
.btn-ghost { background: none; border: 1px solid rgba(255,255,255,.3); color: #fff;
  border-radius: 10px; height: 40px; padding: 0 18px; cursor: pointer; font-size: 13px; }

/* ============ 메인 ============ */
/* 상단 바 고정(2026-07-18 사용자 논의 — 얇은 바만 sticky: 화면 손실 최소·벨/아이콘 상시 접근) */
.topbar { background: linear-gradient(90deg, var(--deep) 0%, var(--deep2) 100%); color: #fff;
  position: sticky; top: 0; z-index: 60; box-shadow: 0 2px 10px rgba(11,38,22,.18); }
.topbar-in { max-width: 1080px; margin: 0 auto; padding: 14px 16px;
  display: flex; align-items: center; gap: 12px; }
.topbar .logo { display: flex; align-items: center; gap: 9px; font-weight: 800; font-size: 16px; }
/* 브랜딩 1차 — 헤더 공식 엠블럼(흰 칩 안, 다크 배경 대비) */
.logo-emblem { width: 30px; height: 30px; border-radius: 50%; background: #fff; padding: 2px; flex: none; }
/* 로그인 히어로 브랜드 스테이트먼트 — 명륜당 카드 인용문 */
.hero-brand { display: flex; align-items: center; gap: 12px; margin-top: 34px; padding-left: 14px;
  border-left: 3px solid var(--lime); }
.hero-brand img { width: 40px; height: 40px; border-radius: 50%; background: #fff; padding: 3px; flex: none; }
.hero-brand span { font-size: 13px; line-height: 1.75; color: rgba(255,255,255,.72); font-style: italic; }
.topbar .logo i { color: var(--lime2); font-size: 20px; }
.topbar .slg { font-size: 12px; font-style: italic; color: rgba(255,255,255,.45); margin-left: 6px; }
.topbar .right { margin-left: auto; display: flex; align-items: center; gap: 12px; font-size: 12px;
  color: rgba(255,255,255,.75); }
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--lime);
  color: var(--deep); display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 800; overflow: hidden; cursor: pointer; flex: none; }
.avatar:hover { box-shadow: 0 0 0 2px rgba(255,255,255,.5); }
/* 공지·댓글 작성자 아바타(사진 or 이니셜) */
.ava { border-radius: 50%; object-fit: cover; display: inline-flex; align-items: center;
  justify-content: center; vertical-align: middle; flex: none; }
.ava-t { background: var(--lime); color: var(--deep); font-weight: 800; }
.ntx .d .ava { margin-right: 4px; }
/* 공지 댓글(직원 전용) */
.cmts { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 10px; }
.cmt { display: flex; align-items: center; gap: 7px; padding: 5px 0; font-size: 13px; }
.cmt .cn { font-weight: 700; flex: none; }
.cmt .ct { flex: 1; min-width: 0; word-break: break-word; }
.cmt .cd { color: var(--mut); font-size: 11px; flex: none; }
.cmt .cx { border: 0; background: none; color: var(--mut); cursor: pointer; flex: none;
  font-size: 13px; padding: 2px 4px; }
.cmt .cx:hover { color: var(--red); }
.cmtadd { display: flex; gap: 6px; margin-top: 8px; }
.cmtin { flex: 1; min-width: 0; padding: 7px 10px; border: 1px solid var(--line);
  border-radius: 8px; font-family: inherit; font-size: 13px; background: #fff; color: var(--ink); }
.cmtin:focus { outline: none; border-color: var(--skku-green, #00843D); }
.cmtbtn { flex: none; }
/* ★터치 영역(UI/UX 점검 2026-07-31) — 데스크톱에서 29×22px 이었다. 모바일은 아래 미디어
   블록들이 34×38·40×40 으로 키워 두었는데 **데스크톱만 브라우저 기본으로 남아** WCAG 2.2
   최소치(24×24)를 밑돌았다. 상단바에서 가장 자주 누르는 쪽지·알림 버튼이다.
   특이도 검산: .iconbtn(0,1,0). ≤640·≤760 의 .iconbtn 규칙은 같은 특이도이면서 소스가
   뒤라 그대로 이긴다 — 모바일 값은 안 건드린다.
   (행 번호는 적지 않는다: 2026-08-27 에 줄이 밀려 「193행」이 전혀 다른 곳을 가리키게 됐다.) */
.iconbtn { border: 0; background: none; color: rgba(255,255,255,.6); cursor: pointer; font-size: 17px;
  display: inline-flex; align-items: center; justify-content: center; min-width: 32px; min-height: 32px; }
/* 모바일 상단바 정리(M2·M7) — 슬로건·이메일 숨김, 터치 타깃 40px */
@media (max-width: 640px) {
  .topbar .slg, .topbar #userEmail { display: none; }
  .topbar-in { padding: 10px 12px; }
  .iconbtn { min-width: 40px; min-height: 40px; }
  .hero-brand { display: none; }   /* 모바일 로그인 화면 절약 */
}
/* 이용가이드(상단) — 푸터에만 있어 눈에 띄지 않던 것을 상단으로. 아이콘 버튼(.6 흐림)보다 또렷한 pill. */
.tbguide { display: inline-flex; align-items: center; gap: 5px; padding: 5px 11px; border-radius: 999px;
  background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.22); color: #fff;
  text-decoration: none; font-size: 12px; font-weight: 700; white-space: nowrap; }
.tbguide:hover { background: var(--lime); color: var(--deep); border-color: var(--lime); }
.tbguide i { font-size: 15px; }
/* ★상단바 넘침 안전망 — «폭과 무관»(주간심화점검 2026-08-27).
   위 임계폭은 내가 «잰 폭»만 덮는다. 정작 바의 우측 끝은 로그인한 사람의 이메일 길이와 배지
   자릿수에 따라 움직이므로, 임계 하나로 못 박으면 다음에 또 어긋난다. 게다가 바로 아래
   overflow-x:clip 때문에 넘친 부분은 밀어 볼 수조차 없다 — 즉 넘치는 순간 그 버튼은 «없는 것»이 된다.
   그래서 어느 폭에서든 넘치면 줄을 바꾸도록 해 둔다.
   ㆍ넘치지 않는 한 flex-wrap:wrap 은 배치를 바꾸지 않는다 — 1280·1024 에서는 그대로다(실측).
     ★그러나 「데스크톱 외형은 그대로」는 «잰 두 폭에서만» 참이다(경량점검 2026-08-28 정정).
       그 사이 띠는 안 재 봤고, 거기서는 성립하지 않는다 — 같은 도구(_tools/check-ui.js
       /index.html <폭>)로 재니 .topbar 높이가 이렇다 —
         768 · 840 · 900 · 960 → 102px   |   968 · 1024 · 1280 → 60px
       그 띠에서는 .right 가 2행째로 내려가 sticky 헤더가 상시 한 줄 더 먹는다(768 은 태블릿
       세로, 900 안팎은 노트북 반화면·1280 창 분할에서 흔하다). 로고가 온전히 한 줄로 나오는
       대가이므로 되돌리지 않되, 「그대로」라고 적어 두면 다음 사람이 이 띠를 안 잰다.
     ※여기 처음엔 「830~980 대」라고 «구간»을 적었다가 2R 리뷰에 걸려 지웠다 — 아래쪽은 768 까지
       내려가고 위쪽 경계는 960~968 사이라, 양쪽 다 틀렸다. 그래서 잰 «점»만 적는다.
       경계 자체가 로그인한 사람의 이메일 길이·배지 자릿수에 따라 움직이므로(위 문단 참조)
       위 수치는 스텁 계정 기준이고, 구간으로 못 박을 수 있는 값이 아니다.
     ※교훈은 「두 점을 재고 구간을 단언하지 말 것」이다 — 임계는 잰 점 사이에 숨는다.
       그 문장을 적으면서 바로 아래 줄에서 또 구간을 단언했던 것이 이 정정의 전말이다.
   ㆍmin-width:0 이 없으면 flex 자식이 내용폭 아래로 못 줄어 말줄임이 아예 안 걸린다.
   ★소스 «순서»가 이 세 줄의 전부다(1R 적대적 리뷰가 잡았다 — 처음엔 정반대로 적었다).
     `.topbar #userEmail` 은 아래 @media 안의 같은 선택자와 «바이트 단위로 동일»하고, 미디어
     쿼리는 특이도를 더하지 않는다. 그래서 뒤에 오는 쪽이 이긴다 — 처음에 이 블록을 @media
     «뒤»에 두었더니 좁은 화면의 이메일 상한 110px 를 220px 로 덮어, 641~760px 띠에서 바가
     오히려 두 줄이 됐다(이 수리가 없애려던 바로 그 모양이다).
     → 반드시 아래 @media 블록들보다 «앞»에 둔다. 옮기지 말 것. */
.topbar-in { flex-wrap: wrap; }
.topbar .right { flex-wrap: wrap; min-width: 0; }
.topbar #userEmail { min-width: 0; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 모바일: topbar 오버플로 방지(슬로건 숨김·이메일 말줄임·가이드 라벨 숨김)
   ★임계폭 560 → 760 (주간심화점검 2026-08-27 실측). 관리자 상단바는 아이콘 10개라 561~735px
     띠에서 우측이 뷰포트 밖으로 나가는데, 아래 `html,body{overflow-x:clip}` 이 밀어 볼 스크롤까지
     막아 [로그아웃]·[설정]에 아예 손이 닿지 않았다(667×375 실측: span.right 오른쪽 735px > 667px).
     휴대폰 가로모드(667×375·640×360)가 정확히 이 띠에 들어온다 — 종전 560 은 세로폭만 덮었다.
     ★처음엔 700 으로 잡았다가 «실측이 되돌렸다»: 701×400 에서도 그대로 잘렸다(오른쪽 735px).
       바 우측 끝은 화면폭과 무관하게 735px 근처에 있어, 임계는 그보다 위여야 한다. 736×414 는
       통과하지만 그건 «1px 여유»에 기대는 값이고 #userEmail 길이에 따라 그 끝이 움직인다.
       이미 이 파일이 쓰는 760 으로 잡아 여유를 둔다(768 태블릿 세로는 종전대로 데스크톱 배치).
     ㆍ그래도 임계는 «추정»이다 — 아주 긴 이메일이면 데스크톱 폭에서도 넘칠 수 있다. 그래서
       아래에 폭과 무관한 안전망(줄바꿈 허용 + 이메일 말줄임)을 따로 둔다.
     ㆍ아래 [PT-04] 접기 블록도 «같은 값»으로 함께 올린다 — 하나만 올리면 줄바꿈은 되는데
       ⋯ 더보기가 안 나와 두 줄짜리 상단바가 된다. */
@media (max-width: 760px) {
  .topbar .slg { display: none; }
  .topbar .right { gap: 8px; min-width: 0; }
  .topbar #userEmail { max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* ★헤더 아이콘 무게 통일(디자인 마감 2026-07-28) — 바로 아래 .lbl 숨김으로 이 항목만 "글자 없는
     아이콘 + 테두리 pill"이 돼, 테두리·배경이 없는 형제 아이콘(.iconbtn) 사이에서 혼자 튀었다.
     ≤760 에서만 .iconbtn 과 동일 무게(무배경·투명테두리·.6 흐림·34×38)로 맞춘다. >760 데스크톱 pill 은 무변경.
     (임계폭은 2026-08-27 에 560→760 으로 올렸다 — 이 블록 머리 주석 참조.)
     특이도 검산: .tbguide(0,1,0)는 파일 위쪽 기본 정의와 동급이나 이 미디어 블록이 소스 뒤라 승리.
       hover 역시 .tbguide:hover(0,2,0)로 기본 hover(라임 채움)와 동급 — 소스 뒤라 승리(아이콘 hover 와 동형).
     (행 번호는 적지 않는다 — 2026-08-27 에 위쪽이 밀려 「158·161행」이 전혀 다른 곳을 가리켰다.) */
  .tbguide { padding: 0; background: none; border-color: transparent; color: rgba(255,255,255,.6);
    min-width: 40px; min-height: 40px; justify-content: center; gap: 0; }
  .tbguide:hover { background: none; border-color: transparent; color: #fff; }
  .tbguide i { font-size: 16px; }
  .tbguide .lbl { display: none; }   /* 아이콘만 — 오버플로 방지 */
  /* ★가로 오버플로 근본 수정(2026-07-18 실측): 관리자 상단바는 아이콘이 여러 개라 한 줄 375px 를
     넘어 페이지가 옆으로 늘어났다(우측 빈 공간). 줄바꿈 허용 + ⋯ 접기로 뷰포트 안에 가둔다.
     ※개수는 적지 않는다 — 「8개」라 적어 둔 사이에 감사기록이 늘어 이미 낡았다(정본은 index.html). */
  .topbar-in { flex-wrap: wrap; row-gap: 2px; }
  .topbar .right { flex-wrap: wrap; justify-content: flex-end; row-gap: 2px; }
  /* ★«좁히는» 값은 여기 두지 않는다(주간심화점검 2026-08-27, 3R 적대적 리뷰).
     이 블록의 임계를 560→760 으로 올릴 때 34×38 이 딸려 올라가, 561~640px 에서 아이콘이
     40×40(위 ≤640 블록의 값) → 34×38 로 «줄었다». 그 띠에서 종전보다 나빠지는 것은 이 수리의
     목적이 아니다 — 여기서 하려던 일은 「줄바꿈·접기로 화면 안에 가두기」이지 축소가 아니다.
     ㆍ40×40 은 위 ≤640 블록과 같은 값이라 561~640 이 HEAD 와 동일해진다.
     ㆍ진짜로 좁혀야 하는 ≤560 은 바로 아래 블록에서 따로 줄인다. */
  .iconbtn { min-width: 40px; min-height: 40px; }
  .topbar .logo { font-size: 14px; }
  .logo-emblem { width: 26px; height: 26px; }
}
/* ★≤560 전용 축소 — 위 블록에서 갈라 낸 것(2026-08-27). 값·대상은 2026-07-18 실측 그대로다.
   아이콘 열 개가 접혀도 360~560px 에서는 40×40 이 한 줄에 안 들어가 바가 세 줄이 된다. */
@media (max-width: 560px) {
  .iconbtn, .tbguide { min-width: 34px; min-height: 38px; }
  .iconbtn { font-size: 16px; }
}
/* [PT-04] 관리자 상단바 접기 — 저빈도 아이콘을 ≤760px 에서 ⋯ 더보기 메뉴로(임계폭 2026-08-27 에 560→760).
   ※항목 수는 여기 적지 않는다 — 「3종」이라 적어 둔 사이에 감사기록이 늘어 이미 한 번 낡았다.
     실제 목록은 index.html 의 .admtools 안이 정본이다.
   데스크톱은 display:contents 라 기존 flex 행에 그대로 참여(레이아웃·핸들러·배지 전부 무변경). */
.admtools { display: contents; }
#btnMore { display: none; }
@media (max-width: 760px) {   /* ★위 topbar 블록과 «같은 값»이어야 한다(2026-08-27) */
  #btnMore { display: inline-flex; align-items: center; justify-content: center;
    font-size: 20px; line-height: 1; letter-spacing: 1px; }
  /* 컨테이닝 블록 = .topbar(position:sticky) — 바 바로 아래에 우측 정렬로 펼친다 */
  .admtools { display: none; position: absolute; right: 8px; top: 100%; z-index: 71;
    background: #fff; border: 1px solid var(--line); border-radius: 12px;
    box-shadow: var(--shadow-up); padding: 6px; min-width: 172px; }
  .admtools.open { display: flex; flex-direction: column; align-items: stretch; }
  /* ⚠ justify-content 를 되돌린다. 기본 .iconbtn 이 center 를 쓰는데 여기서 안 되돌리면
     메뉴 항목마다 라벨 길이가 달라 아이콘이 제각각 들여쓰기된다(39.5 / 46 / 52.5px 로 측정). */
  .admtools .iconbtn { color: var(--ink); width: 100%; min-width: 0; min-height: 44px; justify-content: flex-start;
    display: flex; align-items: center; gap: 10px; padding: 0 10px; font-size: 17px; }
  .admtools .iconbtn:hover { color: var(--pine); background: var(--bg); border-radius: 8px; }
  .admtools .iconbtn i { order: 1; }
  .admtools .iconbtn::after { order: 2; content: attr(title); font-size: 13px; font-weight: 700; }
  .admtools .iconbtn .nbadge { order: 3; position: static; margin-left: auto; }
}
/* 캠퍼스 전환(2026-08-28) — 「양캠」 지정 회원에게만 지도 위에 뜬다.
   ㆍ터치 타깃 44px(모바일 권고) — 이 줄은 지도 바로 위라 손가락이 자주 간다.
   ㆍ선택 상태는 색«만»으로 말하지 않는다: aria-pressed 를 app.js 가 함께 쓴다(색각 이상·AT 대비).
   ㆍ대비 검산: .on 은 --pine 바탕에 흰 글자(8.9:1, .sv-scale label.on 과 같은 조합).
     비활성은 --ink on --white(테두리 --mut 5.16:1) — 비텍스트 대비 3:1 을 넘긴다. */
.mapcamp { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; }
.mapcamp-lbl { font-size: 12.5px; font-weight: 700; color: var(--mut); margin-right: 2px; }
.mapcamp-btn { min-height: 44px; padding: 6px 14px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--mut); background: var(--white); color: var(--ink);
  font: inherit; font-size: 13px; font-weight: 700; }
.mapcamp-btn:hover { border-color: var(--pine); color: var(--pine); }
.mapcamp-btn.on { background: var(--pine); border-color: var(--pine); color: #fff; }
.mapcamp-btn:focus-visible { outline: 2px solid var(--pine); outline-offset: 2px; }

/* 가로 스크롤 안전망 — 개별 요소가 넘쳐도 페이지 자체는 옆으로 늘어나지 않게 */
html, body { overflow-x: clip; }
.iconbtn:hover { color: #fff; }

.wrap { max-width: 1080px; margin: 0 auto; padding: 22px 16px 56px; }
/* 히어로 밴드: 좌 인사말 + 우 컴팩트 날씨(클릭 시 3일 예보 팝오버) */
.hero-band { display: flex; align-items: center; gap: 16px; margin: 4px 0 16px; flex-wrap: wrap; }
.hero-band .greet { flex: 1 1 240px; min-width: 220px; margin: 0; align-self: center; }

/* 우측 컴팩트 날씨 영역 — 팝오버(#weatherCard)의 위치 기준점 */
.weather-hero { position: relative; flex: 0 0 auto; margin-left: auto; align-self: center; }

/* 컴팩트 요약: 캠퍼스당 1줄(총 ~2줄), 인사말 높이와 유사. 우측 정렬·절제된 톤. */
.wx-compact { display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 6px 10px; border-radius: 10px; border: 1px solid transparent;
  transition: background .15s, border-color .15s; }
.wx-compact:hover { background: var(--bg); border-color: var(--line); }
/* ★키보드 포커스 가시화(디자인 마감 2026-07-28) — 종전 대체 표시가 바로 위 :hover 와 완전히 동일하고
   배경도 페이지 배경색(--bg)이라 탭 이동 시 포커스 위치를 알 수 없었다. hover 톤은 그대로 두고 링만 추가.
   특이도 검산: (0,2,0) 으로 .wx-compact:hover(0,2,0)와 동급이나 소스가 뒤라 승리(파일 내 다른 재정의 없음). */
.wx-compact:focus-visible { outline: 2px solid var(--skku-green, #00843D); outline-offset: 2px;
  background: var(--bg); border-color: var(--line); }
.wx-compact.wx-c-empty { display: none; }   /* 날씨 전면 실패 시 빈 버튼 잔존 방지(weather.js가 토글) */
.wx-c-lines { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.wx-c-row { font-size: 13px; color: var(--mut); white-space: nowrap; line-height: 1.5; text-align: right; }
.wx-c-ico { margin-right: 3px; }
.wx-c-tmp { color: var(--ink); font-weight: 600; }
.wx-c-rain { color: var(--mut); }
/* 히어로 컴팩트의 «보관본» 표시 — 기상청이 흔들려 낡은 예보를 보고 있을 때만 뜬다(weather.js staleNote).
   이 줄이 포털에서 기본으로 보이는 유일한 날씨 표면이라, 여기에 안 달면 사용자는 낡은 값인지 모른다. */
.wx-c-stale { color:#B45309; font-size:10px; font-weight:700; white-space:nowrap; }
.wx-c-load { font-size: 13px; color: var(--mut); }
.wx-c-chev { color: var(--mut); font-size: 12px; line-height: 1; transition: transform .2s; }
.weather-hero.wx-pop-open .wx-c-chev { transform: rotate(180deg); }

/* 3일 예보 팝오버 — 컴팩트 아래로 드롭다운, 카드 그림자 */
.hero-band #weatherCard { position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
  width: 360px; max-width: 88vw; margin: 0;
  box-shadow: 0 12px 32px rgba(15,23,42,.18); }
.hero-band #weatherCard .weather-widget.wx-portal { grid-template-columns: 1fr; gap: 8px; }
.hero-band #weatherCard .weather-widget.wx-portal .wx-title { font-size: 13px; }
@media (max-width: 760px) {
  .hero-band { flex-direction: column; align-items: stretch; gap: 6px; }
  /* ★모바일 상단 공백 버그(2026-07-18): column 방향에선 flex-basis(240px)가 높이로 적용돼
     인사말 아래 240px 공백이 생겼다 — 세로 배치에선 내용 높이만 차지하도록 재정의. */
  .hero-band .greet { flex: 0 0 auto; min-width: 0; }
  .weather-hero { margin-left: 0; width: 100%; }
  .wx-compact { justify-content: flex-end; width: 100%; }
  /* 모바일: 팝오버 전체 폭 */
  .hero-band #weatherCard { left: 0; right: 0; width: auto; max-width: none; }
}
.greet { display: flex; align-items: baseline; gap: 10px; margin: 4px 0 14px; }
.greet h2 { font-size: 20px; font-weight: 800; letter-spacing: -.01em; }
.greet .date { color: var(--mut); font-size: 12px; }
.notice { background: var(--gold-t); border: 1px solid #eedfae; color: #7a5c00;
  border-radius: 10px; padding: 8px 14px; font-size: 12px; margin-bottom: 12px; }

/* ============ 플로팅 도우미 (챗봇 v1) ============ */
.asst-fab { position: fixed; right: 24px; bottom: calc(24px + env(safe-area-inset-bottom)); z-index: 60;
  width: 56px; height: 56px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--deep); color: var(--lime); font-size: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-up); transition: transform .15s, box-shadow .15s, background .15s; }
.asst-fab:hover { transform: translateY(-3px); background: var(--deep2);
  box-shadow: 0 6px 14px rgba(11,38,22,.22), 0 16px 36px rgba(11,38,22,.26); }
.asst-fab:focus-visible { outline: 3px solid rgba(15,189,96,.55); outline-offset: 2px; }

.asst-panel { position: fixed; right: 24px; bottom: 92px; z-index: 61;
  width: 360px; max-width: calc(100vw - 32px); max-height: 520px;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--white); border: 1px solid var(--line); border-radius: 16px;
  box-shadow: var(--shadow-up);
  transform-origin: bottom right; animation: asstIn .18s ease-out; }
@keyframes asstIn { from { opacity: 0; transform: translateY(12px) scale(.98); } to { opacity: 1; transform: none; } }
.asst-head { display: flex; align-items: center; gap: 8px; padding: 15px 16px;
  border-bottom: 1px solid var(--line); background: var(--deep); color: #fff; }
.asst-title { font-weight: 700; font-size: 15px; letter-spacing: -.01em; }
.asst-x { margin-left: auto; border: 0; background: rgba(255,255,255,.12); color: #fff;
  width: 28px; height: 28px; border-radius: 8px; cursor: pointer; font-size: 15px;
  display: inline-flex; align-items: center; justify-content: center; }
.asst-x:hover { background: rgba(255,255,255,.22); }
.asst-body { padding: 16px; overflow-y: auto; }
.asst-sec { margin-bottom: 18px; }
.asst-sec:last-child { margin-bottom: 0; }
.asst-sec h4 { font-size: 12px; font-weight: 700; color: var(--mut); text-transform: uppercase;
  letter-spacing: .06em; margin-bottom: 10px; }
/* 단축키 뱃지(2026-08-09) — Ctrl+K 로 이 검색창이 바로 열린다는 표시.
   ≤560px 은 하단시트(모바일)라 Ctrl 키가 없다 → 그 폭에서는 감춘다(없는 조작을 안내하지 않는다). */
.asst-kbd { float: right; text-transform: none; letter-spacing: 0; font-size: 10.5px; font-weight: 700;
  padding: 1px 6px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: var(--mut); }
@media (max-width: 560px) { .asst-kbd { display: none; } }
.asst-links { display: flex; flex-wrap: wrap; gap: 8px; }
.asst-chip { display: inline-flex; align-items: center; gap: 7px; padding: 9px 13px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--bg);
  color: var(--ink); font-size: 13px; font-weight: 600; text-decoration: none; }
.asst-chip i { color: var(--pine); font-size: 15px; }
.asst-chip:hover { border-color: var(--skku-green, #00843D); background: var(--skku-green-tint, #E8F5E9); color: var(--pine); }
.asst-field { width: 100%; border: 1.5px solid var(--line); border-radius: 12px;
  padding: 10px 12px; font-size: 13px; background: var(--bg); font-family: inherit;
  color: var(--ink); margin-bottom: 8px; }
.asst-field:focus { outline: none; border-color: var(--skku-green, #00843D); background: #fff;
  box-shadow: 0 0 0 3px rgba(0,132,61,.16); }
.asst-ta { height: 96px; resize: vertical; line-height: 1.5; }
.asst-submit { height: 40px; width: 100%; border: 0; border-radius: 12px;
  background: var(--skku-green, #00843D); color: var(--skku-on-green, #fff); font-size: 14px; font-weight: 700; cursor: pointer;
  font-family: inherit; }
.asst-submit:hover { background: var(--skku-green-dark, #006B31); }
.asst-submit:disabled { opacity: .6; cursor: default; }
.asst-msg { font-size: 12px; margin-top: 8px; min-height: 1em; }
/* 모바일: 하단 시트(전체 폭) */
@media (max-width: 560px) {
  .asst-panel { right: 0; left: 0; bottom: 0; width: auto; max-width: none;
    max-height: 80vh; border-radius: 16px 16px 0 0; }
  .asst-fab { right: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); }
  /* [PT-11] 하단시트 safe-area 잔여 갭(7/22) — [제출]이 홈 인디케이터와 겹치던 결함 */
  .asst-body { padding-bottom: calc(16px + env(safe-area-inset-bottom)); }
}

/* ============ 관리자 건의함 (suggestBox) ============ */
.sgst { padding: 12px 2px; border-top: 1px solid #eef2ec; }
.sgst:first-child { border-top: 0; }
.sgst-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.sgst-cat { font-size: 11px; font-weight: 700; color: var(--pine);
  background: var(--lime-t); border-radius: 999px; padding: 2px 10px; }
.sgst-date { font-size: 11px; color: var(--mut); }
.sgst-status { margin-left: auto; height: 30px; border: 1px solid var(--line); border-radius: 8px;
  padding: 0 8px; font-size: 12px; background: #fff; font-family: inherit; color: var(--ink); cursor: pointer; }
.sgst-text { font-size: 13px; color: var(--ink); white-space: pre-wrap; cursor: pointer; }
.sgst-text.clamp { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.sgst-who { font-size: 11px; color: var(--mut); margin-top: 5px; }
/* 건의함 완료 처리: 완료건은 흐리게 + '완료함'으로 접어 보관 */
.sgst.done { opacity: .62; }
.sgst-donehd { display: flex; align-items: center; gap: 6px; margin-top: 12px; padding: 9px 2px;
  border-top: 1px solid var(--line); font-size: 12px; font-weight: 700; color: var(--mut); cursor: pointer; }
.sgst-donehd:hover { color: var(--pine); }
.sgst-donebox { border-left: 2px solid var(--line); padding-left: 10px; margin-top: 2px; }
.sgst-donebox .sgst:first-child { border-top: 0; }

.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 16px 0; }
@media (max-width: 760px) { .grid4 { grid-template-columns: repeat(2, 1fr); } }
.metric { background: var(--white); border: 1px solid var(--line); border-radius: var(--skku-radius, 14px);
  padding: 18px 18px 20px; box-shadow: var(--shadow); transition: transform .12s, box-shadow .12s; }
.metric:hover { transform: translateY(-2px); box-shadow: var(--shadow-up); }
.metric .ic { width: 44px; height: 44px; border-radius: 12px; display: inline-flex;
  align-items: center; justify-content: center; font-size: 20px; margin-bottom: 14px; }
.metric .lb { font-size: 13px; color: var(--mut); font-weight: 600; margin-bottom: 7px; }
.metric .val { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.metric .val b { font-size: 40px; font-weight: 800; letter-spacing: -.03em; line-height: 1.02; color: var(--ink); }
.metric .val .unit { font-size: 13px; color: var(--mut); font-weight: 600; }
/* C1 — 지표 전일 대비 칩 */
.dchip { font-size: 11px; font-weight: 800; border-radius: 8px; padding: 2px 6px; margin-left: 7px; vertical-align: 3px; }
.dchip.bad { background: #faeae8; color: var(--status-reject, #C0392B); }
.dchip.good { background: #eef7e2; color: var(--status-done, #177A43); }
.dchip.neu { background: #f0f2ee; color: #64715f; }
/* 봇 통합 검색 결과 */
.asst-results { display: grid; gap: 6px; margin-top: 8px; }
.asst-result { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border: 1px solid var(--line);
  border-radius: 10px; text-decoration: none; color: var(--ink); background: #fff; }
.asst-result:hover { border-color: var(--skku-green, #00843D); background: #f7faf3; }
.asst-result .ar-tag { flex: none; font-size: 11px; font-weight: 800; border-radius: 6px; padding: 2px 6px; }
.asst-result .ar-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.asst-result .ar-main b { font-size: 12px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asst-result .ar-sub { font-size: 11px; color: var(--mut); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asst-result > i { flex: none; font-size: 14px; color: var(--mut); }
/* 결과 행이 <button> 인 경우(건물 카드 — 같은 페이지에서 처리, 2026-08-09). 위 규칙은 <a> 를 전제해
   버튼 기본값(가운데 정렬·시스템 폰트·회색 배경·테두리)이 남으면 링크 행과 다른 모양이 된다. */
button.asst-result { width: 100%; text-align: left; font: inherit; cursor: pointer; }
.ar-more { font-size: 11px; color: var(--mut); text-align: center; padding: 4px 0; }
.metric .val .badge { align-self: center; font-size: 12px; font-weight: 700; color: var(--mut);
  background: #eef1ec; border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; }

.cols { display: grid; grid-template-columns: 1.25fr 1fr; gap: 14px; }
@media (max-width: 860px) { .cols { grid-template-columns: 1fr; } }
.card { background: var(--white); border: 1px solid var(--line); border-radius: var(--skku-radius, 14px);
  padding: 17px 19px; box-shadow: var(--shadow); }
h3.sec { font-size: 15px; color: var(--ink); font-weight: 700; margin-bottom: 12px;
  display: flex; align-items: center; gap: 7px; letter-spacing: -.01em; }
h3.sec i { color: var(--pine); }

.feed .row { display: flex; gap: 9px; align-items: center; padding: 10px 2px;
  border-top: 1px solid #eef2ec; font-size: 13px; }
.feed .row:first-of-type { border-top: 0; }
.feed .row > span:not(.t):not(.fbadge) { flex: 1; min-width: 0; }
.feed .row .t { margin-left: auto; color: var(--mut); font-size: 12px; white-space: nowrap; }
/* 피드 출처 시스템 배지 */
.fbadge { flex: none; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 999px; line-height: 1.4; }
.fbadge.haja { background: var(--red-t); color: var(--red); }
.fbadge.cms { background: var(--lime-t); color: var(--pine); }
.fbadge.fms { background: var(--navy-t); color: var(--navy); }

.tiles { display: grid; gap: 8px; }
.tile { display: flex; gap: 11px; align-items: center; padding: 12px 14px; border: 1px solid var(--line);
  border-radius: 12px; font-size: 13px; font-weight: 600; cursor: pointer; background: var(--white);
  color: var(--ink); text-decoration: none; transition: transform .12s, box-shadow .12s, border-color .12s; }
.tile:hover { transform: translateY(-1px); box-shadow: var(--shadow-up); border-color: var(--skku-green, #00843D); }
.tile .ic { width: 30px; height: 30px; border-radius: 10px; display: inline-flex;
  align-items: center; justify-content: center; font-size: 16px; }
/* [PT-20] .tnbadge(우상단 알림 배지)와 y 18~26 구간이 물리적으로 겹치던 결함 — 우측 여백 확보 */
.tile .st { margin-left: auto; margin-right: 14px; font-size: 12px; padding: 2px 9px; border-radius: 999px; font-weight: 600; }
.st.ok { background: var(--lime-t); color: var(--pine); }
.st.warn { background: var(--gold-t); color: var(--status-delay, #96600F); }
.st.err { background: var(--red-t); color: var(--status-reject, #C0392B); }

.mapbox svg { width: 100%; height: auto; display: block; }
/* [PT-28] flex-wrap 부재 + overflow-x:clip → "지도 미표시 n건" 문구가 조용히 잘리던 결함 */
.legend { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 12px; color: var(--mut); margin: 8px 0 10px; }
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.detail { border: 1px solid var(--line); border-radius: 12px; padding: 13px 15px;
  font-size: 13px; color: var(--mut); background: var(--bg); }
.detail .tag { font-size: 11px; padding: 2px 9px; border-radius: 999px; margin-right: 8px; font-weight: 700; }
.detail b { color: var(--ink); }
.detail a.go { display: inline-block; margin-top: 9px; font-size: 12px; color: var(--pine);
  text-decoration: none; border: 1px solid var(--line); padding: 6px 12px; border-radius: 10px; background: #fff; }
.detail a.go:hover { border-color: var(--skku-green, #00843D); background: var(--skku-green-tint, #E8F5E9); }

/* ===== 건물 카드(2026-08-09) — 한 건물의 공사·민원·하자를 구획별로 ===== */
.bldg-sec { border-top: 1px solid var(--line); padding: 12px 0 4px; }
.bldg-sec:first-child { border-top: 0; padding-top: 2px; }
.bldg-h { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
.bldg-h .tag { font-size: 11px; padding: 2px 9px; border-radius: 999px; font-weight: 700; }
.bldg-h b { font-size: 13px; color: var(--ink); }
.bldg-row { display: flex; align-items: baseline; gap: 10px; padding: 6px 2px;
  border-bottom: 1px solid #f1f5ef; font-size: 13px; }
.bldg-row:last-child { border-bottom: 0; }
.bldg-row .nm { flex: 1; min-width: 0; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 부가정보는 데스크톱에서 우측 정렬 — 좁아지면 줄바꿈해 이름을 가리지 않게 한다 */
.bldg-row .sub { flex: none; max-width: 52%; font-size: 12px; color: var(--mut); text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 640px) {
  .bldg-row { flex-direction: column; gap: 1px; }
  .bldg-row .nm, .bldg-row .sub { max-width: 100%; white-space: normal; text-align: left; }
}

footer.pf { margin-top: 30px; font-size: 12px; color: var(--mut); text-align: center; }
/* ★법정 고지 링크 터치 타깃(디자인 마감 2026-07-28)
   푸터의 [개인정보처리방침]·[사용 가이드]·[회원 탈퇴] 는 글자 높이가 그대로 탭 영역이라 실측 14px 이었다
   (개인정보 보호법 제30조② 첫 화면 고지 경로라 '거의 안 누르니 괜찮다'로 넘길 성질이 아니다).
   ※44px 대신 34px 로 잡은 이유: 푸터 링크는 · 로 이어져 모바일에서 2~3줄로 접히는데, 44px 히트영역을
     주면 줄 사이가 겹쳐 오히려 오탭이 는다. 34px 는 WCAG 2.5.8(AA) 최소 24px 를 넉넉히 넘기면서
     푸터 높이 증가를 한 줄당 20px 로 억제한다.
   ※문장 속에 박힌 링크(.pii3-note 안 '개인정보처리방침을 참고하세요')는 손대지 않는다 —
     WCAG 2.5.8 의 inline 예외이고, 늘리면 문단 줄간이 깨진다. */
.hero-foot a, footer.pf a { display: inline-block; min-height: 34px; line-height: 34px; padding: 0 4px; }

.muted { color: var(--mut); font-size: 12px; }
a { color: var(--pine); }

/* ===== v2: 회원가입·게스트 인증 UI ===== */
/* [H-02] ?next= 착지 맥락 안내 배지 — 로그인 카드 최상단(탭 위) 1줄. 다크 유리 카드 위라 라임 계열
   토큰(--lime-hi)으로 대비 확보. 높이 예산 절약이 요구사항: PT-01(≤640 폴드 안 #btnLoginStaff) 회귀를
   막으려고 1줄 고정(nowrap+말줄임)·컴팩트 패딩으로 묶는다. 표시/비표시는 app.js renderNextHint(). */
.next-hint { display: flex; align-items: center; gap: 6px; margin: 0 0 12px; padding: 6px 10px;
  border-radius: 10px; background: rgba(126,195,26,.14); border: 1px solid rgba(126,195,26,.28);
  color: var(--lime-hi); font-size: 12px; line-height: 1.45; font-weight: 600; }
.next-hint i { flex: 0 0 auto; font-size: 13px; line-height: 1; opacity: .85; }
/* 라벨이 길거나 글꼴 배율이 커도 2줄로 번지지 않게 — 넘치면 말줄임(높이 예산 고정) */
.next-hint span { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* ≤640 압축 — PT-01 폴드 예산에서 배지가 가져가는 몫을 43px → 39px 로 줄인다.
   ※ 이 블록은 반드시 위 .next-hint 기본 정의 "뒤"에 와야 승리한다(둘 다 클래스 특이도 → 소스 순서로
     결정). 상단 PT-01 미디어 블록(92행)에 넣었더니 여기 기본 정의에 밀려 무효였다(실측 확인) — 이동 금지. */
@media (max-width: 640px) {
  .next-hint { margin-bottom: 10px; padding: 5px 9px; font-size: 11px; }
}
.authtabs { display: flex; gap: 6px; margin-bottom: 18px; }
.authtabs button { flex: 1; min-height: 44px; padding: 6px 10px; border-radius: 12px; border: 1px solid rgba(255,255,255,.18);
  background: transparent; color: rgba(255,255,255,.85); font-size: 13px; font-weight: 700;
  cursor: pointer; font-family: inherit; transition: background .15s, color .15s, border-color .15s; }
.authtabs button.on { background: #fff; color: #123a22; border-color: #fff; }
/* [PT-30] 고정 height → min-height — Android 글꼴 배율(대/최대)에서 텍스트가 잘리던 결함 */
.field { width: 100%; min-height: 46px; border-radius: 12px; border: 1px solid rgba(255,255,255,.2);
  background: rgba(255,255,255,.09); color: #fff; padding: 0 14px; font-size: 14px;
  margin-bottom: 10px; font-family: inherit; transition: border-color .15s, box-shadow .15s, background .15s; }
.field::placeholder { color: rgba(255,255,255,.4); }
/* 다크 로그인 카드 위 포커스 — 성균그린은 파인 배경에서 대비 급락(#00843D on 카드 ≈ 낮음)이라
   명도 올린 --skku-green-on-dark(#0FBD60, 대비 7.7:1)로 포커스 링 가시성 확보(WCAG AA). */
.field:focus { outline: none; border-color: var(--skku-green-on-dark, #0FBD60); box-shadow: 0 0 0 3px rgba(15,189,96,.22); }
/* 주 CTA — 라임→성균그린. 다크 카드 위 대비 확보 위해 흰 텍스트(#fff on #00843D = 4.81:1 AA). */
.btn-primary { width: 100%; min-height: 48px; border: 0; border-radius: var(--skku-radius-sm, 10px);
  background: linear-gradient(135deg, var(--skku-green, #00843D), var(--skku-green-dark, #006B31));
  color: var(--skku-on-green, #fff); font-weight: 700; font-size: 14px; cursor: pointer; font-family: inherit;
  box-shadow: 0 6px 18px rgba(0,132,61,.28); transition: transform .15s, box-shadow .15s, filter .15s; }
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 10px 26px rgba(0,132,61,.36); filter: brightness(1.06); }
.btn-primary:active { transform: translateY(0); box-shadow: 0 4px 12px rgba(0,132,61,.24); }
.linkrow { display: flex; gap: 8px; justify-content: center; align-items: center; margin-top: 14px;
  color: rgba(255,255,255,.55); font-size: 12px; }
.linkbtn { border: 0; background: none; color: rgba(255,255,255,.65); font-size: 12px;
  cursor: pointer; text-decoration: underline; font-family: inherit;
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 0 8px; }
.linkbtn:hover { color: #fff; }
.guest-desc { text-align: center; color: rgba(255,255,255,.75); font-size: 13px;
  margin-bottom: 14px; line-height: 1.7; }
.guest-desc b { color: var(--lime-hi); }
#authMsg { margin-top: 12px; font-size: 12px; text-align: center; }

/* ===== 개인정보 동의 ===== */
.consent-row { display: flex; align-items: center; gap: 8px; margin: 2px 0 8px;
  font-size: 13px; color: rgba(255,255,255,.85); cursor: pointer; }
.consent-row input { width: 16px; height: 16px; accent-color: var(--skku-green, #00843D); }
.consent-box { margin-bottom: 12px; font-size: 12px; color: rgba(255,255,255,.65); }
.consent-box summary { cursor: pointer; color: rgba(255,255,255,.55); text-decoration: underline; }
.consent-box div { margin-top: 8px; background: rgba(0,0,0,.18); border-radius: 10px;
  padding: 10px 12px; line-height: 1.7; }
.consent-box p { margin: 0 0 4px; }
.consent-box b { color: rgba(255,255,255,.85); font-weight: 700; margin-right: 6px; }
.consent-box a { color: var(--skku-green-on-dark, #0FBD60); }

/* ===== 관리자 설정 카드 ===== */
.setgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 8px; }
.sfield { min-height: 40px; border: 1.5px solid var(--line); border-radius: 10px; padding: 0 12px;
  font-size: 13px; font-family: inherit; background: var(--bg); }
.sfield:focus { outline: none; border-color: var(--skku-green, #00843D); background: #fff; }
.btn-set { min-height: 38px; padding: 0 16px; border: 0; border-radius: 10px; background: var(--skku-green, #00843D);
  color: var(--skku-on-green, #fff); font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit; }
.btn-set.ghost { background: #fff; color: var(--ink); border: 1px solid var(--line); }
.btn-set.ghost:hover { border-color: var(--skku-green, #00843D); background: var(--skku-green-tint, #E8F5E9); }
/* ★적대리뷰 P2-7: PT-27 로 .btn-set ghost 를 물려받은 "권한 삭제"(명부 말소·로그인 차단)가
   hover 시 승인색(성균그린)으로 물들어 파괴 동작을 안전 동작처럼 보이게 했다 — .mdel 스코프로만 위험색. */
#memberList .mtable .mdel .btn-set.ghost:hover {
  border-color: var(--red, #b03a2e); background: var(--red-t, #faeae8); color: var(--red, #b03a2e); }

/* ===== 캠퍼스 맵 (실사 이미지 + 핀 오버레이) ===== */
/* [PT-NEW-1] 지도 이미지는 loading="lazy" 라 로드 전 래퍼 높이가 0 으로 붕괴하고, %기준 핀 20개가
   한 줄로 적층됐다(실측 uniqueCy=1). 원본 2000×1129 비율로 높이를 미리 예약해 붕괴를 차단한다. */
.mapwrap { position: relative; border-radius: 12px; overflow: hidden; background: #f3f6f0;
  aspect-ratio: 2000 / 1129; }
.mapwrap.noimg { aspect-ratio: auto; }   /* 로드 실패 안내문은 자연 높이 */
.mapwrap img { width: 100%; height: 100%; display: block; object-fit: cover; }
.mapwrap.noimg img { display: none; }
.map-missing { display: none; padding: 40px 16px; text-align: center; color: var(--mut); font-size: 13px; }
.mapwrap.noimg .map-missing { display: block; }
.mapwrap.noimg #pinLayer { display: none; }
#pinLayer { position: absolute; inset: 0; pointer-events: none; }
.mpin { position: absolute; transform: translate(-50%, -100%); pointer-events: auto; cursor: pointer;
  width: 26px; height: 26px; border-radius: 50% 50% 50% 0; rotate: -45deg; display: flex;
  align-items: center; justify-content: center; box-shadow: 0 2px 6px rgba(0,0,0,.25); border: 2px solid #fff; }
.mpin span { rotate: 45deg; color: #fff; font-size: 11px; font-weight: 800; }
.mpin:hover { filter: brightness(1.1); }
/* [U1-4] 키보드 포커스 링 — 핀에 role=button·tabindex=0 을 준 뒤(app.js) 데스크톱 탭 이동 대상이 된다.
   특이도 검산: `.mpin:focus-visible`(0,2,0) > `.mpin`(0,1,0) — 뒤쪽 990행(pinDrop)·1124행(≤640 크기)
     정의보다 높아 소스순서와 무관하게 이긴다. 인라인 style 은 left/top/background/animation-delay 만
     쓰므로 outline/box-shadow/z-index 를 가리지 않는다(인라인에 밀려 무효화된 전례 회피).
   지도 사진 위라 단색 링은 배경에 묻힌다 → 흰 헤일로(box-shadow) 위에 짙은 링(outline) 2중으로 둔다.
     ★.mpin 의 기존 그림자(0 2px 6px)를 함께 적어야 box-shadow 통째 대체로 그림자가 사라지지 않는다.
   ★핀은 rotate:-45deg 라 링도 물방울 모양을 따라 회전한다(정상). 겹친 핀 아래에 깔려 링이 안 보이는
     것을 막으려 z-index 로 형제 핀·날씨 오버레이(.map-rain::before z:4) 위로 올린다 — 포커스 중에만. */
.mpin:focus-visible { outline: 3px solid var(--pine, #0b2616); outline-offset: 2px;
  box-shadow: 0 0 0 6px rgba(255,255,255,.92), 0 2px 6px rgba(0,0,0,.25); z-index: 5; }
/* 클러스터 핀 건수 배지 — 부모(.mpin)가 -45deg 회전이라 배지도 45deg 역회전 */
.mpin .cnt { position: absolute; top: -8px; left: -8px; rotate: 45deg; font-style: normal;
  background: var(--pine, #0b2616); color: #fff; font-size: 10px; font-weight: 800;
  min-width: 16px; height: 16px; border-radius: 8px; padding: 0 3px;
  display: flex; align-items: center; justify-content: center; border: 1.5px solid #fff; }

/* ===== 공지사항 ===== */
/* ★터치 영역 — 글자 높이 16px 가 그대로 탭 영역이었다(데스크톱). 패딩으로 24px 를 만들고
   같은 크기의 음수 마진으로 되돌려 주변 배치는 그대로 둔다. 모바일은 아래 ≤640 에서 44px.
   ⚠ **세로만** 넓힌다. 처음엔 좌우로도 -6px 을 줬다가, 한 줄에 나란히 놓인 동기화 토글
   4개(index.html 457~463)의 히트영역이 서로 8.5px 겹쳐 **옆 버튼이 눌리는** 회귀를 만들었다
   (elementFromPoint 로 확인). 폭은 이미 24px 를 넘으므로 좌우를 넓힐 이유도 없었다. */
.linkbtn2 { border: 0; background: none; color: var(--pine); font-size: 12px; cursor: pointer; text-decoration: underline; font-family: inherit;
  display: inline-flex; align-items: center; min-height: 24px; padding: 4px 0; margin: -4px 0; }
.ntc { padding: 10px 2px; border-top: 1px solid #eef2ec; font-size: 13px; }
.ntc:first-child { border-top: 0; }
.ntc .t { font-weight: 700; }
.ntc.imp .t { color: var(--red); }
.ntc .b { color: var(--mut); font-size: 12px; margin-top: 2px; white-space: pre-wrap; }
.ntc .d { color: var(--mut); font-size: 11px; margin-top: 2px; }

/* 공지 아코디언 (한 줄 제목 → 클릭 시 펼침) */
.ntx { border-top: 1px solid #eef2ec; font-size: 13px; }
.ntx:first-child { border-top: 0; }
.ntx summary, .ntx .row { display: flex; align-items: center; gap: 10px; padding: 9px 2px; list-style: none; }
.ntx summary { cursor: pointer; }
.ntx summary::-webkit-details-marker { display: none; }
.ntx summary:hover .t { color: var(--pine); }
.ntx .t { font-weight: 700; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ntx.imp .t { color: var(--red); }
.ntx .d { color: var(--mut); font-size: 11px; white-space: nowrap; }
.ntx .cv { color: var(--mut); font-size: 14px; transition: transform .15s; }
.ntx[open] .cv { transform: rotate(180deg); }
.ntx .b { color: var(--mut); font-size: 12px; padding: 2px 2px 10px; white-space: pre-wrap; }

/* ===== 회원관리 테이블 ===== */
.mtable { width: 100%; border-collapse: collapse; font-size: 12px; }
.mtable th, .mtable td { border-bottom: 1px solid #eef2ec; padding: 7px 8px; text-align: left; white-space: nowrap; }
.mtable th { color: var(--mut); font-weight: 600; font-size: 11px; }
/* [디자인 3단계] 모바일 전용 권한 요약 칩 — 데스크톱 표에는 같은 정보가 열로 이미 있어 숨긴다. */
#memberList .mtable .msum { display: none; }
/* 이름·부서 컬럼 고정 — 가로 스크롤 시에도 누구 행인지 식별 유지 */
.mtable th:nth-child(1), .mtable td:nth-child(1) { position: sticky; left: 0; width: 104px; min-width: 104px; max-width: 104px; overflow: hidden; text-overflow: ellipsis; background: #fff; z-index: 1; }
.mtable th:nth-child(2), .mtable td:nth-child(2) { position: sticky; left: 104px; min-width: 96px; background: #fff; z-index: 1; box-shadow: 4px 0 6px -4px rgba(0,0,0,.12); }
.mtable th:nth-child(1), .mtable th:nth-child(2) { z-index: 2; }
.badge-adm { font-size: 10px; background: var(--lime-t); color: var(--pine); border-radius: 999px; padding: 1px 7px; font-weight: 700; }
.mcampus { height: 28px; border: 1px solid var(--line); border-radius: 8px; font-family: inherit; font-size: 12px; background: #fff; color: var(--ink); padding: 0 4px; }
/* 캠퍼스 미지정 — 서버는 이걸 «양캠»으로 해석한다. 목록에서 눈에 띄어야 골라 고칠 수 있다.
   #7a4a00 on #fff4d6 = 6.83:1(브라우저 실측). */
.mcampus.unset { background: #fff4d6; border-color: #f0d089; color: #7a4a00; font-weight: 700; }

/* ===== 감사기록 표(2026-08-09) =====
   .mtable 골격을 쓰되(class="mtable audtbl") 세 가지를 덮는다. ★반드시 위 .mtable 규칙 «뒤»에
   와야 한다 — 선택자 특이도가 (0,2,1) 로 동일해 소스 순서로만 이긴다.
   ① 1열 폭: .mtable 은 «이름»(104px 고정·overflow:hidden)을 전제하는데 여기 1열은
      "2026-08-09 14:23" 이라 그대로 두면 잘린다.
   ② 2열 sticky 의 left 오프셋도 1열 폭에 맞춰 함께 옮긴다 — 안 옮기면 두 열이 겹쳐 깔린다.
   ③ 상세(meta)는 240자까지 오므로 nowrap 을 풀고 폭 상한을 준다. */
.audtbl th:nth-child(1), .audtbl td:nth-child(1) { width: 132px; min-width: 132px; max-width: 132px; }
.audtbl th:nth-child(2), .audtbl td:nth-child(2) { left: 132px; min-width: 132px; }
.audtbl td.aud-t, .audtbl td.aud-ip { color: var(--mut); font-variant-numeric: tabular-nums; }
.audtbl td.aud-m { white-space: normal; max-width: 420px; word-break: break-word; color: var(--mut); }
.audtbl .aud-role { display: inline-block; margin-left: 5px; padding: 0 5px; border-radius: 5px;
  background: var(--lime-t); color: var(--pine); font-size: 10px; font-weight: 700; }
@media (max-width: 640px) { .audtbl td.aud-m { max-width: 240px; } }

/* [A11Y-1 · 2026-08-10] 감사기록 표 — 좁은 폭에서 1·2열 sticky 해제.
   위 .mtable 의 1·2열 sticky(104px / left:104px) 에 .audtbl 의 132px / left:132px 가 얹히면
   두 열이 264px 를 «영구» 점유한다. 375px 기기의 #auditList 내부 폭은 303px(wrap 32 + card 38 + 테두리 2 차감)
   뿐이라 동작·상세·접속지 3열이 지나갈 창이 39px 밖에 안 남는다. 게다가 2열은 nowrap 아래 «이메일 + 역할 배지»를
   담아 132px 하한을 상시 넘기므로(≈170px) 실제 잔여 창은 한 자릿수 — 현행 아이폰(390/393px)에서도 본문 3열에
   도달하지 못한다. 이 카드는 열람 전용이라 상세 팝업 같은 대체 경로가 화면에 없다.
   ★특이도 검산: `#auditList .mtable th:nth-child(1)` = (1,2,1) 로 `.mtable …`(0,2,1)·`.audtbl …`(0,2,1) 을
   소스 순서와 «무관하게» 이긴다. 파일 안에 #auditList 를 잡는 다른 규칙은 없고 해당 속성에 !important 도 없다.
   ★아래 #memberList 카드접기 패턴을 이식하지 «않은» 이유: 그 패턴은 data-label·.mp·행 토글 3요소를 전제하는데
   감사기록 행에는 없어 빈 카드로 붕괴한다(같은 이유로 2026-07-21 에 스코프가 붙었다). sticky 만 푼다. */
@media (max-width: 640px) {
  #auditList .mtable th:nth-child(1), #auditList .mtable td:nth-child(1),
  #auditList .mtable th:nth-child(2), #auditList .mtable td:nth-child(2) {
    position: static; left: auto; width: auto; min-width: 0; max-width: none;
    box-shadow: none; z-index: auto; overflow: visible;
  }
  /* 1열(시각)은 폭 고정이 풀렸으니 두 줄로 접히게 둔다 — .mtable 의 nowrap(0,1,1)을 (1,2,1)로 덮는다 */
  #auditList .mtable td.aud-t { white-space: normal; }
}

/* [N-C12] 회원표 모바일 접기 — ≤640px에서만 카드형. 데스크톱(>640px)은 위 규칙 그대로 무변경.
   ★#memberList 스코프 필수: FMS/CMS 동기화 패널(gap 테이블)도 .mtable을 쓰지만 카드 3요소(mp/data-label/토글)가
   없어 전역 적용 시 모바일에서 빈 카드로 붕괴한다(2026-07-21 적대 검증 발견) — 회원표에만 한정. */
@media (max-width: 640px) {
  /* 표 → 세로 블록. 헤더행(첫 tr)·데스크톱 sticky 컬럼 해제 */
  #memberList .mtable, #memberList .mtable tbody, #memberList .mtable tr, #memberList .mtable td { display: block; width: auto; }
  #memberList .mtable tr:first-child { display: none; }     /* th 헤더행 숨김(카드마다 data-label로 라벨 표기) */
  #memberList .mtable th:nth-child(1), #memberList .mtable td:nth-child(1),
  #memberList .mtable th:nth-child(2), #memberList .mtable td:nth-child(2) {
    position: static; left: auto; width: auto; min-width: 0; max-width: none;
    box-shadow: none; z-index: auto; overflow: visible;
  }
  /* 데이터행 = 카드 */
  #memberList .mtable tr { position: relative; margin: 0 0 10px; padding: 10px 12px 8px;
    border: 1px solid var(--line); border-radius: 12px; background: #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,.05); cursor: pointer; }
  #memberList .mtable tr::after { content: "▾"; position: absolute; top: 10px; right: 12px;
    color: var(--mut); font-size: 13px; transition: transform .15s; }
  #memberList .mtable tr.mopen::after { transform: rotate(180deg); }
  /* 셀: 라벨:값 한 줄. 44px 터치타깃 확보 */
  /* ★라벨 정렬(디자인 3단계 2026-07-28): 종전 justify-content:space-between 은 라벨을 맨 왼쪽,
     값을 맨 오른쪽으로 밀어 카드 폭 335px 안에 빈 공간이 230px 까지 벌어졌다(사용자 지적
     "라벨↔값이 멀다"). 라벨을 고정폭 88px 열로 세우고 값을 그 바로 옆에 붙인다 —
     여러 줄이 같은 x 에서 시작해 훑어 읽기가 된다. */
  #memberList .mtable td { border: 0; padding: 0; white-space: normal;
    display: flex; align-items: center; justify-content: flex-start; gap: 10px;
    min-height: 44px; }
  #memberList .mtable td::before { content: attr(data-label); color: var(--mut); font-size: 11px;
    font-weight: 600; flex: 0 0 88px; }
  /* 셀렉트는 남은 폭을 채워 탭 면적을 키운다(라벨 정렬로 오른쪽이 비게 된 자리를 회수) */
  #memberList .mtable td > select { flex: 1 1 auto; min-width: 0; }
  /* 이름 셀 = 카드 헤더(라벨 숨김·강조) */
  #memberList .mtable td.mname { display: block; min-height: 0; padding-right: 22px; margin-bottom: 4px;
    font-size: 15px; font-weight: 700; }
  #memberList .mtable td.mname::before { display: none; }
  /* 접힘 상태에서도 "이 사람이 무엇에 접근하는지"를 보여주는 요약 칩(app.js _memSummaryChips).
     펼치기 화살표(tr::after, right:12px)와 겹치지 않게 이름 셀의 padding-right 22px 를 승계한다. */
  #memberList .mtable .msum { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
  #memberList .mtable .msum-c { font-size: 11px; font-weight: 700; line-height: 1;
    padding: 4px 7px; border-radius: 999px; background: var(--skku-green-tint, #E8F5E9);
    color: var(--deep, #0b2616); border: 1px solid rgba(11,38,22,.10); white-space: nowrap; }
  #memberList .mtable .msum-c.sys { font-weight: 600; letter-spacing: -.01em; }
  #memberList .mtable .msum-c.off { background: var(--red-t, #faeae8); color: var(--red, #b03a2e); border-color: rgba(176,58,46,.22); }
  /* 상세 셀(mp 아님)은 접힘 상태에서 숨김 → 펼치면(mopen) 노출 */
  #memberList .mtable tr:not(.mopen) td:not(.mp) { display: none; }
  #memberList .mtable tr.mopen td:not(.mp) { display: flex; }
  #memberList .mtable tr.mopen td.mname { display: block; }
  /* 다중 체크 셀(공종·섹터·탭권한)은 라벨 위·칩 아래로 (open 상태 flex 규칙보다 뒤·동급 특정성으로 승리) */
  #memberList .mtable tr.mopen td.ckcell { display: block; padding: 4px 0; }
  #memberList .mtable td.ckcell::before { display: block; margin-bottom: 4px; }
  #memberList .mtable .ckl { min-height: 32px; font-size: 12px; }
  #memberList .mtable td.mdel { justify-content: flex-end; }
  /* [PT-25] 즉시 저장(setMemberAccess) 컨트롤의 오탭 방지 — 체크박스·셀렉트 확대.
     셀(라벨) 탭으로도 토글되도록 app.js onMemberCardToggle 이 단일 체크 셀을 위임 처리. */
  #memberList .mtable input[type="checkbox"] { width: 22px; height: 22px; }
  #memberList .mtable .ckl input[type="checkbox"] { width: 18px; height: 18px; }
  #memberList .mcampus { height: 40px; }
  /* [PT-27] 권한 삭제 버튼(구 .btn-gh 미정의 → UA 기본 22×24px)을 .btn-set ghost 로 정리 후 44px */
  #memberList .mtable .btn-set { height: 44px; }
}


/* ============ 알림 벨 ============ */
.notifwrap { position: relative; display: inline-flex; }
.notifwrap .iconbtn { position: relative; }
/* ★알림 배지 통일(디자인 마감 2026-07-28) — 같은 의미의 배지가 벨(#e03131·r5)과 타일(.tnbadge #e5484d·r9)로
   빨강·모양이 달랐고, 두 값 모두 토큰에 없으며 브랜드 --red(#b03a2e)보다 채도가 높아 화면에서 가장 강했다.
   → 두 배지 모두 var(--red) + 완전 pill 로 수렴(위치·크기·글꼴은 무변경). */
.nbadge { position: absolute; top: -7px; right: -9px; min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 999px; background: var(--red, #b03a2e); color: #fff; font-size: 11px; font-weight: 800;
  display: inline-flex; align-items: center; justify-content: center; line-height: 1; }
.notifpanel { position: absolute; right: 0; top: 34px; width: 300px; background: #fff; color: var(--ink);
  border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow-up); z-index: 70;
  overflow: hidden; text-align: left; }
/* 모바일: 벨 버튼 기준 absolute(300px)가 화면 밖으로 밀리며 깨짐(2026-07-18 실기기 확인) → 화면 기준 고정 */
@media (max-width: 640px) {
  /* [PT-03] top 58px 고정은 관리자 2~3줄 접힘 topbar(실측 90~130px)를 덮었다 — app.js ResizeObserver 가
     기록하는 --tbh(실높이)를 기준으로. JS 미실행·숨김(높이 0) 구간은 폴백 58px 유지. */
  .notifpanel { position: fixed; left: 8px; right: 8px; top: calc(var(--tbh, 58px) + 4px); width: auto; max-height: 72vh; }
  /* ★UI점검(2026-07-23): #msgList 에도 동일 스크롤 — 규칙 부재로 72vh 초과 쪽지가 잘려 접근 불가하던 결함
     ※ 이 calc 는 아래 900행대 `.notifpanel #notifList,#msgList{max-height:340px}` 와 동급 특이도(1,1,0)인데
       소스가 앞서 항상 진다(CSSOM 실측 확인) — 즉 작성 시점부터 사문이었다. 실제 상한은 파일 말미
       PT 블록의 flex 모델이 담당한다. 상수 조정으로는 아무 일도 일어나지 않으니 여기를 고치지 말 것. */
}
.nhead { display: flex; justify-content: space-between; align-items: center; font-weight: 800;
  font-size: 13px; padding: 11px 14px; border-bottom: 1px solid var(--line); }
.nrow { display: flex; align-items: center; gap: 10px; padding: 10px 14px; font-size: 13px;
  border-bottom: 1px solid #f0f3ee; text-decoration: none; color: var(--ink); }
a.nrow:hover { background: var(--bg); }
.nrow.off { color: var(--mut); }
.nic { width: 28px; height: 28px; border-radius: 8px; display: inline-flex; align-items: center;
  justify-content: center; font-size: 15px; flex: none; }
.nt { flex: 1; }
.nc { font-weight: 800; font-size: 12px; color: var(--mut); }
.nc.on { color: #e03131; }
.nc.mut { font-weight: 400; font-size: 11px; }
.nfoot { padding: 9px 14px; font-size: 11px; color: var(--mut); background: var(--bg); }


/* ===== 날씨 위젯 (CMS 위젯 CSS 이식: styles.css 1191-1430) ===== */
/* 접기 버튼 — 위젯 우상단 */
.wx-collapse-btn{
  position:absolute;
  top:6px;
  right:8px;
  width:22px;
  height:22px;
  border-radius:50%;
  border:1px solid #cfe0f5;
  background:#fff;
  color:#1B3A5C;
  font-size:16px;
  font-weight:900;
  line-height:1;
  display:flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
  padding:0;
  z-index:2;
  transition:background .15s, transform .1s;
}
.wx-collapse-btn:hover{ background:#e0ecfc; }
.wx-collapse-btn:active{ transform:scale(.92); }

.weather-widget .wx-title{
  font-weight:900;font-size:13px;margin-bottom:8px;
  display:flex;align-items:center;gap:6px;width:100%;
}
.weather-widget .wx-title .wx-sub{
  font-size:10px;color:#6b7a8a;font-weight:600;
}
.wx-refresh-btn{
  background:#fff;border:1px solid #cfe0f5;color:#1B3A5C;
  border-radius:10px;padding:2px 8px;line-height:1.3;
  font-size:10px;font-weight:800;cursor:pointer;
  transition:background .15s, transform .1s;
  white-space:nowrap;
}
.wx-refresh-btn:hover{ background:#e0ecfc; }
.wx-refresh-btn:active{ transform:scale(0.95); }
.weather-topbar .wx-refresh-btn{ margin-left:auto; }
.weather-widget .wx-campus{
  background:#fff;border:1px solid #e3eaf4;border-radius:10px;
  padding:8px 8px 6px;margin-bottom:8px;
}
.weather-widget .wx-campus.wx-campus-warn{
  background:#fff7ed;border-color:#fdba74;
}
.weather-widget .wx-campus-title{
  font-size:11px;font-weight:800;color:#334155;margin-bottom:6px;
  display:flex;align-items:center;gap:4px;
}
.weather-widget .wx-badge{
  display:inline-block;background:#fed7aa;color:#9a3412;
  font-size:10px;font-weight:800;padding:1px 5px;border-radius:6px;
}
.weather-widget .wx-days{
  display:grid;grid-template-columns:repeat(3,1fr);gap:4px;
}
.weather-widget .wx-day{
  background:#f8fafc;border-radius:6px;padding:6px 2px;
  text-align:center;font-size:10px;line-height:1.3;
}
.weather-widget .wx-day.wx-warn{ background:#fee2e2;color:#991b1b; }
.weather-widget .wx-day-label{ font-weight:700;color:#475569; }
.weather-widget .wx-day.wx-warn .wx-day-label{ color:#991b1b; }
.weather-widget .wx-day-icon{ font-size:18px;line-height:1;margin:2px 0; }
.weather-widget .wx-day-tmp{ font-weight:800;font-size:11px; }
.weather-widget .wx-day-pop{ font-size:10px;color:#0369a1; }
.weather-widget .wx-day.wx-warn .wx-day-pop{ color:#991b1b; }
.weather-widget .wx-foot{
  font-size:10px;color:#64748b;margin-top:4px;
  display:flex;align-items:center;justify-content:space-between;gap:6px;
}
.weather-widget .wx-loading,.weather-widget .wx-err{
  font-size:11px;color:#64748b;padding:14px 4px;text-align:center;
}

/* 좁은 화면(<1400px)에서 월별탭 상단 인라인 날씨 바 */
.weather-topbar{
  display:block;
  margin-top:8px;
  background:linear-gradient(90deg,#f0f7ff 0%,#f8fbff 100%);
  border:1px solid #cfe0f5;border-radius:10px;
  padding:6px 10px;
  font-size:11px;
  color:#1B3A5C;
}
.weather-topbar .wx-bar-inner{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
}
.weather-topbar .wx-bar-item{
  display:inline-flex;align-items:center;gap:3px;
  padding:2px 6px;border-radius:6px;background:#fff;
  border:1px solid #e3eaf4;font-weight:700;
}
.weather-topbar .wx-bar-item.wx-warn{
  background:#fee2e2;color:#991b1b;border-color:#fecaca;
}
.weather-topbar .wx-bar-sep{ color:#cbd5e1;font-weight:400; }
.weather-topbar .wx-err{ color:#991b1b; }
@media (min-width:1400px){
  .weather-topbar{ display:none; }
}

/* =====================================================
   Weather widget — 날짜 클릭 → 시간별 모달, 풍속 표시
   ===================================================== */
.weather-widget .wx-day{
  cursor:pointer;
  transition:background .15s ease, transform .1s ease;
}
.weather-widget .wx-day:hover{
  background:#eef5ff;
  transform:translateY(-1px);
}
.weather-widget .wx-day.wx-warn:hover{ background:#fecaca; }
.weather-widget .wx-day-sublabel{
  font-size:9px;color:#64748b;font-weight:600;margin-top:1px;
}
.weather-widget .wx-day.wx-warn .wx-day-sublabel{ color:#b91c1c; }
.weather-widget .wx-day-wsd{
  font-size:10px;color:#64748b;margin-top:1px;
}
.weather-widget .wx-day-wsd.wx-wind-warn{
  color:#854d0e;font-weight:800;
}
.weather-widget .wx-day.wx-warn .wx-day-wsd{ color:#991b1b; }
.weather-widget .wx-badge.wx-badge-wind{
  background:#fef3c7;color:#854d0e;
}
.weather-topbar .wx-bar-item.wx-wind-warn{
  background:#fef3c7;color:#854d0e;border-color:#fde68a;
}

/* 시간별 날씨 모달 */
.wx-hourly-overlay{
  position:fixed;inset:0;z-index:17000;
  background:rgba(15,23,42,0.62);
  display:flex;align-items:center;justify-content:center;
  padding:16px;
  animation:wxHourlyFadeIn .18s ease;
}
@keyframes wxHourlyFadeIn{
  from{ opacity:0; }
  to  { opacity:1; }
}
.wx-hourly-box{
  background:#fff;border-radius:16px;
  width:100%;max-width:680px;max-height:90vh;
  display:flex;flex-direction:column;
  box-shadow:0 18px 60px rgba(15,23,42,0.35);
  overflow:hidden;
}
.wx-hourly-hdr{
  display:flex;align-items:center;justify-content:space-between;
  padding:14px 18px;border-bottom:1px solid #e2e8f0;
  background:linear-gradient(90deg,#f0f7ff 0%,#f8fbff 100%);
}
.wx-hourly-title{
  font-size:14px;font-weight:900;color:#1B3A5C;
  display:flex;flex-direction:column;gap:2px;
}
.wx-hourly-title .wx-hourly-sub{
  font-size:11px;color:#64748b;font-weight:600;
}
.wx-hourly-close{
  width:32px;height:32px;border-radius:8px;border:1px solid #cbd5e1;
  background:#fff;color:#334155;font-size:16px;font-weight:900;
  cursor:pointer;display:flex;align-items:center;justify-content:center;
  transition:background .15s;
}
.wx-hourly-close:hover{ background:#f1f5f9; }
.wx-hourly-body{
  overflow:auto;padding:4px 0 10px;
}
.wx-hourly-legend,
.wx-h-row{
  display:grid;
  grid-template-columns:54px 38px 1fr 52px 72px 78px 68px 60px;
  gap:6px;
  padding:7px 18px;
  font-size:11px;align-items:center;
}
.wx-hourly-legend{
  font-weight:800;color:#64748b;
  border-bottom:1px solid #e2e8f0;background:#f8fafc;
  position:sticky;top:0;
}
.wx-h-row{
  border-bottom:1px solid #f1f5f9;color:#334155;
}
.wx-h-row:last-child{ border-bottom:none; }
.wx-h-row .wx-h-ico{ font-size:16px;text-align:center; }
.wx-h-row .wx-h-tmp{ font-weight:800;color:#1B3A5C; }
.wx-h-row .wx-h-pop{ color:#0369a1;font-weight:700; }
.wx-h-row .wx-h-wsd{ color:#475569; }
.wx-h-row.wx-h-rain{
  background:#eff6ff;
}
.wx-h-row.wx-h-rain .wx-h-pop{ color:#1d4ed8; }
.wx-h-row.wx-h-wind{
  background:#fef9c3;
}
.wx-h-row.wx-h-wind .wx-h-wsd{ color:#854d0e;font-weight:800; }
.wx-h-row.wx-h-rain.wx-h-wind{
  background:linear-gradient(90deg,#eff6ff 0%,#fef9c3 100%);
}
.wx-h-lbl{ display:none; }

@media (max-width:720px){
  .wx-hourly-legend,
  .wx-h-row{
    grid-template-columns:46px 34px 1fr 46px 62px 66px 60px;
    padding:6px 12px;
    font-size:10px;
  }
  .wx-hourly-legend .wx-h-reh,
  .wx-h-row .wx-h-reh{ display:none; }
}
@media (max-width:520px){
  .wx-hourly-box{ max-width:100%; }
  .wx-hourly-legend{ display:none; }
  .wx-h-row{
    grid-template-columns:1fr 1fr;
    grid-auto-flow:row;
    gap:2px 8px;
    padding:8px 14px;
  }
  .wx-h-row > span{
    display:flex;align-items:center;gap:4px;
  }
  .wx-h-row .wx-h-lbl{
    display:inline;
    font-size:9px;color:#64748b;font-weight:700;
    margin-right:2px;
  }
  .wx-h-row .wx-h-time{ grid-column:1 / -1;font-weight:900;color:#1B3A5C; }
}


/* ============================================================
   날씨 위젯 — 포털 인라인 카드 배치 (위 이식분을 오버라이드)
   ============================================================ */
.weather-widget.wx-portal{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:10px;
  align-items:start;
  position:relative;
  background:linear-gradient(180deg,#f0f7ff 0%,#f8fbff 100%);
  border:1px solid #cfe0f5;
  border-radius:12px;
  padding:12px 14px;
  color:#1B3A5C;
}
.weather-widget.wx-portal .wx-title{ grid-column:1 / -1; font-size:14px; }
.weather-widget.wx-portal .wx-foot{ grid-column:1 / -1; }
.weather-widget.wx-portal .wx-collapse-btn{ display:none; }
.weather-widget.wx-portal .wx-campus{ margin-bottom:0; }
@media (max-width:640px){
  .weather-widget.wx-portal{ grid-template-columns:1fr; }
}


/* ===== 외부 시스템 바로가기 칩 (킹고포털·통합행정·SSO·KingoGPT) ===== */
.extbar{ display:flex; flex-wrap:wrap; gap:8px; margin-bottom:14px; }
.extchip{
  display:inline-flex; align-items:center; gap:6px;
  padding:8px 14px; border-radius:10px;
  background:#fff; border:1px solid #d7e3d0;
  color:var(--ink); font-size:13px; font-weight:700;
  text-decoration:none; transition:background .15s, border-color .15s, transform .1s;
}
.extchip i{ font-size:16px; color:#2f7d32; }
.extchip:hover{ background:#eef6ea; border-color:#9cc79a; }
.extchip:active{ transform:scale(.97); }
/* ★외부/내부 구분 (디자인 점검 2026-07-29) — 이 줄에는 새 탭으로 나가는 외부 사이트
   (킹고포털·KingoGPT·성대뉴스)와 포털 내부 이동(개인상황실·상황판·주간계획·성과관리)이
   똑같은 모양으로 섞여 있어, 누르기 전에는 창이 바뀌는지 알 수 없었다.
   target="_blank" 인 칩에만 ↗ 를 붙인다(마크업 무변경, 목적지 제거 없음).
   ::after 텍스트라 스크린리더가 읽지 않도록 하지 않는다 — rel/target 은 이미 보조기술이 안내한다. */
.extchip[target="_blank"]::after{
  content:"↗"; font-size:11px; font-weight:800; color:#7a8b74; margin-left:-1px; line-height:1;
}
@media (max-width:560px){ .extchip{ padding:7px 11px; font-size:12px; } }


/* (Phase C1) 날씨 카드는 히어로 밴드 우측 컬럼에 인라인 배치 — 구 고정 오버레이(≥1200px) 제거.
   배치 규칙은 위 .hero-band 로 일원화. */

/* 회원관리 공종·섹터 체크박스 */
.ckcell{ white-space:nowrap; }
.ckl{ display:inline-flex; align-items:center; gap:2px; font-size:11px; margin:1px 6px 1px 0; color:#334155; }
.ckl input{ margin:0; }

/* 알림 벨 — 건별 항목 (#msgList 도 동일 스크롤 상한 — UI점검 2026-07-23) */
.notifpanel #notifList, .notifpanel #msgList{ max-height:340px; overflow:auto; }
.notifpanel .nsec{ display:flex; align-items:center; gap:6px; font-size:12px; font-weight:800; color:#334155; padding:9px 12px 4px; border-top:1px solid #eef2f7; }
.notifpanel .nsec:first-child{ border-top:none; }
.notifpanel .nrow.item{ display:flex; align-items:flex-start; gap:8px; padding:5px 12px; }
.notifpanel .nrow.item .nt{ font-size:12px; line-height:1.35; color:var(--ink); }
.notifpanel .ntg{ display:inline-block; background:#eef2f7; color:#475569; font-size:10px; font-weight:700; padding:0 5px; border-radius:6px; }
.notifpanel .nwh{ color:#64748b; font-size:10px; }
.notifpanel .nbtn{ margin-left:auto; flex:0 0 auto; font-size:11px; font-weight:800; padding:3px 9px; border-radius:8px; border:1px solid #cbd5e1; background:#fff; color:#334155; cursor:pointer; text-decoration:none; white-space:nowrap; }
.notifpanel .nbtn.ok{ border-color:#86bd5b; color:#3f6e21; background:#f2f9ec; }
.notifpanel .nbtn:hover{ filter:brightness(.96); }
.notifpanel .nrow.off.sm{ font-size:11px; color:#64748b; padding:2px 12px 6px; }

/* 알림 벨 — 시스템별/전체 모두읽음 버튼 */
.notifpanel .nsec-all{ margin-left:auto; font-size:10px; font-weight:700; color:#3f6e21; background:none; border:0; cursor:pointer; padding:2px 4px; }
.notifpanel .nsec-all:hover{ text-decoration:underline; }
.notifpanel .nfoot{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.notifpanel .nfoot-all{ font-size:11px; font-weight:800; color:#fff; background:var(--deep); border:0; border-radius:8px; padding:4px 11px; cursor:pointer; white-space:nowrap; }
.notifpanel .nfoot-all:hover{ background:#12351f; }
.notifpanel .nfoot-note{ font-size:10px; color:#64748b; }

/* 시스템 바로가기 타일 — 미확인 알림 배지 (우측 상단 빨강) */
.tile{ position:relative; }
/* ★.nbadge 와 통일(디자인 마감 2026-07-28) — #e5484d → var(--red), r9 → 999px(h18 이라 모양은 동일) */
.tnbadge{ position:absolute; top:6px; right:6px; min-width:18px; height:18px; padding:0 4px; box-sizing:border-box; background:var(--red, #b03a2e); color:#fff; font-size:10px; font-weight:800; border-radius:999px; display:none; line-height:18px; text-align:center; z-index:2; box-shadow:0 1px 3px rgba(0,0,0,.2); }

/* 모바일: 입력 16px — iOS Safari 포커스 자동확대 방지 (원 정의보다 뒤에 있어야 소스순서로 승리) */
/* [PT-12] .cmtin(공지 댓글 입력)만 승격 목록에서 누락돼 iOS 줌이 잔존하던 7/22 잔여 갭 */
@media (max-width: 560px) {
  .field, .sfield, .asst-field, .pm-ta, .cmtin { font-size: 16px; }
  .cmtin { min-height: 44px; }
}

/* ── 살아있는 캠퍼스(2026-07-18) — 공사 글로우·민원 펄스·야간 창문 점등 ── */
.glowspot { position: absolute; width: 84px; height: 58px; transform: translate(-50%,-58%); border-radius: 50%;
  background: radial-gradient(ellipse, rgba(94,163,21,.35), rgba(94,163,21,0) 68%); pointer-events: none;
  animation: lc-breathe 3.4s ease-in-out infinite; }
@keyframes lc-breathe { 0%,100% { opacity:.45; } 50% { opacity:1; } }
.mpin--ticket::after { content:""; position: absolute; left: 50%; top: 50%; width: 12px; height: 12px; border-radius: 50%;
  border: 3px solid #1d3f6e; transform: translate(-50%,-50%); animation: lc-ping 1.6s ease-out infinite; pointer-events: none; }
@keyframes lc-ping { 0% { width:12px; height:12px; opacity:.9; } 100% { width:70px; height:70px; opacity:0; } }
.map-night #mapImg { filter: brightness(.55) saturate(.75); transition: filter 1.2s; }
/* 살아있는 캠퍼스 — 비/눈 파티클(app.js MapFX 캔버스 · 기준=현재 시간대 기상청 PTY).
   캔버스가 빗줄기·스플래시·눈송이를 그리고, 여기서는 대기 톤(감광·채도·안개)만 담당. */
#wxFxCanvas { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.map-rain #mapImg { filter: brightness(.84) saturate(.72) contrast(.96); transition: filter 1.2s; }
.map-night.map-rain #mapImg { filter: brightness(.46) saturate(.6); }
.map-snow #mapImg { filter: brightness(.96) saturate(.7) contrast(.94); transition: filter 1.2s; }
.map-rain::before { content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 4;
  background: linear-gradient(to bottom, rgba(158,178,204,.16), rgba(158,178,204,.05) 45%, rgba(158,178,204,.14)); }
/* ── 3주차 연출(2026-07-18): 황혼 그라데이션 · 구름 그림자 · 계절 톤 ──────────
   전부 오버레이 div(핀 아래·클릭 통과) — #mapImg filter 와 독립이라 밤·비와 자유 조합 */
.duskveil, .seasonveil { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity 2.5s; z-index: 3; }
.map-dusk .duskveil { opacity: 1;
  background: linear-gradient(to bottom, rgba(255,140,60,.14), rgba(200,90,120,.10) 55%, rgba(90,60,140,.14)); }
.season-spring .seasonveil { opacity: 1; background: rgba(140,205,100,.07); }
.season-autumn .seasonveil { opacity: 1;
  background: linear-gradient(rgba(210,130,50,.09), rgba(190,100,40,.12)); }
.season-winter .seasonveil { opacity: 1; background: rgba(150,180,220,.11); }
.cloudshadow { position: absolute; width: 58%; height: 46%; border-radius: 50%; pointer-events: none; opacity: 0;
  background: radial-gradient(ellipse, rgba(25,35,55,.15), transparent 68%); filter: blur(8px);
  transition: opacity 3s; z-index: 3; left: -60%; }
.cloudshadow.c1 { top: 6%; }
.cloudshadow.c2 { top: 44%; width: 44%; height: 38%; }
.map-cloudy .cloudshadow.c1 { opacity: 1; animation: cloudDrift 210s linear infinite; }
.map-cloudy .cloudshadow.c2 { opacity: 1; animation: cloudDrift 150s linear infinite; animation-delay: -70s; }
@keyframes cloudDrift { from { transform: translateX(0); } to { transform: translateX(320%); } }
@media (prefers-reduced-motion: reduce) { .cloudshadow { animation: none !important; opacity: .5; left: 20%; } }

/* ── 디테일 1주차(2026-07-18): 스켈레톤 · 스태거 등장 · 핀 드롭 ───────────── */
.skel { position: relative; overflow: hidden; background: #eceff0; border-radius: 14px; min-height: 96px; }
.skel::after { content: ""; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.7), transparent);
  animation: skelShine 1.15s infinite; }
@keyframes skelShine { to { transform: translateX(100%); } }
.skel-row { min-height: 14px; height: 14px; margin: 13px 0; border-radius: 8px; }
.fx-stagger > * { opacity: 0; animation: fxRise .5s cubic-bezier(.2,.7,.3,1) forwards; }
.fx-stagger > *:nth-child(1) { animation-delay: .03s; }
.fx-stagger > *:nth-child(2) { animation-delay: .09s; }
.fx-stagger > *:nth-child(3) { animation-delay: .15s; }
.fx-stagger > *:nth-child(4) { animation-delay: .21s; }
.fx-stagger > *:nth-child(5) { animation-delay: .27s; }
.fx-stagger > *:nth-child(6) { animation-delay: .33s; }
.fx-stagger > *:nth-child(7) { animation-delay: .39s; }
.fx-stagger > *:nth-child(n+8) { animation-delay: .45s; }
@keyframes fxRise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.mpin { animation: pinDrop .55s cubic-bezier(.25,1.35,.4,1) backwards; }  /* 딜레이는 인라인(80ms 스태거) */
@keyframes pinDrop { from { opacity: 0; transform: translate(-50%,-170%); } to { opacity: 1; transform: translate(-50%,-100%); } }
@media (prefers-reduced-motion: reduce) {
  .fx-stagger > *, .mpin { animation: none !important; opacity: 1 !important; }
  .skel::after { animation: none; }
}

.lamp { position: absolute; width: 6px; height: 6px; border-radius: 50%; background: #ffd873; pointer-events: none;
  box-shadow: 0 0 12px 4px rgba(255,205,90,.5); animation: lc-flick 4s ease-in-out infinite; }
@keyframes lc-flick { 0%,100% { opacity:.85; } 50% { opacity:.4; } }

/* ============================================================
   Task #4 (2026-07-18) — 쪽지·전화 메모 / 작업지시 / PWA 푸시 / 빈 상태 일러스트
   ============================================================ */
/* 쪽지 행(패널·전화 메모 큐·작업지시 수신함 공용) */
.msgrow { padding:9px 12px; border-top:1px solid #eef2ec; font-size:13px; cursor:pointer; }
.msgrow:first-child { border-top:0; }
.msgrow.unread { background:#f4f9ec; }
.msgrow:hover { background:#fafcf7; }
.msgchip { font-size:11px; font-weight:800; border-radius:6px; padding:2px 7px; flex:none; }
.msgtext { margin-top:4px; color:var(--ink); line-height:1.55; white-space:pre-line; word-break:break-word; }
.msgtext.clamp { display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }

/* 쪽지 작성 모달 */
.pm-overlay { position:fixed; inset:0; background:rgba(11,38,22,.45); z-index:130; display:flex;
              align-items:center; justify-content:center; padding:16px; }
.pm-modal { background:#fff; border-radius:14px; width:min(480px,100%); max-height:88vh; overflow-y:auto; overscroll-behavior:contain; padding:16px; }
.pm-head { display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; }
/* [PT-10] 닫기 ✕ 가 ≈15×18px(패딩 0) — 푸시 배너에서는 오탭 시 권한 프롬프트로 직행했다.
   pm 모달 헤더·푸시 배너 공용이라 전역 44px(정렬은 inline-flex 로 중앙 고정). */
.pm-x { border:0; background:none; font-size:15px; cursor:pointer; color:var(--mut);
        min-width:44px; min-height:44px; display:inline-flex; align-items:center; justify-content:center; padding:0; }
.pm-x:hover { color:var(--ink); }
.pm-ta { width:100%; height:84px; resize:vertical; padding:10px 12px; }
.pm-tolist { position:absolute; left:0; right:0; top:100%; background:#fff; border:1px solid var(--line);
             border-radius:10px; box-shadow:0 8px 24px rgba(0,0,0,.14); z-index:6; max-height:220px; overflow-y:auto; }
.pm-to { padding:8px 12px; font-size:13px; cursor:pointer; }
.pm-to:hover { background:#eef7e2; }

/* 제3자 개인정보 간접수집 고지문(법 제20조, 2026-07-26 신설) — 정보주체가 아닌 직원이 제3자의
   이름·연락처를 입력하는 지점에 공통 삽입. 6개 앱 공통 클래스명 .pii3-note(문구도 동일). */
.pii3-note { font-size:11px; line-height:1.6; color:var(--mut); margin:6px 0 0; }
.pii3-note a { color:var(--pine); font-weight:600; }

/* 개정 방침 재동의 모달 본문(#rcOverlay) — 로그인 히어로용 .consent-box 는 어두운 배경 전용이라
   흰 카드(.pm-modal) 위에서는 글자가 보이지 않는다. 같은 정보 구조를 밝은 배경용으로 별도 정의. */
.rc-box { font-size:12px; line-height:1.7; color:var(--mut); background:var(--bg);
          border:1px solid var(--line); border-radius:12px; padding:11px 13px;
          max-height:46vh; overflow-y:auto; overscroll-behavior:contain; }
.rc-box p { margin:0 0 5px; }
.rc-box p:last-child { margin-bottom:0; }
.rc-box b { color:var(--ink); font-weight:700; }
.rc-box .rc-hl { color:var(--pine); font-size:13px; }
.rc-box a { color:var(--pine); font-weight:600; }

/* 만족도 설문 모달(#svOverlay, 2026-08-26) — 위 .rc-box 와 같은 이유로 «밝은 카드 위» 전용이다.
   ★터치 타깃: 5점 척도 칸과 4번 선택지 행을 통째로 라벨로 감싸고 입력은 시각적으로만 남긴다.
     입력을 그대로 두면 실제로 누를 수 있는 곳이 16px 짜리 동그라미뿐이라 모바일에서 옆 칸이
     눌린다 — 이 저장소에서 같은 형태로 두 번 나온 결함이다(UI/UX 점검 2026-07-31·경량점검 08-14).
   ★선택 표시는 :checked 형제 선택자가 아니라 app.js 가 붙이는 .on 클래스로 한다. CSS 특이도로
     이기고 지는 자리를 하나 더 만들지 않기 위함이다(2026-07-23 리뷰가 잡은 무효 규칙과 같은 종류).
   ★글자 크기 전면 상향(2026-08-26 2차, 운영자 실기기 보고 「너무 작아 시인성이 나쁘다」).
     종전은 본문·선택지가 12.5px, 보조문구가 11px 이었다 — 포털의 다른 카드 관습을 따른 값인데,
     이 모달은 «읽고 판단해서 답하는» 화면이라 훑어보는 카드와 요구가 다르다. 본문 14.5~15px,
     보조 12.5px, 척도 숫자 17px 로 올린다.
     ㆍ입력칸(.sv-etc·.sv-ta)은 «16px 미만이면 iOS 사파리가 포커스 시 화면을 확대»한다. 확대되면
       모달이 화면 밖으로 밀려 나가 되돌아오지 못한다. 그래서 16px 은 취향이 아니라 하한이다
       (check-ui.js 가 재는 「iOS 입력 확대」 항목과 같은 기준). */
/* ★제목 줄 고정(2026-08-26 3차, 운영자 요청 「아래로 드래그해도 헤드라인이 보이게」).
   문항이 5개라 모바일에서 두세 화면을 내려가는데, 그 사이 이게 무슨 창인지 알려 주는 것이
   화면에서 사라졌다. 닫기(✕)도 함께 사라져 이탈 수단까지 위로 되돌아가야 했다.
   ㆍ스크롤 컨테이너는 .pm-modal(overflow-y:auto)이고 .pm-head 는 그 직계 자식이라 sticky 가 먹는다.
   ㆍ★사방으로 «패딩 밖까지» 넓히는 것이 핵심이다. 카드에 패딩이 있어 그냥 붙이면 배경이 본문
     폭만 덮고, 좌우 틈으로 글자가 제목 옆을 비껴 지나간다.
   ㆍ★위쪽은 음수 마진으로 풀지 «않는다». sticky 가 붙잡는 것은 «마진 박스»라, margin-top:-16px
     이면 고정된 순간 테두리 박스가 16px 아래로 내려가 그만큼 투명한 띠가 생기고 그 띠로 본문이
     지나간다(브라우저 실측: 고정 시 headTop=14px). 대신 카드의 위쪽 패딩 자체를 0 으로 두고
     헤더가 그 패딩을 제공하게 한다 — 스크롤 시작점과 헤더 상단이 같아져 계산이 사라진다.
   ㆍ패딩 값이 화면 크기에 따라 달라지므로(≤640px 에서 .pm-modal 은 14px) 변수로 둔다 — 한쪽만
     고치면 모바일에서 2px 씩 어긋난 틈이 남는다. */
.sv-modal { width:min(580px,100%); --sv-pad:16px; }
/* ★복합 선택자(0,2,0)로 쓴다 — `.sv-modal` 한 겹(0,1,0)으로 쓰면 아래 @media 안의
   `.pm-modal { padding:14px }` 이 «뒤에 있다는 이유만으로» 이겨서 규칙이 죽는다.
   실제로 그렇게 썼다가 브라우저 실측에서 헤더 위 14px 틈으로 본문이 비쳐 지나갔다
   (elementFromPoint 가 헤더 위에서 LABEL·SPAN 을 집었다). 순서에 기대지 않는다. */
.sv-modal.pm-modal { padding-top:0; }
.sv-modal .pm-head {
  position:sticky; top:0; z-index:3;
  margin:0 calc(var(--sv-pad) * -1) 13px;
  padding:var(--sv-pad) var(--sv-pad) 10px;
  background:#fff; border-bottom:1px solid var(--line);
}
.sv-modal .pm-head b { font-size:16.5px; }
.sv-modal:focus { outline:none; }   /* 프로그램적 포커스(열림 시)라 외곽선은 불필요 — 키보드 이동은 안쪽 컨트롤이 각자 표시한다 */
/* 관리자 미리보기 배너 — 「저장되지 않는다」가 문서를 안 읽어도 보여야 한다. 경고색(호박)에
   진한 글자로 대비를 확보한다(#7a4a00 on #fff4d6 = 6.83:1, 브라우저 실측). */
.sv-preview { margin:0 0 11px; padding:9px 12px; border-radius:10px; background:#fff4d6;
              border:1px solid #f0d089; color:#7a4a00; font-size:13px; font-weight:700; line-height:1.55; }
.sv-q { border:0; margin:0 0 17px; padding:0; min-width:0; }
.sv-q legend { font-size:15px; font-weight:700; color:var(--ink); line-height:1.5; margin:0 0 9px; padding:0; }
.sv-opt-tag { font-size:12.5px; font-weight:600; color:var(--mut); }
.sv-scale { display:flex; gap:6px; }
.sv-scale label { position:relative; flex:1 1 0; min-width:0; display:flex; align-items:center; justify-content:center;
                  min-height:54px; padding:5px 2px; cursor:pointer; border:1px solid var(--line); border-radius:10px;
                  background:#fff; font-size:17px; font-weight:700; color:var(--mut); }
.sv-scale label:hover { border-color:var(--pine); }
/* 입력은 화면에서 지우되 접근성 트리에는 남긴다(display:none 이면 키보드 포커스도 사라진다). */
.sv-scale input, .sv-opts input.sv-hide { position:absolute; opacity:0; width:1px; height:1px; pointer-events:none; }
.sv-scale label.on { background:var(--pine); border-color:var(--pine); color:#fff; }   /* 흰 글자 대비 8.9:1 */
.sv-scale label:focus-within { outline:2px solid var(--pine); outline-offset:2px; }
.sv-cap { display:flex; justify-content:space-between; font-size:12.5px; color:var(--mut); margin:6px 2px 0; }
.sv-opts { display:flex; flex-direction:column; gap:6px; }
.sv-opts label { position:relative; display:flex; align-items:center; gap:11px; min-height:50px; padding:9px 13px;
                 cursor:pointer; border:1px solid var(--line); border-radius:10px; background:#fff;
                 font-size:14.5px; line-height:1.45; color:var(--ink); word-break:keep-all; }
.sv-opts label:hover { border-color:var(--pine); }
.sv-opts label.on { border-color:var(--pine); background:#eef7e2; font-weight:600; }
.sv-opts label:focus-within { outline:2px solid var(--pine); outline-offset:2px; }
/* 복수선택이므로 «네모 체크박스»다(2026-08-26). 동그라미는 「하나만」을 뜻해서, 여러 개 고를 수
   있다는 사실을 legend 만으로 알리게 된다 — 모양이 먼저 말하게 한다.
   체크 표시는 아이콘 폰트가 아니라 CSS 로 그린다(타블러 서브셋을 건드리지 않으려는 것). */
/* ★테두리색 --line(#e2e8df, 흰 배경 대비 1.25:1) → --mut(#64715f, 5.16:1) — 주간심화점검 2026-08-27.
   위 .sv-scale/.sv-opts 의 input 숨김 규칙(opacity:0·1px)이 네이티브 체크박스를 지웠으므로
   «고를 수 있는 칸»이라는 단서는 이
   네모 하나뿐인데, 그 윤곽이 WCAG SC 1.4.11(비텍스트 대비 3:1) 에 한참 못 미쳤다.
   ㆍ감싼 label 의 1px --line 테두리는 그대로 둔다 — 저건 «상태 지시자»가 아니라 카드 구획이고,
     --line 1px 은 이 디자인 시스템 전반의 토큰이라 여기서 바꿀 것이 아니다(설문 고유 결함만 고친다).
   ㆍ선택된 상태(.on)는 아래에서 --pine 채움 + 흰 체크라 대비가 이미 충분하다. */
.sv-opts .sv-dot { flex:none; position:relative; width:21px; height:21px; border-radius:6px;
                   border:2px solid var(--mut); background:#fff; }
.sv-opts label.on .sv-dot { border-color:var(--pine); background:var(--pine); }
.sv-opts label.on .sv-dot::after { content:""; position:absolute; left:5px; top:1px; width:6px; height:11px;
                                   border:solid #fff; border-width:0 2.5px 2.5px 0; transform:rotate(45deg); }
.sv-etc, .sv-ta { width:100%; margin-top:8px; padding:11px 13px; border:1px solid var(--line); border-radius:10px;
                  font:inherit; font-size:16px; color:var(--ink); background:#fff; }
.sv-ta { height:92px; resize:vertical; margin-top:0; }
.sv-count { margin:5px 2px 0; font-size:12px; color:var(--mut); text-align:right; }
.sv-modal .btn-set { font-size:14.5px; min-height:46px; }
@media (max-width:640px) {
  /* 척도 숫자만 남기면 375px 에서도 5칸이 들어간다. 양끝 설명은 .sv-cap 이 이미 달고 있다. */
  .sv-scale { gap:5px; }
  /* .pm-modal 패딩이 14px 로 줄므로(위 .pm-modal 규칙) 고정 헤더의 상쇄값도 같이 줄인다. */
  .sv-modal { --sv-pad:14px; }
  .sv-q legend { font-size:14.5px; }        /* 데스크톱 15px 에서 «조금만» 줄인다 — 종전 12.5px 은 너무 작았다 */
  .sv-opts label { font-size:14px; }
  .sv-modal .pm-head b { font-size:16px; }
}

/* 만족도 조사 현황(관리자 설정 카드 안, 2026-08-26) — 폰에서 읽는 것이 주 용도라
   한 줄에 «라벨 : 값» 두 칸으로만 두고 값은 굵게. 표를 쓰면 375px 에서 접힌다. */
.svstat { font-size:13.5px; line-height:1.75; color:var(--mut); }
.svstat .r { display:flex; gap:10px; align-items:baseline; padding:3px 0; }
.svstat .k { flex:none; width:74px; color:var(--mut); font-weight:600; }
.svstat .v { flex:1; min-width:0; color:var(--ink); font-weight:700; word-break:keep-all; }
.svstat .sub { display:block; font-weight:500; color:var(--mut); font-size:12.5px; }
.svstat .warn { color:#7a4a00; }      /* 호박 — 흰 카드 위 6.83:1(브라우저 실측) */
.svstat .off { color:var(--mut); }
/* 자유서술 — 기본 접힘. 요약 줄이 곧 터치 타깃이라 44px 을 확보한다. */
.sv-texts { margin-top:12px; border:1px solid var(--line); border-radius:10px; background:var(--bg); }
.sv-texts > summary { min-height:44px; display:flex; align-items:center; gap:8px; padding:0 12px;
                      cursor:pointer; font-size:13px; font-weight:700; color:var(--pine); list-style:none; }
.sv-texts > summary::-webkit-details-marker { display:none; }
.sv-texts > summary::before { content:"▸"; font-size:11px; }
.sv-texts[open] > summary::before { content:"▾"; }
.sv-texts > summary:focus-visible { outline:2px solid var(--pine); outline-offset:-2px; border-radius:10px; }
.sv-textwarn { margin:0 12px 8px; font-size:12px; line-height:1.5; color:#7a4a00; }   /* 호박 6.83:1 */
.sv-text { margin:0 12px 10px; padding:9px 11px; background:#fff; border:1px solid var(--line); border-radius:8px; }
.sv-textwho { font-size:12px; font-weight:700; color:var(--mut); margin-bottom:3px; word-break:keep-all; }
.sv-textbody { margin:0; font-size:13.5px; line-height:1.6; color:var(--ink); white-space:pre-wrap; word-break:keep-all; }

/* 푸시 권유 배너(하단 고정, 1회) + 인앱 토스트 */
.push-banner { position:fixed; left:12px; right:12px; bottom:calc(14px + env(safe-area-inset-bottom)); z-index:125; max-width:520px; margin:0 auto;
               background:var(--pine); color:#fff; padding:11px 14px; border-radius:12px;
               box-shadow:0 8px 24px rgba(11,38,22,.35); display:flex; align-items:center; gap:10px; font-size:13px; }
.push-banner .pm-x { color:#cfe3d3; }
/* [점검 UIX-A-4] 한 줄 말줄임이라 «원인 + 조치»·«결과 + 전달 채널» 두 절 문구가 375px 폭에서 뒷부분이
   통째로 잘렸다(한글 약 23자 한계) — 줄바꿈 허용. overflow:hidden 은 둥근 모서리 클리핑용으로 유지하되,
   세로로 늘어나므로 더 이상 아무것도 자르지 않는다. radius 는 여러 줄에서 럭비공이 되는 999px → 14px.
   word-break:keep-all 로 한글 어절 중간 분리를 막고, 긴 URL·오류코드는 overflow-wrap 으로 흘린다.
   ★width:max-content 는 필수다 — left:50% 고정이라 shrink-to-fit 가용폭이 «화면의 오른쪽 절반»뿐이라,
     줄바꿈만 허용하면 375px 에서 폭이 335→188px 로 쪼그라들어 5줄짜리 기둥이 된다(실측). 단문은
     max-content 가 짧아 종전대로 shrink-wrap 된다. */
.portal-toast { position:fixed; left:50%; bottom:calc(22px + env(safe-area-inset-bottom)); transform:translate(-50%, 16px); z-index:140;
                background:#1d2b22; color:#fff; padding:11px 18px; border-radius:14px; font-size:13px; font-weight:600;
                box-shadow:0 8px 24px rgba(0,0,0,.3); opacity:0; transition:opacity .3s, transform .3s;
                width:max-content; max-width:min(480px, calc(100vw - 40px)); overflow:hidden;
                white-space:normal; word-break:keep-all; overflow-wrap:anywhere; line-height:1.45; }
.portal-toast.show { opacity:1; transform:translate(-50%, 0); }
.portal-toast.err { background:#b3271e; }   /* R-04 실패 피드백 */
/* PT-07 후속(2026-07-26): 액션(실행취소) 토스트 — 텍스트만 말줄임하고 버튼은 잘리지 않게 flex 로 분리.
   .has-act 가 붙었을 때만 적용되므로 기존 단문 토스트는 종전 그대로. */
.portal-toast.has-act { display:flex; align-items:center; gap:12px; white-space:normal; overflow:visible; padding-right:8px; }
/* [점검 UIX-A-4] .has-act 래퍼만 nowrap 을 풀고 본문(.pt-txt)은 여전히 잘리던 것 — 여기도 풀어 준다.
   ★flex:1 1 auto; min-width:0 은 유지할 것(없으면 flex 항목이 안 줄어 [실행취소] 버튼이 밀려난다). */
.portal-toast .pt-txt { flex:1 1 auto; min-width:0; white-space:normal; word-break:keep-all; overflow-wrap:anywhere; }
.portal-toast .pt-act { flex:none; font:inherit; font-weight:800; color:#0b2616; background:#e8f2e2; border:0;
                        border-radius:999px; padding:0 14px; min-height:32px; cursor:pointer; }
.portal-toast .pt-act:hover { background:#fff; }
.portal-toast .pt-act:focus-visible { outline:2px solid #fff; outline-offset:2px; }
.portal-toast.err .pt-act { color:#7c1912; background:#ffe3e0; }

/* 빈 상태 일러스트 — 은행잎 마스코트 v3(2026-07-24 확정: 가을 그린→골드 + USB-C 단자 = AX/DX·FMIS의 I). 원본 assets/brand/ginkgo-mark.svg. desk.html 에도 동일 정의 */
/* ★data-URI SVG → PNG 교체(디자인 마감 2026-07-28): ①브라우저 강제 다크모드가 SVG 색을 반전시켜
   눈(#0B2616)이 흰색이 되던 결함 차단(래스터는 반전 대상 아님) ②인라인 데이터 2.1KB 를 CSS 에서 제거.
   표시 크기 60px·opacity .9 는 종전 그대로. character-192.png = 동일 마스코트 원본(투명 배경, 6.9KB). */
.empty-art { width:60px; height:60px; margin:0 auto 8px; opacity:.9;
  background:url("/assets/brand/character-192.png") center/contain no-repeat; }

/* 모바일 — 쪽지 패널은 종 패널과 동일하게 화면 고정 폭 */
@media (max-width:640px) {
  .pm-modal { padding:14px; }
  .push-banner { left:8px; right:8px; }
}

/* ============================================================
   모바일 사용성 수리 (2026-07-26 · PT 배치) — 원 정의보다 뒤에 두어 동급 특이도를 소스순서로 이긴다.
   위쪽 정의(.notifpanel .nbtn 등)를 옮기면 무효화되므로 이 블록은 파일 말미 유지.
   ============================================================ */
/* [PT-08] 캠퍼스 맵 모바일 리스트 — 데스크톱에서는 존재하지 않는다(지도만 표시).
   ※ 기본값(display:none)은 아래 ≤640 노출 규칙보다 반드시 앞 — 순서를 바꾸면 모바일에서 사라진다. */
.mapswitch { display: none; margin-bottom: 10px; }
#mapList { display: none; }

/* [S3-1] #detail 도 같은 행 모양을 쓴다 — 건물 합침 핀을 누르면 상세 자리에 그 건물의 항목 목록이 뜬다.
   (지도 병합은 전 폭 공통이라 데스크톱에도 필요 — ≤640 블록 안에 두면 안 된다.) */
#mapList .mrow, #detail .mrow { display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  min-height: 48px; padding: 8px 12px; margin-bottom: 6px; border: 1px solid var(--line);
  border-radius: 10px; background: #fff; color: var(--ink); font-family: inherit; font-size: 13px; cursor: pointer; }
#mapList .mrow:last-child, #detail .mrow:last-child { margin-bottom: 0; }
/* margin-right:0 필수 — `.detail .tag{margin-right:8px}`(393행)이 헤더 배지용으로 걸려 있어, 그대로 두면
   행 안에서 gap 9px 위에 8px 이 더해져 리스트(#mapList)와 간격이 어긋난다. */
#mapList .mrow .tag, #detail .mrow .tag { flex: none; font-size: 11px; font-weight: 700; padding: 2px 9px; border-radius: 999px; margin-right: 0; }
#mapList .mrow .nm, #detail .mrow .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }

@media (max-width: 640px) {
  /* ★적대리뷰 P2-2: PT-05(.mchip 44px)로 쪽지 .nhead 가 49px, PT-07 로 .nfoot 가 58px 까지 자랐는데
     리스트 상한은 340px 고정이라 "헤더+리스트+푸터"가 패널 max-height(72vh)를 넘을 수 있었다
     (.notifpanel 은 overflow:hidden → 하단이 조용히 잘림). 상수를 재산정하는 대신 flex 로 전환해
     크롬 높이가 어떻게 변하든 리스트가 남은 공간에 자동으로 맞도록 한다 — 상수 드리프트 자체를 제거. */
  .notifpanel { display: flex; flex-direction: column; }
  .notifpanel .nhead, .notifpanel .nfoot { flex: none; }
  .notifpanel #notifList, .notifpanel #msgList { max-height: none; flex: 1 1 auto; min-height: 0; overflow: auto; }
  /* [PT-06] 알림 항목 [확인]/[열기] ≈21px — 벨 패널의 유일한 액션 */
  .notifpanel .nbtn { min-height: 40px; display: inline-flex; align-items: center; padding: 0 12px; }
  /* [PT-07] 무확인 일괄 처리 버튼([모두 읽음] 16px·[민원 외 모두 읽음] 24px) */
  .notifpanel .nsec-all { min-height: 40px; display: inline-flex; align-items: center; padding: 0 8px; }
  .notifpanel .nfoot-all { min-height: 40px; display: inline-flex; align-items: center; }
  /* [PT-07 후속] 일괄 읽음 [실행취소] — 되돌릴 기회가 6초뿐이라 터치 실패가 곧 유실이다 */
  .portal-toast .pt-act { min-height: 40px; padding: 0 16px; }
  /* [PT-31] 38px 이하 보조 버튼군 일괄 — 링크형 토글(.linkbtn2)·모달 닫기·건의 상태 셀렉트 */
  /* ⚠ margin:0 을 반드시 되돌린다. 기본 규칙의 -4px 이 그대로 남으면 상쇄할 패딩이 없어
     이 버튼을 품은 제목(.sec)이 8px 줄고 버튼 상자가 위아래로 4px 씩 삐져나온다. */
  .linkbtn2 { min-height: 44px; display: inline-flex; align-items: center; padding: 0 6px; margin: 0; }
  .btn-set { min-height: 44px; }
  .asst-x { width: 40px; height: 40px; }
  .wx-hourly-close { width: 40px; height: 40px; }
  .sgst-status { min-height: 40px; }
  /* [PT-08 → 2026-07-28 되돌림] 모바일 기본을 다시 "지도+핀"으로. 리스트는 [목록 보기] 토글 시에만.
     핀은 26→34px 유지(축소 지도에서 인접 핀을 손가락으로 분간하기 위한 최소치). */
  .mapswitch { display: inline-flex; }
  #mapList { display: none; }
  .mapbox.list-shown #mapList { display: block; }
  .mpin { width: 34px; height: 34px; }
  .mpin span { font-size: 13px; }
  .mpin .cnt { top: -9px; left: -9px; }
}
@media (max-width: 560px) {
  /* [PT-24] "ID 기억하기"/"자동 로그인" — 라이브 실측(7/26)에서 클릭 라벨은 이미 40px 로 확인돼
     세로 배치 전환은 철회하고 ①44px 충족 ②인접 오탭 방지 gap 확대 ③체크박스 20px 로 최소 수리.
     자동 로그인은 세션 지속성 변경이라 오탭 비용이 크다(가로 배치는 유지 — 데스크톱과 동형).
     인라인 style 의 gap·width·height 를 넘기기 위한 !important — ≤560px 한정이라 데스크톱 무영향. */
  .rememberrow { gap: 22px !important; flex-wrap: wrap; }
  .rememberrow label { min-height: 44px; }
  .rememberrow input[type="checkbox"] { width: 20px !important; height: 20px !important; }
  .consent-row { min-height: 44px; }
  .consent-row input { width: 20px; height: 20px; }
}
/* [PT-22] 폰 가로모드(667~932×375) — 폭 기준(≤640) 보정이 비활성이라 데스크톱 레이아웃이
   ※2026-08-27: 상단바 접기 임계가 560→760 으로 올라가 667~760 구간에서는 «상단바만» 모바일 배치가 된다.
     이 블록은 높이 기준이라 그와 교집합이 없고, 아래 .notifpanel 주석의 「≤640 이 적용되지 않는다」도 그대로 유효하다.
   375px 높이에 들어오며 도우미 닫기·알림 하단이 화면 밖으로 나갔다. 폭 규칙과 교집합 없는 높이 기준 블록. */
/* ★적대리뷰 P2-1: max-width 를 함께 걸어 데스크톱 낮은 창(예: 1440×450 분할화면)에서 오발하지 않게 한다 —
   이 블록의 대상은 "폰 가로모드"이지 "낮은 데스크톱 창"이 아니다. */
@media (max-height: 480px) and (max-width: 960px) {
  .asst-panel { max-height: calc(100vh - 108px); max-height: calc(100dvh - 108px); }
  .notifpanel { position: fixed; left: 8px; right: 8px; top: 8px; width: auto;
                max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px);
                display: flex; flex-direction: column; }
  /* 가로모드는 폭 667px 이라 위 ≤640 flex 블록이 적용되지 않는다 — 동일 모델을 여기서도 명시.
     상수(calc(100vh-112px))로는 크롬 101.8px+리스트가 패널을 넘겨 하단이 잘렸다. */
  .notifpanel .nhead, .notifpanel .nfoot { flex: none; }
  .notifpanel #notifList, .notifpanel #msgList { max-height: none; flex: 1 1 auto; min-height: 0; overflow: auto; }
  .pm-modal { max-height: calc(100vh - 32px); max-height: calc(100dvh - 32px); }
}
/* [PT-09] 푸시 배너(z125·높이≈80px·bottom 14~94)가 챗봇 FAB(z60·bottom 24~80)을 완전히 가려
   ✕ 를 누르기 전에는 도우미 진입 자체가 불가했다. 배너 표시 중에만 FAB·토스트를 위로 밀어 올린다.
   (app.js showPushBanner/dismissPushBanner/enablePortalPush 가 body.has-pushbanner 를 토글) */
body.has-pushbanner .asst-fab { bottom: calc(104px + env(safe-area-inset-bottom)); }
body.has-pushbanner .portal-toast { bottom: calc(104px + env(safe-area-inset-bottom)); }

/* ============================================================
   ★누름 피드백(:active) — 디자인 마감 2026-07-28
   터치 기기에는 hover 가 없어 "눌렀는데 아무 반응이 없다"가 전면적이었다(파일 내 :hover 36 : :active 4).
   평상시 모양은 무변경 — 손가락이 닿아 있는 동안에만 .97 로 눌린다. :disabled 는 제외(눌리면 안 되니까).
   ★transition 은 "선언이 없던 선택자"에만 새로 넣는다 — 이미 transition 을 가진 선택자(.tile·.asst-fab·
     .btn-google·.authtabs button)에 통째로 다시 쓰면 기존 background/box-shadow/border-color 전이가
     사라지는 회귀가 난다. .authtabs button 만 기존 목록에 transform 을 덧붙여 별도 선언.
   ★특이도 검산: 각 :active 규칙은 (0,2,0)~(1,2,0) 으로 해당 요소의 기본 정의를 항상 이기고, 같은 요소의
     :hover(동급 0,2,0)와는 이 블록이 파일 말미라 소스순서로 이긴다(.tile:hover 의 translateY, .asst-fab:hover
     의 translateY 를 누르는 동안 대체 — 손을 떼면 즉시 복귀). 인라인 style 에 transform 을 쓰는 요소 없음. */
.btn-set, .mchip, .iconbtn, .tbguide, .asst-chip, .asst-submit, .linkbtn2, .nt-att,
.notifpanel .nbtn, .notifpanel .nsec-all, .notifpanel .nfoot-all,
.portal-toast .pt-act, #mapList .mrow, #detail .mrow { transition: transform .1s; }
.authtabs button { transition: background .15s, color .15s, border-color .15s, transform .1s; }

.btn-set:not(:disabled):active, .mchip:active, .iconbtn:active, .tbguide:active,
.asst-chip:active, .asst-submit:not(:disabled):active, .linkbtn2:active, .nt-att:active,
.notifpanel .nbtn:active, .notifpanel .nsec-all:active, .notifpanel .nfoot-all:active,
.portal-toast .pt-act:active, .authtabs button:active, .tile:active, .btn-google:active,
#mapList .mrow:active, #detail .mrow:active { transform: scale(.97); }
.asst-fab:active { transform: scale(.94); }   /* 원형 FAB 는 .97 이 잘 안 보인다 */

/* ============================================================
   ★우하단 FAB(챗봇) 충돌 3종 해소 — 디자인 마감 2026-07-28
   .asst-fab 는 56px @ right16/bottom16(≤560, safe-area 포함) 로 화면 우하단을 점유한다. 같은 자리를
   ①[실행취소] 토스트 버튼 ②회원카드 [권한 삭제] 가 지나가며, 특히 ①은 되돌릴 기회가 6초뿐이라
   조준 실패가 곧 작업 유실이다(FAB 가 아래 레이어라 오조준 시 도우미만 열리고 6초가 지나간다).
   해결은 저장소 선례(body.has-pushbanner .asst-fab{bottom:104px})와 같은 방식 — 겹치는 쪽을 비켜세운다.
   ★JS(app.js)는 이번 범위 밖이라 body 클래스 추가가 불가능하므로, 이미 app.js 가 붙여 주는 .has-act 를
     그대로 훅으로 쓴다(액션 토스트에만 적용 → 일반 단문 토스트 위치는 종전 그대로).
   ★특이도 검산:
     · .portal-toast.has-act(0,2,0) > .portal-toast(0,1,0) 기본 bottom — 승리.
     · 단, body.has-pushbanner .portal-toast(0,2,1) 에는 소스순서와 무관하게 진다 → 조합 규칙을
       (0,3,1) 로 따로 둔다(푸시 배너 표시 중에는 FAB 도 104px 로 올라가 재충돌하기 때문).
     · #memberList .mtable td.mdel(1,2,1) 은 580행과 완전 동급이라 반드시 "뒤"에 와야 이긴다 — 이 블록이
       파일 말미인 이유. 위로 옮기면 무효가 된다.
   ============================================================ */
@media (max-width: 640px) {
  /* ① 실행취소 토스트를 FAB(상단 72~80px) 위로 — 일반 토스트는 무변경 */
  .portal-toast.has-act { bottom: calc(88px + env(safe-area-inset-bottom)); }
  /* ② 푸시 배너 동시 표시 시: 배너(~94px) → FAB(104~160px) → 토스트(176px~) 순으로 쌓는다 */
  body.has-pushbanner .portal-toast.has-act { bottom: calc(176px + env(safe-area-inset-bottom)); }
  /* ③ 회원카드 [권한 삭제] — 카드 우하단은 FAB 와 같은 열이라 조준이 겹친다. 모바일 카드에서만
        왼쪽(라벨 옆)으로 이동시켜 열 자체를 비운다. 데스크톱 표(>640px)는 무변경. */
  #memberList .mtable td.mdel { justify-content: flex-start; }
}

/* ★공지 행 — 모바일에서 메타가 제목보다 넓던 문제 (디자인 점검 2026-07-29)
   375px 화면의 공지 행(303px) 실측: 제목 .t = 143px(47%), 나머지 160px 이
   「7. 18. · [아바타20px] 장상민」(.d 88px) + 삭제 24px + 펼침 14px + gap 이었다.
   제목이 "시설통합포털 정식 오…" 로 잘리는데 정작 옆 공간은 메타가 쓰고 있었다.
   작성자(.nby = 아바타+이름)만 접는다 — 날짜·삭제·펼침은 유지. 데스크톱(>640px)은 무변경.
   작성자는 펼친 본문 끝의 서명(예: "— 관리팀 장상민")과 회원관리에서 확인 가능하므로 경로가 남는다. */
@media (max-width:640px) {
  .ntx .d .nby { display: none; }
}

/* ★가로 노치(safe-area) — index.html 은 viewport-fit=cover 인데 style.css 의 env() 9곳이 전부
   bottom·고정요소 전용이라 **좌우가 비어 있었다.** desk.html:284 와 같은 방식.
   세로에서는 inset 이 0 이라 실질 무변경이므로 landscape 로 한정한다.

   ⚠ **반드시 파일 맨 끝에 둔다.** 2026-07-31 최초 도입 때 파일 앞머리에 뒀다가 세 규칙이
   전부 무효였다 — 미디어쿼리는 특이도를 올리지 않으므로 (0,1,0) 동급이고, 뒤에 오는 **단축**
   `padding` 이 padding-left/right 를 통째로 되돌린다. 되돌리는 주범은 이 셋:
     `.hero { … padding: 0 20px }` · `.topbar-in { … padding: 14px 16px }` · `.wrap { … padding: 22px 16px 56px }`
   고쳤다고 커밋됐지만 화면은 변경 전과 같았다(경량점검 2026-07-31 에서 적발).

   ★각 max() 의 앞 숫자는 위 단축값의 **좌우 값과 같아야 한다**(.hero 20 · .topbar-in 16 · .wrap 16).
     노치가 없는 기기는 inset 이 0 이라 max() 가 그 숫자를 그대로 남기므로, 다르면 그 자체가 회귀다.
   ※행 번호로 적지 않는 이유: 처음 이 주석을 행 번호로 달았다가 같은 커밋에서 앞머리 줄 수가
     바뀌어 **전부 다른 규칙을 가리켰다**. 선택자와 값으로 찾을 것. */
@media (orientation: landscape) {
  .hero { padding-left: max(20px, env(safe-area-inset-left)); padding-right: max(20px, env(safe-area-inset-right)); }
  .topbar-in { padding-left: max(16px, env(safe-area-inset-left)); padding-right: max(16px, env(safe-area-inset-right)); }
  .wrap { padding-left: max(16px, env(safe-area-inset-left)); padding-right: max(16px, env(safe-area-inset-right)); }
}
/* ⚠ 위 블록이 `@media (max-width:640px)` 안의 `.topbar-in { padding: 10px 12px }` 까지 덮어
   좁은 가로화면 좌우가 12→16px 로 벌어진다. 그 화면의 기준값 12px 를 지키면서 노치만 피하도록
   여기서 되돌린다. 위 블록보다 **뒤**에 와야 이긴다(동급 특이도 + 소스 순서). */
@media (orientation: landscape) and (max-width: 640px) {
  .topbar-in { padding-left: max(12px, env(safe-area-inset-left)); padding-right: max(12px, env(safe-area-inset-right)); }
}
