/* ==========================================================================
   EDUPIC LMS 유틸리티 레이어

   ⚠️ 이 파일은 모든 레이아웃에서 **가장 마지막**에 로드된다.
      common.css 안에 있으면 영역 CSS(admin/web/member/learning/tutor)가 뒤에 와서
      같은 특이도일 때 영역 쪽이 이긴다 — 실측으로 대시보드 아이콘 색이 밀렸다.

   ⚠️ 선택자를 겹쳐 쓴 이유(.ep-t-xs.ep-t-xs): 특이도는 순서보다 세다.
      영역 CSS 에 .bo-stat .icon 같은 2-클래스 규칙이 있으면 한 겹(0-1-0)짜리 유틸은
      마지막에 로드돼도 진다. 0-2-0 으로 맞춰야 비긴 뒤 순서로 이긴다.
      !important 를 쓰지 않는 이유는, 정말 덮어써야 할 때 방법이 남아 있어야 해서다.

   ⚠️ 유틸을 늘리기 전에 컴포넌트 클래스로 될 일인지 먼저 보라.
      (.ep-panel-title 처럼 역할이 있는 클래스가 먼저다)
   ========================================================================== */

.ep-muted.ep-muted { color: var(--ep-text-faint); }
.ep-num.ep-num { font-variant-numeric: tabular-nums; }
.ep-mt0.ep-mt0 { margin-top: 0; } .ep-mb0.ep-mb0 { margin-bottom: 0; }

/* ── 텍스트 유틸리티 ────────────────────────────────────────────────────
   화면에서 style="font-size: var(--ep-text-xs)" 를 쓰지 않게 하는 최소 세트다.
   토큰을 직접 쓰는 대신 이 클래스를 쓰면, 나중에 스케일을 조정할 때
   CSS 한 곳만 고치면 전 화면이 따라온다(= "공통으로 관리" 의 실체).

   ⚠️ 크기 클래스를 남발하지 마라. 컴포넌트에 이미 크기가 정해져 있으면
      (.ep-panel-title, .ep-label 등) 그걸 쓰는 게 먼저다.

   클래스를 겹쳐 쓴 이유(.ep-t-xs.ep-t-xs): 영역 CSS(web/admin/…)는 common.css
   **뒤에** 로드되므로, 클래스 하나짜리 규칙이면 거기 있는 font-size 에 그냥 진다.
   인라인 style 을 걷어내면서 실제로 1건이 12px→13px 로 밀렸다(admin/banners 의
   .ep-dl 과 겹친 곳). 특이도를 0-2-0 으로 올려 이기게 한다.
   font-size 를 클래스 2개 이상으로 정하는 규칙은 전 CSS 에 없어 이걸로 충분하다.
   !important 를 쓰지 않는 이유는, 정말 덮어써야 할 때 방법이 남아 있어야 해서다. */
.ep-t-2xs.ep-t-2xs { font-size: var(--ep-text-2xs); }
.ep-t-xs.ep-t-xs   { font-size: var(--ep-text-xs); }
.ep-t-sm.ep-t-sm   { font-size: var(--ep-text-sm); }
.ep-t-md.ep-t-md   { font-size: var(--ep-text-md); }
.ep-t-lg.ep-t-lg   { font-size: var(--ep-text-lg); }
.ep-t-xl.ep-t-xl   { font-size: var(--ep-text-xl); }
.ep-t-2xl.ep-t-2xl { font-size: var(--ep-text-2xl); }
.ep-t-3xl.ep-t-3xl { font-size: var(--ep-text-3xl); }
.ep-t-4xl.ep-t-4xl { font-size: var(--ep-text-4xl); }
.ep-t-5xl.ep-t-5xl { font-size: var(--ep-text-5xl); }   /* 결과 화면 대형 아이콘 — style="font-size:52px" 를 대신한다 */

