/* ==========================================================================
   EDUPIC LMS 공용 스타일 — 디자인 토큰 + 공용 컴포넌트
   토큰 출처: EDUPIC LMS 시안 (메인/로그인/수강생/관리자/교강사 .dc.html)
   영역별 스타일은 web.css / member.css / learning.css / admin.css / tutor.css
   ========================================================================== */

/* ── 웹폰트 (자체 호스팅 — 규율서 §20.1 public/vendor) ──────────────────
   이전에는 jsdelivr·fonts.googleapis.com 을 직접 참조했다. 외부 CDN 은
   ① 우리가 통제할 수 없는 장애 지점이고(그쪽이 죽으면 전 화면 글꼴이 깨진다)
   ② 학습자 IP 가 외부로 나가며(개인정보)
   ③ 폐쇄망·사내망 배포에서 아예 로드되지 않는다.

   본문 폰트(Pretendard)는 pretendard.css 가 unicode-range 로 92개 서브셋을
   정의한다 — 브라우저가 실제 쓰는 글자 범위만 받으므로 한 페이지가 전부를
   내려받지 않는다. 통짜 2MB 파일 대신 이 방식을 쓰는 이유다.

   아이콘은 실제 사용 중인 76개만 서브셋했다(4MB → 9KB).
   ⚠️ 새 아이콘 이름을 쓰면 그 글리프는 없다. 아이콘 추가 시 서브셋을 다시 만들어야 한다. */
@import url('./pretendard.css');

@font-face {
  font-family: 'Material Symbols Outlined';
  font-weight: 400;
  font-style: normal;
  font-display: block;   /* 아이콘은 swap 하면 대체글자(리터럴 텍스트)가 잠깐 보인다 */
  src: url('../vendor/material-symbols/MaterialSymbolsOutlined.woff2') format('woff2');
}

:root {
  --ep-primary: #2857E0;        /* 브랜드 프라이머리 */
  --ep-primary-hover: #1E46C4;
  --ep-primary-soft: #E8EEFD;   /* 프라이머리 연한 배경 */
  --ep-primary-softer: #F1F5FE;
  --ep-primary-chip: #D7E3FC;
  --ep-navy: #10265E;           /* 딥 네이비 (헤더 강조·푸터) */
  --ep-navy-dark: #0B1B3F;
  --ep-bg: #F6F8FC;             /* 페이지 배경 */
  --ep-surface: #FFFFFF;
  --ep-border: #E5E9F2;
  --ep-border-soft: #F0F3FA;
  --ep-text: #191D28;
  --ep-text-sub: #3D4A5F;
  --ep-text-muted: #5A6274;
  /* 대비 실측으로 조정 (WCAG 4.5:1). 이전 #8A94AD 는 흰 배경 3.03:1 ·
     페이지 배경 2.85:1 로 보조 텍스트가 전부 기준 미달이었다.
     #6A7285 는 두 배경에서 4.82 / 4.53 — 가장 밝으면서 기준을 넘는 값이다
     (더 어둡게 하면 muted 와 구분이 죽는다). */
  --ep-text-faint: #6A7285;
  --ep-icon-faint: #B4BED4;
  /* NEW 뱃지가 이 색 배경 + 흰 글자인데 3.21:1 이었다.
     #0B7FA3 은 흰 글자 4.58:1. */
  --ep-cyan: #0B7FA3;           /* 보조 포인트(NEW·무료 미리보기) */
  --ep-cyan-soft: #E3F5FB;
  /* #E0900A 는 흰 배경 2.57:1 — '점검' 배지·0% 진도 글자가 기준 미달이었다.
     #9B6307 은 흰/페이지/amber-soft 세 배경 모두 4.5+ (5.02/4.72/4.57 실측). */
  --ep-amber: #9B6307;          /* 경고·대기 */
  --ep-amber-soft: #FEF3E2;
  /* #E0472F 는 흰 배경 3.86:1 — '잠금' 배지·위험 글자가 기준 미달이었고
     danger 버튼의 흰 글자도 4.11:1 이었다. #C13D28 은 흰/페이지/red-soft
     5.31/4.99/4.53, 버튼 흰 글자 5.31 — 한 값으로 양방향 모두 통과한다. */
  --ep-red: #C13D28;            /* 위험·실패 */
  --ep-red-soft: #FCE9E5;
  --ep-green: #1E7A46;          /* 성공·수료 */
  --ep-green-soft: #E4F1EC;
  --ep-star: #F1A417;
  --ep-radius-card: 16px;
  --ep-radius-panel: 20px;
  --ep-radius-input: 12px;
  --ep-shadow-card: 0 1px 3px rgba(16, 38, 94, .08);

  /* ── 타이포 스케일 (DESIGN_RULES §1 확장) ──────────────────────────────
     왜 필요한가: 색상 토큰만 있고 타이포 토큰이 없어서 화면마다 인라인으로
     font-size 를 박았다. 그 결과 **폰트 크기가 32종**으로 갈렸고,
     11.5/12.5/13.5 같은 0.5px 값이 69개 파일 134곳에 흩어져
     한 곳에서 통제할 방법이 없었다.

     기준은 실측이다 — 참조 사이트를 직접 열어 계산 스타일을 셌다:
       인프런    폰트 10종, 주력 14/16/12
       Coursera  폰트 12종, 주력 14/16/12, 간격은 4의 배수
     둘 다 0.5px 단위를 쓰지 않는다. 그래서 여기서도 정수만 둔다.

     각 토큰은 **현재 실사용량**에 근거한다. 안 쓰는 크기는 만들지 않는다. */
  --ep-text-2xs: 11px;    /* 캡션·타임스탬프·표 보조 (11 + 흡수: 10.5, 11.5) */
  --ep-text-xs:  12px;    /* 라벨·배지·도움말      (12 + 흡수: 12.5) */
  --ep-text-sm:  13px;    /* 조밀한 본문·표 셀     (13 + 흡수: 13.5) */
  --ep-text-md:  14px;    /* 기본 본문             (14 + 흡수: 15) */
  --ep-text-lg:  16px;    /* 강조 본문·소제목      (16 + 흡수: 17) */
  --ep-text-xl:  19px;    /* 패널 제목             (19 + 흡수: 18, 20) */
  --ep-text-2xl: 23px;    /* 페이지 제목           (23 + 흡수: 22, 24) */
  --ep-text-3xl: 27px;    /* 섹션 헤드라인         (27 + 흡수: 26) */
  --ep-text-4xl: 34px;    /* 서브 히어로 */
  --ep-text-5xl: 52px;    /* 히어로 — 여기만 크게 (본문과 같아지면 히어로가 죽는다) */

  /* ── 간격 스케일 — 4의 배수 (Coursera 실측 방식) ──────────────────────
     "적당히 10px, 여기는 18px" 를 없앤다. 리듬이 맞아야 통일감이 생긴다. */
  --ep-space-1:  4px;
  --ep-space-2:  8px;
  --ep-space-3: 12px;
  --ep-space-4: 16px;
  --ep-space-5: 20px;
  --ep-space-6: 24px;
  --ep-space-7: 28px;
  --ep-space-8: 32px;
  --ep-space-10: 40px;
  --ep-space-11: 44px;   /* 공개 섹션 사이 — DESIGN_RULES §6 */
  --ep-space-14: 56px;   /* 마지막 섹션 하단 — DESIGN_RULES §6 */

  /* 레이아웃 그리드 간격 — 모든 그리드가 이 하나를 쓴다.
     섹션마다 12·16·18px 을 섞어 쓰면 위아래 열 경계가 영영 안 맞는다. */
  --ep-gutter: 16px;

  /* ── 컨테이너 폭 — DESIGN_RULES §1 표의 값을 변수로 (하드코딩 금지) ── */
  --ep-container: 1160px;   /* 공개 본문 — 참조: 인프런 1200 / Coursera 1169 */
  --ep-prose: 860px;        /* 읽는 글(정책·안내) — 한 줄이 길면 다음 줄을 못 찾는다 */
  --ep-form: 560px;         /* 단일 컬럼 폼 */
  --ep-bo-form: 760px;      /* 백오피스 폼 전용(.bo-form) — 공개 폼보다 칸이 많아 조금 넓다 */
  --ep-auth: 440px;         /* 로그인·가입 */
  --ep-sheet: 720px;        /* 인쇄 시트(수료증) — A4 인쇄 가능폭 794px 안에 들어가야 잘리지 않는다 */
  /* 표가 든 모달 — 아래위 두 값 사이에서 고른 하나이지 임의 숫자가 아니다(2026-09-01).
     아래: 3열(차시명 240 + 항목명 240 + 영상ID 192 + 칸 여백 72)이 «겨우» 들어가는 744,
           차시 제목이 잘리지 않고 읽히려면 앞 두 열이 300씩은 돼야 해 864 + 모달 좌우 여백 52 = 916.
     위:   1024px 노트북에서 배경 여백(16×2)까지 들어가야 하므로 992.
     둘 사이에서 960. 이보다 넓히면 작은 노트북에서 모달이 화면을 넘는다. */
  --ep-modal-grid: 960px;
}

* { box-sizing: border-box; }

/* ⚠️ 이 저장소에서 **다섯 번** 같은 자리를 밟은 함정을 여기서 끝낸다(2026-08-28).

   브라우저 기본값은 `[hidden] { display: none }` 인데, 이건 «작성자 스타일» 이 아니라
   사용자 에이전트 스타일이라 우리가 쓴 `display: flex|grid|block` 이 **언제나 이긴다.**
   그래서 hidden 으로 껐다고 생각한 것이 그대로 보였다:
     · 탭 패널이 전부 펼쳐짐            (=[data-panel][hidden])
     · 히어로 배너 넷이 세로로 쌓임      (=.web-hero-slide[hidden])
     · 팝업이 항상 떠 있음              (=.web-popup[hidden])
     · 하단 알림 줄이 안 닫힘            (=.web-ticker[hidden])
     · 미리보기의 빈 이미지 상자가 남음   (=[data-preview-image][hidden])
   자리마다 한 줄씩 못박아 왔는데, 그건 **다음에 만드는 자리를 못 막는다.**
   여기서 한 번에 막으면 새 자리가 생겨도 같은 실수가 재발하지 않는다.

   숨겨 놓고 «보이게» 하려는 곳은 없다 — 그런 곳은 hidden 이 아니라 다른 표식을 써야 한다. */
[hidden] { display: none !important; }

html { height: 100%; }
body {
  margin: 0; min-height: 100%;
  background: var(--ep-bg);
  font-family: 'Pretendard Variable', Pretendard, -apple-system, 'Malgun Gothic', sans-serif;
  -webkit-font-smoothing: antialiased;
  color: var(--ep-text);
  word-break: keep-all;
  font-size: var(--ep-text-md); line-height: 1.6;
}
a { color: var(--ep-primary); text-decoration: none; }
a:hover { color: var(--ep-primary-hover); }
img { max-width: 100%; }
h1, h2, h3 { text-wrap: balance; }

/* Material Symbols 아이콘 */
.msi {
  font-family: 'Material Symbols Outlined';
  font-weight: normal; font-style: normal; line-height: 1;
  display: inline-block; white-space: nowrap; direction: ltr;
  -webkit-font-smoothing: antialiased;
  font-size: var(--ep-text-xl); vertical-align: -3px;
}