/* 패널 안 구획 구분선 — style="border-top:…; padding-top:16px" 12곳을 대신한다 */
.ep-divider-top.ep-divider-top { border-top: 1px solid var(--ep-border-soft); padding-top: 16px; }
/* 위에 선이 있는 행 — 목록 행(.ep-list-row 는 아래 선)과 달리 머리글 아래부터 긋는 자리 */
.ep-row-divided.ep-row-divided { padding: var(--ep-space-2) 0; border-top: 1px solid var(--ep-border-soft); }
.ep-row-divided.is-roomy { padding: var(--ep-space-4) 0; }

/* ── 글자색 유틸 ─────────────────────────────────────────────────────────
   토큰 이름을 그대로 딴다(--ep-text-sub → .ep-c-sub). 화면에서
   style="color: var(--ep-text-muted)" 를 쓰지 않게 하는 세트다.
   .ep-t-* 와 같은 이유로 특이도를 겹쳤다 — 영역 CSS 가 뒤에 로드되기 때문. */
.ep-c-sub.ep-c-sub       { color: var(--ep-text-sub); }
.ep-c-muted.ep-c-muted   { color: var(--ep-text-muted); }
.ep-c-faint.ep-c-faint   { color: var(--ep-text-faint); }
.ep-c-primary.ep-c-primary { color: var(--ep-primary); }
.ep-c-red.ep-c-red       { color: var(--ep-red); }
.ep-c-amber.ep-c-amber   { color: var(--ep-amber); }
.ep-c-green.ep-c-green   { color: var(--ep-green); }
.ep-c-cyan.ep-c-cyan     { color: var(--ep-cyan); }
/* 본문색으로 되돌리는 자리 — 상위가 muted 인 블록 안에서 한 조각만 진하게 */
.ep-c-text.ep-c-text     { color: var(--ep-text); }
/* 아이콘 전용 회색 — --ep-text-faint 와 다른 토큰이다(#B4BED4 vs #6A7285).
   글자에 쓰면 대비 미달이라 이름을 따로 둔다. */
.ep-c-icon-faint.ep-c-icon-faint { color: var(--ep-icon-faint); }

/* ── 간격 유틸 ───────────────────────────────────────────────────────────
   실사용 값만 만든다. margin-bottom 만 179곳이 인라인이었고 그중 103곳이 16px,
   49곳이 8px 이었다 — 같은 뜻을 179번 다시 적고 있었던 셈이다.
   숫자는 --ep-space-N 의 N 을 그대로 쓴다(.ep-mb-4 = 16px). */
.ep-m0.ep-m0 { margin: 0; }   /* mt/mb 보다 앞에 둔다 — «ep-m0 ep-mt-1» 로 '위 4px 만' 을 표현하기 위해 */
.ep-mt-1.ep-mt-1 { margin-top: var(--ep-space-1); }
.ep-mt-2.ep-mt-2 { margin-top: var(--ep-space-2); }
.ep-mt-3.ep-mt-3 { margin-top: var(--ep-space-3); }
.ep-mt-4.ep-mt-4 { margin-top: var(--ep-space-4); }
.ep-mt-5.ep-mt-5 { margin-top: var(--ep-space-5); }
.ep-mt-6.ep-mt-6 { margin-top: var(--ep-space-6); }
.ep-mb-1.ep-mb-1 { margin-bottom: var(--ep-space-1); }
.ep-mb-2.ep-mb-2 { margin-bottom: var(--ep-space-2); }
.ep-mb-3.ep-mb-3 { margin-bottom: var(--ep-space-3); }
.ep-mb-4.ep-mb-4 { margin-bottom: var(--ep-space-4); }
.ep-mb-5.ep-mb-5 { margin-bottom: var(--ep-space-5); }
.ep-mb-6.ep-mb-6 { margin-bottom: var(--ep-space-6); }

/* ── 정렬·흐름 유틸 ──────────────────────────────────────────────────────
   .ep-grow 에 min-width:0 을 함께 넣지 않는다 — 그건 동작을 바꾸는 결정이라
   인라인을 클래스로 옮기는 작업(보이는 것이 안 바뀌어야 한다)과 섞으면 안 된다. */