/* ── 버튼 ── */
.ep-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--ep-space-2);
  border: 0; border-radius: 999px; cursor: pointer;
  font: inherit; font-size: var(--ep-text-sm); font-weight: 700;
  padding: 11px 22px; white-space: nowrap; flex-shrink: 0;
  transition-property: background-color, color, border-color, scale;
  transition-duration: .15s;
}
.ep-btn:active { scale: 0.96; }
.ep-btn-primary { background: var(--ep-primary); color: #fff; }
.ep-btn-primary:hover { background: var(--ep-primary-hover); color: #fff; }
.ep-btn-navy { background: var(--ep-navy); color: #fff; }
.ep-btn-navy:hover { background: var(--ep-navy-dark); color: #fff; }
.ep-btn-outline { background: var(--ep-surface); color: var(--ep-text-sub); border: 1px solid #D9DFEC; }
.ep-btn-outline:hover { border-color: var(--ep-primary); color: var(--ep-primary); }
.ep-btn-danger { background: var(--ep-red); color: #fff; }
.ep-btn-danger:hover { background: #A63422; color: #fff; }
.ep-btn-ghost { background: transparent; color: var(--ep-text-muted); }
.ep-btn-ghost:hover { background: var(--ep-border-soft); }
/* 목록 안의 «지우기» — 채운 빨간 버튼은 상세 화면처럼 «그 한 번» 을 묻는 자리의 것이다.
   목록에 그대로 두면 스무 줄에 빨간 덩어리가 스무 개 늘어서 눈이 그것만 좇는다
   (2026-08-28 주인님 지적). 여기서는 색만 남기고 채움을 뺀다 — 위험은 여전히 읽히고
   확인창이 실제 방어를 맡는다. */
.ep-btn-ghost.is-danger { color: var(--ep-red); }
.ep-btn-ghost.is-danger:hover { background: var(--ep-red-soft); color: var(--ep-red); }
.ep-btn-sm { font-size: var(--ep-text-xs); padding: 8px 15px; min-height: 36px; }
.ep-btn-lg { font-size: var(--ep-text-md); font-weight: 800; padding: 14px 28px; }
.ep-btn-block { display: flex; width: 100%; }
.ep-btn[disabled] { opacity: .5; cursor: not-allowed; }

/* ── 카드·패널 ── */
/* 카드는 여백이 기본이다.
   예전에는 padding 이 아예 없어서, 안쪽 여백을 따로 안 준 카드는 글자가 테두리에
   1px 붙어 나왔다(홈 공지사항·문의 등록 폼·수강권 등록 폼 실측).
   같은 줄에 있는 .web-support-card 는 24px 을 갖고 있어 나란히 놓으면 어긋나 보였다.
   여백을 기본값으로 두면 새로 만드는 카드도 자동으로 맞는다 — 잊어서 깨지는 길을 없앤다. */
.ep-card {
  background: var(--ep-surface);
  border: 1px solid var(--ep-border);
  border-radius: var(--ep-radius-card);
  padding: var(--ep-space-6) 26px;   /* .ep-panel 과 같은 리듬 */
  min-width: 0;
  max-width: 100%;
}
/* 내용이 가장자리까지 차야 하는 카드만 되돌린다.
   표는 th/td 가 자기 여백(10~13px×12px)을 갖고, 가로 스크롤 영역도 카드 끝까지 써야 한다.
   .ep-empty·.my-course-card 는 같은 요소에 붙었을 때 뒤 정의가 자연히 이긴다. */
.ep-card.ep-table-wrap { padding: 0; }
/* 빈 상태는 반드시 카드와 **같은 요소**에 붙인다 — <div class="ep-card ep-empty">.
   자식으로 감싸면(.ep-card > .ep-empty) 카드 여백 24px 과 .ep-empty 의 56px 이 겹쳐
   한 줄짜리 안내가 240px 빈 상자가 된다(문의 상세 실측).
   :has() 로 CSS 에서 덮지 않는다 — 미지원 브라우저는 규칙을 통째로 버려서
   그 화면만 조용히 어긋난다(ResponsiveLayoutTest 가 :has 사용을 막는 이유). */
.ep-panel {
  background: var(--ep-surface);
  border: 1px solid var(--ep-border);
  border-radius: var(--ep-radius-panel);
  padding: 24px 26px;
  /* 좁은 화면에서 좌우 패딩(26px×2 + 테두리)이 컨테이너 폭을 넘겨 페이지를 밀었다
     (마이페이지 375px 에서 패널 폭 391px 실측). 상자 크기를 폭 안에 가둔다. */
  min-width: 0;
  max-width: 100%;
}
@media (max-width: 560px) {
  .ep-panel { padding: 18px 16px; border-radius: var(--ep-radius-card); }
}
.ep-panel-title { font-size: var(--ep-text-lg); font-weight: 800; margin: 0 0 14px; }

/* ── 배지 ── */
.ep-badge {
  display: inline-block; border-radius: 999px; padding: 4px 10px;
  font-size: var(--ep-text-2xs); font-weight: 800; letter-spacing: .3px; white-space: nowrap;
  line-height: 1;
}
.ep-badge-primary { background: var(--ep-primary-soft); color: var(--ep-primary); }
.ep-badge-navy { background: var(--ep-navy); color: #fff; }
/* cyan-soft 위 4.53:1 실측값 — 이전 #0E7FA3 은 4.08:1 로 살짝 미달 */
.ep-badge-cyan { background: var(--ep-cyan-soft); color: #0D7799; }
.ep-badge-amber { background: var(--ep-amber-soft); color: var(--ep-amber); }
.ep-badge-red { background: var(--ep-red-soft); color: var(--ep-red); }
.ep-badge-green { background: var(--ep-green-soft); color: var(--ep-green); }
.ep-badge-muted { background: var(--ep-border-soft); color: var(--ep-text-muted); }

/* ── 행·카드 상태 (목록에서 «섞인 상태» 를 눈으로 가른다) ──
   배지만으로는 목록을 훑을 때 안 읽힌다 — 작은 알약 하나라 행 전체가 똑같아 보인다.
   상태를 담은 컨테이너(.ep-panel 카드 / <tr> 표 행)에 함께 붙인다.

   ⚠️ 투명도(opacity)를 쓰지 않는다. 글자 대비가 같이 깎여 WCAG AA(4.5:1)가 무너진다.
      배경으로만 표현한다.

   ⚠️ **좌측 강조선(inset box-shadow)을 쓰지 않는다** (2026-09-01 주인님 지적).
      전에는 «색을 유일한 지표로 두지 않는다(적록색약)» 는 이유로 행 왼쪽에 3px 색 막대를
      그었다. 뜻은 맞았지만 그 모양이 **«AI 가 만든 화면»** 의 전형이라 우리 화면과 겉돌았다
      — 주인님이 전에도 같은 지적을 했다.

      색약 대비는 막대가 아니라 **배지의 글자**가 이미 하고 있다. 이 표현을 쓰는 화면
      여섯 곳(수강권·회원·환불·교강사·수업·수강생)을 전수로 확인했고, 전부 그 행 안에
      「정지」·「취소」·「실패」 같은 **글자 배지**를 함께 그린다(실측). 글자는 막대보다
      강한 지표다 — 색을 못 가려도 읽을 수 있다.
      그래서 행은 «옅은 배경» 으로만 말하고, 무엇인지는 배지가 말한다.
      («꺼짐» 은 원래 점선 테두리라는 형태 차이를 따로 갖는다.)

   의미는 배지 색 체계(DESIGN_RULES §5)를 그대로 확장한다.
     .is-off      숨김·비활성·종료·만료·초안   (muted)
     .is-danger   취소·반려·실패·정지          (red)
     .is-pending  대기·검토중·채점대기          (amber) */
.ep-state-off,
.ep-state-danger,
.ep-state-pending { position: relative; }

/* 꺼짐 — 배경을 눕히고 테두리를 점선으로. 형태만 봐도 «지금 안 도는 것» 이 읽힌다 */
.ep-state-off {
  background: var(--ep-border-soft);
  border-style: dashed;

  /* 배경이 흰색에서 연회색으로 내려오면 대비가 같이 깎인다 — faint(#6A7285)는
     흰 배경 4.7:1 로 통과하지만 이 배경 위에서는 4.34:1 로 AA 미달이다(실측).
     **토큰 자체를 여기서 덮어쓴다** — 셀렉터로 클래스를 하나씩 잡던 방식은
     var(--ep-text-faint)를 쓰는 클래스가 새로 생길 때마다 조용히 뚫렸다
     (.ep-c-faint·.ep-dl dt 를 막았더니 .my-course-meta 가 같은 값으로 미달, 실측).
     이제 이 안에서 faint 를 쓰는 모든 자손이 자동으로 muted(5.51:1)로 올라간다.
     흐리게 보이는 효과는 배경·점선이 이미 내고 있어 글자까지 흐릴 필요가 없다. */
  --ep-text-faint: var(--ep-text-muted);
}
.ep-state-off .ep-w-bold,
.ep-state-off .bo-cell-title { color: var(--ep-text-muted); }
/* ⚠️ 아래 둘은 색을 **직접** 박아 둔 곳이라 토큰 덮어쓰기가 닿지 않는다.
      .ep-dl dt 는 같은 특이도(0,1,1)로 뒤에 선언돼 있어 클래스를 하나 더 얹는다. */
.ep-state-off .ep-dl dt,
.ep-state-off .ep-c-faint { color: var(--ep-text-muted); }

/* 위험 — 아주 옅은 배경만. */
.ep-state-danger { background: var(--ep-red-soft); }

/* 대기 — 처리해야 할 것. 위험보다 한 단 약하게. */
.ep-state-pending { background: var(--ep-amber-soft); }

/* 표 행에 붙일 때 — <tr> 은 background 가 셀에 가려지므로 td 로 내린다.
   box-shadow inset 도 tr 에서는 안 그려져 첫 칸에만 준다.

   ⚠️ 점선은 **끈다**. 위 .ep-state-off 의 dashed 는 «카드» 를 전제로 만든 표현인데,
      표에 그대로 걸면 그 행 하나만 점선 상자로 둘러싸여 «편집 중이거나 임시로 끼워 넣은 줄»
      처럼 읽힌다 — 실제로는 그냥 기간이 지난 배너다(2026-08-28 주인님 지적).
      표에서 «지금 안 도는 줄» 은 배경을 눕히고 글자를 죽이는 것이 관습이고,
      그 둘은 바로 아래·위에서 이미 하고 있다. */
tr.ep-state-off,
tr.ep-state-off > td { border-style: none; }
tr.ep-state-off > td { background: var(--ep-border-soft); }
/* 행 구분선은 표 자신의 규칙(.ep-table td 하단선)이 계속 그리게 둔다 —
   위에서 끈 것은 «상자를 두르는 테두리» 뿐이다. */
tr.ep-state-off > td { border-bottom: 1px solid var(--ep-border-soft); }
tr.ep-state-danger > td { background: var(--ep-red-soft); }
tr.ep-state-pending > td { background: var(--ep-amber-soft); }

/* 상태 행 위의 «같은 색» 배지는 배경에 통째로 녹아 사라진다.
   무효·기한만료 행에서 상태 배지가 맨글자로 보였다 — 상태를 보여주려고 넣은 열인데
   정작 그 열만 배지가 아니게 된다. 흰 바탕 + 테두리로 형태를 되살린다(글자색은 그대로 AA).

   ⚠️ 2026-09-01 — 위험·대기 행에도 같은 일이 난다. 좌측 막대를 걷어내면서 그 두 행에도
      배경을 주었는데, 배지 색이 행 배경과 **같은 토큰**이다(red-soft / amber-soft).
      막대가 있을 때는 막대가 대신 눈에 띄어 가려져 있던 문제다. 세 경우를 함께 고친다. */
.ep-state-off .ep-badge-muted,
tr.ep-state-off > td .ep-badge-muted,
.ep-state-danger .ep-badge-red,
tr.ep-state-danger > td .ep-badge-red,
.ep-state-pending .ep-badge-amber,
tr.ep-state-pending > td .ep-badge-amber,
/* 꺼진 행 위의 «파랑» 배지도 같은 일을 겪는다 — 배지 배경(#E8EEFD)과 꺼진 행
   배경(#F0F3FA)의 차이가 눈에 안 보일 만큼 작다(실측: 배너 목록의 «기간외»).
   같은 색끼리 겹치는 자리를 하나씩 찾을 게 아니라, 이 규칙이 세 상태 × 그 색을 덮는다. */
.ep-state-off .ep-badge-primary,
tr.ep-state-off > td .ep-badge-primary {
  background: var(--ep-surface);
  box-shadow: inset 0 0 0 1px currentColor;
}
/* 꺼짐만 테두리를 회색 선으로 — 글자색(muted)을 그대로 두르면 상태 배지치고 진하다 */
.ep-state-off .ep-badge-muted,
tr.ep-state-off > td .ep-badge-muted { box-shadow: inset 0 0 0 1px var(--ep-border); }

/* ── 고를 목록을 좁히는 검색줄 ──
   긴 목록을 다는 select 는 «찾기» 가 필요한데, 검색칸을 폼 밖에 따로 두면
   «이 페이지를 검색하는 칸» 으로 읽힌다. 고를 목록 바로 위에 붙여 자리로 뜻을 말한다.
   아래 select 와 붙어 보이도록 간격을 좁히고 모서리를 맞춘다. */
.ep-picker-search { display: flex; align-items: center; gap: var(--ep-space-2); margin-bottom: 8px; }
.ep-picker-search .ep-input { min-width: 0; }

/* ── 폼 ── */
.ep-field { margin-bottom: 16px; }
.ep-label { display: block; font-size: var(--ep-text-xs); font-weight: 700; color: var(--ep-text-sub); margin-bottom: 8px; }
.ep-input, .ep-select, .ep-textarea {
  width: 100%; font: inherit; font-size: var(--ep-text-sm); color: var(--ep-text);
  border: 1px solid var(--ep-border); border-radius: var(--ep-radius-input);
  padding: 12px 15px; background: var(--ep-surface);
  transition-property: border-color, box-shadow; transition-duration: .15s;
}
.ep-input:focus, .ep-select:focus, .ep-textarea:focus {
  outline: none; border-color: var(--ep-primary);
  box-shadow: 0 0 0 3px rgba(40, 87, 224, .12);
}
.ep-input::placeholder, .ep-textarea::placeholder { color: var(--ep-text-faint); }
/* 못 고치는 칸은 «못 고치게 생겨야» 한다.
   브라우저는 disabled 만 흐리게 그리고 readonly 는 일반 칸과 똑같이 그린다 —
   구조가 잠긴 강좌에서 영상 ID·길이·합격점이 평소처럼 보이고, 눌러서 타이핑해 봐야
   안 된다는 걸 알았다(2026-09-01 실측). 값은 읽어야 하므로 흐리게가 아니라 «면» 으로 말한다.
   ⚠️ select 는 readonly 가 없어 disabled 를 쓴다 — 두 상태를 같은 모양으로 묶는다. */
.ep-input[readonly], .ep-textarea[readonly],
.ep-input:disabled, .ep-select:disabled, .ep-textarea:disabled {
  background: var(--ep-border-soft); border-color: var(--ep-border);
  color: var(--ep-text-muted); cursor: not-allowed;
}
.ep-input[readonly]:focus, .ep-textarea[readonly]:focus { border-color: var(--ep-border); box-shadow: none; }
.ep-textarea { resize: vertical; min-height: 110px; }
.ep-help { font-size: var(--ep-text-2xs); color: var(--ep-text-faint); margin-top: 6px; }
.ep-error { font-size: var(--ep-text-xs); color: var(--ep-red); margin-top: 6px; font-weight: 600; }
/* 라벨 전체가 클릭 영역이다. 글자 높이(23px)만으로는 터치 기준(40px)에 못 미쳐
   손가락으로 누르기 어렵다 — 세로 여백으로 영역을 확보한다(시각 변화는 최소). */
.ep-check { display: flex; align-items: center; gap: var(--ep-space-2); font-size: var(--ep-text-xs);
  color: var(--ep-text-sub); cursor: pointer; min-height: 40px; }
.ep-check input { width: 17px; height: 17px; accent-color: var(--ep-primary); flex-shrink: 0; }

/* ── 알림 박스 ── */
.ep-alert {
  border-radius: var(--ep-radius-input); padding: 13px 16px;
  font-size: var(--ep-text-sm); font-weight: 600; margin-bottom: 18px;
}
.ep-alert-success { background: var(--ep-green-soft); color: var(--ep-green); }
.ep-alert-error { background: var(--ep-red-soft); color: var(--ep-red); }
.ep-alert-info { background: var(--ep-primary-soft); color: var(--ep-primary); }
.ep-alert-warn { background: var(--ep-amber-soft); color: var(--ep-amber); }

/* ── 테이블 ── */
.ep-table { width: 100%; border-collapse: collapse; font-size: var(--ep-text-sm); }
.ep-table th {
  text-align: left; font-size: var(--ep-text-2xs); font-weight: 700; color: var(--ep-text-faint);
  padding: 10px 12px; border-bottom: 1px solid var(--ep-border);
  white-space: nowrap; text-transform: uppercase; letter-spacing: .3px;
}
.ep-table td { padding: 13px 12px; border-bottom: 1px solid var(--ep-border-soft); vertical-align: middle; }
.ep-table tr:last-child td { border-bottom: 0; }
.ep-table tbody tr:hover { background: #FAFBFE; }
/* 숫자 칸은 한 덩어리다 — 「6 / 200」이 「6 /」와 「200」으로 갈라지면 값이 둘로 보인다.
   좁은 화면에서 실측된 결함(개설 수업 목록 802px, 정원 칸 67px). 표를 넓히지는 않는다 —
   못 들어가면 .ep-table-wrap 이 스스로 가로로 구른다. */
.ep-table .num { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* 표를 감싸는 상자는 스스로 가로 스크롤을 갖고, **자신은 0까지 줄어든다**.
   overflow-x 만 주면 상자가 표의 최소폭을 그대로 받아 부모를 민다 —
   flex/grid 안에서는 min-width:auto 가 기본이라 그렇다(문의 목록 375px 에서 +6px 실측).

   ⚠️ 한때 `.ep-card:has(> .ep-table)` 로 규약을 안 지킨 뷰까지 CSS 에서 덮었는데,
      (가) 셀렉터 리스트에 :has() 가 섞이면 미지원 UA 가 **규칙 전체**를 버리고
      (나) 함께 넣은 `min-width: max-content` 가 데스크탑에서도 표를 안 접어
      34개 뷰에 가로 스크롤을 만들었다.
      마크업을 DESIGN_RULES §46(`.ep-card` + `.ep-table-wrap` + `.ep-table`)로 통일해
      우회를 걷어낸다 — 규약 위반은 CSS 가 아니라 마크업에서 고친다. */
.ep-table-wrap { overflow-x: auto; min-width: 0; max-width: 100%; }

/* ── 진행바 ── */
.ep-progress { height: 8px; border-radius: 99px; background: var(--ep-border-soft); overflow: hidden; }
.ep-progress > i { display: block; height: 100%; border-radius: 99px; background: var(--ep-primary); transition: width .3s; }
.ep-progress.is-done > i { background: var(--ep-green); }

/* ── 페이지네이션 ──
   Laravel 기본은 Tailwind 클래스 전제다. 이 프로젝트는 별도 CSS를 쓰므로 전용 Blade 뷰와
   이 클래스만 사용한다. 화면마다 `links()` 호출은 그대로 두고 모양·키보드 포커스만 한 곳에서 통일한다. */
.ep-pagination { display: flex; justify-content: center; margin-top: 24px; max-width: 100%; overflow-x: auto; padding: 2px; }
.ep-pager { display: flex; align-items: center; justify-content: center; gap: var(--ep-space-2); min-width: max-content; }
.ep-page-link, .ep-page-ellipsis {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; height: 40px; padding: 0 10px; border-radius: var(--ep-radius-input);
  font-size: var(--ep-text-xs); font-weight: 700; font-variant-numeric: tabular-nums;
}
.ep-page-link { color: var(--ep-text-muted); border: 1px solid var(--ep-border); background: var(--ep-surface); }
a.ep-page-link:hover { color: var(--ep-primary); border-color: var(--ep-primary); background: var(--ep-primary-softer); }
.ep-page-link.is-current { background: var(--ep-primary); border-color: var(--ep-primary); color: #fff; }
.ep-page-link.is-disabled { color: var(--ep-text-faint); background: var(--ep-bg); cursor: not-allowed; }
.ep-page-ellipsis { min-width: 20px; padding: 0; color: var(--ep-text-faint); }
.ep-page-link .msi { font-size: var(--ep-text-lg); }
@media (max-width: 640px) {
  .ep-pagination { justify-content: flex-start; }
  .ep-pager { gap: var(--ep-space-1); }
  .ep-page-link { min-width: 36px; height: 36px; padding: 0 8px; }
}

/* ── 빈 상태 ── */
.ep-empty { text-align: center; padding: 56px 20px; color: var(--ep-text-faint); }
.ep-empty .msi { font-size: 44px; color: var(--ep-icon-faint); display: block; margin: 0 auto 12px; }
.ep-empty-title { font-size: var(--ep-text-md); font-weight: 700; color: var(--ep-text-muted); margin-bottom: 4px; }

/* ── 목록 행 — 패널 안에서 줄로 쌓이는 항목 ─────────────────────────────
   같은 조합(padding:8px 0 + 밑줄)을 26곳이 인라인으로 다시 적고 있었다.
   값이 아니라 역할에 이름을 준다 — "표까지는 아닌 목록의 한 줄".
   기본 8px, 넉넉한 행은 is-roomy(12px). */
.ep-list-row { padding: var(--ep-space-2) 0; border-bottom: 1px solid var(--ep-border-soft); }
.ep-list-row.is-roomy { padding: var(--ep-space-3) 0; }

/* ── 소형 컨트롤 — 툴바·행 안에 끼는 input/select ────────────────────────
   기본 컨트롤(12px 15px)은 표의 행 높이를 밀어낸다. 같은 목적의 인라인
   (width:auto;padding:8px 12px)이 31곳이었다. 폭은 내용대로, 패딩만 줄인다. */
.ep-input.is-compact, .ep-select.is-compact { width: auto; padding: 8px 12px; }
/* 폭이 내용에 맞춰 줄어드는 셀렉트는 화살표가 글자 위에 겹친다.
   화살표는 오른쪽에서 13px 자리(폭 12px)를 쓰므로 그만큼 오른쪽 여백을 더 준다.
   width:100% 일 때는 남는 폭이 있어 드러나지 않던 결함이다. */
.ep-select.is-compact { padding-right: 34px; }

/* 읽기 전용으로 보여주는 값 — 입력칸과 같은 자리에 놓되 만질 수 없음이 보여야 한다.
   (수정 화면에서 "바꿀 수 없는 항목"을 빈칸처럼 두면 왜 안 써지는지 알 수 없다) */
.ep-input.is-readonly {
  background: var(--ep-bg); color: var(--ep-text-muted);
  display: flex; align-items: center;
}

/* 본문을 쓰는 칸 — rows 만으로는 짧아서 긴 글을 쓸 때 계속 스크롤해야 한다.
   같은 목적의 인라인(resize:vertical)이 10곳이었다. */
.ep-textarea-tall { min-height: 220px; resize: vertical; }
/* HTML 코드를 붙여넣는 칸 — 태그가 섞인 글은 고정폭이라야 여닫는 짝이 눈에 들어온다.
   줄바꿈을 접지 않아야 강사가 준 들여쓰기가 그대로 보인다(2026-08-28 강좌 상세 설명). */
.ep-code-area {
  font-family: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;
  font-size: var(--ep-text-xs); line-height: 1.6; white-space: pre; overflow-wrap: normal; overflow-x: auto;
}
.ep-input[rows], .ep-textarea { resize: vertical; }

/* ── 유틸 ── */
.ep-flex { display: flex; align-items: center; gap: var(--ep-space-2); }
/* 버튼끼리 붙는 자리 — 기본 gap(8px)은 표의 관리 열에서 너무 벌어진다. 인라인 5곳을 흡수한다 */
.ep-flex.ep-gap-xs { gap: var(--ep-space-1); }
.ep-between { display: flex; align-items: center; justify-content: space-between; gap: var(--ep-space-3); }

/* flex 아이템의 기본값은 min-width:auto 라 내용보다 작아지지 않는다.
   그래서 입력이 둘만 들어가도 컨테이너를 밀어낸다 — 화면 폭과 무관하게,
   컨테이너가 좁으면 생긴다. 실측: 관리자 수강권의 340px 사이드 패널 안에서
   flex:1 자식 둘이 각 181px 을 고집해 286px 컨테이너를 84px 넘겼다(뷰포트는 1280px).
   min-width:0 은 "줄어들어도 된다"는 허가라 항상 켜 둔다 — 안쪽 요소는
   .ep-table-wrap 의 overflow-x 나 말줄임으로 각자 넘침을 처리한다. */
.ep-flex > *, .ep-between > * { min-width: 0; }
/* 다만 누르는 라벨은 줄어들면 안 된다.
   위 규칙이 .ep-check 까지 0 으로 만들어 라벨 폭이 14px 로 눌렸다
   (관리자 수업상세 '상단 고정' 실측) — 그러면 누를 곳 자체가 사라진다.
   min-width:0 의 취지는 "넘치느니 줄어들어라"인데, 클릭 대상에는 해당하지 않는다.
   .ep-check 정의보다 뒤에 둬야 순서로 이긴다. */
.ep-check { min-width: 120px; }
.ep-flex .ep-input, .ep-flex .ep-select,
.ep-between .ep-input, .ep-between .ep-select { max-width: 100%; }

/* 좁은 화면에서는 한 줄 배치를 아예 접는다.
   줄바꿈까지 상시로 켜면 넓은 화면의 툴바가 의도치 않게 두 줄이 되므로 여기만 둔다. */
@media (max-width: 860px) {
  .ep-flex, .ep-between { flex-wrap: wrap; }
}
/* 재생시간 라벨 — 커리큘럼/학습항목 소요시간 공통(인라인 font-size 대체) */
.ep-dur { font-size: var(--ep-text-2xs); color: var(--ep-text-faint); font-variant-numeric: tabular-nums; }

/* ── 표 열 폭 ────────────────────────────────────────────────────────────
   열 폭은 "그 열이 무엇을 담는가"로 정한다. 화면마다 눈대중으로 정하니
   같은 것을 담는 열의 폭이 갈렸다 — 실측: "상태" 한 열에 80/90/100/110px,
   같은 YYYY-MM-DD 날짜에 110/120px, 버튼이 든 "관리" 열에 140/170/200px.
   여기 없는 성격의 열이면 클래스를 늘리기 전에 기존 것에 맞출 수 있는지 먼저 보라. */
.ep-th-check  { width:  40px; }   /* 다중선택 체크박스 한 칸 */
.ep-th-thumb  { width:  72px; }   /* 목록의 작은 미리보기 그림 */
.ep-th-tiny   { width:  80px; }   /* 유형·말머리·조회 등 짧은 코드 */
.ep-th-badge  { width:  96px; }   /* 배지 하나 */
.ep-th-date   { width: 112px; }   /* 날짜·일시 */
.ep-th-name   { width: 144px; }   /* 사람·대상 이름 */
.ep-th-action { width: 160px; }   /* 버튼이 드는 열 */
.ep-th-wide   { width: 176px; }   /* 긴 식별자·진행률 */

/* 목록 안의 작은 미리보기 그림 — «무엇인지 알아보는» 용도다.
   크게 두면 표가 다시 카드가 되어 한 화면에 몇 줄 못 담는다(배너 목록에서 겪었다).
   비율이 제각각인 그림이 들어오므로 상자를 고정하고 cover 로 채운다. */
.ep-thumb {
  width: 56px; height: 36px; object-fit: cover; display: block;
  border-radius: 6px;
  /* 흰 배경 그림이 흰 표와 붙어 경계가 사라지는 것을 막는다.
     순수 검정 저투명도 — 색이 섞인 회색은 사진 가장자리에서 때처럼 보인다. */
  outline: 1px solid rgba(0, 0, 0, .1); outline-offset: -1px;
}
/* 그림이 없는 행도 같은 자리를 차지해야 줄이 어긋나지 않는다 */
.ep-thumb.is-empty {
  display: flex; align-items: center; justify-content: center;
  background: var(--ep-bg); color: var(--ep-text-faint); outline-color: var(--ep-border-soft);
  font-size: var(--ep-text-2xs);
}

/* 한 줄로 자르고 말줄임 — 표 안의 보조 문구가 칸을 밀어내지 않게 */
.ep-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 자주 붙어 다니는 조합 — "작은 회색 보조문구" 는 전 영역에 반복된다 */
.ep-caption { font-size: var(--ep-text-2xs); color: var(--ep-text-faint); }
.ep-meta    { font-size: var(--ep-text-xs);  color: var(--ep-text-muted); }
.ep-strong  { font-weight: 700; color: var(--ep-text); }

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0s !important; animation: none !important; }
}

/* ==========================================================================
   폴리싱 — 키보드 접근성·폼 컨트롤·아이콘 안정화 (프론트 완성 단계)
   ========================================================================== */
/* 키보드 포커스 링 — 마우스 클릭에는 안 뜨고 Tab 이동에만 (접근성) */
:focus-visible {
  outline: 2px solid var(--ep-primary);
  outline-offset: 2px;
  border-radius: 6px;
}
.ep-btn:focus-visible { outline-offset: 3px; }

/* select 커스텀 화살표 — 브라우저 기본 화살표 대체 */
.ep-select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%238A94AD' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  padding-right: 34px;
}

/* ── 필터 바 ──
   관리자 전용(admin.css)이었는데 회원 화면도 쓰게 돼 여기로 옮겼다.
   두 영역 이상이 쓰는 클래스는 common.css 에 둔다 — 안 그러면 그 CSS 를 안 읽는
   화면에서 스타일 없이 떨어진다. */
/* ⚠️ 세로 가운데 정렬을 유지한다. 이 줄의 라벨은 **칸 위가 아니라 옆**에 선다
   (교강사 채점 화면이 쓰는 관례 — `<label class="ep-label ep-m0">` + select 형제).
   라벨을 위에 쌓는 .ep-field 를 여기 넣으면 그 칸만 라벨 높이만큼 내려앉아 줄이 어긋난다
   (2026-09-02 실측 — 정렬 칸을 그 모양으로 넣었다가 바로 드러났다). 폼 화면과 규칙이 다르다. */
.bo-filters { display: flex; align-items: center; gap: var(--ep-space-3); flex-wrap: wrap; margin-bottom: 16px; }
.bo-filters.is-flush { margin-bottom: 0; }
.bo-filters .ep-input, .bo-filters .ep-select { width: auto; padding: 9px 13px; font-size: var(--ep-text-xs); }
/* ⚠️ select 에 padding 단축속성을 쓰면 .ep-select 의 padding-right(34px)가 통째로 덮인다.
      화살표는 배경이미지로 «오른쪽 13px 자리» 에 그려지므로, 오른쪽 여백을 되돌리지 않으면
      화살표가 글자 위에 겹쳐 찍힌다 — 실제로 8개 화면 10개 select 가 그 상태였다(2026-08-19).
      위쪽 .ep-select.is-compact 는 같은 이유로 padding-right 를 되살려 둔다.
   그리고 width:auto 는 가장 긴 선택지에 맞춰 늘어난다 — 긴 수업명이 들어가면
      좁은 화면에서 카드를 뚫고 나가 페이지 전체가 가로로 밀린다. 상한을 둔다. */
.bo-filters .ep-select { padding-right: 30px; max-width: 100%; }
.bo-filters .ep-input { max-width: 100%; }

/* 목록 검색칸 — 「무엇으로 찾는지」 를 적어 두고 그만큼 못 보여주면 안내가 거짓이 된다.
   ⚠️ 바로 위 `.bo-filters .ep-input { width:auto }` 가 .ep-input 의 폭을 브라우저
      기본(약 20자)으로 되돌린다. 그래서 `is-w-lg`(240) 를 붙여도 안 먹었고,
      「이름·아이디·이메일 검색」 이 186px 에 들어가 있었다 — 한글 열두 자.
      13개 목록 화면 전부가 그 상태였다(2026-08-26 브라우저 실측).
   화면마다 is-compact / is-w-md(192) / is-w-lg(240) 로 갈려 있던 세 종류를 여기로 모은다.
   컨테이너(.bo-filters · .ep-flex)를 가리지 않게 «클래스 하나» 로 정한다 —
   자리마다 규칙이 갈리면 새 화면에서 또 어긋난다.
   flex-basis 가 주축 크기를 정하므로 위의 width:auto 와 싸우지 않는다.

   ⚠️ 다만 선택자는 `.ep-input.ep-search` 로 «둘» 을 붙여야 한다. 클래스 하나로 쓰면
      바로 위 `.bo-filters .ep-input { max-width:100% }` 이 특이도로 이겨 상한이 풀린다 —
      검색칸이 줄 전체를 차지했다(실측 906px). 검색 하나가 툴바를 독차지하면 옆 필터가 밀려난다. */
.ep-input.ep-search { flex: 1 1 300px; min-width: 240px; max-width: 460px; }

/* 폼 한 줄 — 제목·상태·유형별 칸·저장이 한 줄에 선다.
   좁은 칼럼에서 flex 는 «늘어나는 칸» 부터 줄인다. 강좌 상세(613px 칼럼)의 중첩된 폼에서
   「학습항목 제목」 이 66px 까지 눌렸다 — 한글 네 자라 무엇을 적는 칸인지도 안 보인다
   (2026-08-26 브라우저 실측).

   ⚠️ 「최소 폭」 과 「줄 접힘」 은 항상 짝이다 — 최소 폭만 주고 안 접으면 그 폭이
      컨테이너를 뚫어 화면이 옆으로 밀린다. 그래서 **클래스 하나가 둘을 다 한다.**
      («문장칸» 과 «접히는 줄» 을 따로 두면 언젠가 한쪽을 빠뜨린다.)
      :has() 로 안전망을 두는 길도 있지만 이 저장소는 :has() 를 금한다 —
      미지원 UA 가 규칙 전체를 버리기 때문이다(ResponsiveLayoutTest). 규약은 마크업에서 지킨다.

   폭을 스스로 정한 칸(is-w-*)·숫자·파일은 건드리지 않는다 — 짧아도 되는 칸들이다. */
.ep-flex.ep-form-row { flex-wrap: wrap; row-gap: var(--ep-space-2); }
.ep-flex.ep-form-row > .ep-input:not([class*="is-w-"]):not([type="number"]):not([type="file"]),
.ep-flex.ep-form-row > .ep-textarea:not([class*="is-w-"]) { flex: 1 1 240px; min-width: 200px; }
/* 라벨을 단 칸(.ep-field)은 입력칸이 한 겹 안으로 들어가 위 규칙의 직계 자식이 아니게 된다 —
   그러면 최소 폭이 사라져 «항목명» 이 한글 네 자로 눌린다(2026-09-01 실측, 라벨 도입 직후).
   늘어나기로 표시한 칸에만 같은 최소 폭·줄 접힘을 준다. */
.ep-flex.ep-form-row > .ep-field.ep-grow { flex: 1 1 240px; min-width: 200px; }
/* 여러 줄 상자는 줄을 통째로 쓴다.
   .ep-textarea 의 min-height 가 110px 이라 한 줄 칸(46px) 옆에 서면 그 줄만 두 배로 높아지고,
   부모의 align-items:flex-end 때문에 라벨이 위로 떠 다른 칸의 라벨과 어긋난다(2026-09-01 실측). */
.ep-flex.ep-form-row > .ep-field.is-row-full { flex: 1 0 100%; min-width: 0; }

/* 아이콘 폰트 로딩 전 리거처 텍스트 노출(FOUT) 최소화
   ⚠️ .msi 자체에 배경·고정폭을 줘 «상자» 로 쓰는 곳에서는 이 max-width 를 반드시 풀어야 한다
      (예: admin.css 의 .bo-stat .icon — 안 풀면 38px 원이 30px 로 찌그러진다). */
.msi {
  font-feature-settings: 'liga';
  overflow: hidden;
  max-width: 1.6em;
}

/* 작은 버튼의 터치 히트영역 (DESIGN_RULES §8 — 최소 40).
 *
 * ⚠️ 2026-08-26 — `::after` 로 위아래 6px 씩 «넓히던» 방식을 버리고 **실제 높이**를 올렸다.
 *
 * 왜 바꿨나: 넓힌 6px+6px = 12px 가 줄 간격(8px)보다 커서, `flex-wrap` 컨테이너에서
 * 줄바꿈이 일어나면 **윗줄과 아랫줄의 누르는 면이 4px 겹쳤다.** 겹친 자리에서는
 * DOM 순서상 뒤인 아랫줄이 이기므로, 윗줄 버튼을 누른 사람이 아랫줄 버튼을 눌렀다.
 * 브라우저로 확인했다 — 강좌 상세에서 「삭제」 아래를 누르니 「관리」가 잡혔다.
 * **삭제 옆에서 이런 일이 나면 누가 무엇을 눌렀는지 아무도 확신할 수 없다.**
 *
 * 흉내(::after)는 «보이는 것보다 큰 척» 이라 이웃과 부딪친다. 실제로 그 크기가 되면
 * 레이아웃이 자리를 계산해 주므로 겹칠 것 자체가 없다.
 * 덤으로 예전 주석이 걱정하던 «의사요소가 문서를 넓혀 가로 스크롤» 문제도 사라진다. */
.ep-btn-sm { min-height: 40px; }

/* 나머지 «간발의 차로 모자란» 조작 요소 (DESIGN_RULES §8, 최소 40×40).
   2026-08-26 전 화면 실측: 칩 37px · 필터 입력/선택 39px — 규칙에서 1~3px 부족했다.
   한 곳만 고치면 다음 화면에서 또 나오므로 여기 한 곳에 묶는다.

   ⚠️ 처방이 요소마다 다르다:
     · 칩은 <a> 라 위 버튼과 같은 ::after 확장이 먹는다(세로로만 — 좌우로 뻗으면
       오른쪽 끝 요소가 문서를 넓혀 좁은 화면에 가로 스크롤이 생긴다, 실측).
     · **<input>·<select> 는 치환 요소라 ::before/::after 를 아예 그리지 않는다.**
       그쪽은 실제 높이를 40 으로 올린다 — 확장 흉내를 내면 «했는데 안 되는» 상태가 된다. */
/* ⚠️ 칩은 ::after 로 «흉내» 내지 않고 **실제 높이**를 40 으로 올린다.
   왜 바꿨나(2026-08-26 실측): 칩은 `flex-wrap: wrap` 컨테이너에 들어가고 줄 간격은 8px 이다.
   위아래 6px 씩 넓히면 확장분 12px 이 그 8px 을 넘겨 **윗줄과 아랫줄의 누르는 면이 4px 겹친다.**
   겹친 자리에서는 DOM 순서상 뒤인 아랫줄이 이겨, 윗줄 칩을 누른 사람이 **아랫줄 필터를 누르게 된다**
   (브라우저로 확인: 「전체」 아래 2px 을 누르니 「기한만료」가 잡혔다).
   높이를 실제로 키우면 겹칠 것 자체가 없어진다 — 흉내가 아니라 그 크기가 된다. */
.ep-chip { min-height: 40px; }
/* 필터 줄의 입력·선택은 padding 만으로 39px 이라 1px 이 모자랐다.
   is-compact 뿐 아니라 필터 줄 전체를 대상으로 한다 — 검색칸은 is-compact 가 아닌 자리도 있다. */
.bo-filters .ep-input, .bo-filters .ep-select,
.ep-input.is-compact, .ep-select.is-compact { min-height: 40px; }

/* 카드 이미지·썸네일 미세 윤곽 — 배경과의 경계 안정화 */
.web-course-thumb, .my-course-thumb { outline: 1px solid rgba(0, 0, 0, .06); outline-offset: -1px; }

/* 표 가로 스크롤 시 안내 그림자 */
.ep-table-wrap { -webkit-overflow-scrolling: touch; }

/* 알림 뱃지·상태 배지 수직 정렬 */
.ep-badge { vertical-align: middle; }

/* 파일 input 스타일 통일 */
input[type="file"].ep-input { padding: 9px 12px; }
input[type="file"].ep-input::file-selector-button {
  font: inherit; font-size: var(--ep-text-xs); font-weight: 700;
  border: 0; border-radius: 999px; padding: 6px 14px; margin-right: 10px;
  background: var(--ep-primary-soft); color: var(--ep-primary); cursor: pointer;
}

/* 프린트 — 수료·결과 화면 인쇄 대비 */
@media print {
  .web-header, .web-footer, .bo-sidebar, .learn-sidebar, .ep-btn { display: none !important; }
  .web-print-hide { display: none !important; }
  .cert-paper { border: none !important; box-shadow: none !important; margin: 0 !important; }
}

/* ==========================================================================
   수료증 인쇄 서식 — PDF 라이브러리 없이 브라우저 인쇄로 처리한다.
   규율서 §2.2 는 PDF 생성을 Job·Queue 도입 조건으로 두는데 v1 은 Queue 미도입이다.
   ========================================================================== */
.cert-paper {
  border: 1px solid var(--ep-border);
  border-radius: 4px;
  padding: 56px 48px;
  background: #fff;
  text-align: center;
}
.cert-brand {
  display: flex; align-items: center; justify-content: center; gap: var(--ep-space-2);
  font-weight: 800; color: var(--ep-navy); margin-bottom: 34px;
}
.cert-title {
  font-size: var(--ep-text-4xl); font-weight: 800; letter-spacing: 12px;
  color: var(--ep-navy); margin: 0 0 40px;
}
.cert-fields {
  display: grid; grid-template-columns: 120px 1fr; gap: 12px 16px;
  max-width: 460px; margin: 0 auto 36px; text-align: left; font-size: var(--ep-text-md);
}
.cert-fields dt { color: var(--ep-text-muted); font-weight: 700; }
.cert-fields dd { margin: 0; font-weight: 600; }
.cert-statement { font-size: var(--ep-text-md); line-height: 2; margin: 0 0 44px; }
.cert-issuer { margin-bottom: 28px; }
.cert-date { font-size: var(--ep-text-md); color: var(--ep-text-muted); margin-bottom: 10px; }
.cert-org { font-size: var(--ep-text-xl); font-weight: 800; color: var(--ep-navy); letter-spacing: 2px; }
.cert-verify { font-size: var(--ep-text-2xs); color: var(--ep-text-faint); word-break: break-all; }

@media (max-width: 560px) {
  .cert-paper { padding: 32px 20px; }
  .cert-title { font-size: var(--ep-text-3xl); letter-spacing: 8px; }
  .cert-fields { grid-template-columns: 88px 1fr; font-size: var(--ep-text-sm); }
}

/* ==========================================================================
   표준 필터·카드 클래스 (DESIGN_RULES §3·§4) — 영역 공통
   ========================================================================== */
/* 칩 필터 — 활성 상태를 배경 채움 + 글자 반전으로 확실히 (선택 표시 불명확 해결) */
.ep-filter-row { display: flex; align-items: center; gap: var(--ep-space-2); flex-wrap: wrap; margin-top: 12px; }
/* 다른 요소와 한 줄에 놓을 때(.ep-between 안) — 자체 상단 여백은 끈다 */
.ep-filter-row.is-flush { margin-top: 0; }
.ep-filter-label { font-size: var(--ep-text-xs); font-weight: 700; color: var(--ep-text-faint); margin-right: 4px; flex-shrink: 0; }
.ep-chip {
  font-size: var(--ep-text-xs); font-weight: 600; border-radius: 999px; padding: 8px 16px;
  background: var(--ep-surface); color: var(--ep-text-muted); border: 1px solid var(--ep-border);
  white-space: nowrap; transition-property: background-color, color, border-color; transition-duration: .12s;
}
.ep-chip:hover { border-color: var(--ep-primary); color: var(--ep-primary); }
.ep-chip.is-active { background: var(--ep-navy); color: #fff; border-color: var(--ep-navy); font-weight: 700; }
.ep-chip.is-active:hover { color: #fff; }

/* 반응형 카드 그리드 — 데스크탑 다열 → 태블릿 2열 → 모바일 1열 (카드 높이 균일은 개별 카드가 보장) */

/* 숫자 정렬 전역 별칭 (테이블 밖에서도 tabular-nums) — .ep-num 과 동일 */
.num { font-variant-numeric: tabular-nums; }
/* ==========================================================================
   레이아웃 그리드 — 12열 공통 (DESIGN_RULES §1.1)

   왜 12열인가: 섹션마다 1.5fr 1fr / 2fr 1fr / repeat(3,1fr) 을 따로 쓰고
   gap 도 12·16·18px 이 섞여 있어서, 위아래 섹션의 열 경계가 서로 안 맞았다.
   실측: 같은 페이지에서 위는 360·360·360, 아래는 656·438 (모두 폭 1160 안).
   12는 2·3·4·6 으로 나뉘어 카드 2~4열 배치를 한 벌의 트랙으로 다 덮는다.

   모든 레이아웃 그리드는 같은 gutter 를 쓴다 — gap 이 다르면 열이 절대 안 맞는다.
   ========================================================================== */
.ep-row { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--ep-gutter); align-items: start; }
/* 짝을 이루는 박스(같은 역할·같은 무게)는 아래끝을 맞춘다 — 나란히 놓인 칸이 어긋나 보이지 않게 */
.ep-row.is-paired { align-items: stretch; }
.ep-col-2  { grid-column: span 2; min-width: 0; }
.ep-col-3  { grid-column: span 3; min-width: 0; }
.ep-col-4  { grid-column: span 4; min-width: 0; }
.ep-col-6  { grid-column: span 6; min-width: 0; }
.ep-col-8  { grid-column: span 8; min-width: 0; }
.ep-col-9  { grid-column: span 9; min-width: 0; }
.ep-col-12 { grid-column: span 12; min-width: 0; }

/* 태블릿: 12열을 6열로 접는다(4열 카드 → 2열, 8+4 → 위아래) */
@media (max-width: 960px) {
  .ep-row { grid-template-columns: repeat(6, 1fr); }
  .ep-col-3, .ep-col-4 { grid-column: span 3; }
  .ep-col-8, .ep-col-9, .ep-col-6 { grid-column: span 6; }
}
/* 모바일: 단일 열 */
@media (max-width: 640px) {
  .ep-row { grid-template-columns: 1fr; }
  .ep-col-2, .ep-col-3, .ep-col-4, .ep-col-6, .ep-col-8, .ep-col-9, .ep-col-12 { grid-column: 1 / -1; }
}

/* 세로 스택 — 패널·카드를 위아래로 쌓을 때.
   이걸 안 만들어놨더니 28곳이 인라인으로 display:flex;flex-direction:column;gap:N 을
   각자 적었고, 같은 용도에 gap 이 3·8·9·10·12·14·16px 일곱 가지로 갈렸다.
   간격은 3단만 둔다 — 더 잘게 나누면 다시 갈린다. */
.ep-stack { display: flex; flex-direction: column; gap: var(--ep-space-4); }
.ep-stack-sm { display: flex; flex-direction: column; gap: var(--ep-space-2); }
.ep-stack-lg { display: flex; flex-direction: column; gap: var(--ep-space-6); }
/* 배지처럼 폭이 내용만큼인 자식용 — 기본 stretch 는 flex item 을 컨테이너 폭으로 늘인다 */
.ep-stack.is-start, .ep-stack-sm.is-start, .ep-stack-lg.is-start { align-items: flex-start; }

/* 아래는 12열 그리드의 별칭이다 — 기존 마크업 호환용.
   새 화면은 .ep-row + .ep-col-* 를 쓴다. */

/* 입력 필드 leading 아이콘 + 비밀번호 표시 토글 (인증 폼) */
.ep-input-icon { position: relative; }
.ep-input-icon > .msi.lead { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); font-size: var(--ep-text-xl); color: var(--ep-text-faint); pointer-events: none; }
.ep-input-icon > .ep-input { padding-left: 42px; }
.ep-pw-toggle { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: none; border: 0; cursor: pointer; color: var(--ep-text-faint); padding: 6px; display: flex; }
.ep-pw-toggle:hover { color: var(--ep-primary); }

/* ==========================================================================
 * .ep-prose — 정화된 리치텍스트 본문 (관리자 입력 HTML)
 *
 * 어디에 쓰나: App\Support\HtmlSanitizer::richText() 를 통과한 HTML 을 출력하는 자리.
 *   - 학습 텍스트 자료 (learning/item)
 *   - 공개 게시글 본문 (web/boards/show)
 *
 * 왜 common 인가: 두 화면이 **같은 정화기의 같은 허용 태그 집합**을 출력한다.
 *   각 영역 CSS 에 따로 두면 표·인용·제목 모양이 시간이 지나며 갈린다.
 *   실제로 학습 자료에만 서식이 있고 게시글 본문은 브라우저 기본값으로 나오고 있었다.
 *
 * 범위: 좁은 프로파일(richText — h2 h3 h4 p ul ol li table thead tbody tr th td
 *      strong em b i u a code pre blockquote)에 더해, 2026-08-28 부터
 *      **넓은 프로파일**(wideContent — 강좌 상세 설명)이 통과시키는 것도 함께 다룬다:
 *      h1 h5 h6 · img · iframe(영상) · hr · figure · dl · 인라인 강조 몇 가지.
 *      그 밖은 정화기가 지운다.
 * ========================================================================== */
.ep-prose { font-size: var(--ep-text-md); line-height: 1.85; color: var(--ep-text-sub); min-width: 0; }
.ep-prose > :first-child { margin-top: 0; }
.ep-prose > :last-child { margin-bottom: 0; }
.ep-prose h2 { font-size: var(--ep-text-xl); font-weight: 800; margin: 26px 0 10px; color: var(--ep-text); }
.ep-prose h3 { font-size: var(--ep-text-lg); font-weight: 700; margin: 20px 0 8px; color: var(--ep-text); }
.ep-prose h4 { font-size: var(--ep-text-md); font-weight: 700; margin: 16px 0 6px; color: var(--ep-text); }
.ep-prose p { margin: 0 0 12px; }
.ep-prose ul, .ep-prose ol { margin: 0 0 12px; padding-left: 22px; }
.ep-prose li { margin: 4px 0; }
.ep-prose a { color: var(--ep-primary); text-decoration: underline; text-underline-offset: 2px; }
.ep-prose strong, .ep-prose b { font-weight: 700; color: var(--ep-text); }
.ep-prose code { background: var(--ep-bg); border-radius: 5px; padding: 1px 5px; font-size: var(--ep-text-xs); }
.ep-prose pre { background: #0B1B3F; color: #DDE6FA; border-radius: 12px; padding: 16px 18px; overflow-x: auto; font-size: var(--ep-text-xs); margin: 14px 0; }
.ep-prose pre code { background: none; padding: 0; color: inherit; }
.ep-prose blockquote { border-left: 3px solid var(--ep-primary); margin: 14px 0; padding: 4px 16px; color: var(--ep-text-muted); background: var(--ep-primary-softer); border-radius: 0 10px 10px 0; }

/* 관리자가 넣은 표는 열 수를 예측할 수 없다 — 좁은 화면에서 페이지를 밀지 않도록 자체 스크롤.
   본문 HTML 은 정화기가 뱉은 그대로라 표마다 래퍼 div 를 씌울 수 없다.
   그래서 표 자신을 블록 상자로 만들어 넘치는 만큼만 안에서 스크롤시킨다. */
.ep-prose table {
  display: block; overflow-x: auto;
  /* fit-content = «내용만큼만, 단 100% 를 넘지 않게».
     이게 없으면 display:block 때문에 상자가 부모 폭을 통째로 먹어,
     셀은 왼쪽에 몰려 있는데 바깥 테두리만 오른쪽 끝까지 그어진다(2026-08-28 실측).
     넘칠 때는 max-width 가 잡아 세우고 overflow-x 가 안에서 스크롤한다. */
  width: fit-content; max-width: 100%;
  border-collapse: collapse; margin: 14px 0;
}
.ep-prose th, .ep-prose td { border: 1px solid var(--ep-border); padding: 8px 12px; font-size: var(--ep-text-sm); text-align: left; }
.ep-prose th { background: var(--ep-bg); font-weight: 700; color: var(--ep-text); }

/* 강사가 준 HTML 을 담는 상자 — 안쪽은 shadow root 라 여기 규칙이 닿지 않는다.
   바깥에서 정할 수 있는 것은 «이 상자가 문서에서 차지하는 자리» 뿐이다.
   partial: resources/views/web/partials/instructor-html.blade.php */
.ep-html-scope { display: block; min-width: 0; }

/* ── 넓은 프로파일 추가분 (강좌 상세 설명 — 강사가 준 HTML) ──
   강사는 자기 화면 폭을 기준으로 만든다. width="800" 짜리 그림이 그대로 들어오면
   좁은 화면에서 페이지 전체가 가로로 밀린다 — 그림 하나가 레이아웃을 깨뜨린다.
   그래서 넘치는 것은 여기서 가둔다. 높이는 비율을 지키도록 auto 로 되돌린다. */
.ep-prose img {
  max-width: 100%; height: auto; display: block; margin: 14px 0;
  border-radius: 10px;
  /* 흰 배경 그림이 흰 판과 붙어 경계가 사라지는 것을 막는다.
     순수 검정 저투명도 — 색이 섞인 회색은 사진 가장자리에서 때처럼 보인다. */
  outline: 1px solid rgba(0, 0, 0, .1); outline-offset: -1px;
}
/* 글 흐름 안에 놓인 작은 그림(아이콘·인라인 뱃지)은 줄에서 튀지 않게 */
.ep-prose p > img, .ep-prose li > img { display: inline-block; margin: 0 2px; vertical-align: middle; }

/* 영상은 16:9 로 눕힌다 — 강사가 적어 온 width/height 를 그대로 두면 폭이 안 맞는다 */
.ep-prose iframe { display: block; width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0; border-radius: 12px; margin: 16px 0; }

.ep-prose h1 { font-size: var(--ep-text-2xl); font-weight: 800; margin: 28px 0 12px; color: var(--ep-text); }
.ep-prose h5, .ep-prose h6 { font-size: var(--ep-text-sm); font-weight: 700; margin: 14px 0 6px; color: var(--ep-text); }
.ep-prose hr { border: 0; border-top: 1px solid var(--ep-border); margin: 22px 0; }
.ep-prose figure { margin: 16px 0; }
.ep-prose figcaption { font-size: var(--ep-text-xs); color: var(--ep-text-muted); margin-top: 6px; text-align: center; }
.ep-prose dl { margin: 0 0 12px; }
.ep-prose dt { font-weight: 700; color: var(--ep-text); margin-top: 10px; }
.ep-prose dd { margin: 2px 0 0 16px; }
.ep-prose mark { background: var(--ep-primary-softer); padding: 0 3px; border-radius: 3px; }
.ep-prose small { font-size: var(--ep-text-xs); color: var(--ep-text-muted); }
.ep-prose s, .ep-prose del { text-decoration: line-through; color: var(--ep-text-muted); }
/* 강사가 style 로 색·여백을 넣어 온 경우 그 값이 이긴다 — 우리 기본값은 «없을 때» 만 쓴다.
   그래서 위 규칙들은 전부 낮은 특정도(단일 클래스+태그)로 둔다. 여기서 !important 를 쓰면
   붙여넣은 서식이 통째로 무시되어 «HTML 을 넣을 수 있다» 는 말이 거짓이 된다. */

/* ==========================================================================
   영역 공용 조각 — 두 개 이상 영역이 쓰는 것은 반드시 여기 있어야 한다.
   영역 CSS(web/admin/learning/…)에 두면 그 파일을 안 읽는 화면에서 스타일이
   통째로 안 먹는다. 실제로 그랬다 — 예: 마이페이지 대시보드가 진도율 색상을
   계산해놓고도 admin.css 를 안 읽어 막대·퍼센트 둘 다 색이 죽어 있었다.
   레이아웃별 로드: web=common+web / member=common+web+member /
                   learning=common+learning / admin=common+admin / tutor=common+admin+tutor
   ========================================================================== */

/* ── 세그먼티드 컨트롤 — "둘 중 하나"를 형태로 보여준다 ──────────────────
   칩과 나눠 쓴다: **배타적 선택은 세그먼트, 목록에서 고르기는 칩**.
   그래야 형태만 보고 "하나만 켜지는 줄"인지 "여러 후보 중 고르는 줄"인지 안다.

   정렬을 텍스트 링크로 두었을 때 지적을 받았다 — 활성 표시가 색·굵기뿐이라
   무엇이 켜져 있는지 안 읽혔다. 네이티브 select 로도 바꿔봤지만 열린 목록은
   브라우저가 그리므로(파란 하이라이트·각진 모서리) 나머지 화면과 따로 논다.
   트랙 안의 흰 알약은 우리가 끝까지 그릴 수 있고, 활성이 떠 보여 한눈에 읽힌다. */
.ep-segmented {
  display: inline-flex; align-items: center;   /* 알약은 붙여 둔다 — 세그먼트는 한 트랙이다 */
  background: var(--ep-bg); border: 1px solid var(--ep-border);
  border-radius: 999px; padding: 3px;
}
.ep-segmented > a {
  padding: 7px 16px; border-radius: 999px; white-space: nowrap;
  font-size: var(--ep-text-xs); font-weight: 600; color: var(--ep-text-muted);
  transition-property: background-color, color; transition-duration: .15s;
  position: relative;
}
/* 트랙 안 알약은 33px 이라 DESIGN_RULES §8(40) 에 못 미친다 — **실제 높이**를 올린다.
   ::after 로 «넓히는» 흉내는 쓰지 않는다: 이 트랙은 가로 배치라 지금은 겹치지 않지만,
   같은 방식이 칩·작은 버튼에서 실제로 이웃을 가로채는 사고를 냈다(2026-08-26 실측).
   한 저장소 안에서 같은 목적에 두 방식을 두면 다음 사람이 위험한 쪽을 고른다. */
.ep-segmented > a { min-height: 40px; }
.ep-segmented > a:hover { color: var(--ep-primary); }
.ep-segmented > a.is-active {
  background: var(--ep-surface); color: var(--ep-navy); font-weight: 700;
  box-shadow: 0 1px 2px rgba(16, 38, 94, .10);
}
.ep-segmented > a.is-active:hover { color: var(--ep-navy); }

/* 폼 입력용 세그먼트 (2026-08-20) — 위 링크형과 같은 모양이되 **값을 전송**한다.
   링크형은 필터 전용이라 <a> 다. 폼에서 고른 값은 POST 로 나가야 하므로 radio 를 쓴다.

   왜 필요한가: 선택지가 둘·셋뿐인 폼 칸까지 네이티브 select 로 두면, 그걸 열 때마다
   브라우저가 그린 목록(파란 하이라이트·각진 모서리)이 화면과 따로 논다.
   위 주석이 필터에 대해 적어 둔 것과 **똑같은 문제**가 폼에서도 난다.
   트랙 안의 알약은 우리가 끝까지 그리고, 게다가 선택지가 열지 않아도 다 보인다.

   접근성: radio 는 그대로 두고 시각만 label 로 옮긴다 — 키보드 화살표 이동·
   그룹 읽기가 네이티브 그대로 동작한다. 포커스는 :focus-visible 로 알약에 표시한다. */
.ep-segmented.is-form { padding: 3px; }
.ep-segmented.is-form input[type="radio"] {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.ep-segmented.is-form label {
  padding: 7px 16px; border-radius: 999px; white-space: nowrap; cursor: pointer;
  font-size: var(--ep-text-xs); font-weight: 600; color: var(--ep-text-muted);
  transition-property: background-color, color; transition-duration: .15s;
}
.ep-segmented.is-form label:hover { color: var(--ep-primary); }
.ep-segmented.is-form input[type="radio"]:checked + label {
  background: var(--ep-surface); color: var(--ep-navy); font-weight: 700;
  box-shadow: 0 1px 2px rgba(16, 38, 94, .10);
}
/* 키보드로 들어왔을 때 어디 있는지 보여야 한다 — radio 를 숨겼으므로 알약이 대신 받는다 */
.ep-segmented.is-form input[type="radio"]:focus-visible + label {
  outline: 2px solid var(--ep-primary); outline-offset: 2px;
}
/* 잠긴 항목(수강생이 있어 못 바꾸는 학습정책 등)은 만질 수 없음이 보여야 한다 */
.ep-segmented.is-form input[type="radio"]:disabled + label {
  cursor: not-allowed; color: var(--ep-text-faint);
}

/* 브랜드 로고 마크 (공개 헤더 · 관리자 로그인) */
.ep-logo-mark {
  width: 32px; height: 32px; border-radius: 9px; background: var(--ep-primary);
  color: #fff; display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: var(--ep-text-lg);
}
/* 로그인 카드용 큰 마크. 반경은 32:9 비율을 그대로 키운 값이다.
   글자 크기는 여기서 정하지 않는다 — 2-클래스로 font-size 를 잡으면 .ep-t-* 유틸이 진다
   (DesignSystemTest 가 막는다). 마크업에서 .ep-t-xl 을 함께 붙인다. */
.ep-logo-mark.is-lg { width: 38px; height: 38px; border-radius: 11px; }
/* 어두운 배경 위(푸터) — 흰 바탕에 브랜드색 글자. 글자 크기는 .ep-t-md 로. */
.ep-logo-mark.is-inverse { width: 30px; height: 30px; border-radius: 8px; background: #fff; color: var(--ep-primary); }

/* 라벨-값 요약 목록 (강의 상세 · 주문 · 결제 · 학습) */
.ep-facts { border-top: 1px solid var(--ep-border-soft); padding-top: 16px; display: flex; flex-direction: column; gap: var(--ep-space-2); font-size: var(--ep-text-xs); color: var(--ep-text-muted); }
.ep-facts > div { display: flex; justify-content: space-between; }
.ep-facts b { color: var(--ep-text); font-weight: 600; }
.ep-facts.is-flush { border-top: 0; padding-top: 0; }   /* 패널 첫 요소로 올 때 — 위 선·여백 제거 */

/* 정의 목록 (상세 화면 공통) */
.ep-dl { display: grid; grid-template-columns: 130px 1fr; gap: 8px 16px; font-size: var(--ep-text-sm); }
.ep-dl dt { color: var(--ep-text-faint); font-weight: 600; margin: 0; }
.ep-dl dd { margin: 0; font-weight: 600; }

/* 목록 안 짧은 빈 상태 */
.ep-empty-sm { font-size: var(--ep-text-xs); color: var(--ep-text-faint); padding: 8px 0; }

/* 첨부파일 목록 */
.ep-file-list { display: flex; flex-direction: column; gap: var(--ep-space-2); }

/* 단일 컬럼 폼 카드 — 백오피스는 .bo-form(760px), 그 밖은 이것(--ep-form) */
.ep-form-card { max-width: var(--ep-form); }

/* 진도 임계 색상 — 80%↑ green / 40%↑ primary / 미만 amber */
.ep-progress.is-high > i { background: var(--ep-green); }
.ep-progress.is-mid > i { background: var(--ep-primary); }
.ep-progress.is-low > i { background: var(--ep-amber); }
.ep-prog-pct.is-high { color: var(--ep-green); }
.ep-prog-pct.is-mid { color: var(--ep-primary); }
.ep-prog-pct.is-low { color: var(--ep-amber); }

/* ── 목표 눈금이 있는 진도 막대 (DESIGN_RULES §9.2) ──────────────────────
   진도는 100% 가 아니라 **수료 기준**이 목표다. 26% 를 100 기준 막대로만 그리면
   절반쯤 찬 것처럼 보이고, 기준선(90%)은 다른 카드에 있어서 머릿속으로 빼야 했다.
   .ep-progress 는 overflow:hidden 이라 자기 몸에 눈금을 못 그린다 → 겉을 한 겹 싼다. */
.ep-meter { position: relative; }
.ep-meter > .ep-progress { position: relative; z-index: 0; }
/* 눈금은 **목표가 있을 때만** 그린다. 기본값(100%)으로 두면 수료 기준이 없는 수업에도
   막대 끝에 선이 생겨 "저기까지 채워야 하나?" 로 읽힌다 — .has-goal 을 명시적으로 요구한다. */
.ep-meter.has-goal::after {
  content: ''; position: absolute; top: -3px; bottom: -3px; left: var(--goal, 100%);
  width: 2px; margin-left: -1px; border-radius: 2px;
  background: var(--ep-text-faint); z-index: 1;
}
/* 목표에 도달했으면 눈금을 초록으로 — 넘겼다는 사실이 색으로 먼저 읽힌다 */
.ep-meter.has-goal.is-reached::after { background: var(--ep-green); }

/* ── 접이식 블록 (DESIGN_RULES §9.8) ─────────────────────────────────────
   시스템이 이미 끝낸 일(자동채점된 객관식 등)은 접어 둔다. 사람이 할 일과
   같은 크기로 펼쳐 두면 처음 보는 사람은 그것도 자기가 해야 하는 줄 안다. */
.ep-fold > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: var(--ep-space-2);
}
.ep-fold > summary::-webkit-details-marker { display: none; }
/* 펼침 표시를 직접 그린다 — 브라우저 기본 삼각형은 OS 마다 모양·위치가 달라 줄이 흔들린다.
   ⚠️ 아이콘 폰트는 서브셋이다. expand_more 는 서브셋에 없어서 글자가 그대로 나온다 —
   목록에 있는 chevron_right 를 돌려 쓴다(public/vendor/material-symbols/icons.txt). */
.ep-fold > summary::after {
  content: 'chevron_right'; font-family: 'Material Symbols Outlined';
  font-size: var(--ep-text-xl); color: var(--ep-icon-faint);
  margin-left: auto; transform: rotate(90deg); transition: transform .15s;
}
.ep-fold[open] > summary::after { transform: rotate(-90deg); }
.ep-fold > summary:hover::after { color: var(--ep-text-muted); }
.ep-fold[open] > summary { margin-bottom: var(--ep-space-3); }

/* ── 브레드크럼 (DESIGN_RULES §9.4) ──────────────────────────────────────
   상세 화면에서 "여기가 어디인지"를 알려준다. 뒤로가기 링크 하나만 두면
   두 단계 위로 갈 방법이 없어 사용자가 브라우저 뒤로가기를 연타하게 된다. */
.ep-crumb {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--ep-space-1);
  font-size: var(--ep-text-xs); margin-bottom: 12px;
}
.ep-crumb a { color: var(--ep-text-muted); font-weight: 600; }
.ep-crumb a:hover { color: var(--ep-primary); text-decoration: underline; }
/* 구분자를 icon-faint(#B4BED4)로 두면 흰 배경에서 1.75:1 — 거의 안 보여서
   «관리자 › 시험 결과» 가 한 덩어리로 읽혔다(실측). 아이콘용 회색은 큰 도형 기준이라
   1글자 문자에는 약하다. 비문자 최소선(3:1)을 넘기는 faint 로 올린다. */
.ep-crumb .sep { color: var(--ep-text-faint); }
.ep-crumb .is-current { color: var(--ep-text); font-weight: 700; }

/* ── 탭 (DESIGN_RULES §9.3) ─────────────────────────────────────────────
   한 대상의 정보가 3덩어리를 넘으면 한 화면에 쌓지 않고 나눈다.
   링크형(<a href>)과 버튼형(JS 전환) 둘 다 같은 모양이어야 한다. */
.ep-tabs {
  display: flex; align-items: stretch;
  /* overflow-x 만 auto 로 두면 y 축도 자동으로 auto 가 되어(CSS 규칙) 세로 스크롤 화살표가 생긴다.
     밑줄(margin-bottom:-1px) 때문에 내용이 1px 넘쳐서 실제로 나타났다 — y 는 못박는다. */
  overflow-x: auto; overflow-y: hidden;
  border-bottom: 1px solid var(--ep-border); margin-bottom: 20px;
}
.ep-tabs > a, .ep-tabs > button {
  display: inline-flex; align-items: center; gap: var(--ep-space-1);
  padding: 11px 16px; white-space: nowrap; cursor: pointer;
  font-size: var(--ep-text-sm); font-weight: 700; color: var(--ep-text-muted);
  background: none; border: 0; border-bottom: 2px solid transparent;
  margin-bottom: -1px; transition: color .15s, border-color .15s;
}
.ep-tabs > a:hover, .ep-tabs > button:hover { color: var(--ep-text); }
.ep-tabs > .is-active { color: var(--ep-primary); border-bottom-color: var(--ep-primary); }
/* 탭 패널의 [hidden] 은 파일 맨 위의 «[hidden] { display:none !important }» 가 처리한다 */
.ep-tabs .count {
  font-size: var(--ep-text-2xs); font-weight: 700; padding: 1px 7px; border-radius: 999px;
  background: var(--ep-bg); color: var(--ep-text-faint); font-variant-numeric: tabular-nums;
}
.ep-tabs > .is-active .count { background: var(--ep-primary-soft); color: var(--ep-primary); }

/* ── 시험 채점 문항 (shared/exam-answer) ─────────────────────────────────
   관리자·교강사 두 화면이 같은 마크업을 각자 들고 있었고, 인라인 style 로
   문제 여백·답안 배경·보기 줄·아이콘 크기를 매번 다시 적고 있었다. */
.grading-q { margin-bottom: var(--ep-space-4); }
/* 아직 점수를 안 넣은 서술형 — 20문항 중 어디를 봐야 하는지 훑을 수 있게 한다.
   ⚠️ 처음엔 왼쪽에 3px 선을 그었는데, 카드의 둥근 모서리를 뚫고 나와 그 카드만
   각져 보였다. 오른쪽 «미채점» 배지와 같은 말을 두 번 하는 것이기도 했다.
   테두리는 모서리를 따라가니 카드 모양을 깨지 않는다 — 색만 바꾼다. */
.grading-q.is-pending { border-color: var(--ep-amber); }
.grading-q-text { margin: var(--ep-space-2) 0 var(--ep-space-3); }

/* 수강생이 쓴 답안 — 읽는 글이라 줄간격을 넓히고 줄바꿈을 그대로 살린다 */
.grading-answer {
  background: var(--ep-bg); border-radius: var(--ep-radius-input);
  padding: var(--ep-space-4); line-height: 1.7; white-space: pre-wrap;
  margin-bottom: var(--ep-space-3);
}

/* 과제 지시문 — «무엇을 하라는 과제인가». 답안(.grading-answer)과 역할이 달라 옷을 구분한다.
   학습자는 이걸 보고 쓰고, 채점자는 이걸 보고 매긴다 — 그래서 두 영역이 같은 파샬을 쓴다.
   (예전에는 DB 에 필수로 저장하면서 어느 화면에도 그리지 않아, 학습자는 무엇을 하라는지 모른 채
    답안을 쓰고 채점자는 무엇을 시켰는지 모른 채 점수를 매겼다.) */
/* ⚠️ 좌측 액센트 바를 두지 않는다(2026-08-27).
   이 블록은 이미 테두리·배경·「과제 안내」 라는 제목으로 구분된다. 거기에 파란 세로줄을
   더해도 «무엇이 다른가» 를 아무것도 더 말하지 않는다 — 장식일 뿐이다.
   이 저장소가 좌측 바를 쓰는 다른 여섯 곳은 전부 구분을 전달한다(전수 확인):
     토스트=성공/실패 · 문의 답변=운영자가 쓴 것 · 메뉴=지금 있는 자리 ·
     표절 검사=겹친 구간 · 인용문=인용 · 강의실 항목=현재 항목.
   좌측 바를 새로 쓰려거든 «이 줄이 없으면 무엇을 구별 못 하나» 에 먼저 답할 것. */
.assignment-brief {
  border: 1px solid var(--ep-border);
  border-radius: var(--ep-radius-input); background: var(--ep-surface);
  padding: var(--ep-space-4); line-height: 1.7; white-space: pre-wrap; word-break: break-word;
  margin-bottom: var(--ep-space-4);
}
/* 줄바꿈을 지키는 본문 — 공지·안내처럼 «읽는 글» 자리.
   답안 상자(.grading-answer)는 «학습자가 낸 것» 을 담는 회색 상자라 역할이 다르다.
   공지 본문을 그 상자에 담았더니, 강사의 공지가 학습자 답안 제출창처럼 보였다. */
.ep-plaintext {
  font-size: var(--ep-text-md); line-height: 1.85; color: var(--ep-text-sub);
  white-space: pre-wrap; word-break: break-word;
}

.assignment-brief-label {
  display: block; font-size: var(--ep-text-2xs); font-weight: 800;
  color: var(--ep-primary); letter-spacing: .3px; margin-bottom: var(--ep-space-2);
}

/* 객관식 보기 한 줄 */
.grading-option {
  display: flex; align-items: center; gap: var(--ep-space-2);
  padding: var(--ep-space-2) 0;
}
.grading-option .msi { font-size: var(--ep-text-lg); }

/* 배점 칸 — 숫자 두세 자리면 충분하다. 넓으면 옆의 '/ N점' 이 멀어져 짝으로 안 읽힌다 */
.grading-points { width: 100px; flex: none; }

/* 영상 재생 자리 — 16:9 검은 상자.
   학습자 강의실이 쓰는 .learn-player 와 같은 모양인데, 그쪽은 learning.css 라
   관리자 화면이 안 읽는다. 두 벌로 두지 않고 공통에 둔 이 클래스를 함께 쓴다. */
.ep-video-frame {
  background: #000; border-radius: var(--ep-radius-card);
  overflow: hidden; aspect-ratio: 16 / 9; position: relative;
}
.ep-video-frame > iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* ── 접어두는 행 편집 (.ep-row-edit) ────────────────────────────────────
   목록의 항목마다 편집 폼을 펼쳐 두면 항목이 늘어난 만큼 입력칸이 늘어난다.
   강좌 상세는 차시 20개 × 항목 5개면 입력칸이 120개였다.

   그렇다고 항목마다 별도 편집 페이지로 빼면 순서를 보며 손보는 작업에서
   20번을 왕복해야 한다 — 그래서 **평소엔 한 줄로 읽고, 고칠 때만 펼친다**.
   (게시판처럼 "글" 을 다루는 곳은 별도 화면이 맞다. 여기는 구조를 다룬다) */
.ep-row-edit { border-bottom: 1px solid var(--ep-border-soft); }
.ep-row-edit > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: var(--ep-space-2);
  padding: var(--ep-space-2) var(--ep-space-1);
}
.ep-row-edit > summary::-webkit-details-marker { display: none; }
.ep-row-edit > summary:hover { background: var(--ep-border-soft); }
/* 펼침 표시 — 서브셋에 있는 chevron_right 를 돌려 쓴다(expand_more 는 없다) */
.ep-row-edit > summary::after {
  content: 'chevron_right'; font-family: 'Material Symbols Outlined';
  font-size: var(--ep-text-lg); color: var(--ep-icon-faint);
  margin-left: auto; transform: rotate(90deg); transition: transform .15s;
}
.ep-row-edit[open] > summary::after { transform: rotate(-90deg); }
.ep-row-edit[open] > summary { background: var(--ep-border-soft); }
/* 펼친 편집 영역 — 요약줄과 구분되게 안쪽으로 들인다.
   ⚠️ align-items 는 flex-end 다. 이 줄에는 «라벨이 있는 칸»(이름·상태)과 «없는 칸»(버튼)이
      섞이는데, center 로 두면 라벨 높이만큼 버튼이 위로 떠서 입력칸과 밑선이 어긋난다
      (2026-08-28 주인님 지적 — 화면에서 높이가 안 맞아 보이던 원인).
      바닥을 맞추면 라벨 유무와 무관하게 조작부의 밑선이 한 줄로 선다. */
.ep-row-edit-body {
  display: flex; align-items: flex-end; flex-wrap: wrap; gap: var(--ep-space-2);
  padding: var(--ep-space-3) var(--ep-space-1) var(--ep-space-4);
}
/* 그 줄 전체를 차지하는 안내 — 조작부 아래로 내려온다 */
.ep-row-edit-body > .ep-help { flex: 1 0 100%; margin: 0; }

/* 첨부파일 한 줄 — 아이콘·이름·크기·받기 버튼. 인라인으로 매번 그리고 있었다 */
.ep-file-row {
  display: flex; align-items: center; gap: var(--ep-space-2);
  border: 1px solid var(--ep-border); border-radius: var(--ep-radius-input);
  padding: var(--ep-space-3) var(--ep-space-4);
}

/* 패널 자체를 접기로 쓸 때 — 위험하거나 예외적인 조작을 평소엔 숨긴다.
   .ep-panel 의 여백을 그대로 쓰되 요약줄이 제목 노릇을 한다. */
details.ep-panel.ep-fold > summary { margin-bottom: 0; }
details.ep-panel.ep-fold[open] > summary { margin-bottom: var(--ep-space-3); }

/* ── 위험작업 확인 모달 (정본 §7.4) ───────────────────────────────────────
   정본: «환불, 수강취소, 무효화, 재채점, 보완요청, 수료취소는 Modal 또는 별도
   확인화면을 사용한다 / 영향대상·현재상태·변경 후 상태·금액·수료영향을 표시한다
   / 사유를 필수 입력으로 받는다 / 성공 전 버튼을 비활성화하고 중복요청을 차단한다.»

   브라우저 기본 confirm 은 이 중 «영향도 표시» 를 구조적으로 못 한다 —
   한 줄 문자열만 띄우므로 무엇이 어떻게 바뀌는지 보여줄 자리가 없다. */
.ep-modal-backdrop {
  display: none; position: fixed; inset: 0; z-index: 100;
  background: rgba(11, 27, 63, .5);
  align-items: center; justify-content: center; padding: var(--ep-space-4);
}
.ep-modal-backdrop.is-open { display: flex; }
.ep-modal {
  background: var(--ep-surface); border-radius: var(--ep-radius-panel);
  width: min(520px, 100%); max-height: 88vh; overflow-y: auto;
  padding: var(--ep-space-6) 26px;
  box-shadow: 0 24px 60px rgba(16, 38, 94, .28);
}
.ep-modal-title { font-size: var(--ep-text-xl); font-weight: 800; margin: 0 0 var(--ep-space-3); }
.ep-modal-lead { color: var(--ep-text-sub); line-height: 1.7; margin-bottom: var(--ep-space-4); }
/* 영향도 표 — «무엇이 지금 어떤 상태이고 무엇으로 바뀌는지» */
.ep-modal-impact { border: 1px solid var(--ep-border); border-radius: var(--ep-radius-input); overflow: hidden; margin-bottom: var(--ep-space-4); }
.ep-modal-impact div { display: flex; gap: var(--ep-space-3); padding: 10px 14px; font-size: var(--ep-text-sm); }
.ep-modal-impact div + div { border-top: 1px solid var(--ep-border-soft); }
.ep-modal-impact dt, .ep-modal-impact .k { flex: 0 0 116px; color: var(--ep-text-faint); font-weight: 700; }
.ep-modal-impact .v { flex: 1; min-width: 0; font-weight: 600; }
.ep-modal-actions { display: flex; justify-content: flex-end; gap: var(--ep-space-2); margin-top: var(--ep-space-4); }

/* ── 「만들기」 폼 모달 (form-modal.js) ───────────────────────────────────
   목록 화면 옆에 생성 폼을 상주시키면 화면이 두 가지 일을 동시에 한다.
   서버가 렌더한 폼을 그대로 옮겨 띄우므로 검증 오류·입력 복원이 평소대로 동작한다. */
.ep-modal-form[hidden] { display: none; }
.ep-modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--ep-space-3); }
.ep-modal-head .ep-modal-title { margin-bottom: var(--ep-space-4); }
/* 모달 폭은 세 개뿐이다 — 기본 520 / is-wide / is-grid. 화면마다 새 숫자를 만들지 않는다.
   칸이 많은 폼(문항 + 보기 4줄)은 520 에 넣으면 보기 입력이 손가락만 해진다 →
   폼 표준 폭(.bo-form)과 같은 자를 쓴다. 숫자를 여기 다시 적으면 두 곳이 갈린다. */
.ep-modal.is-wide { width: min(var(--ep-bo-form), 100%); }
/* 표(여러 줄 × 여러 칸)가 든 모달. 760 에 넣으면 표가 상자보다 넓어 스스로 가로 스크롤을 만들고,
   열이 양쪽에서 잘려 나간다 — 「영상 한 번에 넣기」에서 실측(2026-09-01 주인님 지적). */
.ep-modal.is-grid { width: min(var(--ep-modal-grid), 100%); }
/* 모달 안에서 구역을 나눌 때 (여러 작업을 한 모달에 담는 경우) */
.ep-modal-sub { font-size: var(--ep-text-md); font-weight: 800; margin: 0 0 var(--ep-space-2); }
.ep-modal-form > * + .ep-modal-sub { margin-top: var(--ep-space-6); padding-top: var(--ep-space-5); border-top: 1px solid var(--ep-border-soft); }

/* ── 계층 목록 (카테고리처럼 부모-자식이 있는 목록) ──────────────────────
   들여쓰기만으로 깊이를 보여준다. 선 하나를 왼쪽에 그어 어느 부모에 속했는지 잇는다. */
/* 최상위 하나와 그 하위 목록을 «한 덩어리» 로 묶는 자리.
   순서를 바꿀 때 부모만 움직이고 하위가 제자리에 남으면, 하위가 다른 분류에 붙은 것처럼 보인다
   (실측으로 확인). 묶어 두면 부모가 움직일 때 자식이 따라온다 — 보이는 모양은 바뀌지 않는다. */
.ep-tree-node { display: block; }
.ep-tree-child { padding-left: 26px; position: relative; }
.ep-tree-child::before {
  content: ''; position: absolute; left: 10px; top: 0; bottom: 0;
  border-left: 1px solid var(--ep-border-soft);
}

/* ── 처리 결과 알림 ──────────────────────────────────────────────────────
   «눌렀는데 아무 변화가 없다» 를 없앤다. 저장·삭제 뒤 무엇이 처리됐는지
   화면 위에서 분명히 말한다(정본 §21 상태·메시지). */
.ep-toast-area { position: fixed; right: 20px; bottom: 20px; z-index: 120; display: flex; flex-direction: column; gap: var(--ep-space-2); }
.ep-toast {
  display: flex; align-items: flex-start; gap: var(--ep-space-2);
  /* ⚠️ 왼쪽 색 막대 없음 (2026-09-01 주인님 지적 — 반복).
     카드·행·토스트 왼쪽이나 위에 색 막대를 두는 것은 이 저장소가 금지한 «AI 가 만든 티» 다.
     성공/실패는 **아이콘 색**이 말하고(toast 안의 msi), 상자는 흰 면과 그림자로만 뜬다.
     지난번에 카드·행만 걷어내고 토스트를 빠뜨려 다시 지적받았다 — 남은 자리는
     .ep-answer.is-staff / .sim-span / 사이드바 활성 표시였고 함께 정리했다. */
  background: var(--ep-surface); border: 1px solid var(--ep-border);
  border-radius: var(--ep-radius-input); padding: 14px 18px;
  box-shadow: 0 12px 28px rgba(16, 38, 94, .18);
  max-width: min(420px, 90vw); font-size: var(--ep-text-sm); line-height: 1.6;
  animation: ep-toast-in .22s cubic-bezier(.2, 0, 0, 1);
}
/* 성공/실패는 아이콘이 말한다 — 마크업이 이미 초록 check_circle / 빨강 block 을 넣는다
   (confirm-modal.js). 그래서 색 막대를 걷어내도 잃는 정보가 없다. */
.ep-toast .msi { flex-shrink: 0; }
@keyframes ep-toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .ep-toast { animation: none; } }

/* ── 알림함 항목 ────────────────────────────────────────────────────────
   「제목을 눌러 들어간다」는 관습이다. 이동 수단이 우측 버튼 하나뿐이면
   제목을 눌러 본 사람은 반응이 없는 것을 고장으로 읽는다.
   제목·본문 자체를 제출 버튼으로 만들어 행 전체가 눌리게 한다. */
.ep-notif-item {
  display: flex; gap: var(--ep-space-3); align-items: flex-start;
  padding: var(--ep-space-4); border-bottom: 1px solid var(--ep-border-soft);
}
.ep-notif-item:last-child { border-bottom: 0; }
.ep-notif-item:hover { background: var(--ep-border-soft); }
.ep-notif-body {
  flex: 1; min-width: 0; text-align: left; cursor: pointer;
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
}
.ep-notif-body:focus-visible { outline: 2px solid var(--ep-primary); outline-offset: 3px; border-radius: var(--ep-radius-input); }

/* ── 돌아가기 ──────────────────────────────────────────────────────────
   한때 버튼형·텍스트형·전용 클래스 세 가지가 섞여 있었다. 화면을 옮길 때마다
   「누를 수 있는 것」의 시각 신호가 흔들린다.
   돌아가기는 «주 행동» 이 아니라 보조 내비게이션이다 — outline 버튼으로 두면
   저장·제출 같은 진짜 행동과 무게가 같아져 눈이 먼저 뒤로 간다. 텍스트형으로 통일한다.
   (상세 화면의 «현재 위치» 는 브레드크럼이 맡는다 — DESIGN_RULES §9.4) */
.ep-back {
  display: inline-flex; width: fit-content; align-items: center; gap: var(--ep-space-1);
  font-size: var(--ep-text-xs); font-weight: 600; color: var(--ep-text-muted);
  margin-bottom: var(--ep-space-4);
  min-height: 40px;   /* 작은 글씨라도 손가락이 닿는 크기는 확보한다 */
}
.ep-back:hover { color: var(--ep-primary); }
.ep-back .msi { font-size: var(--ep-text-lg); }

/* ── 순서 변경 ▲▼ ──────────────────────────────────────────────────────
   드래그가 아니라 한 칸씩 옮기는 버튼이다 — 마우스가 없어도 되고,
   «어디에 놓였는지» 를 놓치지 않는다.
   아이콘 서브셋(72개)에 위/아래 화살표가 없어 arrow_back 을 돌려 쓴다. */
/* 순서 변경 ▲▼ — DESIGN_RULES §8 은 조작 요소를 최소 40×40 으로 정한다.
   예전에는 22×20 을 세로로 쌓아 뒀다(실측). 누르는 면이 규칙의 4분의 1이라 옆 버튼을
   잘못 누르기 쉬웠다 — 차시 순서는 잘못 누르면 수강생이 보는 차례가 바뀐다.

   ⚠️ 세로로 쌓은 채 각각 40 높이를 주면 한 덩어리가 81px 이 되어 줄마다 배로 커진다.
      그래서 **나란히** 놓는다 — 줄 높이(57px)는 그대로 두고 40×40 을 만족한다.
      (::after 로 늘리는 방법은 못 쓴다. 위아래로 늘리면 두 버튼의 누르는 면이 서로 겹쳐
       «어느 쪽을 눌렀는지» 가 좌표에 따라 갈린다.) */
.ep-reorder { display: inline-flex; flex: none; gap: var(--ep-space-1); }
.ep-reorder-btn {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0; cursor: pointer;
  background: none; border: 1px solid var(--ep-border); border-radius: 8px;
  color: var(--ep-text-faint);
}
.ep-reorder-btn:hover { color: var(--ep-primary); border-color: var(--ep-primary); }
.ep-reorder-btn:focus-visible { outline: 2px solid var(--ep-primary); outline-offset: 1px; }
.ep-reorder-btn .msi { font-size: 15px; line-height: 1; vertical-align: 0; }
.ep-reorder-btn[data-reorder-move="up"] .msi { transform: rotate(90deg); }
.ep-reorder-btn[data-reorder-move="down"] .msi { transform: rotate(-90deg); }
/* 차시 패널 — 정렬 버튼이 들어가며 여백이 좁아진다(인라인 style 대체) */
.bo-lesson-panel { padding: 16px 20px; }

/* ── 카드 안쪽 여백 — 값이 아니라 «역할» 로 부른다 ─────────────────────
   인라인 padding 이 화면마다 16/20/24/36/40 으로 갈려 나란히 놓으면 어긋나 보였다. */
.ep-pad-snug  { padding: var(--ep-space-4) var(--ep-space-5); }   /* 목록 안의 한 덩어리 */
.ep-pad-roomy { padding: var(--ep-space-6); }                      /* 읽는 본문(게시글·설문) */
.ep-pad-hero  { padding: var(--ep-space-8) var(--ep-space-6); }    /* 화면 하나를 채우는 안내·결과 */

/* 문의 답변 — 운영자 답변만 왼쪽에 표식을 둔다(누가 썼는지가 먼저 읽혀야 한다) */
.ep-answer { margin-bottom: var(--ep-space-3); }
/* 운영자 답변 — 왼쪽 색 막대 대신 옅은 면으로 가른다(색 막대 금지 규칙) */
.ep-answer.is-staff { background: var(--ep-primary-softer); }

/* 드래그 손잡이 — ▲▼ 와 나란히 선다. 잡는 곳임이 보여야 하되 버튼처럼 보이면 안 된다
   (누르는 것이 아니라 «끄는» 것이라, 테두리를 주면 세 번째 버튼으로 읽힌다). */
.ep-reorder-grip {
  display: flex; align-items: center; justify-content: center;
  /* §8 최소 히트영역 40×40 — 24px 로 뒀다가 규약 위반으로 잡혔다(2026-09-01 리뷰).
     바로 위 ▲▼ 가 같은 이유로 22×20 에서 40×40 이 된 이력이 있는데 새 손잡이가 그걸 안 따랐다. */
  width: 40px; height: 40px; flex: none;
  color: var(--ep-text-faint); cursor: grab;
  /* 끌 때 글자가 딸려 잡히지 않게 */
  user-select: none;
}
.ep-reorder-grip:hover { color: var(--ep-primary); }
.ep-reorder-grip:active { cursor: grabbing; }
.ep-reorder-grip .msi { font-size: var(--ep-text-lg); line-height: 1; vertical-align: 0; }
/* 끌고 있는 행 — 어느 것을 잡았는지 보이지 않으면 어디에 놓았는지도 모른다 */
.ep-reorder-row-dragging { opacity: .45; }

/* ── 표가 든 모달 — 머리글·버튼은 제자리, 표만 구른다 (2026-09-01) ──────────
   차시 11개 × 영상 5칸이면 55줄이다. 모달 전체가 구르면 스크롤한 순간
   **표 머리글도 「등록」 버튼도 같이 사라져**, 지금 무슨 칸을 채우는지 모른 채 치다가
   저장하려고 끝까지 다시 내려가야 했다.

   ⚠️ 이 배치는 .is-grid 에만 건다. 짧은 폼 모달까지 flex 로 바꾸면 얻는 것 없이
      내부 여백만 달라진다. */
.ep-modal.is-grid {
  display: flex; flex-direction: column;
  overflow: hidden;              /* 스스로는 구르지 않는다 — 안쪽 한 곳만 구른다 */
}
/* 구르는 자리를 «표 근처» 로 좁히려면 그 위 조상들이 전부 높이를 넘겨줘야 한다.
   min-height:0 이 없으면 flex 아이템이 «내용만큼» 커져 버려 아무것도 구르지 않는다. */
.ep-modal.is-grid .ep-modal-host,
.ep-modal.is-grid .ep-modal-form,
.ep-modal.is-grid .bo-vb-form { display: flex; flex-direction: column; min-height: 0; }
.ep-modal.is-grid .ep-modal-form,
.ep-modal.is-grid .bo-vb-form { flex: 1; }
/* 가로·세로 «둘 다» 여기서 구른다 — 아래 .ep-table-wrap 의 자체 스크롤을 껐기 때문이다 */
.ep-modal.is-grid .bo-vb-scroll { flex: 1; min-height: 0; overflow: auto; }
/* ⚠️ 표 감싸개의 자체 스크롤을 끈다.
   .ep-table-wrap 은 좁은 화면용으로 overflow-x:auto 를 갖는데, 그러면 **자기가 스크롤 상자**가
   되어 sticky 머리글이 그쪽에 붙는다. 감싸개는 높이 제한이 없어 세로로 구르지 않으므로
   머리글은 아무 데도 안 붙고 그냥 위로 사라진다 — 처음에 이렇게 짰다가 실측으로 잡았다.
   가로 스크롤은 바깥(.bo-vb-scroll)이 대신 맡는다. */
.ep-modal.is-grid .bo-vb-scroll .ep-table-wrap { overflow: visible; }
/* 표 머리글은 구르는 자리의 맨 위에 붙는다. 배경이 없으면 줄이 비쳐 글자가 겹쳐 보인다. */
.ep-modal.is-grid .bo-vb-scroll .ep-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--ep-surface);
  /* sticky 로 뜬 칸은 원래 border-bottom 이 함께 안 따라온다 — 그림자로 선을 만든다 */
  box-shadow: inset 0 -1px 0 var(--ep-border);
}
/* 버튼 줄은 구르는 자리 «밖» 이라 늘 보인다 — 위에 선을 그어 표와 갈라 준다 */
/* margin-top(16) 위에 padding-top(16) 을 또 주면 선이 32px 여백 «한가운데» 뜬다 —
   표에 붙어야 할 선이라 margin 을 0 으로 두고 padding 만 남긴다(2026-09-01 리뷰 지적). */
.ep-modal.is-grid .ep-modal-actions {
  flex: none; margin-top: 0; padding-top: var(--ep-space-4);
  border-top: 1px solid var(--ep-border-soft);
}

/* 한 단계 아래 줄의 순서 조작.

   ⚠️ 크기를 줄이지 않는다. 처음에 30×30 으로 줄였다가 §8(최소 히트영역 40×40) 위반으로
      리뷰 셋에 동시에 잡혔다 — 그것도 바로 위에서 손잡이를 24→40 으로 올리며
      「§8 위반으로 잡혔다」 고 적어 놓은 그 커밋에서. 화면에서 개수가 가장 많은 줄이라
      오조준 피해도 거기서 제일 크다.
      계층은 «크기» 가 아니라 들여쓴 회색 트레이(.bo-lesson-items)와 옅은 테두리가 말한다. */
.ep-reorder.is-sm .ep-reorder-btn,
.ep-reorder.is-sm .ep-reorder-grip { width: 40px; height: 40px; }
.ep-reorder.is-sm .ep-reorder-btn { border-color: var(--ep-border-soft); }
.ep-reorder.is-sm .ep-reorder-btn .msi,
.ep-reorder.is-sm .ep-reorder-grip .msi { font-size: var(--ep-text-sm); }