.ep-ta-left.ep-ta-left   { text-align: left; }
.ep-ta-center.ep-ta-center { text-align: center; }
.ep-ta-right.ep-ta-right  { text-align: right; }
.ep-grow.ep-grow      { flex: 1; }
.ep-hidden.ep-hidden    { display: none; }
/* .ep-grow 의 빠진 반대짝들. grow 에 합치지 않는다 — 늘어나는 것과 줄지 않는 것은 다른 뜻이다. */
.ep-min0.ep-min0        { min-width: 0; }        /* flex 자식이 내용 최소폭까지 벌어지는 것을 막는다 */
.ep-flex-none.ep-flex-none { flex-shrink: 0; }   /* 배지·아이콘처럼 줄면 안 되는 것 */
.ep-block.ep-block { display: block; }   /* button 안에는 div 를 못 넣는다 — span 을 줄바꿈시킬 때 */
.ep-push-right.ep-push-right { margin-left: auto; }  /* 이 뒤는 반대쪽 끝으로 */

/* ── 텍스트 처리 유틸 ──────────────────────────────────────────────────── */
.ep-truncate.ep-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ep-multiline.ep-multiline { white-space: pre-line; }   /* 저장된 개행만 살린다(연속 공백은 접는다) */
.ep-value-break.ep-value-break { word-break: break-all; }  /* URL·토큰처럼 띄어쓰기 없는 값 */
/* 한 덩어리로 읽어야 하는 값 — 좁아져도 쪼개지 않는다.
   좁은 화면에서 「2026-09-12」가 「2026-09-」/「12」로, 「1 / 100」이 「1 /」/「100」으로
   갈라지던 것을 막는다. 한 낱말을 두 동강 내면 값이 두 개처럼 보인다.
   ⚠️ 표를 통째로 넓히지는 않는다 — 예전에 `.ep-table { min-width: max-content }` 를 넣었다가
      34개 화면이 데스크탑에서도 가로로 굴러 되돌린 이력이 있다(common.css 주석).
      여기서는 값 하나만 안 쪼개고, 정 안 들어가면 .ep-table-wrap 이 스스로 스크롤한다. */
.ep-nowrap.ep-nowrap { white-space: nowrap; }
/* 곁다리 열의 긴 이름을 두 줄에서 끊는다 — 전문은 hover(title)로 남긴다.
   목록의 «주» 열에는 쓰지 않는다. 거기서 잘리면 무엇인지 알 수 없다.
   공개·학습자 화면이 이미 같은 처리를 하고 있어(web.css·member.css) 여기로 이름을 맞춘다. */
.ep-clamp-2.ep-clamp-2 {
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
}
/* 링크지만 링크색을 쓰지 않는 자리(카드 전체가 링크인 경우). hover 색은 그대로 둔다. */
.ep-link-plain.ep-link-plain { color: inherit; }
.ep-icon-inline.ep-icon-inline { vertical-align: middle; }  /* .msi 기본 -3px 를 덮는다 */

/* ── 글자 굵기 유틸 ──────────────────────────────────────────────────────
   참조 실측: 인프런 5종(주력 400·700), Coursera 2종(400·600).
   우리는 본문 400 + 아래 3단. 관리자 표가 조밀해 강조 단계가 하나 더 필요하다.
   화면에서 font-weight 를 직접 적지 않는다 — 굵기가 화면마다 갈리는 원인이었다.
   .ep-t-* 와 같은 이유로 겹쳐 썼다(영역 CSS 가 뒤에 로드돼 한 겹으론 진다). */
/* 굵은 칸 «안» 의 보조 줄을 원래 굵기로 되돌린다 — 부모의 굵기가 그대로 내려오면
   제목 아래 요약 줄까지 굵어져 무엇이 제목인지 안 갈린다(2026-09-01 강좌 목록). */
.ep-w-reg.ep-w-reg     { font-weight: 400; }   /* 굵기 되돌리기 */
.ep-w-semi.ep-w-semi   { font-weight: 600; }   /* 라벨·보조 강조 */
.ep-w-bold.ep-w-bold   { font-weight: 700; }   /* 값·소제목 */
.ep-w-heavy.ep-w-heavy { font-weight: 800; }   /* 제목·숫자 강조 */
/* 입력칸을 줄 전체로 — style="width:100%" 를 대신한다. flex 자식일 때는 .ep-grow 를 쓴다 */
.ep-w-full.ep-w-full    { width: 100%; }

/* ── 입력칸 폭 단계 ──────────────────────────────────────────────────────
   style="width:NNpx" 가 12가지 값으로 23곳에 흩어져 있었다(84·90·100·110·
   120·130·140·180·190·200·220·240). 같은 뜻인데 값이 다르면 나란히 놓았을 때
   어긋난다. 4단계로 모은다 — flex 안에서 늘어나지 않도록 flex:none 을 함께 준다. */
.ep-input.is-w-xs, .ep-select.is-w-xs { width: 96px;  flex: none; }
.ep-input.is-w-sm, .ep-select.is-w-sm { width: 128px; flex: none; }
.ep-input.is-w-md, .ep-select.is-w-md { width: 192px; flex: none; }
.ep-input.is-w-lg, .ep-select.is-w-lg { width: 240px; flex: none; }
/* 좁은 화면에서는 줄 전체를 쓴다 — 고정폭이 겹쳐 가로 스크롤을 만드는 것보다 낫다 */
@media (max-width: 560px) {
  /* 속성 선택자 한 겹은 영역 CSS 의 2-클래스 규칙에 진다 — 클래스를 그대로 나열한다 */
  .ep-input.is-w-xs, .ep-select.is-w-xs,
  .ep-input.is-w-sm, .ep-select.is-w-sm,
  .ep-input.is-w-md, .ep-select.is-w-md,
  .ep-input.is-w-lg, .ep-select.is-w-lg { width: 100%; }
}
/* 내용에 맞춰 줄어드는 셀렉트 — 4단계 고정폭이 과한 자리(학습항목 유형 등 6곳).
   .is-compact 와 달리 padding 은 기본을 쓴다 */
.ep-input.is-w-auto, .ep-select.is-w-auto { width: auto; }
/* 위아래만 띄운 줄 — 구분선 없는 목록 행(통계·설문 집계 5곳) */
.ep-py-2.ep-py-2 { padding-top: var(--ep-space-2); padding-bottom: var(--ep-space-2); }
/* 클릭 가능한 label·summary — 커서 모양이 안 바뀌면 눌리는지 모른다 */
.ep-clickable.ep-clickable { cursor: pointer; }

/* 폼 한 칸의 폭 — 라벨+입력을 묶은 .ep-field 에도 같은 단계를 쓴다 */
.ep-field.is-w-sm { width: 128px; flex: none; }
/* 화면에는 안 보이고 스크린리더만 읽는 텍스트 — 체크박스 열의 th 처럼
   시각적으로는 비어야 하지만 이름은 있어야 하는 자리 */
.ep-sr-only.ep-sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* 폼 한 줄에 여러 칸을 늘어놓을 때 — 좁아지면 접히고, 각 칸이 최소폭을 지킨다 */
.ep-flex.ep-items-start { align-items: flex-start; }
/* 라벨이 있는 칸과 없는 칸(버튼)이 한 줄에 섞일 때 — **밑선**을 맞춘다.
   가운데 정렬로 두면 라벨 높이만큼 버튼이 위로 떠서 입력칸과 어긋난다
   (2026-08-28 주인님 지적. 그전에는 버튼마다 인라인 align-self 를 박아 개별로 맞췄고,
   그 규칙 밖에 있던 요소는 혼자 떠 있었다). */
.ep-flex.ep-items-end   { align-items: flex-end; }
/* 라벨 없이 높이가 다른 것들끼리(색 선택기·작은 버튼·경고 문구) 한 줄에 설 때 */
.ep-flex.ep-items-center { align-items: center; }
.ep-flex.ep-gap-lg      { gap: var(--ep-space-4); }
.ep-min-180.ep-min-180  { min-width: 180px; }
.ep-min-220.ep-min-220  { min-width: 220px; }
.ep-grow-2.ep-grow-2    { flex: 2; }
