/* 작업 도구용 공통 스타일. 정보 밀도 우선, 여백은 아끼고, 색은 최소한으로.
   상태 색(녹/빨/파/보라)은 블록 화면에서 쓸 예정이라 여기서는 무채색만 사용한다. */

/* 꾸미기 텍스트 레이어("고딕체"/"명조체")와 텍스트 블록 RTE 폰트 선택지 전용
   웹폰트. 배포 서버(특히 리눅스)에 한글 폰트가 전혀 없어도 편집 화면과 PNG
   내보내기(src/lib/decorationRender.js, src/lib/fontConfig.js)가 항상 같은
   public/fonts/*.ttf 파일을 쓰도록 프로젝트에 직접 번들한다 — 시스템 폰트
   이름(맑은 고딕/Malgun Gothic 등)에 기대지 않는다. */
@font-face {
  font-family: 'KoPubWorld Dotum';
  src: url('/fonts/KoPubWorldDotum-Medium.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'KoPubWorld Dotum';
  src: url('/fonts/KoPubWorldDotum-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'KoPubWorld Batang';
  src: url('/fonts/KoPubWorldBatang-Medium.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'KoPubWorld Batang';
  src: url('/fonts/KoPubWorldBatang-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: #f4f4f3;
  --surface: #ffffff;
  --border: #e0e0dd;
  --border-strong: #cbcbc7;
  --text: #1c1c1a;
  --text-muted: #6b6b66;
  --text-faint: #9a9a95;
  --primary: #24211d;
  --primary-hover: #000000;
  --danger: #8a2f2f;
  /* 확정 표시 전용 색. 상태 열의 초안(녹)/피드백(빨)/수정(파)/완료(보라)와
     겹치지 않도록 호박색 계열을 쓴다. */
  --confirm: #a8790a;
  --confirm-bg: #faf1de;
  /* 레이어 목록 항목 hover 시 캔버스 오브젝트 강조 전용. 선택(--primary,
     근검정)·확정(--confirm, 호박)·상태 열의 초안/피드백/수정/완료(녹/빨/파/보라)
     와 안 겹치는 청록 계열로 분리했다. */
  --layer-hover-highlight: #0ea5b7;
  /* 상태 열(작업자 흐름 보기 .flow-col-status, 모바일 스트립
     .mobile-strip-status) 색의 단일 정의처 — 두 군데(+ review.html은 같은
     CSS를 공유하니 사실상 세 화면)에 각자 색을 박아두면 한쪽만 고쳐지는
     드리프트가 생기기 쉬워 여기 하나로 모은다. 예전엔 옅은 파스텔이라
     스토리 배경색이 흰색일 때만 어울렸다(배경색을 바꾸면 셀이 묻힘) — 진한
     원색 + 흰 글자로 바꿔 배경색과 무관하게 항상 읽히게 했다. 초안(녹)/
     피드백(빨)/수정(파)/완료(보라) 기존 색상 계열은 그대로 두고 명도만
     낮췄다. 흰 글자 기준 WCAG 대비: 초안 5.0 · 피드백 6.5 · 수정 6.7 ·
     완료 7.1(전부 AA 4.5:1 이상, 대부분 AAA 7:1도 통과). */
  --status-draft-bg: #15803d;
  --status-feedback-bg: #b91c1c;
  --status-revising-bg: #1d4ed8;
  --status-done-bg: #6d28d9;
  --status-fg: #ffffff;
  --radius: 4px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Pretendard", "Malgun Gothic", sans-serif;
  /* 일반 페이지·editor·review·admin이 전부 같은 .app-header를 쓴다(오버레이
     메뉴 개편) — 상단 바 높이와 햄버거 크기를 여기 한 곳에서만 정해서 페이지마다
     다른 값을 쓰는 일이 없게 한다. .app-menu-toggle은 이 두 값으로 top을
     calc()해 항상 상단 바 세로 중앙에 오도록 계산한다(고정 top:10px처럼 상단
     바 높이가 조금만 달라져도 어긋나는 값을 안 쓴다). */
  --app-header-height: 56px;
  --app-menu-toggle-size: 36px;
  /* 햄버거 오른쪽 끝과 상단 바 콘텐츠 사이 최소 여백. 이게 없으면(0이면)
     콘텐츠가 햄버거 바로 옆에 딱 붙어 좁은 폭에서 서로 침범하는 것처럼
     보인다 — 로고 글자가 실제로 햄버거 박스 위에 겹치지는 않아도(패딩이
     그건 막아준다) 여백 없이 붙어 있으면 그렇게 읽힌다. */
  --app-menu-toggle-gutter: 8px;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

/* overflow-x: hidden은 html에만 둔다(body에는 두지 않는다).
   [원래 목적] 모바일 스트립(.mobile-strip)이 닫힌 상태에서 transform:
   translateX(100%)로 화면 밖에 위치하는데, 이게 문서의 가로 스크롤 폭을
   실제로 늘려버려서 position: fixed; right: 0인 핸들의 기준점이 진짜 화면
   오른쪽이 아니라 그 늘어난 문서 폭의 오른쪽이 되어 화면 밖으로 밀려나는
   문제가 있었다. off-canvas 패널 패턴에서는 항상 필요한 가드.
   [왜 body에는 안 두는가 — 2026-08-15 발견] 예전엔 html/body 둘 다에 걸려
   있었는데, 이게 후보 바(.slot-candidate-bar)의 position: sticky를 완전히
   무력화시키는 원인이었다(실측 확인). CSS overflow 스펙상 overflow-x가
   visible이 아니면 같은 요소의 overflow-y도 강제로 auto가 된다 — body에
   overflow-x: hidden을 두면 body의 overflow-y도 auto가 되어 body 자체가
   "스크롤 박스"가 된다. 그러면 position: sticky가 기준으로 삼는 스크롤
   컨테이너가 실제 스크롤이 일어나는 html이 아니라 이 (스스로는 절대
   스크롤되지 않는) body가 되어버려서, sticky 요소가 항상 제자리(정적 위치)에
   머무는 것처럼 보인다 — 화면 하단에 전혀 붙지 않는 버그로 나타났다.
   html에만 남겨도 위 목적은 그대로 유지된다 — document.scrollingElement가
   html이라(html 자체의 overflow가 non-visible이라 body→html propagation이
   안 됨), 사용자가 체감하는 가로 스크롤(휠/터치/스크롤바)은 html의
   overflow-x만으로 이미 완전히 막힌다. body에 남겨 봐야 얻는 게 없고
   sticky만 깨뜨렸다. */
html {
  overflow-x: hidden;
}

body {
  font-family: var(--font);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text);
  background: var(--bg);
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

h1,
h2,
h3,
p {
  margin: 0;
}

.hidden {
  display: none !important;
}

/* --- layout --- */

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--app-header-height);
  box-sizing: border-box;
  /* 왼쪽 여백 = 햄버거 left(16px) + 햄버거 너비 + gutter — 좁은 폭에서도
     콘텐츠가 항상 햄버거 오른쪽에서 최소 gutter만큼 떨어져 시작한다. */
  padding: 0 20px 0 calc(16px + var(--app-menu-toggle-size) + var(--app-menu-toggle-gutter));
  gap: 12px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.app-header .brand-group {
  display: flex;
  align-items: baseline;
  gap: 6px;
  /* 로고+워크스페이스명이 header-actions(벨/로그인 버튼)를 밀어내거나
     그 위로 넘치지 않고, 남는 폭 안에서 줄어들다가 잘리게 한다. */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
}

.app-header .brand {
  flex-shrink: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: inherit;
  text-decoration: none;
}

.app-header .workspace-link {
  flex-shrink: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text-muted);
  font-weight: 400;
  font-size: 13px;
  text-decoration: none;
}

.app-header .workspace-link:hover {
  text-decoration: underline;
}

.app-header .header-actions {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: 12px;
}

.client-name-display {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: var(--text-muted);
}

.name-confirm-desc {
  font-size: 12.5px;
  color: var(--text-muted);
  margin: -4px 0 14px;
}

.main {
  max-width: 1040px;
  margin: 0 auto;
  padding: 20px;
}

/* --- 오버레이 메뉴 (햄버거 + 슬라이드 패널) ---
   구 좌측 고정 사이드바(펼침 220px/접힘 60px가 본문을 밀어내던 2단 레이아웃)를
   대체한다. 콘텐츠는 항상 전체 폭을 쓰고, 메뉴는 본문 위에 뜨는 오버레이라
   열고 닫아도 레이아웃이 움직이지 않는다.
   마크업 자체를 public/js/app-menu.js가 만들어 body에 붙인다 — 페이지마다
   손으로 복붙하던 걸 없애는 게 이번 작업의 목적이라 HTML에는 이 구조를 다시
   손으로 두지 않는다. 일반 페이지(9곳+admin.html)와 editor.html/review.html이
   전부 이 CSS 클래스 하나를 공유한다.
   z-index는 .modal-overlay(10)/decoration-modal(8)보다 낮게 둔다 — 메뉴는
   상시 떠 있는 내비게이션 chrome일 뿐이라, 실제 확인/편집을 막는 모달이 열려
   있으면 모달이 항상 메뉴 위에 그려져야 한다(모달이 열린 채로 메뉴를 여는
   경우 자체가 없어야 정상이지만, z-index만으로도 그 우선순위를 보장해둔다). */

.app-menu-toggle {
  position: fixed;
  top: calc((var(--app-header-height) - var(--app-menu-toggle-size)) / 2);
  left: 16px;
  z-index: 7;
  width: var(--app-menu-toggle-size);
  height: var(--app-menu-toggle-size);
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 8px;
  background: var(--surface);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.16);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
}

.app-menu-toggle:hover {
  background: #f0f0f0;
}

.app-menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 5;
  background: rgba(0, 0, 0, 0);
  opacity: 0;
  transition: opacity 0.2s ease, background-color 0.2s ease;
}

.app-menu-backdrop.is-open {
  background: rgba(0, 0, 0, 0.35);
  opacity: 1;
}

.app-menu-panel {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 6;
  width: 300px;
  max-width: 85vw;
  box-sizing: border-box;
  /* 위쪽 여백을 늘려 항상 같은 자리(top:10px/left:16px)에 뜨는
     .app-menu-toggle(높이 36px, 닫기 상태에선 ✕)와 첫 메뉴 항목이 겹치지
     않게 한다. */
  padding: 56px 12px 20px;
  background: var(--surface);
  box-shadow: 2px 0 16px rgba(0, 0, 0, 0.18);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  transform: translateX(-100%);
  transition: transform 0.2s ease;
}

.app-menu-panel.is-open {
  transform: translateX(0);
}

/* 메뉴가 열려 있는 동안 배경 스크롤을 잠근다 — 메뉴 항목이 화면 높이를
   넘으면 패널 안(overflow-y:auto)에서만 스크롤돼야 하고 본문은 움직이면
   안 된다. */
html.app-menu-open {
  overflow: hidden;
}

.app-menu-workspace-mobile {
  display: none;
  padding: 4px 12px 14px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}

.app-menu-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.app-menu-item {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  text-decoration: none;
  text-align: left;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-menu-item:hover {
  background: #f5f5f5;
}

.app-menu-item.active {
  background: #eef2ff;
  font-weight: 600;
}

.app-menu-divider {
  height: 1px;
  margin: 8px 4px;
  background: var(--border);
}

.app-menu-section-label {
  padding: 4px 12px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-faint);
}

.app-menu-bell-mount:empty {
  display: none;
}

/* editor.html/review.html 메뉴는 새 버튼을 만드는 대신 기존 툴바 컨트롤
   (가로 폭 배지 .story-width-badge, 배경색 드롭다운 .rte-dropdown, 내보내기/
   초대 관리 .btn, 흐름·목록 보기 .view-toggle)을 그대로 옮겨 붙인다. 이
   선택자들은 .app-menu-list 밑에 있을 때만 적용돼 다른 화면의 같은 클래스에는
   영향이 없다 — 툴바용 필/세그먼트 모양 대신 메뉴 목록에 맞는 전체 폭 행으로
   보이게 스코프를 좁혀 재정의한다. */
.app-menu-list .story-width-badge,
.app-menu-list .btn {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  margin: 0;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font-size: 13px;
  text-align: left;
}

.app-menu-list .story-width-badge:hover,
.app-menu-list .btn:hover {
  background: #f5f5f5;
  border-color: transparent;
  color: inherit;
}

.app-menu-list .view-toggle {
  display: flex;
  flex-direction: column;
  gap: 2px;
  border: none;
  margin: 0;
}

.app-menu-list .view-toggle button {
  padding: 10px 12px;
  border-radius: 8px;
  text-align: left;
  font-size: 13px;
  color: inherit;
  background: transparent;
}

.app-menu-list .view-toggle button.active {
  background: #eef2ff;
  color: inherit;
  font-weight: 600;
}

.app-menu-list #story-bgcolor-control {
  width: 100%;
}

.app-menu-list .client-name-display {
  padding: 10px 12px 4px;
  font-size: 13px;
  color: var(--text);
}

@media (max-width: 767px) {
  /* 워크스페이스명은 PC에선 상단바(.app-header .workspace-link)에 두고,
     모바일에선 헤더 자리가 좁아 메뉴 맨 위로 옮긴다 — 표시 위치가 하나로
     고정돼 있어야 하므로 PC 쪽은 숨긴다. */
  .app-header .workspace-link {
    display: none;
  }

  .app-menu-workspace-mobile:not(:empty) {
    display: block;
  }

  .app-menu-panel {
    width: 70vw;
  }

  /* 44px 터치 타겟 규칙과 상단 바 높이를 같이 키운다 — 버튼 크기만 min-width/
     min-height로 키우고 top(고정 px)은 그대로 두면, 상단 바 높이는 그대로인데
     버튼만 커져서 위/아래로 삐져나오거나 중앙이 어긋난다. 두 토큰을 함께
     바꾸면 .app-menu-toggle의 top: calc(...)이 자동으로 다시 맞는 값을 낸다.
     :root {} 안에서 재정의해야 하위 규칙에 실제로 반영된다(다른 셀렉터에
     새로 선언하면 특정도 문제로 씹힐 수 있다). */
  :root {
    --app-header-height: 64px;
    --app-menu-toggle-size: 44px;
  }

  .app-menu-item {
    display: flex;
    align-items: center;
    min-height: 44px;
  }

  /* editor.html/review.html에서 메뉴 안으로 옮겨 붙인 기존 컨트롤들
     (.app-menu-item 클래스가 없는 .story-width-badge/.btn/.view-toggle
     button)도 같은 44px 터치 타겟 규칙을 받아야 한다 — 이 프로젝트에서
     반복된 지점(우상단 칩 등)이라 놓치지 않는다. */
  .app-menu-list .story-width-badge,
  .app-menu-list .btn,
  .app-menu-list .view-toggle button {
    display: flex;
    align-items: center;
    min-height: 44px;
    box-sizing: border-box;
  }
}

/* --- 홈 배너(2레이어 크로스페이드 슬라이드) --- */

.home-banner {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 1;
  border-radius: 12px;
  overflow: hidden;
  margin-bottom: 20px;
  background: #f0f0f0;
}

.home-banner-layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  transform: translateX(20px);
  transition: opacity 0.6s ease, transform 0.6s ease;
  pointer-events: none;
}

.home-banner-layer.active {
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}

.home-banner-layer.exiting {
  opacity: 0;
  transform: translateX(-20px);
}

.home-banner-layer a {
  display: block;
  width: 100%;
  height: 100%;
}

.home-banner-layer img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.home-banner-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.4);
  color: #fff;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
}

.home-banner-nav.prev {
  left: 12px;
}

.home-banner-nav.next {
  right: 12px;
}

.home-banner-dots {
  position: absolute;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 6px;
  z-index: 2;
}

.home-banner-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  cursor: pointer;
}

.home-banner-dot.active {
  background: #fff;
}

.page-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.page-title-row h1 {
  font-size: 16px;
  font-weight: 600;
}

/* --- buttons --- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 12px;
  font-size: 13px;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

.btn:hover {
  border-color: var(--text-faint);
}

.btn-primary {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

.btn-primary:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
}

.btn-text {
  border: none;
  background: none;
  padding: 4px 6px;
  color: var(--text-muted);
  cursor: pointer;
}

.btn-text:hover {
  color: var(--text);
}

/* --- table --- */

/* 좁은 화면에서 표를 억지로 욱여넣으면(특히 공백 없는 한글 제목) 글자 단위로
   세로 줄바꿈되며 레이아웃이 무너진다 — 열 너비를 자연스러운 크기로 유지하고,
   대신 표 전체를 가로 스크롤시킨다. */
.table-scroll {
  overflow-x: auto;
}

.story-table {
  width: 100%;
  min-width: 640px;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.story-table th,
.story-table td {
  padding: 9px 14px;
  text-align: left;
  border-bottom: 1px solid var(--border);
}

.story-table th {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: #fafaf9;
}

.story-table tbody tr:hover {
  background: #fafaf9;
}

.story-table tbody tr:last-child td {
  border-bottom: none;
}

.story-table td.num {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.story-table td.editable-cell {
  cursor: pointer;
}

.story-table td.editable-cell:hover {
  color: var(--text);
  text-decoration: underline dashed var(--text-faint);
  text-underline-offset: 3px;
}

.story-table td.muted {
  color: var(--text-faint);
  font-size: 12.5px;
  white-space: nowrap;
}

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  margin-top: 16px;
}

.pagination-page-btn {
  min-width: 32px;
  justify-content: center;
}

.pagination-arrow-btn {
  min-width: 32px;
  justify-content: center;
  font-size: 15px;
  line-height: 1;
}

.pagination-page-btn.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

.pagination button:disabled {
  opacity: 0.4;
  cursor: default;
}

.pagination button:disabled:hover {
  border-color: var(--border-strong);
}

.story-table .empty {
  text-align: center;
  color: var(--text-faint);
  padding: 32px 14px;
}

/* 좁은 화면에서는 가로 스크롤 대신 행마다 카드로 쌓는다 — 표 콘텐츠 폭(640px)이
   화면 폭보다 훨씬 넓어지면 스크롤 힌트가 약해 "작업" 열이 가려진 걸 알아채기
   어렵다. thead를 숨기고 각 셀 앞에 data-label(JS가 채움)로 라벨을 붙여
   제목/가로 폭/단 개수/수정일/작업을 세로로 쌓는다. */
@media (max-width: 767px) {
  .table-scroll {
    overflow-x: visible;
  }

  .story-table {
    display: block;
    min-width: 0;
  }

  .story-table thead {
    display: none;
  }

  .story-table tbody {
    display: block;
  }

  .story-table tbody tr {
    display: block;
    border-bottom: 8px solid var(--bg);
  }

  .story-table tbody tr:last-child {
    border-bottom: none;
  }

  .story-table td {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    white-space: normal;
  }

  .story-table tbody tr td:last-child {
    border-bottom: none;
  }

  .story-table td::before {
    content: attr(data-label);
    font-size: 10px;
    font-weight: 600;
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }

  .story-table .story-title-cell-inner,
  .story-table .story-actions-cell-inner {
    height: auto;
  }
}

.story-link {
  font-weight: 500;
  white-space: nowrap;
}

.list-toolbar {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
}

.list-toolbar #story-search {
  flex: 1;
  max-width: 320px;
  padding: 7px 9px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

.list-toolbar #story-sort {
  padding: 7px 9px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}

/* <td>에 직접 display:flex를 주면 그 셀이 테이블 레이아웃에서 벗어나 행 높이가
   아니라 내용물 높이만큼만 차지하게 돼, 같은 행의 다른 셀보다 짧아지고
   border-bottom(구분선)이 위로 어긋나 보인다. <td> 자체는 일반 table-cell로 두고,
   가로 정렬은 안쪽 래퍼 div(.story-title-cell-inner)에서만 담당한다. */
.story-title-cell-inner {
  display: flex;
  align-items: center;
  height: 100%;
  gap: 6px;
}

.btn-icon {
  border: none;
  background: none;
  padding: 2px 4px;
  color: var(--text-faint);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
}

.btn-icon:hover {
  color: var(--text);
}

/* .story-title-cell-inner와 같은 이유로 <td> 자체가 아니라 안쪽 래퍼에만 flex를 준다. */
.story-actions-cell-inner {
  display: flex;
  align-items: center;
  height: 100%;
  gap: 6px;
  white-space: nowrap;
}

.account-dropdown-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 12.5px;
  padding: 4px 0;
}

.account-dropdown-row + .account-dropdown-row {
  border-top: 1px solid var(--border);
}

.account-dropdown-label {
  color: var(--text-faint);
}

.notif-menu {
  position: relative;
}

.notif-bell-btn {
  position: relative;
  font-size: 15px;
}

.notif-badge {
  position: absolute;
  top: -2px;
  right: -4px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 9.5px;
  font-weight: 600;
  line-height: 15px;
  text-align: center;
}

.notif-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: 320px;
  max-height: 420px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  z-index: 20;
}

.notif-dropdown-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 12.5px;
  font-weight: 600;
  position: sticky;
  top: 0;
  background: var(--surface);
}

.notif-empty {
  padding: 24px 12px;
  text-align: center;
  color: var(--text-faint);
  font-size: 12.5px;
}

.notif-item {
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}

.notif-item:last-child {
  border-bottom: none;
}

.notif-item:hover {
  background: var(--bg);
}

.notif-item.is-unread {
  background: var(--confirm-bg);
}

.notif-item.is-unread:hover {
  background: var(--confirm-bg);
  filter: brightness(0.97);
}

.notif-item-message {
  font-size: 12.5px;
  color: var(--text);
  margin-bottom: 3px;
}

.notif-item-meta {
  font-size: 11px;
  color: var(--text-faint);
}

.story-link:hover {
  text-decoration: underline;
}

/* --- forms --- */

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 12px;
}

.field label {
  font-size: 12px;
  color: var(--text-muted);
}

/* 여러 .field를 한 줄에 나란히(예: 기간 조회의 시작일/종료일/버튼) */
.field-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  flex-wrap: wrap;
}

.field-row .field {
  margin-bottom: 12px;
}

.field-row .btn {
  margin-bottom: 12px;
}

.field input {
  padding: 7px 9px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

.field input:focus {
  outline: none;
  border-color: var(--text);
}

.field-checkbox {
  flex-direction: row;
  align-items: center;
}

.field-checkbox label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
}

.field-checkbox input[type='checkbox'] {
  width: auto;
  padding: 0;
  border: none;
  background: none;
}

.field-error {
  font-size: 12px;
  color: var(--danger);
  margin: -4px 0 12px;
}

.field-hint {
  font-size: 12px;
  color: var(--danger);
  margin-top: 2px;
}

.phone-field-row {
  display: flex;
  gap: 6px;
}

.phone-field-row select {
  flex: 0 0 auto;
  max-width: 45%;
  padding: 7px 9px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

.phone-field-row input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 9px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

/* --- modal --- */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
}

/* confirmModal()은 다른 모달(예: 초대 관리) 위에 중첩되어 열릴 수 있는 공용
   확인창이라, 같은 z-index로는 DOM 순서에 따라 뒤에 가려질 수 있다. 항상
   맨 위에 뜨도록 별도로 높인다. */
#confirm-modal {
  z-index: 20;
}

.modal {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 20px;
  width: 320px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}

.modal h2 {
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 14px;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 6px;
}

/* 피드백·이력 모달의 닫기/등록 버튼만 왼쪽 끝으로 옮긴다 — 모바일에서 이
   모달을 열어둔 채 본문 컨트롤 스트립(.mobile-strip, 화면 오른쪽에 겹치는
   z-index:20)이 함께 열려 있으면, 오른쪽 끝에 있던 버튼이 스트립에 가려져
   눌리지 않는 문제가 있었다. */
#feedback-modal .modal-actions,
#history-modal .modal-actions {
  justify-content: flex-start;
}

/* --- auth page --- */

.auth-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.auth-box {
  width: 320px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
}

.auth-box h1 {
  font-size: 15px;
  font-weight: 600;
  margin-bottom: 16px;
  text-align: center;
}

.verify-email-message {
  font-size: 13px;
  text-align: center;
  margin-bottom: 16px;
}

.verify-email-cta {
  display: block;
  width: 100%;
  text-align: center;
}

.tabs {
  display: flex;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  margin-bottom: 18px;
  overflow: hidden;
}

/* 탭이 9개라 좁은 폭에서는 한 줄에 다 안 들어간다 — select 드롭다운으로
   바꾸는 대신 가로 스크롤로 처리한다(데스크톱과 상호작용 방식을 그대로
   유지하기 위해: 클릭 한 번으로 전환되는 게 desktop과 동일). 스크롤이
   더 있다는 단서는 JS(admin.html)가 scrollLeft를 보고 .has-more-right를
   토글해 오른쪽 페이드로 보여준다 — 잘린 탭이 있는 줄 모르면 없는 기능이
   되므로 필수. */
.admin-tabs-wrap {
  position: relative;
  margin-bottom: 24px;
}

.admin-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.admin-tabs-wrap.has-more-right::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 1px;
  width: 28px;
  background: linear-gradient(to right, transparent, var(--surface));
  pointer-events: none;
}

.admin-tab {
  flex: 0 0 auto;
  white-space: nowrap;
  border: none;
  background: none;
  padding: 10px 14px;
  font-size: 13px;
  color: var(--text-muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.admin-tab:hover {
  color: var(--text);
}

.admin-tab.active {
  color: var(--text);
  font-weight: 600;
  border-bottom-color: var(--primary);
}

.admin-tab-panel {
  margin-bottom: 36px;
}

@media (max-width: 767px) {
  /* 다른 곳(오버레이 메뉴)의 44px 터치 타겟 규칙과 맞춘다 — 가로 스크롤로
     좁게 나열되는 탭일수록 오터치가 더 잘 나므로 오히려 이쪽이 더 중요하다. */
  .admin-tab {
    display: flex;
    align-items: center;
    min-height: 44px;
  }
}

.dashboard-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
  max-width: 720px;
}

.dashboard-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 16px;
}

.dashboard-card-value {
  font-size: 28px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.dashboard-card-label {
  font-size: 12px;
  color: var(--text-muted);
  margin-top: 4px;
}

/* --- 대시보드/사용량 페이지: 사용량 도넛(파이) 차트 --- */

.usage-chart-section {
  margin-top: 28px;
}

.usage-chart-row {
  display: flex;
  align-items: center;
  gap: 32px;
  flex-wrap: wrap;
}

.usage-donut-fill {
  transition: stroke-dasharray 0.4s ease;
}

.usage-donut-percent {
  font-size: 20px;
  font-weight: 700;
  fill: var(--text);
}

.usage-donut-sublabel {
  font-size: 9px;
  fill: var(--text-muted);
}

.usage-legend {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: 320px;
}

.usage-legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}

.usage-legend-swatch {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 3px;
}

.usage-legend-swatch.used {
  background: var(--primary);
}

.usage-legend-swatch.remaining {
  background: var(--border);
}

.usage-legend-text {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.usage-legend-text strong {
  font-weight: 600;
}

.usage-legend-text span:last-child {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.dashboard-card-signup-limit .dashboard-card-value {
  font-size: 16px;
}

/* 라벨/버튼이 카드 폭에 눌려 "저장"이 "저"/"장"으로, "가입 제한 인원(현재/제한)"이
   "한)"만 떨어져 세로로 줄바꿈되던 문제 — 한 줄 유지 + 줄어들지 않게 고정한다. */
.dashboard-card-signup-limit .dashboard-card-label {
  white-space: nowrap;
}

.signup-limit-form {
  display: flex;
  gap: 6px;
  margin-top: 10px;
}

.signup-limit-form input {
  width: 80px;
  flex-shrink: 0;
  padding: 6px 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

.signup-limit-form button {
  white-space: nowrap;
  flex-shrink: 0;
}

.admin-clickable-row {
  cursor: pointer;
}

.user-detail-panel {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.user-detail-panel h2 {
  font-size: 15px;
  font-weight: 600;
}

.user-detail-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.user-detail-profile {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 24px;
  margin-bottom: 20px;
  font-size: 12.5px;
}

.user-detail-profile .account-dropdown-row {
  border-top: none;
  padding: 0;
  gap: 6px;
}

.note-form {
  margin: 10px 0 18px;
}

.note-form textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  font-family: var(--font);
  resize: vertical;
  margin-bottom: 8px;
}

.note-form-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.note-item {
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}

.note-item:last-child {
  border-bottom: none;
}

.note-item-meta {
  font-size: 11.5px;
  color: var(--text-faint);
  margin-bottom: 4px;
}

.note-item-body {
  font-size: 13px;
  white-space: pre-wrap;
}

.note-item-thumbs {
  display: flex;
  gap: 8px;
  margin-top: 8px;
  flex-wrap: wrap;
}

.note-item-thumbs img {
  width: 72px;
  height: 72px;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

.admin-profile-form {
  max-width: 360px;
}

.field-success {
  font-size: 12px;
  color: #2f7d32;
  margin: -4px 0 12px;
}

.admin-section {
  margin-bottom: 36px;
}

.admin-section .page-title-row {
  align-items: center;
}

.admin-inline-form {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
}

.admin-inline-form input {
  flex: 1;
  padding: 7px 9px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

.admin-totals {
  font-size: 12px;
  color: var(--text-muted);
}

.admin-subheading {
  font-size: 13px;
  font-weight: 600;
  margin: 0 0 8px;
}

.pending-queue-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 18px;
}

.pending-queue-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: var(--confirm-bg);
  border: 1px solid var(--confirm);
  border-radius: var(--radius);
  font-size: 13px;
}

.pending-queue-row span {
  flex: 1;
}

.status-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 500;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid transparent;
}

.status-badge-approved {
  color: #2f7d32;
  border-color: #2f7d32;
}

.status-badge-pending {
  color: var(--confirm);
  border-color: var(--confirm);
}

.status-badge-rejected {
  color: var(--danger);
  border-color: var(--danger);
}

.notify-cta {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 100;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.18);
  border-radius: 999px;
  transition: bottom 0.15s ease;
}

/* 후보 바(.slot-candidate-bar)가 펼쳐져 화면 하단에 걸려 있는 동안만 그
   높이만큼 밀어 올린다(review.js가 candidatebar:toggle 이벤트로 토글) —
   접힌 핸들은 높이가 작아 겹쳐도 무리가 없어서 그 상태는 그대로 둔다. */
.notify-cta.is-shifted {
  bottom: 170px;
}

.notify-cta .btn {
  border-radius: 999px;
  padding: 12px 22px;
  font-size: 13.5px;
  font-weight: 600;
}

.notify-cta .btn.is-sent {
  background: var(--text-faint);
  border-color: var(--text-faint);
  cursor: default;
}

.beta-banner {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  margin: 0 0 16px;
}

.email-verify-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--confirm);
  background: var(--confirm-bg);
  border: 1px solid var(--confirm);
  border-radius: var(--radius);
  padding: 10px 14px;
  margin: 0 0 16px;
}

.email-verify-banner-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.tabs button {
  flex: 1;
  padding: 7px 0;
  border: none;
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 13px;
}

.tabs button.active {
  background: var(--primary);
  color: #fff;
}

.auth-box .btn-primary {
  width: 100%;
  justify-content: center;
  padding: 8px 0;
  margin-top: 4px;
}

/* --- editor page --- */

.back-link {
  font-size: 14px;
  font-weight: 600;
}

.back-link:hover {
  text-decoration: underline;
}

.editor-title-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 14px;
}

.editor-title-row h1 {
  font-size: 17px;
  font-weight: 600;
}

.story-width-badge {
  font-size: 12px;
  color: var(--text-muted);
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 2px 8px;
  border-radius: 999px;
  cursor: pointer;
}

.story-width-badge:hover {
  border-color: var(--text-faint);
  color: var(--text);
}

#export-open-btn {
  margin-left: auto;
}

/* --- 클라이언트 초대 모달 --- */

.invite-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: 18px 0 14px;
}

.invite-list-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 10px;
}

.invite-empty {
  font-size: 13px;
  color: var(--text-faint);
  font-style: italic;
}

.invite-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.invite-row:last-child {
  border-bottom: none;
}

.invite-row.is-revoked {
  opacity: 0.55;
}

.invite-row-info {
  min-width: 0;
}

.invite-row-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 6px;
}

.invite-revoked-badge {
  font-size: 10.5px;
  font-weight: 500;
  color: var(--danger);
  border: 1px solid var(--danger);
  border-radius: 999px;
  padding: 1px 6px;
}

.invite-row-meta {
  font-size: 11.5px;
  color: var(--text-muted);
  margin-top: 2px;
}

.invite-row-actions {
  display: flex;
  gap: 6px;
  flex: 0 0 auto;
}

.invite-upload-toggle {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  color: var(--text-muted);
  margin-top: 4px;
  cursor: pointer;
}

/* --- 내보내기 모달 --- */

.export-warnings {
  background: #fbe6e4;
  border: 1px solid #f0c4bf;
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-bottom: 14px;
}

.export-warnings p {
  font-size: 12.5px;
  color: #8a3428;
  margin: 0;
}

.export-warnings p + p {
  margin-top: 4px;
}

.export-section {
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.export-section:first-of-type {
  border-top: none;
  padding-top: 0;
}

.export-section h3 {
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 4px;
}

.export-desc {
  font-size: 12px;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.export-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.export-status {
  font-size: 12px;
  color: var(--confirm);
}

.export-video-list {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 8px 9px;
  font-size: 12.5px;
  font-family: inherit;
  margin-bottom: 8px;
}

.main-wide {
  max-width: none;
}

.view-toggle {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 14px;
}

.view-toggle button {
  padding: 6px 14px;
  border: none;
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 12.5px;
}

.view-toggle button.active {
  background: var(--primary);
  color: #fff;
}

/* --- 흐름 보기: 초기 설계의 5열 구조 (설정/번호/본문/상태/피드백·이력) --- */

.flow-container {
  /* --story-content-bg는 :root에 인라인으로 세팅된다(스토리별 배경색 설정,
     renderHeader()/story-bgcolor 컨트롤 참고) — 지정 안 하면 기존 흰색 그대로. */
  background: var(--story-content-bg, var(--surface));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px 0;
  display: flex;
  justify-content: center;
}

.flow-page {
  display: flex;
  flex-direction: column;
}

.flow-row {
  display: flex;
  align-items: stretch;
}

.flow-row.is-held {
  filter: grayscale(1);
  opacity: 0.5;
}

/* 블록 행과 같은 5열 폭에 맞춘 항목명 행 */
.flow-header-row {
  display: flex;
  align-items: stretch;
}

/* 5개 칸이 각자 다른 padding-top/flex-direction/font-size 등을 물려받아
   세로 위치가 제각각이었다(예: .flow-col-status는 font-size:12px를 따로 갖고
   있어서 나머지 11px 칸들과 미묘하게 어긋났다). 클래스 2개짜리 선택자
   (.flow-col-X.flow-header-cell)로 개별 열 규칙보다 항상 더 높은 우선순위를
   갖게 하고, 레이아웃·타이포그래피 값을 전부 이 한 규칙으로 통일한다 —
   5칸 모두 같은 높이, 같은 폰트, 텍스트는 정확히 세로 가운데(= 같은
   baseline y좌표)에 오도록. */
.flow-col-settings.flow-header-cell,
.flow-col-no.flow-header-cell,
.flow-col-body.flow-header-cell,
.flow-col-status.flow-header-cell,
.flow-col-feedback.flow-header-cell {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  padding: 0 0 10px;
  margin: 0;
  line-height: 1.4;
  text-align: center;
  font-size: 11px;
  font-weight: 600;
  color: var(--story-content-text-faint, var(--text-faint));
  white-space: nowrap;
}

/* 나머지 3개 열(설정/번호/피드백·이력)은 본문에 집중할 수 있도록 평소에는
   보이지 않다가, 그 행에 마우스를 올렸을 때만 나타난다 — 예전의 회색 hover
   배경 효과는 삭제하고 opacity 전환으로 대체한다. 헤더 행(.flow-header-cell)의
   항목명은 이 규칙 대상이 아니라 항상 보인다.
   반드시 .flow-row 안에 있는 것만 대상으로 한다 — .flow-col-feedback은
   buildFeedbackColumn()이 모바일 컨트롤 스트립(.mobile-strip-row-inner)에도
   그대로 재사용하는데, 클래스만 보고(.flow-header-cell 여부만) 걸면 그쪽까지
   opacity:0으로 숨어버리고 모바일에는 :hover가 없어 다시 나타날 방법이
   없었다(실제로 이 버그가 있었다 — 모바일에서 피드백·이력 버튼이 통째로
   안 보임). */
.flow-row > .flow-col-settings,
.flow-row > .flow-col-no,
.flow-row > .flow-col-feedback {
  opacity: 0;
  transition: opacity 0.12s ease;
}

.flow-row:hover > .flow-col-settings,
.flow-row:hover > .flow-col-no,
.flow-row:hover > .flow-col-feedback {
  opacity: 1;
}

/* 1. 설정 */

.flow-col-settings {
  flex: 0 0 40px;
  width: 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.flow-col-settings .menu-dropdown {
  left: 0;
  right: auto;
}

/* 2. 번호 */

.flow-col-no {
  flex: 0 0 50px;
  width: 50px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.flow-no {
  font-size: 15px;
  font-weight: 700;
  color: var(--story-content-text-faint, var(--text-faint));
}

.flow-round {
  font-size: 10px;
  color: var(--story-content-text-faint, var(--text-faint));
  margin-top: 2px;
}

/* 3. 본문 — 경계선 없음, 위아래 블록이 완전히 이어짐 */

.flow-col-body {
  flex: 0 0 auto;
  margin: 0;
  padding: 0;
  border: none;
  min-width: 0;
}

.flow-row.is-editing .flow-col-body {
  background: #fafafa;
}

.mobile-block-item.is-editing {
  background: #fafafa;
}

/* 4. 상태 — 배지(.flow-status-badge)는 모든 블록에서 같은 크기로 고정하고,
   블록의 실제 세로 범위는 .flow-status-range-line(회색 점선 + 화살표)으로
   따로 보여준다. 예전엔 이 열 자체가 상태 색 배경으로 블록 높이를 꽉 채워서
   세로로 긴 블록일수록 색 면적이 커져 시선이 분산됐다 — 색이 차지하는 면적을
   배지 하나로 줄이고, 범위는 가는 점선만으로 표시하는 쪽으로 바꿨다.
   .flow-col-status(바깥 wrapper)는 이제 위치만 잡는 컨테이너다: 위아래 6px씩
   여백(margin)을 둬서 실제로 배경이 안 칠해지는 투명한 틈을 블록 사이에 만드는
   역할은 그대로 유지한다 — flex 컨테이너(.flow-row)의 align-items:stretch가
   margin만큼 뺀 나머지 높이로 자동으로 채워주므로 블록 높이를 따라가는 구조는
   그대로다. position:relative는 자식 .flow-status-range-line을 이 범위
   안에서 절대 위치시키기 위함이다. */
.flow-col-status {
  position: relative;
  flex: 0 0 72px;
  width: 72px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 6px 0;
}

/* [배지 고정 크기] 가장 짧은 셀(빈 블록 한 줄)의 실측 높이가 약 40.8px였다 —
   그 값을 배지 높이로 고정해, 블록이 아무리 길어도 배지 자체는 항상 같은
   크기로 보이게 한다. border-radius 20px(= 높이의 정확히 절반)라 항상 완전한
   알약(스타디움) 모양이 된다(예전엔 셀 높이에 따라 상대적으로 덜 둥글어
   보이는 긴 셀이 있었는데, 이제 배지가 고정 크기라 그 차이가 없다). wrapper가
   flex로 가운데 정렬해주므로 배지는 항상 블록 범위의 세로 중앙에 온다.
   z-index:1은 뒤에 깔리는 .flow-status-range-line 위에 항상 그려지도록. */
.flow-status-badge {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 40px;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  padding: 0 2px;
  border-radius: 20px;
}

.flow-col-status.status-draft .flow-status-badge {
  background: var(--status-draft-bg);
  color: var(--status-fg);
}

.flow-col-status.status-feedback .flow-status-badge {
  background: var(--status-feedback-bg);
  color: var(--status-fg);
}

.flow-col-status.status-revising .flow-status-badge {
  background: var(--status-revising-bg);
  color: var(--status-fg);
}

.flow-col-status.status-done .flow-status-badge {
  background: var(--status-done-bg);
  color: var(--status-fg);
}

/* [경계 보강] 색만으로는 스토리 배경색을 상태 색과 똑같이(또는 아주
   비슷하게) 고른 극단적인 경우 배지 경계가 완전히 사라진다(실측 확인 —
   배경을 초안 색 #15803d로 맞추면 그 배지만 윤곽 없이 배경에 녹아든다).
   반투명 흰 테두리를 얹으면 이 경우에도(배경이 어둡다는 뜻이므로) 항상
   구분선이 남는다 — 반대로 옅은/흰 배경에서는 이 테두리 자체가 거의
   안 보여 평소 디자인에는 영향이 없다. */
.flow-col-status.status-draft .flow-status-badge,
.flow-col-status.status-feedback .flow-status-badge,
.flow-col-status.status-revising .flow-status-badge,
.flow-col-status.status-done .flow-status-badge {
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.55);
}

/* [범위 점선] 블록이 어디서 시작해 어디서 끝나는지를 배지 위아래로 이어지는
   점선 + 화살표로 보여준다. 색은 기본 #555555(요구사항 그대로)인데,
   flow-status-column.spec.js에서 검증했듯 스토리 배경이 어두우면 이 값의
   대비가 부족해진다(#555555 on #000000 ≈ 3:1, 얇은 2px 선에는 부족) — 이미
   있는 --story-content-text-faint 어두운-배경 전환 메커니즘(story-bg-color.js)과
   동일한 방식으로 --flow-status-range-line-color를 함께 바꿔 밝은 회색으로
   교체한다(정확히 --story-content-text-faint의 어두운-배경 값과 같은
   #b3b3ad를 재사용 — 이미 검증된 값이라 새 매직 넘버를 늘리지 않았다).
   짧은 블록(플러그인이 .flow-col-status-no-range를 붙인 경우)에서는 배지
   위아래에 점선을 그릴 자리가 없어(화살표조차 배지에 붙어버림) 아예 숨긴다. */
.flow-status-range-line {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 0;
  border-left: 2px dashed var(--flow-status-range-line-color, #555555);
  transform: translateX(-50%);
  pointer-events: none;
}

.flow-status-range-line::before,
.flow-status-range-line::after {
  content: '';
  position: absolute;
  left: 50%;
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  transform: translateX(-50%);
}

.flow-status-range-line::before {
  top: -1px;
  border-bottom: 5px solid var(--flow-status-range-line-color, #555555);
}

.flow-status-range-line::after {
  bottom: -1px;
  border-top: 5px solid var(--flow-status-range-line-color, #555555);
}

.flow-col-status-no-range .flow-status-range-line {
  display: none;
}

/* 5. 피드백·이력 — 한 셀에 두 줄, 내용에 맞춘 폭 */

.flow-col-feedback {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 6px 10px;
}

.feedback-btn,
.history-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  border: none;
  background: none;
  padding: 1px 0;
  font-size: 12px;
  color: var(--text-muted);
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
}

.feedback-btn:hover,
.history-btn:hover {
  color: var(--text);
}

.feedback-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--border);
  color: var(--text-muted);
  font-size: 10px;
  font-weight: 600;
}

/* 위지윅 본문 타이포그래피. 보기 모드(rte-view)와 편집 모드(rte-editable)가 같은 값을 공유해
   전환할 때 시각적으로 튀지 않게 한다. 실제 서식(색/크기 등)은 인라인 style로 따로 붙는다. */

/* --story-content-text[-faint]는 story-bg-color.js가 스토리 배경색이
   지정될 때만 :root에 얹는다(어두운 배경이면 밝은 값, 그 외엔 아예 안
   얹음) — 지정 안 된 스토리는 var()의 두 번째 인자(기존 --text)로 그대로
   폴백돼 지금과 완전히 같게 보인다. 사용자가 RTE에서 직접 지정한 인라인
   글자색은 이 규칙보다 항상 우선한다(인라인 style이므로). */
.rte-view,
.rte-editable {
  color: var(--story-content-text, var(--text));
}

.rte-view h1,
.rte-editable h1 {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.35;
  margin: 0 0 16px;
}

.rte-view h2,
.rte-editable h2 {
  font-size: 20px;
  font-weight: 700;
  line-height: 1.4;
  margin: 0 0 14px;
}

.rte-view h3,
.rte-editable h3 {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.4;
  margin: 0 0 12px;
}

.rte-view p,
.rte-editable p {
  font-size: 16px;
  line-height: 1.7;
  margin: 0 0 20px;
  word-break: break-word;
}

.rte-view ul,
.rte-editable ul,
.rte-view ol,
.rte-editable ol {
  margin: 0 0 20px;
  padding-left: 22px;
}

.rte-view li,
.rte-editable li {
  font-size: 16px;
  line-height: 1.7;
  margin-bottom: 6px;
}

.rte-view strong,
.rte-editable strong {
  font-weight: 700;
}

.rte-view > *:last-child,
.rte-editable > *:last-child {
  margin-bottom: 0;
}

.rte-view {
  cursor: text;
}

.rte-view p.empty-content {
  color: var(--story-content-text-faint, var(--text-faint));
  font-style: italic;
  margin-bottom: 0;
}

.rte-editable {
  min-height: 60px;
  outline: none;
}

.save-status {
  font-size: 10px;
  color: var(--text-faint);
  margin-bottom: 4px;
  white-space: nowrap;
}

.editing-badge {
  display: block;
  font-size: 10px;
  color: var(--danger);
  margin-bottom: 4px;
  white-space: nowrap;
}

/* --- 흐름 보기: 인라인 편집기 --- */

.flow-editor {
  width: 100%;
}

.flow-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  margin-bottom: 8px;
}

.toolbar-btn {
  border: 1px solid var(--border-strong);
  background: var(--surface);
  border-radius: var(--radius);
  padding: 4px 9px;
  font-size: 12px;
  color: var(--text-muted);
  cursor: pointer;
}

.toolbar-btn:hover {
  border-color: var(--text-faint);
  color: var(--text);
}

.toolbar-btn.align-btn.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

.decoration-grid-toggle-btn.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

.toolbar-divider {
  width: 1px;
  height: 18px;
  background: var(--border-strong);
  margin: 0 4px;
}

/* 모바일에서 본문 편집을 허용하면서 함께 필요해진 것 — 툴바 폭(약 564px)이
   좁은 화면에 한 줄로 안 들어가므로 위 flex-wrap으로 2줄까지 자연스럽게
   넘어가게 하고, 드롭다운/정렬 버튼의 터치 영역을 44px 수준으로 넓힌다. */
@media (max-width: 767px) {
  .toolbar-btn,
  .rte-select {
    min-height: 44px;
    padding: 4px 12px;
    font-size: 13px;
  }
}

.align-hint {
  font-size: 12px;
  color: var(--text-muted);
  font-style: italic;
  margin: 0 0 8px;
}

.rte-select {
  border: 1px solid var(--border-strong);
  background: var(--surface);
  border-radius: var(--radius);
  padding: 4px 6px;
  font-size: 12px;
  color: var(--text-muted);
  cursor: pointer;
  max-width: 92px;
}

.rte-dropdown {
  position: relative;
}

.rte-dropdown-panel {
  position: absolute;
  left: 0;
  top: 32px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
  display: flex;
  flex-direction: column;
  min-width: 100px;
  z-index: 6;
  overflow: hidden;
  padding: 4px;
}

.rte-dropdown-panel button {
  border: none;
  background: none;
  text-align: left;
  padding: 6px 10px;
  font-size: 13px;
  cursor: pointer;
  color: var(--text);
  border-radius: var(--radius);
}

.rte-dropdown-panel button:hover {
  background: #fafaf9;
}

/* 배지 아이콘 선택 팝오버 — 검색창 + 카테고리별 그리드. */
.badge-icon-picker-panel {
  width: 280px;
  max-height: 340px;
  padding: 8px;
}

.badge-icon-search-input {
  width: 100%;
  box-sizing: border-box;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 13px;
  margin-bottom: 6px;
}

.badge-icon-grid {
  overflow-y: auto;
  max-height: 280px;
}

.badge-icon-category-heading {
  font-size: 11px;
  color: var(--text-muted);
  font-weight: 700;
  margin: 8px 2px 4px;
}
.badge-icon-category-heading:first-child {
  margin-top: 2px;
}

.badge-icon-category-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.badge-icon-option {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1.5px solid transparent;
  border-radius: var(--radius);
  background: #fafaf9;
  cursor: pointer;
  color: var(--text);
}
.badge-icon-option svg {
  width: 20px;
  height: 20px;
}
.badge-icon-option:hover {
  background: #f0f0ee;
}
.badge-icon-option.selected {
  border-color: var(--primary);
  background: #fff;
}
.badge-icon-option-none {
  width: auto;
  padding: 0 10px;
  font-size: 12px;
}

/* PC/모바일 공통 자체 제작 컬러피커(public/js/color-picker.js) — OS 네이티브
   <input type="color"> 팝업(PC)과 프리셋 전용 모바일 팔레트를 통합했다.
   채도/명도(SV) 사각형 + 색상(Hue) 슬라이더 + 미리보기/hex 입력 + 프리셋
   스와치 + 닫기 버튼으로 구성된다. */
.color-picker {
  width: 220px;
  padding: 6px;
}

.color-picker-main-row {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}

.color-picker-sv {
  position: relative;
  width: 180px;
  height: 180px;
  border-radius: var(--radius);
  overflow: hidden;
  cursor: crosshair;
  touch-action: none;
  flex: 0 0 auto;
}

.color-picker-sv canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.color-picker-sv-thumb {
  position: absolute;
  width: 12px;
  height: 12px;
  margin: -6px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

.color-picker-hue {
  position: relative;
  width: 20px;
  height: 180px;
  border-radius: var(--radius);
  cursor: pointer;
  touch-action: none;
  flex: 0 0 auto;
  background: linear-gradient(
    to bottom,
    #f00 0%,
    #ff0 17%,
    #0f0 33%,
    #0ff 50%,
    #00f 67%,
    #f0f 83%,
    #f00 100%
  );
}

.color-picker-hue-thumb {
  position: absolute;
  left: -3px;
  right: -3px;
  height: 4px;
  margin-top: -2px;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
  border-radius: 2px;
  pointer-events: none;
}

.color-picker-preview-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.color-picker-preview {
  width: 28px;
  height: 28px;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  flex: 0 0 auto;
}

.rte-color-hex-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 4px 6px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font-size: 12px;
  text-transform: lowercase;
}

.rte-color-hex-input.is-invalid {
  border-color: var(--danger);
}

.color-picker-swatches {
  display: grid;
  grid-template-columns: repeat(9, 1fr);
  gap: 4px;
  margin-bottom: 8px;
}

.color-picker-swatch {
  width: 100%;
  aspect-ratio: 1;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  cursor: pointer;
}

.color-picker-footer {
  display: flex;
  justify-content: flex-end;
}

.color-picker-close-btn {
  padding: 4px 14px;
  font-size: 12px;
}

.hidden-file-input {
  display: none;
}

/* --- 본문 안의 {{slot:ID}} 자리: 이미지/영상 후보 갤러리 --- */

.slot-gallery {
  display: block;
  margin: 10px 0;
}

/* --- 블록 유형(block_type) --- */

.media-block-body {
  min-height: 60px;
  padding: 4px 0;
}

.block-empty-placeholder {
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  color: var(--text-faint);
  font-size: 12.5px;
  cursor: pointer;
  margin: 4px 0;
}

.block-empty-placeholder:hover {
  border-color: var(--text-muted);
  color: var(--text-muted);
}

.block-empty-spacer {
  cursor: pointer;
  min-height: 20px;
}

.block-empty-spacer:hover {
  background: var(--bg);
  outline: 1px dashed var(--border-strong);
  outline-offset: -1px;
}

.type-select-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin-bottom: 14px;
}

.type-select-grid.hidden {
  display: none;
}

.type-select-btn {
  min-height: 48px;
  padding: 10px;
  font-size: 14px;
  font-weight: 600;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

.type-select-btn:hover {
  border-color: var(--primary);
  background: var(--bg);
}

.slot-gallery-empty {
  font-size: 12px;
  color: var(--text-faint);
  font-style: italic;
}

.slot-gallery-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 6px;
}

/* --- [5단계] 시작 선택 화면 — "사진부터 고르기" / "바로 꾸미기" --- */
.start-picker {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}

.start-picker-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 56px;
  padding: 10px;
  font-size: 14px;
  font-weight: 600;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

.start-picker-btn:hover {
  border-color: var(--primary);
  background: var(--bg);
}

.start-picker-btn-sub {
  font-size: 11px;
  font-weight: 400;
  color: var(--text-muted);
}

/* --- 후보 썸네일 sticky 바(public/js/candidate-bar.js) ---
   .slot-gallery-row(위)를 감싸는 접이식 바. 평소엔 핸들만 블록 좌측 하단에
   작게 보이다가, 스크롤로 블록 하단이 뷰포트 하단에 걸리면 이 바 전체가
   화면에 붙어 있다가(position:sticky) 블록이 화면을 벗어나면 같이 사라진다.
   전역 고정 바가 아니라 블록 컨테이너(.slot-gallery/.decoration-block-body)
   기준 sticky이므로, 이 조상에 CSS zoom/transform이 걸려 있으면 안 된다
   (실측 확인 — applyBodyScale 주석 참고). 폭 전체를 차지하되(펼쳤을 때 후보
   줄이 필요한 폭) 핸들 자체는 내용 크기만큼만 그려 "좌측 하단에 작은 핸들"
   처럼 보이게 한다. */
.slot-candidate-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  margin-top: 6px;
  /* 모바일에서 위로 스크롤(관성/모멘텀 스크롤)할 때만 바가 잠깐 사라지는
     보고가 있었다 — window.scrollTo로 위/아래 양방향을 결정적으로 재본
     결과 레이아웃 자체는 완전히 대칭이라(같은 스크롤 위치면 항상 같은 결과),
     CSS 구조 문제는 아니다. 모바일 브라우저가 관성 스크롤 중 sticky 요소를
     별도 컴포지터 레이어로 안 올리면 프레임을 스킵하며 재계산이 밀리는
     경우가 있다고 알려져 있어(주로 위로/역방향 스크롤에서 두드러짐), 이
     요소를 자체 레이어로 승격시켜 재계산이 매 프레임 그대로 따라가게 한다. */
  will-change: transform;
}

.slot-candidate-handle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14);
  font-size: 12px;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  max-width: 100%;
}

.slot-candidate-handle:hover {
  background: var(--bg);
}

.slot-candidate-handle-text {
  overflow: hidden;
  text-overflow: ellipsis;
}

.slot-candidate-handle-warn {
  color: var(--danger);
  margin-left: 4px;
}

.slot-candidate-handle-chevron {
  color: var(--text-muted);
  font-size: 10px;
  flex-shrink: 0;
}

.slot-candidate-panel {
  display: none;
}

.slot-candidate-panel.expanded {
  display: block;
  margin-top: 6px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.12);
}

.slot-candidate-panel .slot-gallery-row {
  padding-bottom: 0;
}

@media (max-width: 480px) {
  /* 좁은 화면에서도 "후보 3 · 확정됨 ⚠" 정도는 잘리지 않아야 하므로 글자만
     살짝 줄인다(핸들 자체는 max-width:100%라 블록 폭을 넘지 않는다). */
  .slot-candidate-handle {
    font-size: 11.5px;
    padding: 5px 8px;
  }
}

.slot-thumb {
  flex: 0 0 auto;
  width: 80px;
  cursor: pointer;
}

.slot-thumb-box {
  position: relative;
  width: 80px;
  height: 80px;
  border: 2px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
}

.slot-thumb-box img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.slot-thumb-video-icon {
  font-size: 22px;
  color: var(--text-faint);
}

.slot-thumb-box .slot-thumb-video-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #fff;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.7);
}

.slot-thumb-warn {
  position: absolute;
  left: 2px;
  right: 2px;
  bottom: 2px;
  background: rgba(138, 47, 47, 0.88);
  color: #fff;
  font-size: 9px;
  line-height: 1.3;
  padding: 2px 3px;
  border-radius: 2px;
  text-align: center;
}

.slot-thumb-filename {
  margin-top: 4px;
  font-size: 10.5px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
}

.slot-thumb-actions {
  display: flex;
  gap: 4px;
  margin-top: 3px;
}

.slot-add-candidate {
  flex: 0 0 auto;
}

/* 후보 썸네일 줄(.slot-gallery-row, 가로 스크롤) 바깥의 별도 줄로 둔다 — 그
   줄 안에 있으면 후보 개수가 늘어날수록 그리드 한 칸처럼 끼어들어 줄바꿈되며
   전체 정렬이 흐트러진다. */
.slot-add-candidate-hint {
  margin: 4px 0 0;
  /* .slot-action-btn과 같은 폰트 크기로 통일 */
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-faint);
}

.slot-add-candidate-btn {
  width: 80px;
  height: 80px;
  border: 1.5px dashed var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.3;
  white-space: pre-line;
  cursor: pointer;
}

.slot-add-candidate-btn:hover {
  border-color: var(--primary);
  color: var(--primary);
}

/* --- '기본 제안': 실제 상세페이지처럼 본문 폭 전체로 크게 --- */

.slot-featured {
  margin-bottom: 8px;
}

.slot-featured-box {
  position: relative;
  display: block;
  width: 100%;
  min-height: 60px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg);
}

.slot-featured-box img {
  display: block;
  width: 100%;
  height: auto;
}

.slot-featured-box .slot-thumb-video-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 44px;
  color: #fff;
  text-shadow: 0 0 6px rgba(0, 0, 0, 0.7);
}

.slot-featured-box-video {
  cursor: pointer;
}

/* "영상 변환 추가"로 만든 애니메이션 WebP 후보는 저장 해상도 자체가
   min(스토리 content_width_px, 원본 영상 해상도)로 처리되므로(muxAnimatedWebp),
   화면 표시 캡도 고정 px가 아니라 같은 content_width_px를 따라가야 항상
   일치한다. --story-content-width는 editor.html/review.html의 renderHeader()가
   스토리를 불러올 때마다 document.documentElement에 주입한다. 이 변수가
   어떤 이유로든 주입되지 않은 경우 var() 참조가 computed-value-time에
   무효화되어 max-width는 초기값(none)으로 계산된다 — 즉 별도의 고정 폴백
   숫자 없이 캡이 그냥 적용되지 않고 블록 폭에 자연스럽게 맞춰진다. 일반
   이미지 후보에는 적용하지 않는다(is-converted-video는 buildFeatured가
   변환된 영상일 때만 붙임). */
.slot-featured-box.is-converted-video {
  max-width: calc(var(--story-content-width) * 1px);
  margin-left: auto;
  margin-right: auto;
}

.slot-featured-box.is-playing {
  cursor: default;
  aspect-ratio: 16 / 9;
}

.slot-featured-box.is-playing iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* [칩 배치 재설계] 확정/삭제(좌하단)·파일명/상태(우하단)를 더 이상 이미지
   아래로 쌓지 않고, 우상단 "🎨 꾸미기 편집/확정완료"(.slot-featured-top-actions)
   와 같은 시각 언어(불투명 표면 + 그림자, 알약형)로 이미지 위 코너에 얹는다 —
   블록마다 세 줄이 쌓여 간격이 벌어지던 걸 이미지 한 장 + 후보 핸들 한 줄로
   줄이기 위해서다. 흰 배경 제품컷 위에서도 읽혀야 하므로 반투명이 아니라
   불투명 var(--surface) 배경을 쓴다. */
.slot-featured-bottom-left,
.slot-featured-bottom-right {
  position: absolute;
  bottom: 6px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: calc(100% - 12px);
  flex-wrap: wrap;
}

.slot-featured-bottom-left {
  left: 6px;
}

.slot-featured-bottom-right {
  right: 6px;
  justify-content: flex-end;
}

.slot-featured-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.slot-action-btn {
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  border: none;
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-muted);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  cursor: pointer;
  white-space: nowrap;
}

.slot-action-btn:hover {
  color: var(--primary);
}

/* [결정 2] 확정된 자산의 삭제 버튼 — 색은 유지하되(무엇을 하는 버튼인지는 계속
   보이게) 흐리게 처리해 지금은 누를 수 없다는 걸 드러낸다. .slot-confirm-btn은
   자신만의 :disabled 규칙(opacity:1, "확정 불가" 상태를 또렷하게 보여줘야 함)이
   따로 있어 이 일반 규칙보다 아래(더 구체적으로 적용)에 온다. */
.slot-action-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.slot-action-btn.is-confirmed {
  background: var(--primary);
  color: #fff;
}

.slot-action-btn.is-confirmed:hover {
  background: var(--primary-hover);
  color: #fff;
}

.slot-action-btn.slot-action-danger {
  color: var(--danger);
}

.slot-action-btn.slot-action-danger:hover {
  color: var(--danger);
}

.slot-featured-filename {
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  border-radius: 999px;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 200px;
}

/* [모바일 44px 터치 타겟] 우상단 칩(.slot-featured-top-actions 아래 주석 참고)에서
   같은 문제가 실제로 있었다 — 이 칩들은 <button>이 아니거나(.slot-featured-filename,
   .slot-featured-status는 div) .slot-action-btn처럼 범용 .toolbar-btn/.rte-select
   44px 규칙과 클래스가 겹치지 않아 지금은 별도 override 없이도 44px로 안
   눌어붙지만, 실측 없이 안전하다고 단정하지 않는다 — 사진 위 좁은 코너에서
   여러 칩이 줄바꿈될 때 서로 겹치지 않도록 모바일에서 한 단계 더 줄인다. */
@media (max-width: 767px) {
  .slot-featured-bottom-left,
  .slot-featured-bottom-right {
    bottom: 4px;
    gap: 4px;
  }

  .slot-featured-bottom-left {
    left: 4px;
  }

  .slot-featured-bottom-right {
    right: 4px;
  }

  .slot-action-btn,
  .slot-featured-filename,
  .slot-featured-status {
    padding: 3px 8px;
    font-size: 10px;
  }

  .slot-featured-filename {
    max-width: 120px;
  }
}

/* --- 확정 표시 --- */

/* 후보 썸네일 쪽: 굵은 테두리 + 작은 원형 ✓ 배지. */
.slot-thumb.is-confirmed .slot-thumb-box {
  border-width: 3px;
  border-color: var(--confirm);
}

.slot-confirmed-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--confirm);
  color: #fff;
  font-size: 11px;
  line-height: 18px;
  text-align: center;
  font-weight: 700;
}

.slot-thumb-box .slot-confirmed-badge {
  width: 14px;
  height: 14px;
  font-size: 9px;
  line-height: 14px;
  top: 2px;
  right: 2px;
}

/* 본문 큰 미리보기(기본 제안)는 굵은 테두리 대신 모서리의 작은 "확정완료"
   알약 라벨만 쓴다 — 상세페이지 콘텐츠를 훑어볼 때 3px 테두리가 시각적으로
   방해가 된다는 피드백에 따른 것. [A] 이후엔 .slot-featured-top-actions(우상단
   플렉스 그룹) 안에 들어가므로 자체 position은 없다 — 그룹이 위치를 잡는다. */
.slot-featured-confirmed-label {
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--confirm);
  color: #fff;
  font-size: 11px;
  line-height: 1.4;
  font-weight: 700;
  white-space: nowrap;
}

/* [A] 미리보기 우상단 그룹 — [🎨 꾸미기 사용/편집 버튼] [확정완료 배지] 순서로
   나란히 둔다(버튼이 배지보다 항상 왼쪽). buildFeatured가 항상 만들고,
   renderGalleryInto가 버튼을 prepend한다 — 버튼이 없으면(video 등) 배지만
   보이거나 완전히 빈 채로 남는다(크기 0, 레이아웃에 영향 없음). */
.slot-featured-top-actions {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 6px;
}

.slot-featured-top-actions .decoration-toggle-row {
  position: relative;
}

.slot-featured-top-actions .decoration-toggle-row .toolbar-btn {
  padding: 4px 10px;
  font-size: 11px;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

/* 토글 실패 시 인라인 에러 문구(field-error) — 이 그룹 안에서는 버튼 바로
   아래로 떨어뜨려 이미지를 크게 가리지 않게 한다. */
.slot-featured-top-actions .field-error {
  position: absolute;
  top: 100%;
  right: 0;
  margin: 4px 0 0;
  padding: 3px 8px;
  background: #fff;
  border-radius: var(--radius);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
  white-space: nowrap;
  z-index: 3;
}

/* [P1] 모바일 본문은 폭 자체가 좁아 이미지가 자연히 작게 보이는데(width:100%),
   우상단 버튼/배지는 데스크톱과 같은 고정 px라 상대적으로 커 보여 이미지 상단을
   가린다(실측 확인된 문제) — 모바일에서만 한 단계 줄인다. editor.html(버튼+배지)과
   review.html(배지만) 둘 다 이 파일을 공유하므로 한 곳만 고치면 된다.

   실측 확인된 원인 — 폰트/패딩만 줄였을 뿐 min-height는 그대로 뒀더니, 모바일
   터치 타겟 공용 규칙(아래, 2098행 부근 ".toolbar-btn,.rte-select{min-height:44px}")
   이 그대로 이겨서 버튼 자체 높이가 44px로 남아 있었다(폰트만 작아지고 버튼
   박스는 안 줄어든 상태 — "여전히 크다"는 증상 그대로). 그 규칙은 촘촘한
   툴바 줄의 터치 영역을 넓히려는 것이라 폭 넓은 버튼 여러 개가 나란히 있는
   상황엔 맞지만, 이 버튼은 사진 위 단독 코너에 뜨는 하나뿐인 탭 타겟이라
   그 정도로 클 필요가 없다. 아래 3단 클래스 체이닝이라 명시도가 더 높아
   min-height를 여기서 직접 덮어써야 이긴다 — WCAG 2.5.8(AA) 최소 권장치인
   24px보다는 넉넉하게, 44px보다는 작게(26px) 잡는다. */
@media (max-width: 767px) {
  .slot-featured-top-actions {
    top: 4px;
    right: 4px;
    gap: 4px;
    /* 잘못 좁은 박스(크롭된 세로 이미지 등)에서도 그룹이 박스 밖으로 밀려나지
       않게 폭 상한을 두고, 그래도 안 맞으면 배지가 잘리는 대신 다음 줄로
       내려가게 한다(clip 대신 wrap). */
    max-width: calc(100% - 8px);
    flex-wrap: wrap;
    justify-content: flex-end;
  }

  .slot-featured-confirmed-label {
    padding: 2px 6px;
    font-size: 9px;
  }

  .slot-featured-top-actions .decoration-toggle-row .toolbar-btn {
    padding: 3px 8px;
    font-size: 10px;
    min-height: 26px;
    height: auto;
  }
}

.slot-featured-status {
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  border-radius: 999px;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  white-space: nowrap;
}

/* "꾸민 이미지"(배경+레이어 합성 후보) 표시 — 후보 썸네일에서만 쓰는 작은 모서리
   배지다. 기본 제안(큰 미리보기)에는 안 붙인다 — 파일명의 "_꾸밈" 접미사와
   확정완료 배지로 이미 알 수 있어 중복이라서다. 예전엔 이미지 하단을 가로지르는
   전체 폭 띠였는데, 이미지를 많이 가려서 확정완료 배지(우측 상단, 원형)와 같은
   방식의 작은 원형 배지로 바꿨다 — 자리가 겹치지 않게 좌측 상단에 둔다. box
   (이미지 컨테이너, position:relative)에 오버레이로 얹어서 카드 레이아웃(썸네일
   높이/정렬)에는 전혀 영향을 주지 않는다. */
.slot-composite-label {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--primary);
  color: #fff;
  font-size: 9px;
  line-height: 14px;
  text-align: center;
}

/* [AI 움짤 5단계] "움짤(애니메이션)" 표시 — .slot-composite-label과 같은 자리·
   크기(한 자산은 절대 composite와 animation 둘 다일 수 없으므로 자리가 겹치지
   않는다). 정지 이미지와 구분되도록 다른 색만 쓴다. 작업자(editor.html)·
   클라이언트(review.html) 화면 모두 표시한다. */
.slot-animation-label {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #d9534f;
  color: #fff;
  font-size: 9px;
  line-height: 14px;
  text-align: center;
}

/* [버그 수정 — 결과물 낡음 표시] 이 결과물 후보가 나온 뒤 블록이 더 편집됐다는
   표시 — composite/animation 배지(좌상단)·확정 배지(우상단)와 자리가 겹치지 않게
   우하단에 둔다(한 후보에 composite/animation 배지와 동시에 뜰 수 있으므로 같은
   자리를 쓸 수 없다). */
.slot-stale-label {
  position: absolute;
  bottom: 2px;
  right: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #f59e0b;
  color: #fff;
  font-size: 9px;
  line-height: 14px;
  text-align: center;
}

.slot-featured-status.is-confirmed {
  color: var(--confirm);
}

.slot-featured-status.is-unconfirmed {
  color: var(--text-faint);
}

/* [결정 2] "확정완료"(비활성)가 아니라 "확정 해제"(클릭 가능)로 바뀌었으므로
   cursor도 pointer로 — 다만 .slot-confirm-btn:disabled(꾸민 이미지라 확정 자체가
   안 되는 경우)일 땐 아래 :disabled 규칙이 이겨서 다시 default가 된다. */
.slot-confirm-btn.is-confirmed {
  background: var(--confirm-bg);
  border-color: var(--confirm);
  color: var(--confirm);
  cursor: pointer;
}

.slot-confirm-btn.is-confirmed:hover {
  border-color: var(--danger);
  color: var(--danger);
}

/* 무한 반복이 아니라 눈에 띄는 정도로 2~3회만 펄스하고 멈춘다. */
.slot-confirm-btn.is-pulsing {
  border-color: var(--confirm);
  color: var(--confirm);
  font-weight: 600;
  animation: slot-confirm-pulse 0.85s ease-in-out 3;
}

@keyframes slot-confirm-pulse {
  0%,
  100% {
    box-shadow: 0 0 0 0 rgba(168, 121, 10, 0.45);
  }
  50% {
    box-shadow: 0 0 0 5px rgba(168, 121, 10, 0);
  }
}

/* --- AI 이미지 편집(배경 제거/생성) 버튼 + 진행 스피너 ---
   [B] 예전엔 미리보기 이미지 위에 오버레이로 얹었지만(확인에 방해가 된다는
   피드백), 지금은 꾸미기 모달의 "캔버스 설정" 아코디언 섹션 안에 정적으로
   자리 잡는다 — 더 이상 이미지를 가리지 않으므로 절대배치/hover 페이드/
   모바일 전용 되돌림이 전부 필요 없다. */
.ai-actions-row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.ai-sandbox-override-label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
  padding: 2px 6px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
}

.ai-action-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  font-size: 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

.ai-action-btn:hover:not(:disabled) {
  border-color: var(--primary);
  color: var(--primary);
}

.ai-action-btn:disabled {
  opacity: 0.6;
  cursor: default;
}

.ai-spinner {
  width: 12px;
  height: 12px;
  border: 2px solid rgba(0, 0, 0, 0.15);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: ai-spinner-spin 0.7s linear infinite;
  flex: 0 0 auto;
}

@keyframes ai-spinner-spin {
  to {
    transform: rotate(360deg);
  }
}

.ai-prompt-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 12px;
}

.ai-prompt-preset-btn {
  padding: 5px 10px;
  font-size: 12px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
}

.ai-prompt-preset-btn:hover {
  border-color: var(--primary);
  color: var(--primary);
}

/* 모바일: "+ 후보 추가"가 80x80 정사각형 안에서 두 줄(+ 후보 / 추가)로 나뉘던
   걸 한 줄로 바꾼다(터치 최소 권장 크기 44px 확보). 예전엔 여기에 썸네일 삭제
   버튼의 터치 타겟 크기 규칙도 있었지만, 그 버튼 자체를 없애면서(삭제는 큰
   미리보기 아래 버튼으로 일원화) 같이 지웠다. */
@media (max-width: 767px) {
  .slot-add-candidate-btn {
    width: auto;
    height: 44px;
    padding: 0 16px;
    white-space: nowrap;
  }
}

/* --- 자산 미리보기 모달 --- */

.modal-wide {
  width: 440px;
}

.modal-xwide {
  width: 640px;
  max-width: 92vw;
  max-height: 88vh;
  overflow-y: auto;
}

/* --- 크롭 UI(image-crop.js가 로직을 담당, 여기는 UI만) — 이미지 "+ 후보 추가"와
   "영상 변환 추가"의 컷 프레임이 공유한다. 영상 추가 모달 위에 중첩되어 열릴
   수 있어 #confirm-modal과 같은 방식으로 z-index를 높인다. */
.crop-overlay {
  z-index: 20;
}

/* 스테이지를 뷰포트에 맞춰 축소 표시하므로(image-crop.js computeMaxStageSize)
   보통 스크롤이 필요 없지만, 모달 자체는 세로 축만 스크롤하고(overflow-x:
   hidden) header(제목/안내)·footer(버튼)는 스크롤 영역 바깥에 둬서 항상 같은
   자리에 보이게 한다 — 이미지 영역(.crop-body)만 필요하면 스크롤된다. */
.crop-overlay .modal-xwide {
  display: flex;
  flex-direction: column;
  overflow-x: hidden;
}

.crop-header,
.crop-footer {
  flex: 0 0 auto;
}

.crop-body {
  flex: 1 1 auto;
  overflow: auto;
  display: flex;
  justify-content: center;
  /* overflow:auto는 padding 경계에서가 아니라 그 안쪽(패딩까지 포함한 콘텐츠
     박스)에서 클리핑한다 — 모서리/변 핸들의 확대된 히트박스(.crop-handle::before,
     스테이지 경계 기준 22px 오버행)가 잘리지 않도록 그보다 넉넉한 여백을 둔다. */
  padding: 24px;
}

.crop-hint {
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 12px;
}

.crop-stage {
  position: relative;
  margin: 0 auto 16px;
  background: #000;
  touch-action: none;
  /* overflow:hidden이 아니다 — 모서리/변에 있는 크롭 핸들은 스테이지 경계에
     걸쳐 있어서(특히 크롭 영역이 전체 프레임일 때는 항상 네 모서리에 있다),
     hidden으로 두면 핸들의 확대된 히트박스(.crop-handle::before, 44x44)
     절반이 잘려 클릭/터치가 안 먹는 문제가 있었다. .crop-image는 스테이지와
     정확히 같은 크기로 그려지므로 클리핑이 애초에 필요하지 않다. */
  flex-shrink: 0;
}

.crop-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  user-select: none;
  pointer-events: none;
}

.crop-rect {
  position: absolute;
  box-sizing: border-box;
  border: 1.5px solid #fff;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
  /* 본체는 클릭을 통과시켜 스테이지가 받는다(= 새 영역 그리기). 핸들만 직접
     상호작용해서 크기 조절한다 — 그렇지 않으면 기본값(전체 프레임)을 클릭할 때
     "이동"으로 처리돼 이동할 곳이 없어(이미 꽉 차 있으므로) 아무 반응이 없는
     것처럼 보인다. */
  pointer-events: none;
  touch-action: none;
}

.crop-handle {
  position: absolute;
  width: 14px;
  height: 14px;
  margin: -7px;
  background: #fff;
  border: 1.5px solid var(--primary);
  border-radius: 50%;
  pointer-events: auto;
  touch-action: none;
}

/* 시각적 크기(14x14)는 그대로 두고, 실제 클릭/터치 판정 영역만 터치 최소
   권장 크기(44x44)로 넓힌다 — 투명한 ::before를 핸들 중심에 겹쳐 히트박스만
   확장한다(배경이 투명해도 pointer-events는 그대로 반응한다). */
.crop-handle::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.crop-handle-nw {
  top: 0;
  left: 0;
  cursor: nwse-resize;
}
.crop-handle-n {
  top: 0;
  left: 50%;
  cursor: ns-resize;
}
.crop-handle-ne {
  top: 0;
  left: 100%;
  cursor: nesw-resize;
}
.crop-handle-e {
  top: 50%;
  left: 100%;
  cursor: ew-resize;
}
.crop-handle-se {
  top: 100%;
  left: 100%;
  cursor: nwse-resize;
}
.crop-handle-s {
  top: 100%;
  left: 50%;
  cursor: ns-resize;
}
.crop-handle-sw {
  top: 100%;
  left: 0;
  cursor: nesw-resize;
}
.crop-handle-w {
  top: 50%;
  left: 0;
  cursor: ew-resize;
}

/* --- 썸네일 뽑기: 사진 편집(자르기 UI는 위 .crop-* 재사용) 각도/밝기 컨트롤 ---
   [컨트롤 위치 이동] .crop-body는 다른 크롭 UI(image-crop.js, video-edit-tool.js)
   와 공유하는 가로 정렬 컨테이너라 그대로 두고, 이미지(stage)+컨트롤을 이
   컬럼 하나로 감싸 .crop-body의 단일 자식으로 넣는다 — 컬럼 내부에서만
   세로로 쌓아 다른 크롭 UI에 영향이 없다. */
.thumb-editor-stage-column {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}
.thumb-editor-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 20px;
  justify-content: center;
  width: 100%;
}
.thumb-editor-control-group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  font-size: 13px;
  color: var(--text-muted);
  /* 컨트롤 컨테이너가 좁아져도 그룹 내부(라벨+버튼/슬라이더)가 찌그러지지
     않는다 — 자리가 부족하면 그룹 전체가 다음 줄로 넘어간다(바깥
     .thumb-editor-controls의 flex-wrap), 그룹 "안"에서도 자리가 더 부족하면
     이 flex-wrap이 항목 단위로 다음 줄로 넘긴다 — 어느 경우든 라벨 글자가
     세로로 눌리는 일은 없다. */
  flex-shrink: 0;
  white-space: nowrap;
}
.thumb-editor-control-group input[type='range'] {
  width: 140px;
}
/* [미세 각도] 한 그룹 안에 라벨+슬라이더+버튼까지 들어가 밝기 슬라이더보다
   자리가 빠듯하다 — 조금 더 좁게 잡는다. */
.thumb-editor-fine-angle-input {
  width: 100px;
}
.thumb-editor-fine-angle-hint {
  flex-basis: 100%;
  margin: 0;
  font-size: 12px;
  white-space: normal;
}

.image-block-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.decoration-toggle-row {
  display: flex;
}

/* --- 꾸미기 블록 캔버스 — public/js/layer-canvas.js가 드래그/리사이즈 로직 담당 --- */

.decoration-canvas-scale-wrap {
  overflow: hidden; /* scale()로 축소했을 때 레이아웃 잔여 공간을 실제 표시 크기로 맞춘다 */
}

.decoration-canvas {
  position: relative;
  background: #fff;
  margin: 0 auto;
  touch-action: none;
  transform-origin: top left;
}

/* 캔버스 높이 드래그 핸들(image 블록 전용, 요구사항 [B]) — 캔버스 하단 중앙에 걸쳐
   있는 얇은 바. 레이어보다 위(z-index)에 있어야 클릭이 씹히지 않는다. */
.decoration-canvas-height-handle {
  position: absolute;
  left: 50%;
  bottom: -5px;
  width: 64px;
  height: 10px;
  transform: translateX(-50%);
  background: var(--primary);
  border: 2px solid #fff;
  border-radius: 5px;
  cursor: ns-resize;
  z-index: 80;
  touch-action: none;
}

.decoration-bg-image {
  /* left/top/width/height는 항상 JS(applyCanvasBackground)가 인라인 스타일로
     정확히 지정한다(P1-1 — 이미지는 항상 원본 비율 그대로, 캔버스 안 여백 위치만
     valign으로 결정) — 여기는 크기와 무관한 속성만 둔다. */
  position: absolute;
  pointer-events: none;
  user-select: none;
}

/* 그리드 표시 토글(요구사항 [2]) — width/height/background-size는 JS
   (applyDecorationGridOverlay)가 캔버스 좌표계(px) 기준으로 인라인 설정한다.
   canvasEl 자신이 transform:scale()되므로 이 값들은 스케일과 무관하게 항상
   캔버스 실제 px 기준이면 되고, 화면에는 부모 스케일이 자동으로 반영된다.
   z-index는 배경(auto) 위, 레이어(2) 아래에 오도록 1로 고정한다. */
.decoration-grid-overlay {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.1s ease;
  background-image:
    linear-gradient(to right, #000 1px, transparent 1px),
    linear-gradient(to bottom, #000 1px, transparent 1px);
  background-repeat: repeat;
}
.decoration-grid-overlay.grid-visible {
  opacity: 0.12; /* 평소엔 옅게 — 작업에 방해되지 않을 정도 */
}
.decoration-grid-overlay.grid-visible.grid-active {
  opacity: 0.4; /* 드래그·리사이즈 중에는 진하게(layer-canvas.js가 토글) */
}

.layer-guide {
  position: absolute;
  background: var(--primary);
  pointer-events: none;
  z-index: 50;
}
.layer-guide-v {
  top: 0;
  bottom: 0;
  width: 1px;
}
.layer-guide-h {
  left: 0;
  right: 0;
  height: 1px;
}

.decoration-select-box {
  position: absolute;
  border: 1px dashed var(--primary);
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  pointer-events: none;
  z-index: 60;
}
.decoration-select-box.hidden {
  display: none;
}

.decoration-layer {
  position: absolute;
  box-sizing: border-box;
  cursor: move;
  touch-action: none;
  outline: 1px dashed transparent;
  z-index: 2; /* 그리드 오버레이(1) 위, 안내선/핸들(20/50/60/80)보다는 아래 */
}
.decoration-layer.selected {
  outline: 1.5px solid var(--primary);
}
/* [레이어 목록 hover 강조] 목록 항목에 마우스를 올렸을 때 선택을 바꾸지 않고
   캔버스에서 어느 오브젝트인지만 알려준다. .selected는 outline(1.5px solid)을
   쓰므로, 이미 선택된 항목에 마우스를 올려도 구분되게 box-shadow로 다른
   시각 요소를 쓴다(둘 다 켜져도 서로 다른 링으로 함께 보인다) — 잠금 표시
   테두리(is-locked는 색이 없다)와도 안 겹친다. */
.decoration-layer.list-hover-highlight {
  box-shadow: 0 0 0 2px var(--layer-hover-highlight) inset;
}
.decoration-layer.layer-preview {
  outline: 2px dashed var(--primary);
  opacity: 0.85;
}
/* [잠금/가시성] 잠긴 오브젝트는 클릭이 그대로 캔버스(빈 공간)로 통과해 러버밴드
   선택이 시작된다 — "캔버스에서 선택·이동되지 않는다"를 핸들러 분기 없이 이
   한 줄로 만족한다(레이어 목록 클릭으로 선택하는 건 별개 경로라 영향 없음).
   숨긴 오브젝트는 그냥 안 그린다. */
.decoration-layer.is-locked {
  pointer-events: none;
}
/* 리사이즈/회전/화살표 끝점 핸들(.layer-handle/.layer-endpoint-handle, layer-
   canvas.js)은 선택 여부와 무관하게 항상 DOM에 있고 자기 pointer-events:auto를
   따로 갖고 있어서(그래야 평소엔 손잡이가 항상 클릭 가능하다), 위
   .decoration-layer.is-locked{pointer-events:none}만으로는 상속이 이 명시적
   auto를 못 이긴다 — 실제로 잠긴 레이어의 핸들이 클릭되는 게 재현됐다. 잠긴
   레이어 밑의 핸들만 콕 집어 다시 꺼야 한다. */
.decoration-layer.is-locked .layer-handle,
.decoration-layer.is-locked .layer-endpoint-handle {
  pointer-events: none;
}
.decoration-layer.is-hidden {
  display: none;
}

.decoration-layer-text-inner {
  display: flex;
  width: 100%;
  height: 100%;
  padding: 4px;
  overflow: hidden;
  white-space: pre-wrap;
  word-break: break-word;
}

.decoration-layer-box-inner {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}

.decoration-layer-arrow-inner {
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none; /* 빈 여백은 클릭이 통과한다 — 실제 선(line/polygon)만 auto로 되켬 */
}

.decoration-layer-badge-inner {
  width: 100%;
  height: 100%;
  overflow: visible;
  /* box와 달리 채워진 도형(원/별 등)이 bbox 전체를 채우지 않을 수 있지만, 배지는
     "이 사각형 영역 어딘가를 클릭하면 선택"이 자연스러운 UX라 화살표처럼 빈
     여백을 투과시키지 않는다(기본값 pointer-events:auto 그대로 둠). */
}

.decoration-layer-image-inner {
  /* 브라우저 기본 <img> 드래그(HTML5 드래그고스트)를 끈다 — 켜져 있으면 우리
     커스텀 pointerdown 기반 이동/리사이즈(layer-canvas.js)와 충돌해 드래그가
     안 먹거나 고스트 이미지가 떠 버린다. */
  -webkit-user-drag: none;
  user-select: none;
}

.layer-handle {
  position: absolute;
  width: 12px;
  height: 12px;
  margin: -6px;
  background: #fff;
  border: 1.5px solid var(--primary);
  border-radius: 50%;
  pointer-events: auto;
  touch-action: none;
  z-index: 20;
}
.layer-handle::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 32px;
  height: 32px;
  transform: translate(-50%, -50%);
}
.layer-endpoint-handle {
  position: absolute;
  width: 12px;
  height: 12px;
  margin: -6px;
  background: #fff;
  border: 1.5px solid var(--primary);
  border-radius: 50%;
  pointer-events: auto;
  touch-action: none;
  cursor: pointer;
  z-index: 20;
}
.layer-endpoint-handle::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 32px;
  height: 32px;
  transform: translate(-50%, -50%);
}

.layer-handle-nw { top: 0; left: 0; cursor: nwse-resize; }
.layer-handle-n { top: 0; left: 50%; cursor: ns-resize; }
.layer-handle-ne { top: 0; left: 100%; cursor: nesw-resize; }
.layer-handle-e { top: 50%; left: 100%; cursor: ew-resize; }
.layer-handle-se { top: 100%; left: 100%; cursor: nwse-resize; }
.layer-handle-s { top: 100%; left: 50%; cursor: ns-resize; }
.layer-handle-sw { top: 100%; left: 0; cursor: nesw-resize; }
.layer-handle-w { top: 50%; left: 0; cursor: ew-resize; }

/* 회전 핸들(지금은 image 레이어에만 붙는다) — 박스 상단 중앙 바깥. 핸들 자체는
   레이어의 다른 8개 리사이즈 핸들과 함께 layerEl의 자식이라, 부모 el에 걸린
   transform:rotate()를 그대로 상속해 박스와 함께 회전한다(회전 중에도 핸들이
   항상 "지금 위쪽"을 가리키는 게 아니라 박스에 고정돼 함께 도는 편이 자연스럽다 —
   대부분의 에디터 도구와 같은 관례). */
.layer-handle-rotate {
  top: -28px;
  left: 50%;
  cursor: grab;
  border-color: var(--primary);
}
.layer-handle-rotate:active { cursor: grabbing; }

.decoration-editor-layout {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

.range-with-number {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
/* [UI-3] 슬라이더의 브라우저 기본 폭(크롬 기준 약 170px)은 flex 컨테이너 안에서
   자동으로 안 줄어든다 — 좁은 반쪽 칸(decoration-field-pair)에 넣으면 옆 숫자
   입력칸이 패널 밖으로 밀려 잘렸다. flex:1 1 auto + min-width:0으로 실제 남은
   폭만큼 줄어들게 한다(원래 한 줄 전체 폭을 쓰던 곳도 결과는 동일해 안전하다). */
.range-with-number input[type='range'] {
  flex: 1 1 auto;
  min-width: 0;
}
.range-number-input {
  width: 56px;
  font-size: 12px;
  padding: 2px 4px;
  border: 1px solid var(--border);
  border-radius: 4px;
}

.decoration-xywh-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px 10px;
}
/* [라벨을 입력창 왼쪽에] 기본 .field는 flex-direction:column(라벨이 입력창
   위 별도 줄)이라, X/Y/W/H 4칸이 세로로 쌓여 4줄 높이를 차지했다 — 여기서만
   flex-direction:row로 라벨을 입력창 왼쪽에 붙이고(라벨<->input의 <label for>
   연결은 그대로라 접근성은 안 바뀐다), 기본 .field의 margin-bottom(12px)도
   0으로 없애 그리드 자체의 gap(4px)과 중복으로 안 쌓이게 한다 — 2줄(X/Y,
   W/H)로 줄어든다. 화살표 레이어의 X1/Y1/X2/Y2도 같은 클래스를 그대로 쓴다.
*/
.decoration-xywh-grid .field {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 5px;
  margin-bottom: 0;
  min-width: 0;
}
.decoration-xywh-grid .field label {
  flex-shrink: 0;
  width: 20px;
  font-size: 12px;
  color: var(--text-muted, #6b7280);
}
.decoration-xywh-grid .field input {
  flex: 1 1 0;
  min-width: 0;
  width: 0; /* flex-basis 역할 — 부모가 좁아져도 min-width:0과 함께 넘치지 않게 */
  font-size: 12px;
  padding: 3px 5px;
  border: 1px solid var(--border);
  border-radius: 4px;
}

/* [UI-3] 관련 있는 필드 둘을 한 행에 나란히(글자색+배경색, 크기+줄간격, 정렬+굵기
   등) — 우측 패널이 300px로 좁으니 각 칸의 select/input이 폭에 맞춰 줄어들게
   min-width:0을 꼭 준다(안 주면 select 기본 크기 때문에 넘친다). */
.decoration-field-pair {
  display: flex;
  gap: 10px;
  align-items: flex-end;
}
.decoration-field-pair > * {
  flex: 1 1 0;
  min-width: 0;
}

/* [UI-2] "회전(°)"처럼 X/Y/W/H보다 라벨이 긴 좌표 필드 — 기본 16px 폭 대신 라벨
   전체가 한 줄에 들어갈 만큼 넓힌다. */
.decoration-coord-field-wide label {
  width: auto !important;
  white-space: nowrap;
}

/* [UI-1] "타입별 고유 속성 / 위치·크기 / 순서·삭제" 세 구간을 시각적으로 구분하는
   랜드마크 — 타입마다 위 구간 높이가 달라도, 이 구분선을 찾으면 항상 같은 종류의
   내용이 이어진다는 걸 바로 알 수 있게 한다. */
.decoration-panel-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: 4px 0;
  width: 100%;
}
.decoration-panel-section-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted, #6b7280);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.decoration-preview-badge {
  position: absolute;
  top: 6px;
  left: 6px;
  background: var(--primary);
  color: #fff;
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 999px;
  z-index: 60;
  pointer-events: none;
}

.decoration-preview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

/* 다중 선택(2개 이상) 정렬/배분/크기맞춤 툴바 — 속성 패널(flex-wrap) 안에서 항상
   한 줄을 다 차지하게 해서 뒤따르는 "N개 선택됨" 안내 문구와 겹치지 않게 한다. */
.decoration-align-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  flex-basis: 100%;
}
.decoration-align-group {
  display: flex;
  align-items: center;
  gap: 4px;
}
.decoration-align-group-label {
  font-size: 12px;
  color: var(--text-muted, #6b7280);
  margin-right: 2px;
  white-space: nowrap;
}

/* 크기 맞춤의 기준 레이어 강조 — 다중 선택(.selected)과 구분되는 실선 강조 */
.decoration-layer.size-reference {
  outline: 2px solid var(--primary);
  outline-offset: 1px;
}

/* --- 꾸미기 편집 전체화면 모달 --- 세로로 긴 캔버스를 편집할 때 좌우 도구가 스크롤에
   밀려 화면 밖으로 나가는 문제를 없애려고, 편집 영역 전체를 브라우저 창을 덮는 모달
   안에 둔다. 내부 DOM(좌측 레일/중앙 캔버스/우측 패널)은 JS가 블록마다 한 번만 만들어
   재사용하고, 열고 닫을 때 이 모달의 슬롯 안팎으로 옮기기만 한다(editor.html
   openDecorationEditor/closeDecorationEditor 참고) — 그래서 이 CSS는 순수하게 레이아웃
   껍데기만 담당한다.
   z-index는 다른 작은 모달(.modal-overlay 기본값 10)보다 낮게 둔다 — AI 배치 제안/AI
   프롬프트(배경 생성)/유형 선택 모달이 이 꾸미기 모달 위에 그대로 열려야 하기 때문에,
   DOM 순서를 건드리지 않고 z-index만으로 항상 뒤에 깔리게 한다. */
.decoration-modal-overlay {
  z-index: 8;
  align-items: stretch;
  justify-content: stretch;
  padding: 0;
}

.decoration-modal {
  width: 100%;
  height: 100%;
  border-radius: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.decoration-modal-body {
  flex: 1;
  min-height: 0;
  display: flex;
}

/* 좌/우 슬롯 자체는 크기·스크롤만 담당하는 껍데기다 — 실제 flex-column 레이아웃은
   그 안에 매번 옮겨지는 state.leftRailEl/rightPanelEl(.decoration-left-rail/
   .decoration-right-panel)에 준다. 슬롯 자신에 flex 스타일을 주면, 모달을 열 때마다
   그 안에 들어오는 내용물 하나(래퍼 div)만 자식이 되어 정렬 스타일이 무의미해진다. */
.decoration-modal-left {
  /* [레일 폭 축소] 220px은 레이어 목록 줄이 손잡이+아이콘+이름+눈+자물쇠 5개를
     한 줄에 동시에(항상 보이는 상태로) 욱여넣던 시절의 폭이다. 손잡이는 없앴다
     (row 자체가 이미 드래그 대상이라 순수 장식이었다). 눈·자물쇠는 처음엔 이름
     칸 위에 겹치는 오버레이(hover 시에만 pointer-events:auto)로 폭을 아예 안
     차지하게 시도했는데, Playwright 기존 클릭 테스트가 무수정 상태로 재현
     가능하게 실패했다(.decoration-layer-row-toggle 주석 참고 — hover가 먼저
     있어야 클릭 판정이 나는데 클릭 판정이 나야 hover가 시작되는 순환). 실사용자도
     같은 이벤트 순서로 눌러야 하므로 오버레이를 버리고 눈·자물쇠 자리를 항상
     flex 흐름에 예약해두는 방식으로 되돌렸다 — 그 결과 124px(도구 버튼 라벨만
     고려한 옛 값)에서는 버튼 라벨은 안 잘려도 이름 칸이 "텍스트"→"텍…"처럼
     잘렸다(실측 스크린샷으로 확인) — 이름 칸에 최소 3글자(가장 긴 라벨)가
     들어갈 여유를 더 줘서 140px로 조정했다. */
  flex: 0 0 140px;
  overflow-y: auto;
  border-right: 1px solid var(--border);
}

.decoration-left-rail {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 8px;
  /* [레이어 목록] min-height 100%였을 때는 레일 전체가 컨테이너보다 커지면
     .decoration-modal-left가 통째로 스크롤됐다(도구 버튼도 같이 밀려 올라감).
     레이어가 많아지면 목록만 자체 스크롤되고 도구 버튼/되돌리기·다시하기는
     항상 보여야 하므로, 레일 자체를 컨테이너 높이에 고정하고 목록 쪽에만
     flex:1 + overflow-y:auto를 준다(아래 .decoration-layer-list). */
  height: 100%;
  box-sizing: border-box;
}

.decoration-left-rail .toolbar-btn {
  width: 100%;
  text-align: center;
}

.decoration-layer-list-heading {
  margin-top: 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--dm-text-muted, var(--text-muted));
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* [레이어 목록] flex:1로 남는 공간을 전부 차지하고 그 안에서만 스크롤된다 —
   도구 버튼(위)과 되돌리기/다시하기(아래, .decoration-left-rail-spacer)는
   레이어 수와 무관하게 항상 화면에 보인다. */
.decoration-layer-list {
  flex: 1;
  min-height: 60px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.decoration-layer-list-empty {
  margin: 8px 0;
  font-size: 12px;
  color: var(--dm-text-muted, var(--text-muted));
  text-align: center;
}

.decoration-layer-row {
  position: relative; /* 눈·자물쇠 오버레이(position:absolute)의 기준 */
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 5px 4px;
  border-radius: var(--radius);
  cursor: pointer;
  border: 1px solid transparent;
}

.decoration-layer-row:hover {
  background: var(--dm-bg-elevated, var(--surface));
}

.decoration-layer-row.selected {
  background: var(--dm-active-bg, var(--primary));
  border-color: var(--dm-border-strong, var(--primary));
}

.decoration-layer-row.locked .decoration-layer-row-name {
  color: var(--dm-text-muted, var(--text-muted));
}

.decoration-layer-row.hidden-layer .decoration-layer-row-name {
  opacity: 0.55;
  font-style: italic;
}

.decoration-layer-row.dragging {
  opacity: 0.4;
}

.decoration-layer-row.drag-over-top {
  border-top: 2px solid var(--dm-border-strong, var(--primary));
}

.decoration-layer-row.drag-over-bottom {
  border-bottom: 2px solid var(--dm-border-strong, var(--primary));
}

.decoration-layer-row-icon {
  flex-shrink: 0;
  width: 18px;
  text-align: center;
  font-size: 12px;
  font-weight: 700;
}

.decoration-layer-row-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
}

/* [레일 폭 축소] 눈·자물쇠는 평소엔 opacity로만 흐려 놓고(공간은 그대로
   flex 흐름에 남겨둔다 — position:absolute 오버레이는 시도했지만 버렸다, 아래
   참고), 줄에 hover하거나 잠김/숨김 상태(.locked/.hidden-layer)일 때만 또렷이
   보인다.
   [폐기된 접근 — 반드시 기록] 처음엔 이름 칸 위에 겹치는 절대좌표 오버레이로
   시도했다(opacity:0 + pointer-events:none, hover 시에만 pointer-events:auto).
   폭은 그게 더 줄었지만, 실제로 Playwright 기존 테스트(decoration-layer-panel-
   ui/zoom.spec.js, hover 없이 바로 .click())가 무수정 상태에서 재현 가능하게
   실패했다 — pointer-events:none인 동안은 그 좌표의 히트테스트가 항상 다른
   요소(이름 span)로 잡혀서, "먼저 hover해야 클릭 가능 판정이 남" ↔ "hover는
   move+hit-test가 그 버튼에 먼저 닿아야 시작됨"이 서로를 막는 순환 의존이
   생겼다(Playwright는 클릭 전 그 지점의 히트테스트가 대상 요소와 일치하는지
   먼저 확인하고, 안 맞으면 재시도만 반복할 뿐 실제 마우스는 이미 그 위치에
   있었는데도 재시도 사이에 hover 상태가 새로 계산되지 않았다 — 재현 확인).
   실사용자도 정확히 같은 조건(같은 좌표, 같은 이벤트 순서)으로 눌러야 하므로
   "테스트만의 문제"로 볼 수 없다고 판단해 되돌렸다 — pointer-events는 항상
   auto로 두고 opacity만 hover/상태로 토글하는 지금 방식으로 바꿨다. */
.decoration-layer-row-toggle {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  background: none;
  font-size: 11px;
  line-height: 20px;
  cursor: pointer;
  border-radius: var(--radius);
  color: var(--dm-text, inherit);
  /* [테스트 안정성] opacity에 transition을 줬더니 hover 직후 바로 읽는 실제
     e2e 체크(getComputedStyle)가 트랜지션 중간값을 잡아 레이스 컨디션이
     생겼다(재현 확인) — 아이콘 하나 나타나는 정도는 즉시 전환이어도 충분해
     transition 없이 즉시 토글한다. */
  opacity: 0;
}

/* 잠김·숨김 상태는 hover 없이도 항상 보인다 — 상태를 고치려면 눈·자물쇠 둘 다
   필요하므로(둘 중 하나만 보이면 나머지를 만지려고 어차피 hover해야 함) 어느
   쪽 상태든 둘 다 노출한다. */
.decoration-layer-row:hover .decoration-layer-row-toggle,
.decoration-layer-row.locked .decoration-layer-row-toggle,
.decoration-layer-row.hidden-layer .decoration-layer-row-toggle {
  opacity: 1;
}

.decoration-layer-row-toggle:hover {
  background: var(--dm-active-bg, var(--surface));
}

/* 되돌리기/다시하기를 레일 바닥에 고정 — 레이어 목록의 flex:1이 남는 공간을
   전부 차지하므로, 이 spacer는 flex-shrink:0(기본값)로 항상 바닥에 남는다.
   margin-top:auto는 목록이 없거나(레이어 0개) 아주 짧을 때도 안전하게 바닥에
   붙여주는 보조 장치로 남겨둔다. */
.decoration-left-rail-spacer {
  flex-shrink: 0;
  margin-top: auto;
}

/* 좌/우 슬롯과 같은 이유로 순수 껍데기다 — 실제 세로 2단(스크롤 캔버스 뷰포트 +
   고정 줌 바) 레이아웃은 매번 옮겨지는 state.centerEl(.decoration-center-wrap)이
   갖는다. */
.decoration-modal-center {
  flex: 1;
  min-width: 0;
  display: flex;
}

.decoration-center-wrap {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.decoration-canvas-viewport {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 24px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: var(--bg, #f4f4f5);
}

/* 캔버스 하단 배율 표시줄(화면 맞춤 "N%") — 뷰포트를 스크롤해도 항상 보인다
   (.decoration-modal-bottom과 같은 "스크롤 영역 + 고정 바" 패턴). 캔버스 자체는
   밝은 배경을 유지하는 중앙 패널 소속이라 이 바도 다크 테마를 안 쓴다(좌/우
   레일과 달리). */
.decoration-zoom-bar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 12px;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

.decoration-zoom-indicator {
  min-width: 44px;
  text-align: center;
  font-size: 12px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* [UI 정리] overflow-y만 지정하면(overflow-x 미지정=visible) 브라우저가 스펙에
   따라 overflow-x도 자동으로 'auto'로 승격시켜(둘 중 하나가 visible이 아니면
   나머지도 visible일 수 없다는 CSS Overflow 규칙) 패널 자체에도 가로 스크롤이
   생겼었다 — 안에서 잘리던 요소(체크박스 라벨, 움짤 문단 카드)를 실측으로 찾아
   각각 줄바꿈되게 고쳤으니, 이제 이 패널 자체는 가로로 넘칠 내용이 없어야 한다.
   그래도 혹시 남을 여지를 막기 위해 overflow-x를 명시적으로 hidden으로 고정한다
   (case: 색상 피커 팝오버는 positionDropdownPanel이 이 패널 경계 안으로 직접
   클램프하므로 hidden이어도 잘리지 않는다). */
.decoration-modal-right {
  flex: 0 0 300px;
  overflow-y: auto;
  overflow-x: hidden;
  border-left: 1px solid var(--border);
}

.decoration-right-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  box-sizing: border-box;
}

.decoration-accordion-section {
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.decoration-accordion-section > summary {
  cursor: pointer;
  padding: 10px 12px;
  font-weight: 600;
  font-size: 13px;
  list-style: none;
}
.decoration-accordion-section > summary::-webkit-details-marker {
  display: none;
}
.decoration-accordion-section > summary::before {
  content: '▸';
  display: inline-block;
  width: 14px;
  color: var(--text-muted, #6b7280);
}
.decoration-accordion-section[open] > summary::before {
  content: '▾';
}

.decoration-accordion-body {
  padding: 4px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* [B] "캔버스 설정" 섹션 안에서 배경 설정과 배경 사진 AI를 시각적으로 구분하는
   작은 소제목. 대상(배경 사진)을 라벨만 보고도 알 수 있게 텍스트 자체에 명시한다 —
   AI 도구 섹션의 "선택한 이미지 레이어 배경 제거"와 짝을 이룬다. */
.decoration-section-subheading {
  margin-top: 2px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted, #6b7280);
}

.decoration-modal-bottom {
  flex-shrink: 0;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

/* [버그 수정] 자동저장 실패 배너 — alert()과 달리 화면을 막지 않고, 닫기 전까지
   그대로 남아있어 "정상처럼 보이는데 사실 저장은 안 되고 있는" 상태를 사용자가
   놓치지 않게 한다. 같은 원인으로 여러 번 실패해도(드래그 중 다중 레이어 저장 등)
   showDecorationSaveError가 텍스트만 갱신하므로 배너가 여러 개 쌓이지 않는다. */
.decoration-modal-save-error {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 16px;
  background: #fdecea;
  color: var(--danger);
  border-top: 1px solid var(--danger);
  font-size: 13px;
}
.decoration-modal-save-error.hidden {
  display: none;
}
.decoration-modal-save-error-dismiss {
  flex-shrink: 0;
  border: none;
  background: none;
  color: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 4px;
}

/* [결과물 생성 통합] "결과물 만들기" 직후 뜨는 결과 배너 — 정지(png)/움짤(webp)
   두 형식이 같은 자리에 같은 모양으로 보이도록 통일했다(예전엔 움짤에만 있었고
   "움짤" 아코디언 안에 있었다). 저장 실패 배너(위)와 같은 "하단 바 바로 위,
   싱글턴" 자리를 쓰지만 실패가 아니라 성공 알림이라 위험색을 쓰지 않는다.
   웹P는 그냥 <img>로 넣으면 브라우저가 알아서 재생하며 보여준다. */
.decoration-modal-output-result {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  background: var(--surface);
  border-top: 1px solid var(--border);
}
.decoration-modal-output-result.hidden {
  display: none;
}
.decoration-modal-output-result-thumb {
  flex-shrink: 0;
  width: 56px;
  height: 56px;
  object-fit: contain;
  background: #f4f4f5;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}
.decoration-modal-output-result-body {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.decoration-modal-output-result-body p {
  margin: 0;
  font-size: 13px;
}

/* [결과물 생성 통합] 하단 바 — [닫기] | [형식 토글 → 결과물 만들기]. 닫기는
   왼쪽 끝, 나머지는 오른쪽 끝으로 갈라(space-between) 예전 ASCII 설계의 "|"
   구분을 CSS 레이아웃만으로 표현한다(별도 구분선 엘리먼트 없이). */
.decoration-modal-bottom {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  background: var(--surface);
}
.decoration-output-action-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  /* 문단 없음 안내(줄바꿈 가능)까지 포함해도 넘치지 않도록 — 우측 패널 폭 정리와
     같은 원칙(어떤 내용도 잘리거나 창 밖으로 나가지 않는다). */
  min-width: 0;
}
.decoration-output-format-toggle {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.decoration-output-format-buttons {
  display: flex;
}
.decoration-output-format-btn {
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  padding: 8px 14px;
  font-size: 13px;
  cursor: pointer;
}
.decoration-output-format-btn:first-child {
  border-radius: var(--radius) 0 0 var(--radius);
  border-right: none;
}
.decoration-output-format-btn:last-child {
  border-radius: 0 var(--radius) var(--radius) 0;
}
.decoration-output-format-btn.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
  font-weight: 600;
}
.decoration-output-format-btn:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
.decoration-output-format-hint {
  margin: 0;
  font-size: 11px;
  color: var(--text-muted);
}
.decoration-output-format-hint.hidden {
  display: none;
}
.decoration-output-action-arrow {
  color: var(--text-muted);
  font-size: 14px;
}

/* [버그 수정 — 결과물 낡음 표시] 마지막 결과물 이후 편집이 있었다는 안내 —
   저장 실패(danger)도 성공(중립)도 아닌 "아직 할 일이 남았다"는 뜻이라 경고색을
   쓴다. output-result 배너와 같은 "하단 바 바로 위, 싱글턴" 자리를 쓴다. */
.decoration-output-stale-hint {
  flex-shrink: 0;
  margin: 0;
  padding: 8px 16px;
  background: #fff7ed;
  color: #9a5b13;
  border-top: 1px solid #f3d19e;
  font-size: 13px;
}
.decoration-output-stale-hint.hidden {
  display: none;
}
/* 낡은 상태에서 "결과물 만들기" 버튼을 강조한다 — 새 버튼을 더 만들지 않고
   이미 있는 버튼의 시선 우선순위만 높인다. */
#decoration-modal-make-output-btn.is-stale {
  box-shadow: 0 0 0 3px #fed7aa;
}

body.decoration-modal-open {
  overflow: hidden;
}

/* [UI-6] 좌측 레일·우측 패널만 어둡게 — 캔버스(.decoration-modal-center)는 작업물
   색감을 그대로 봐야 하는 영역이라 밝게 유지한다. 전부 .decoration-modal-left/
   .decoration-modal-right 스코프 안에서만 적용해 editor.html 본문/review.html에는
   전혀 영향이 없다(common.css의 :root 전역 토큰은 안 건드림).
   대비는 전부 WCAG AA(4.5:1) 이상 확인: 배경(#242428)·기본 글자(#f2f2f4) ≈ 16.9:1,
   보조 글자(#b6b6bd) ≈ 8.6:1. --dm-active-bg는 사이트 --primary(#24211d, 사실상
   검정에 가까움)를 재사용하면 이 어두운 배경과 거의 구분이 안 돼(둘 다 거의 검정)
   따로 밝은 회색 계열로 둔다. */
.decoration-modal-left,
.decoration-modal-right {
  --dm-bg: #242428;
  --dm-bg-elevated: #323238;
  --dm-text: #f2f2f4;
  --dm-text-muted: #b6b6bd;
  --dm-border: #4a4a52;
  --dm-border-strong: #78787f;
  --dm-active-bg: #55555e;
  --dm-danger-text: #ff9b9b;
  background: var(--dm-bg);
  color: var(--dm-text);
}

.decoration-modal-left .field label,
.decoration-modal-right .field label,
.decoration-modal-right .decoration-panel-section-label,
.decoration-modal-right .field-hint,
.decoration-modal-right .badge-icon-category-heading {
  color: var(--dm-text-muted);
}
.decoration-modal-right .field-error {
  color: var(--dm-danger-text);
}

/* 입력창/드롭다운/텍스트영역 — 포커스 테두리는 밝게 둬서 어두운 배경에서도 또렷이
   보이게 한다(기본 .field input:focus의 border-color:var(--text)는 밝은 배경 전용
   어두운 테두리라 여기선 반대로 밝혀야 한다). */
.decoration-modal-left input[type='number'],
.decoration-modal-left input[type='text'],
.decoration-modal-right input[type='number'],
.decoration-modal-right input[type='text'],
.decoration-modal-right textarea,
.decoration-modal-right select.rte-select,
.decoration-modal-right .rte-color-hex-input {
  background: var(--dm-bg-elevated);
  border-color: var(--dm-border);
  color: var(--dm-text);
}
.decoration-modal-left input[type='number']:focus,
.decoration-modal-right input[type='number']:focus,
.decoration-modal-right input[type='text']:focus,
.decoration-modal-right textarea:focus,
.decoration-modal-right select.rte-select:focus {
  border-color: var(--dm-border-strong);
  outline: 1px solid var(--dm-border-strong);
}

/* 슬라이더(밝기/대비/투명도/굵기 등) — accent-color 하나로 트랙+손잡이를 모두
   밝은 회색 계열로 바꾼다(최신 크로미움/파이어폭스 지원, 이 프로젝트 개발 대상
   브라우저 범위 안). */
.decoration-modal-right input[type='range'] {
  accent-color: var(--dm-border-strong);
}

/* 버튼(오브젝트 추가·되돌리기/다시하기·색상 트리거·순서 변경 등) */
.decoration-modal-left .toolbar-btn,
.decoration-modal-right .toolbar-btn,
.decoration-modal-right .btn:not(.btn-primary):not(.btn-danger),
.decoration-modal-right .ai-action-btn {
  background: var(--dm-bg-elevated);
  border-color: var(--dm-border);
  color: var(--dm-text);
}
.decoration-modal-left .toolbar-btn:hover,
.decoration-modal-right .toolbar-btn:hover,
.decoration-modal-right .btn:not(.btn-primary):not(.btn-danger):hover,
.decoration-modal-right .ai-action-btn:hover:not(:disabled) {
  border-color: var(--dm-border-strong);
  color: var(--dm-text);
}
.decoration-modal-right .toolbar-btn.active,
.decoration-modal-right .decoration-grid-toggle-btn.active {
  background: var(--dm-active-bg);
  border-color: var(--dm-border-strong);
  color: var(--dm-text);
}
/* 비활성 상태 — 불투명도만 낮추면 이미 어두운 배경 위에서 거의 안 보일 수 있어,
   글자색 자체를 보조색으로 낮춰 "흐리지만 읽히는" 상태를 만든다. */
.decoration-modal-left .toolbar-btn:disabled,
.decoration-modal-right .toolbar-btn:disabled,
.decoration-modal-right .btn:disabled,
.decoration-modal-right .ai-action-btn:disabled {
  opacity: 1;
  background: var(--dm-bg);
  border-color: var(--dm-border);
  color: var(--dm-text-muted);
  cursor: default;
}
/* '레이어 삭제'(danger)·'배경 제거' 등 이미 채도 있는 배경의 버튼은 그대로 두되
   (자체 대비로 충분), 어두운 패널 배경과 경계가 흐려지지 않도록 테두리만 밝힌다. */
.decoration-modal-right .btn-danger,
.decoration-modal-right .btn-primary {
  border-color: rgba(255, 255, 255, 0.25);
}

/* [문단 구조 2단계] "움짤" 아코디언의 문단 목록 — 각 줄은 순번/대상 요약/누적
   시작 시각/삭제 버튼 헤더 + 프리셋·방향·지속·지연 컨트롤. 레이어 목록
   (.decoration-layer-row)과 같은 카드형 줄 관례를 따르되, 우측 패널(300px) 폭에
   맞춰 컨트롤이 세로로 쌓인다. */
.decoration-paragraph-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.decoration-paragraph-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  border: 1px solid var(--dm-border);
  border-radius: var(--radius);
}
.decoration-paragraph-row.is-empty-paragraph {
  border-style: dashed;
  opacity: 0.75;
}
/* [UI 정리] 순번/요약/프리셋·방향·지속 요약/시작 시각/삭제까지 한 줄에 욱여넣다 보니
   (요약은 이미 ellipsis로 줄어들지만, 프리셋·방향·지속 요약과 시작 시각은
   white-space:nowrap + flex-shrink:0라 전혀 줄어들지 않는다) 긴 프리셋+방향 조합
   ("회전하며 등장 · 반시계 방향 · 0.40초")에서 실측으로 삭제(✕) 버튼이 패널 밖
   (x=1315~1335, 패널은 980~1280)으로 완전히 밀려나는 걸 확인했다. flex-wrap을
   허용해 안 들어가는 항목은 다음 줄로 자연스럽게 내려가게 한다 — ellipsis 요약
   보다(자를 정보가 없는 짧은 값들이라) 줄바꿈이 정보 손실이 없다. */
.decoration-paragraph-row-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
/* [슬라이더 드래그 회귀 수정] 행 전체를 draggable로 두면 그 안의 <input type=range>를
   드래그할 때 네이티브 HTML5 드래그가 슬라이더의 포인터 조작을 가로챈다(첫 이동만
   반영되고 그 뒤로는 값이 안 바뀜 — e2e로 직접 확인). 재정렬은 이 핸들 하나만
   draggable로 두고 나머지 조작(슬라이더 등)은 그 충돌에서 완전히 벗어난다. */
.decoration-paragraph-row-handle {
  flex-shrink: 0;
  cursor: grab;
  font-size: 14px;
  line-height: 1;
  color: var(--text-muted);
  user-select: none;
}
.decoration-paragraph-row-handle:active {
  cursor: grabbing;
}
/* [카드 접기] 평소엔 이 화살표만 눌러서 펼치고 접는다 — summary 자체는 계속
   "클릭하면 캔버스 선택" 동작을 그대로 유지해야 해서(기존 동작) 토글을 별도
   아이콘으로 분리했다.
   [UI 정리] 예전엔 점 하나 크기(12px, 배경/테두리 없음)라 클릭 대상인지 알아보기
   어려웠고, 바로 옆 드래그 핸들(⠿, 순수 텍스트)과도 구분되지 않았다 — 24×24
   이상의 실제 버튼 모양(테두리+배경)을 주고, 펼침/접힘에 따라 방향이 바뀌는
   아이콘(▸/▾)만으로도 상태를 알 수 있게 한다. */
.decoration-paragraph-row-toggle {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 13px;
  line-height: 1;
  color: var(--dm-text-muted, var(--text-muted));
  background: var(--dm-bg-elevated, var(--surface));
  border: 1px solid var(--dm-border, var(--border));
  border-radius: var(--radius);
  cursor: pointer;
  user-select: none;
}
.decoration-paragraph-row-toggle:hover {
  background: var(--dm-active-bg, var(--surface));
  color: var(--dm-text, var(--text));
}
.decoration-paragraph-row-toggle[aria-expanded='true'] {
  color: var(--dm-text, var(--text));
  border-color: var(--dm-border-strong, var(--primary));
}
.decoration-paragraph-row-index {
  flex-shrink: 0;
  font-weight: 700;
  font-size: 12px;
}
.decoration-paragraph-row-summary {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  cursor: pointer;
}
.decoration-paragraph-row-summary:hover {
  text-decoration: underline;
}
/* 접힌 상태에서도 순번/대상/시작 시각과 함께 보여야 하는 프리셋+지속 요약. */
.decoration-paragraph-row-meta {
  flex-shrink: 0;
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
}
.decoration-paragraph-row-start {
  flex-shrink: 0;
  font-size: 11px;
}
.decoration-paragraph-row.dragging {
  opacity: 0.4;
}
.decoration-paragraph-row.drag-over-top {
  border-top: 2px solid var(--dm-border-strong, var(--primary));
}
.decoration-paragraph-row.drag-over-bottom {
  border-bottom: 2px solid var(--dm-border-strong, var(--primary));
}
/* [카드 접기] 목적이 "전체 순서와 타이밍을 한눈에 보는 것"이라 요약 줄이
   기본이다 — 상세(칩/프리셋/방향/지속/지연)는 펼쳤을 때만 그린다. */
.decoration-paragraph-row-detail {
  display: none;
  flex-direction: column;
  gap: 6px;
}
.decoration-paragraph-row.expanded .decoration-paragraph-row-detail {
  display: flex;
}
/* [문단 편집] 문단 삭제 버튼 — .decoration-layer-row-toggle을 그대로 재사용하면
   레이어 목록 전용 hover 규칙(.decoration-layer-row:hover에서만 opacity:1)이
   적용되지 않아 여기서는 항상 투명한 채로 남는다(실제로는 안 보이던 버튼이었음).
   문단 카드는 레이어 목록만큼 촘촘하지 않으니 hover로 숨길 필요 없이 항상 보인다. */
.decoration-paragraph-row-delete {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  background: none;
  font-size: 11px;
  line-height: 20px;
  cursor: pointer;
  border-radius: var(--radius);
  color: var(--text-muted);
}
.decoration-paragraph-row-delete:hover {
  background: var(--dm-active-bg, var(--surface));
  color: var(--dm-text, inherit);
}
/* 문단 하나에 속한 레이어들을 개별로 뺄 수 있는 칩 목록 — 문단을 통째로 지우지
   않고 "박스 + 텍스트" 중 텍스트만 뺄 때 쓴다. */
.decoration-paragraph-target-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.decoration-paragraph-target-chip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 1px 2px 1px 6px;
  border: 1px solid var(--dm-border);
  border-radius: 999px;
  font-size: 11px;
  color: var(--text-muted);
}
.decoration-paragraph-target-chip button {
  width: 16px;
  height: 16px;
  padding: 0;
  border: none;
  background: none;
  font-size: 10px;
  line-height: 16px;
  cursor: pointer;
  border-radius: 50%;
  color: inherit;
}
.decoration-paragraph-target-chip button:hover {
  background: var(--dm-active-bg, var(--surface));
  color: var(--dm-text, inherit);
}

/* [문단 구조 3단계] 자연어 AI 문단 제안 입력 상자 — 문단 목록 바로 위. */
.decoration-paragraph-ai-box {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  border: 1px solid var(--dm-border);
  border-radius: var(--radius);
  background: var(--dm-active-bg, var(--surface));
}
.decoration-paragraph-ai-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--dm-text, inherit);
}
.decoration-paragraph-ai-input {
  width: 100%;
  resize: vertical;
  font: inherit;
  font-size: 13px;
  padding: 6px 8px;
  border: 1px solid var(--dm-border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--dm-text, inherit);
}
.decoration-paragraph-ai-input:disabled {
  opacity: 0.6;
}
/* 표현 못한 지시 — 조용히 다른 것으로 바뀌지 않았다는 걸 알리는 목록. */
.decoration-paragraph-ai-unsupported {
  padding: 8px;
  border: 1px solid var(--warn-border, #e8b64b);
  border-radius: var(--radius);
  background: var(--warn-bg, #fff8e6);
  font-size: 12px;
}
.decoration-paragraph-ai-unsupported p {
  font-weight: 600;
  margin: 0 0 4px;
}
.decoration-paragraph-ai-unsupported ul {
  margin: 0;
  padding-left: 18px;
}
.decoration-paragraph-ai-actions {
  display: flex;
  gap: 8px;
}
/* 미리보기 검토 중인 문단 — 편집 컨트롤 없이 요약만 보여준다(buildReadOnlyParagraphRow
   참고: motion이 keyframes일 수 있어 편집 UI에 그대로 못 태운다). */
.decoration-paragraph-row-readonly {
  cursor: default;
}
.decoration-paragraph-row-readonly .decoration-paragraph-row-summary {
  cursor: default;
}
/* 적용 전과 내용이 달라진 문단(새로 생겼거나 값이 바뀜)만 강조한다. */
.decoration-paragraph-row-readonly.ai-changed {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 8%, transparent);
}

/* [AI 움짤 3단계 — 미리보기 수정] 재생 중엔 선택 핸들·그리드가 결과 WebP에 없는
   편집 전용 UI를 화면에 남겨 실제 결과와 다르게 보이게 한다 — 재생 시작
   (startDecorationAnimationPreview)~정지(stopDecorationAnimationPreview) 동안만
   .decoration-canvas에 이 클래스가 붙는다.
   [UI 정리] 선택 표시는 핸들(.layer-handle 등, DOM에 항상 있고 위 규칙으로 감춰짐)과
   테두리(.decoration-layer.selected의 outline, 레이어 엘리먼트 자신에 직접 걸림)
   두 갈래로 그려지는데, 이 규칙은 핸들만 숨기고 테두리는 그대로 둬서 재생 중에도
   선택된 오브젝트마다 테두리 선이 하나씩 남아 있었다(실제 재현 확인) — 서버
   렌더러(decorationRender.js/decorationAnimationOutput.js)는 "선택" 개념 자체가
   없어(block_overlays 스키마에 선택 여부 컬럼이 없다) 실제 생성되는 WebP 프레임에는
   이 테두리가 들어가지 않지만, 편집 화면 미리보기가 결과물과 달라 보이는 건 그
   자체로 버그이므로 outline도 함께 지운다. */
.decoration-canvas.animation-preview-playing .layer-handle,
.decoration-canvas.animation-preview-playing .layer-endpoint-handle,
.decoration-canvas.animation-preview-playing .decoration-grid-overlay {
  display: none !important;
}
.decoration-canvas.animation-preview-playing .decoration-layer.selected {
  outline: none !important;
}

/* 색상 선택 버튼(글자색/배경색/채움색 등)의 팝오버 + 미리보기 칩 + 배지 아이콘
   팝오버 — 색상 트리거 버튼 자체는 위 .toolbar-btn 규칙으로 이미 어둡게 바뀐다. */
.decoration-modal-right .rte-dropdown-panel {
  background: var(--dm-bg-elevated);
  border-color: var(--dm-border);
  color: var(--dm-text);
}
.decoration-modal-right .rte-dropdown-panel button {
  color: var(--dm-text);
}
.decoration-modal-right .rte-dropdown-panel button:hover {
  background: var(--dm-bg);
}
/* 검정/흰색 등 배경과 비슷한 색을 고를 때도 칩 경계가 항상 보이도록 밝은 테두리로
   고정한다(검증: #000000, #ffffff 둘 다 골라서 확인). */
.decoration-modal-right .color-picker-preview,
.decoration-modal-right .color-picker-swatch {
  border: 2px solid #e4e4e8;
}
.decoration-modal-right .badge-icon-search-input {
  background: var(--dm-bg-elevated);
  border-color: var(--dm-border);
  color: var(--dm-text);
}
.decoration-modal-right .badge-icon-option {
  background: var(--dm-bg-elevated);
}
.decoration-modal-right .badge-icon-option:hover {
  background: var(--dm-active-bg);
}
.decoration-modal-right .badge-icon-option.selected {
  border-color: var(--dm-border-strong);
  background: var(--dm-bg);
}

/* 관리자 전용 "프로덕션으로 호출" 칩 — 이미 자체 어두운 배경(반투명 검정)+흰 글자라
   그대로 둬도 읽히지만, 우리 패널도 어두워졌으니 경계가 흐려지지 않게 테두리만
   더한다.
   [UI 정리] 기본 .ai-sandbox-override-label은 갤러리의 넓은 가로 줄에서 쓰이므로
   white-space:nowrap이 문제없지만, 이 패널은 300px 고정이라 그 nowrap 텍스트가
   라벨 자기 박스보다 넓게 시각적으로 삐져나와(실측: getBoundingClientRect 폭은
   패널 안에 들어오는데, nowrap 때문에 실제로 그려지는 글자가 그 폭을 넘어서
   패널 오른쪽 바깥에서 잘렸다) 결국 "실제 과금)"이 통째로 안 보이는 문제가 있었다.
   줄바꿈을 허용하고, 칩 모양(둥근 배경)이 여러 줄에서는 어색해 보이므로 이 패널
   안에서만 사각 배경으로 바꾼다(체크박스+글자를 세로로도 쌓을 수 있게 align-items
   를 위쪽 정렬로 바꿈). */
.decoration-modal-right .ai-sandbox-override-label {
  border: 1px solid rgba(255, 255, 255, 0.25);
  white-space: normal;
  align-items: flex-start;
  border-radius: var(--radius);
  line-height: 1.35;
  /* 이 라벨의 부모는 두 군데다 — "캔버스 설정"의 .ai-actions-row(가로 flex,
     줄바꿈 허용)와 "AI 도구"의 세로 flex 래퍼. 세로 래퍼에서는 align-items:stretch
     기본값이라 이미 폭 전체를 차지하지만, 가로 줄에서는 다른 버튼과 한 줄을
     나눠 가지려다 좁은 폭에 맞춰 줄바꿈이 이상하게 꼬일 수 있어(min-content 계산이
     간단하지 않음) 항상 자기 줄 전체를 차지하도록 명시한다 — 두 부모 모두에서
     같은 결과(전체 폭 사용, 자연스러운 줄바꿈)가 나온다. */
  flex: 1 1 100%;
}
.decoration-modal-right .ai-sandbox-override-label input[type='checkbox'] {
  flex-shrink: 0;
  margin-top: 2px;
}

/* 아코디언(선택 항목/캔버스 설정/AI 도구) 테두리 + 펼침/접힘 화살표 */
.decoration-modal-right .decoration-accordion-section {
  border-color: var(--dm-border);
}
.decoration-modal-right .decoration-accordion-section > summary::before {
  color: var(--dm-text-muted);
}
.decoration-modal-right .decoration-panel-divider {
  border-top-color: var(--dm-border);
}

/* --- 영상 변환 추가(video-convert.js가 로직을 담당, 여기는 UI만) --- */

.vc-picker {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

.vc-picker-hint {
  font-size: 12px;
}

.vc-unsupported-notice {
  font-size: 13px;
  color: var(--danger);
  background: rgba(138, 47, 47, 0.08);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-bottom: 12px;
}

.vc-reference-player-wrap {
  position: relative;
  margin-bottom: 14px;
}

.vc-reference-player {
  display: block;
  width: 100%;
  max-height: 280px;
  border-radius: var(--radius);
  background: #000;
}

.vc-active-cut-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  font-size: 12px;
  padding: 4px 9px;
  border-radius: var(--radius);
  pointer-events: none;
}

.vc-apply-to-cut-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.vc-apply-to-cut-label {
  font-size: 12px;
  color: var(--text-muted);
}

.vc-apply-to-cut-row select {
  padding: 5px 7px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 12px;
}

.vc-crop-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.vc-tone-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 14px;
  font-size: 13px;
}
.vc-tone-row label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.vc-crop-status {
  font-size: 12px;
}

.vc-time-label {
  font-size: 12px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  width: 40px;
  flex-shrink: 0;
}

.vc-cut-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 12px;
}

.vc-cut-row {
  display: flex;
  align-items: center;
  gap: 8px;
  border-radius: var(--radius);
  transition: background-color 0.15s ease;
}

.vc-cut-row.is-active-cut {
  background: var(--confirm-bg);
}

.vc-cut-row.is-disabled .vc-cut-row-label,
.vc-cut-row.is-disabled .vc-time-label {
  color: var(--text-faint);
}

.vc-cut-checkbox-slot {
  width: 18px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.vc-cut-checkbox-slot input[type='checkbox'] {
  width: 15px;
  height: 15px;
  margin: 0;
}

.vc-cut-row-label {
  font-size: 12px;
  color: var(--text-muted);
  width: 44px;
  flex-shrink: 0;
}

/* 진행바(채움) 없이 얇은 회색 트랙 + 원형 핸들만 보이도록 브라우저 기본
   렌더링을 전부 무시하고 직접 그린다(-webkit/-moz 둘 다 필요). */
.vc-cut-row input[type='range'] {
  flex: 1 1 auto;
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  height: 16px;
  cursor: pointer;
}

.vc-cut-row input[type='range']:disabled {
  cursor: default;
  opacity: 0.4;
}

.vc-cut-row input[type='range']::-webkit-slider-runnable-track {
  height: 2px;
  background: var(--border-strong);
  border-radius: 1px;
}

.vc-cut-row input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -6px;
  border-radius: 50%;
  background: var(--primary);
  border: none;
}

.vc-cut-row input[type='range']:disabled::-webkit-slider-thumb {
  background: var(--text-faint);
}

.vc-cut-row input[type='range']::-moz-range-track {
  height: 2px;
  background: var(--border-strong);
  border-radius: 1px;
}

.vc-cut-row input[type='range']::-moz-range-progress {
  background: transparent;
}

.vc-cut-row input[type='range']::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--primary);
  border: none;
}

.vc-cut-row input[type='range']:disabled::-moz-range-thumb {
  background: var(--text-faint);
}

.vc-status {
  font-size: 12px;
  color: var(--text-muted);
  margin: 8px 0;
}

.vc-result {
  margin: 12px 0;
}

.vc-frame-thumbs {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.vc-frame-thumb {
  width: 64px;
  height: 64px;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--border-strong);
  background: var(--bg);
}

.vc-frame-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.vc-preview-box {
  display: flex;
  justify-content: center;
  background: var(--bg);
  border-radius: var(--radius);
  padding: 12px;
}

.vc-preview-box img {
  max-width: 100%;
  max-height: 280px;
  border-radius: var(--radius);
}

.asset-modal-img {
  display: block;
  max-width: 100%;
  max-height: 360px;
  margin: 0 auto 12px;
  border-radius: var(--radius);
  object-fit: contain;
  background: var(--bg);
}

.asset-modal-video-note {
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 12px;
  word-break: break-all;
}

/* --- 피드백 / 이력 모달 --- */

.feedback-list,
.history-list {
  max-height: 320px;
  overflow-y: auto;
  margin-bottom: 14px;
}

.feedback-empty,
.history-empty {
  font-size: 13px;
  color: var(--text-faint);
  font-style: italic;
}

.feedback-item {
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.feedback-item:last-child {
  border-bottom: none;
}

.feedback-item.is-resolved {
  opacity: 0.7;
}

.feedback-item-meta {
  font-size: 11.5px;
  color: var(--text-muted);
}

.feedback-item-target {
  font-size: 11.5px;
  color: var(--text-faint);
  margin-top: 2px;
}

.feedback-item-body {
  font-size: 13px;
  color: var(--text);
  margin-top: 6px;
  white-space: pre-wrap;
  word-break: break-word;
}

.feedback-resolved-tag {
  display: inline-block;
  margin-top: 6px;
  font-size: 11px;
  color: var(--primary);
  font-weight: 600;
}

.feedback-resolve-btn {
  margin-top: 6px;
  font-size: 12px;
  padding: 4px 10px;
}

.feedback-form {
  border-top: 1px solid var(--border);
  padding-top: 14px;
}

.feedback-form label {
  display: block;
  font-size: 12px;
  color: var(--text-muted);
  margin-bottom: 4px;
}

.feedback-form select {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 13px;
  margin-bottom: 8px;
}

.feedback-form textarea {
  width: 100%;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 13px;
  font-family: inherit;
  resize: vertical;
}

.history-item {
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 12.5px;
  color: var(--text);
}

.history-item:last-child {
  border-bottom: none;
}

/* 완료(done) 후에 일어난 편집 표식 — 작업자·클라이언트 화면 공용. 줄 끝에
   붙는 작은 배지 하나로 충분하다는 요청이라 별도 아이콘/줄바꿈 없이 간다. */
.history-after-done-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  background: #fbe6e4;
  color: #a3372b;
  font-size: 10.5px;
  font-weight: 600;
  white-space: nowrap;
}

/* --- 목록 보기: 압축된 한 줄 + 드래그 정렬 --- */

.list-container {
  max-width: 720px;
  margin: 0 auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.list-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-bottom: 1px solid var(--border);
  cursor: grab;
}

.list-row:last-child {
  border-bottom: none;
}

.list-row:hover {
  background: #fafafa;
}

.list-row.dragging {
  opacity: 0.4;
}

.list-row.drag-over-top {
  box-shadow: inset 0 2px 0 var(--primary);
}

.list-row.drag-over-bottom {
  box-shadow: inset 0 -2px 0 var(--primary);
}

.list-row.is-held {
  filter: grayscale(1);
  opacity: 0.55;
}

.list-drag-handle {
  flex: 0 0 auto;
  color: var(--text-faint);
  font-size: 13px;
  user-select: none;
}

.list-no {
  flex: 0 0 auto;
  width: 24px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-faint);
  text-align: right;
}

.list-preview {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--text);
}

.list-preview.empty-content {
  color: var(--text-faint);
  font-style: italic;
}

/* 확정된 이미지 후보가 있는 image 블록은 텍스트 대신 실제 썸네일을 보여준다
   (public/js/block-list-view.js) — 작업자·클라이언트 목록 보기 공용. */
.list-preview-thumb {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
}

.list-preview-thumb img {
  width: 32px;
  height: 32px;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
}

.list-feedback-count {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--text-faint);
}

.list-feedback-count.has-feedback {
  color: var(--danger);
  font-weight: 600;
}

/* 클라이언트 목록 보기는 편집 메뉴가 없는 대신 행 전체가 "흐름 보기로 이동"
   버튼이다 — 커서로 그 사실을 알려준다(작업자 목록의 기본 cursor:grab과 다름). */
.list-row.is-clickable {
  cursor: pointer;
}

.badge {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  line-height: 1.6;
  white-space: nowrap;
}

.badge-draft {
  background: #e4f3e8;
  color: #29713f;
}

.badge-feedback {
  background: #fbe6e4;
  color: #a3372b;
}

.badge-revising {
  background: #e3ecfb;
  color: #2c58a8;
}

.badge-done {
  background: #ede3fb;
  color: #6b3aad;
}

.badge-held {
  background: #ececea;
  color: #6b6b66;
}

.menu-wrap {
  flex: 0 0 auto;
  position: relative;
}

.menu-trigger {
  border: 1px solid var(--border-strong);
  background: var(--surface);
  border-radius: var(--radius);
  width: 28px;
  height: 28px;
  cursor: pointer;
  color: var(--text-muted);
}

.menu-trigger:hover {
  border-color: var(--text-faint);
  color: var(--text);
}

.menu-dropdown {
  position: absolute;
  right: 0;
  top: 32px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
  display: flex;
  flex-direction: column;
  min-width: 130px;
  z-index: 5;
  overflow: hidden;
}

.menu-dropdown button {
  border: none;
  background: none;
  text-align: left;
  padding: 8px 12px;
  font-size: 13px;
  cursor: pointer;
  color: var(--text);
}

.menu-dropdown button:hover {
  background: #fafaf9;
}

.menu-dropdown button.danger {
  color: var(--danger);
}

.add-block-btn {
  width: 100%;
  justify-content: center;
  border-style: dashed;
  color: var(--text-muted);
}

.btn-danger {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
}

.btn-danger:hover {
  background: #6e2424;
  border-color: #6e2424;
}

/* --- 클라이언트 검토 화면 (review.html) --- */

.review-error-message {
  max-width: 480px;
  margin: 60px auto 0;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: 14px;
  text-align: center;
}

.review-empty {
  color: var(--text-faint);
  font-size: 13px;
  padding: 30px 0;
  text-align: center;
}

.review-width-badge {
  cursor: default;
}

/* ============================================================
   모바일 레이아웃 (768px 미만): 통으로 나오는 오버레이 스트립 방식.
   editor.html / review.html 공용. 본문은 화면 폭 100%를 그대로 쓰고 블록 사이에
   컨트롤 요소를 두지 않는다. 오른쪽 끝의 작은 핸들(position: fixed)만 탭하면
   화면에 보이는 모든 블록의 컨트롤이 각자 자기 블록과 세로 위치가 맞는 행으로
   한꺼번에 나타나는 스트립이 오른쪽에서 슬라이드 인 한다. 스트립 자체는
   position: fixed가 아니라 본문과 같은 문서 흐름(.mobile-strip-wrap 안의
   absolute) 안에 있어서 스크롤하면 본문과 함께 움직인다.
   ============================================================ */

@media (max-width: 767px) {
  .view-toggle {
    display: none;
  }

  .main-wide {
    padding: 14px 0;
  }

  .editor-title-row {
    flex-wrap: wrap;
    row-gap: 8px;
    padding: 0 10px;
  }

  #export-open-btn {
    margin-left: 0;
  }
}

.mobile-banner {
  font-size: 12px;
  color: var(--text-muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  margin: 0 10px 10px;
}

.mobile-body-list {
  display: flex;
  flex-direction: column;
  /* 데스크톱의 .flow-container에 대응 — 모바일은 이 래퍼가 따로 없어서
     배경 없이 body의 회색(--bg)이 그대로 비쳐 보였다. --story-content-bg는
     .flow-container와 동일하게 스토리별 배경색 설정을 그대로 따른다. */
  background: var(--story-content-bg, var(--surface));
}

.mobile-block-item {
  width: 100%;
}

.mobile-block-item .rte-view {
  padding: 12px 14px;
}

.mobile-block-item .empty-content {
  padding: 4px 0;
}

/* 이미지/영상 블록도 문장 블록(.rte-view)과 동일한 좌우 여백을 갖게 한다 —
   기본 .media-block-body는 좌우 여백이 없어(PC 본문 열은 고정폭이라 필요
   없음) 모바일에서만 문장 블록과 어긋나 보였다. 안쪽 요소(.slot-featured-box
   등)는 폭을 %로 쓰므로 패딩을 더해도 넘치지 않는다. */
.mobile-block-item .media-block-body {
  padding: 12px 14px;
}

/* 본문 목록 + 스트립을 함께 담는 위치 기준 컨테이너. 스트립은 이 안에서
   absolute로 오른쪽에 겹치므로 래퍼 자체가 스크롤되면 스트립도 함께 움직인다
   (position: fixed를 쓰지 않는다 — 핸들만 예외).
   overflow-x: clip — 닫힌 스트립(.mobile-strip)이 translateX(100%)로 이 래퍼의
   오른쪽 바깥(래퍼 폭만큼 더 오른쪽)에 놓이는데, 이걸 클리핑해 주는 조상이
   없으면(예전엔 html·body 둘 다의 overflow-x: hidden이 이 역할까지 겸했다)
   모바일에서 문서의 실제 레이아웃 폭이 그 오프캔버스 영역만큼 넓어져 버린다
   (실측: iPhone 크기 에뮬레이션에서 innerWidth가 390 → 580으로 벌어짐).
   그 결과 (1) 화면을 옆으로 드래그하면 원래 안 보여야 할 스트립이 끌려
   나오고, (2) position: fixed; right: 0인 핸들(.mobile-strip-handle)이
   그 넓어진 레이아웃 폭의 오른쪽 끝에 붙어버려 실제 화면 밖으로 밀려나 안
   보인다 — 핸들이 안 보이는 것도 같은 원인.
   overflow-x: hidden이 아니라 overflow-x: clip을 쓰는 이유: hidden은 CSS
   overflow 스펙 규칙상 같은 요소의 overflow-y도 강제로 auto로 만들어 이
   래퍼를 스크롤 컨테이너로 만들어 버리고, 그러면 내부의 position: sticky
   (.mobile-strip-row-inner의 top: 8px)가 (앞서 candidate-bar sticky를 깨뜨린
   것과 같은 이유로) 무력화될 위험이 있다. clip은 시각적으로는 hidden과
   동일하게 잘라내지만 스크롤 컨테이너를 만들지 않는다(overflow-y가 auto로
   강제되지 않음, 실측 확인) — 그래서 sticky에 영향을 주지 않으면서 클리핑만
   할 수 있다. */
.mobile-strip-wrap {
  position: relative;
  overflow-x: clip;
}

/* 스트립이 열려 있을 때 본문(스트립 바깥) 영역을 탭하면 닫히게 하는
   투명한 탭 캐처. 본문을 어둡게 가리지 않는다(본문은 그대로 다 보여야 함) —
   스트립보다 낮고 본문보다 높은 z-index로 딱 그 클릭만 가로챈다. */
.mobile-strip-tap-catcher {
  position: absolute;
  inset: 0;
  z-index: 19;
  pointer-events: none;
}

.mobile-strip-tap-catcher.open {
  pointer-events: auto;
}

/* 스트립: 닫힌 상태에서는 화면 오른쪽 바깥으로 완전히 나가 있다가(translateX),
   핸들을 탭하면 슬라이드 인 한다. 배경을 살짝 반투명하게 둬서 아래 본문이
   비쳐 보이게 한다. */
.mobile-strip {
  position: absolute;
  top: 0;
  right: 0;
  width: 190px;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  background: rgba(255, 255, 255, 0.93);
  box-shadow: -2px 0 6px rgba(0, 0, 0, 0.15);
  transform: translateX(100%);
  transition: transform 0.22s ease;
  z-index: 20;
  pointer-events: none;
}

.mobile-strip.open {
  transform: translateX(0);
  pointer-events: auto;
}

/* 각 행의 높이는 JS(syncMobileStrip)가 대응하는 블록의 실제 렌더 높이에
   맞춰 인라인 style.height로 지정한다 — 여기서는 기본값/레이아웃만 정의. */
.mobile-strip-row {
  width: 100%;
  min-height: 52px;
  box-sizing: border-box;
  display: flex;
  /* 번호/상태/설정/피드백이 행 위쪽에 붙던 것을 세로 가운데로 — 안의
     .mobile-strip-row-inner가 position:sticky라, 짧은 블록에서는 그냥 가운데에
     자리 잡고 블록이 뷰포트보다 길어 스크롤될 때는 top:8px로 화면 위쪽에
     붙는 동작(위 주석 참고) 자체는 그대로 유지된다 — sticky의 "붙는 기준선"이
     달라질 뿐이다. */
  align-items: center;
  justify-content: center;
  border-top: 1px solid rgba(224, 224, 221, 0.8);
}

.mobile-strip-row:first-child {
  border-top: none;
}

/* 블록(=행) 높이가 화면보다 훨씬 클 수 있으므로, 컨트롤은 행 안에서
   position: sticky로 화면 위쪽에 붙여둔다 — 그 블록의 일부라도 보이는 동안은
   계속 손이 닿는 위치에 있다가, 다음 블록 행으로 넘어가면 자연히 넘겨준다.
   내용은 PC의 .flow-row처럼 한 줄로 가로 배치한다(번호/상태/설정/피드백/이력) —
   .mobile-strip 폭(190px)이 부족한 극단적인 경우에만 flex-wrap으로 다음 줄로
   넘어간다. */
.mobile-strip-row-inner {
  position: sticky;
  top: 8px;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 4px;
}

.mobile-strip-no {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-faint);
}

.mobile-strip-status {
  font-size: 10px;
  font-weight: 600;
  padding: 1px 5px;
  border-radius: 999px;
  white-space: nowrap;
}

/* 색은 :root의 --status-*(위쪽 .flow-col-status.status-*와 공유하는 단일
   정의처)를 그대로 쓴다 — 데스크톱과 모바일이 같은 상태를 다른 색으로
   보여주면 안 되므로 값을 여기 따로 두지 않는다. */
.mobile-strip-status.status-draft {
  background: var(--status-draft-bg);
  color: var(--status-fg);
}

.mobile-strip-status.status-feedback {
  background: var(--status-feedback-bg);
  color: var(--status-fg);
}

.mobile-strip-status.status-revising {
  background: var(--status-revising-bg);
  color: var(--status-fg);
}

.mobile-strip-status.status-done {
  background: var(--status-done-bg);
  color: var(--status-fg);
}

/* .flow-col-status와 같은 이유의 같은 보강 — 극단적으로 배경색이 상태
   색과 겹칠 때의 경계 보강. */
.mobile-strip-status.status-draft,
.mobile-strip-status.status-feedback,
.mobile-strip-status.status-revising,
.mobile-strip-status.status-done {
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.55);
}

/* 스트립 안에서 재사용하는 데스크톱 설정 메뉴(⋯)/피드백·이력 버튼은
   폭이 좁으니 데스크톱보다 작게, 그래도 탭은 가능한 크기로 줄이고,
   데스크톱 표 레이아웃 전용 테두리·배경은 제거한다. */
.mobile-strip .menu-trigger {
  width: 28px;
  height: 28px;
  font-size: 14px;
}

.mobile-strip .flow-col-feedback {
  border-bottom: none;
  padding: 0;
  flex-direction: row;
  align-items: center;
  gap: 4px;
}

.mobile-strip .feedback-btn,
.mobile-strip .history-btn {
  font-size: 10px;
  min-height: 24px;
  justify-content: center;
}

/* 핸들: 유일하게 position: fixed라 스크롤을 따라다니는 요소. 화면 오른쪽
   세로 중앙에 작게 붙어 있다가 탭하면 스트립을 열고 닫는다. */
.mobile-strip-handle {
  position: fixed;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  width: 22px;
  height: 44px;
  border: none;
  border-radius: 8px 0 0 8px;
  background: var(--text-faint);
  color: #fff;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  z-index: 40;
  box-shadow: -1px 0 4px rgba(0, 0, 0, 0.25);
}

.mobile-strip-handle.hidden {
  display: none;
}

/* 모달: 모바일에서는 전체 화면 + 입력/버튼을 터치 크기로. */
@media (max-width: 767px) {
  .modal-overlay {
    align-items: stretch;
    justify-content: stretch;
  }

  .modal,
  .modal-wide,
  .modal-xwide {
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    border-radius: 0;
    overflow-y: auto;
    box-sizing: border-box;
  }

  .feedback-form select,
  .feedback-form textarea,
  .field input,
  .field select,
  .field textarea {
    font-size: 16px; /* iOS에서 포커스 시 자동 확대되는 것을 막는 크기 */
    min-height: 44px;
    box-sizing: border-box;
  }

  .feedback-form textarea {
    min-height: 88px;
  }

  .modal-actions .btn {
    min-height: 44px;
    padding: 10px 16px;
    font-size: 14px;
  }

  .feedback-item,
  .history-item {
    font-size: 14px;
  }
}

/* --- 관리자 콘솔: 배너 관리 탭 --- */

.admin-hint {
  font-size: 12.5px;
  color: var(--text-muted);
  margin: -4px 0 16px;
}

.usage-upgrade-section {
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  max-width: 480px;
}

.banner-slots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}

.banner-slot-editor {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  background: var(--surface);
}

.banner-slot-editor h3 {
  font-size: 13px;
  font-weight: 600;
  margin: 0 0 10px;
}

.banner-slot-preview {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 1;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg);
  border: 1px dashed var(--border-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 10px;
}

.banner-slot-preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.banner-slot-empty {
  font-size: 12px;
  color: var(--text-faint);
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  margin: 4px 0 12px;
  cursor: pointer;
}


/* --- index.html 공지사항 최신 5건 위젯 --- */

.notice-widget {
  margin-top: 24px;
}

.notice-widget-list,
.notice-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border);
}

.notice-widget-item,
.notice-list-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 2px;
  border-bottom: 1px solid var(--border);
}

.notice-widget-title {
  color: var(--text);
  text-decoration: none;
  font-size: 13.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.notice-widget-title:hover {
  text-decoration: underline;
}

.notice-widget-date {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--text-faint);
}

.notice-widget-empty {
  padding: 20px 2px;
  color: var(--text-faint);
  font-size: 13px;
  text-align: center;
  list-style: none;
}

/* --- notice.html 상세 --- */

.notice-back-link {
  display: inline-block;
  margin-bottom: 14px;
}

.notice-detail-meta {
  font-size: 12.5px;
  color: var(--text-faint);
  margin: -6px 0 20px;
}

.notice-detail-body {
  font-size: 14px;
  line-height: 1.7;
}

.notice-detail-body img,
.notice-editor-preview img {
  max-width: 100%;
  height: auto;
}

.notice-detail-body :is(h1, h2, h3) {
  margin: 20px 0 10px;
}

.notice-detail-body p {
  margin: 0 0 12px;
}

.notice-detail-body pre {
  background: var(--bg);
  padding: 10px 12px;
  border-radius: var(--radius);
  overflow-x: auto;
}

/* --- admin.html 공지사항 관리 에디터 --- */

.notice-editor-toolbar {
  display: flex;
  gap: 8px;
  margin: 10px 0;
}

.notice-editor-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-bottom: 10px;
}

.notice-editor-split textarea {
  width: 100%;
  min-height: 360px;
  padding: 10px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  font-family: monospace;
  font-size: 13px;
  resize: vertical;
  box-sizing: border-box;
}

.notice-editor-preview {
  min-height: 360px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  overflow-y: auto;
  font-size: 13.5px;
  line-height: 1.6;
}

@media (max-width: 900px) {
  .notice-editor-split {
    grid-template-columns: 1fr;
  }
}

/* --- 회원 관리 상담 메모: 작성자 배지(관리자/회원) --- */

.role-badge {
  display: inline-block;
  font-size: 10.5px;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: 999px;
  margin-right: 6px;
}

.role-badge-admin {
  background: #e3ecfb;
  color: #2c58a8;
}

.role-badge-member {
  background: #ede3fb;
  color: #6b3aad;
}

/* --- 썸네일 뽑기 모달 — 꾸미기 편집 전체화면 모달(.decoration-modal-*)과 같은
   전체화면 골격을 쓰되, 좌/우가 캔버스가 아니라 "입력(영상/사진)"/"결과 목록"
   이라 두 슬롯 모두 자체 스크롤 영역이다. */
.thumbnail-picker-modal-overlay {
  z-index: 8;
  align-items: stretch;
  justify-content: stretch;
  padding: 0;
}

.thumbnail-picker-modal {
  /* [버그 수정] 배경을 빠뜨려 뒤쪽 오버레이 배경(.modal-overlay의
     rgba(0,0,0,0.35))과 그 뒤 페이지(메뉴/본문)가 그대로 비쳐 보이던 문제 —
     다른 모달이 이미 쓰는 값(.modal의 background: var(--surface))을 그대로
     재사용한다(이 창만 다른 값을 새로 만들지 않는다). */
  background: var(--surface);
  width: 100%;
  height: 100%;
  border-radius: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.thumbnail-picker-modal-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border);
}
.thumbnail-picker-modal-header h2 {
  margin: 0;
  font-size: 17px;
}

.thumbnail-picker-modal-body {
  flex: 1;
  min-height: 0;
  display: flex;
}

.thumbnail-picker-modal-left {
  flex: 0 0 420px;
  overflow-y: auto;
  border-right: 1px solid var(--border);
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.thumbnail-picker-modal-right {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 16px 20px;
}

body.thumbnail-picker-modal-open {
  overflow: hidden;
}

@media (max-width: 900px) {
  .thumbnail-picker-modal-body {
    flex-direction: column;
  }
  .thumbnail-picker-modal-left {
    flex: 0 0 auto;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
}

/* 입력 방식 탭(영상에서 뽑기 / 사진 올리기) */
.tp-mode-tabs {
  display: flex;
  gap: 6px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 10px;
}
.tp-mode-tab {
  flex: 1;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  font-size: 13.5px;
  color: var(--text-muted);
}
.tp-mode-tab.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
  font-weight: 600;
}

.tp-section-title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-muted);
  margin: 0;
}

.tp-upload-drop {
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius);
  padding: 28px 16px;
  text-align: center;
  color: var(--text-muted);
  font-size: 13px;
  cursor: pointer;
}
.tp-upload-drop:hover {
  border-color: var(--primary);
  color: var(--primary);
}

.tp-video-player {
  width: 100%;
  max-height: 260px;
  background: #000;
  border-radius: var(--radius);
}

.tp-range-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12.5px;
  color: var(--text-muted);
}
.tp-range-row input[type='range'] {
  width: 100%;
}
.tp-range-value {
  align-self: flex-end;
  font-variant-numeric: tabular-nums;
}

/* [구간 ↔ 영상 연동] 시작·길이 슬라이더가 전체 영상 중 어디를 가리키는지
   보여주는 띠 — .tp-range-timeline-band의 left/width%는 JS(video-edit-tool.js
   updateRangeTimeline)가 인라인 style로 계산해 넣는다. */
.tp-range-timeline {
  margin: 2px 0 6px;
}
.tp-range-timeline-track {
  position: relative;
  height: 6px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
}
.tp-range-timeline-band {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 0;
  background: var(--primary);
  border-radius: 3px;
}

.tp-video-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.tp-size-picker {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.tp-preset-btn.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}

.tp-loading {
  font-size: 12.5px;
  color: var(--text-muted);
}

/* [프레임 수 조절] 구간(움짤) 결과의 실측 프레임 수·용량 표시줄. */
.tp-gif-size-info-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.tp-gif-size-info {
  font-size: 12.5px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.tp-gif-size-info.is-warning {
  color: var(--danger);
  font-weight: 600;
}
/* [용량 확인 실패 처리] "다시 확인" 버튼 — 문구 옆에 작게 붙인다. */
.tp-gif-size-info-row .btn {
  padding: 3px 8px;
  font-size: 12px;
  flex-shrink: 0;
}

/* 결과 목록 */
.tp-list-section + .tp-list-section {
  margin-top: 28px;
}
.tp-list-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
}
.tp-list-count {
  font-size: 12px;
  color: var(--text-muted);
}
.tp-list-count.at-cap {
  color: var(--danger);
  font-weight: 600;
}

.tp-empty-hint {
  font-size: 13px;
  color: var(--text-muted);
  padding: 16px 0;
}

.tp-asset-badge {
  position: absolute;
  top: 6px;
  left: 6px;
  font-size: 11px;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  padding: 1px 6px;
  border-radius: 999px;
}

.tp-intro-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.tp-intro-row {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 10px;
  background: var(--surface);
}
.tp-intro-row.dragging {
  opacity: 0.4;
}
.tp-intro-row.drag-over-top {
  border-top: 2px solid var(--primary);
}
.tp-intro-row.drag-over-bottom {
  border-bottom: 2px solid var(--primary);
}
.tp-intro-row-handle {
  cursor: grab;
  color: var(--text-muted);
  user-select: none;
}
.tp-intro-row-thumb {
  position: relative;
  width: 64px;
  height: 36px;
  flex: 0 0 auto;
  background: #111;
  border-radius: 4px;
  overflow: hidden;
  cursor: pointer;
}

/* 목록 썸네일 크게 보기 — asset-modal-img(삭제 확인창)와 같은 기본 스타일을
   쓰되, 이 창은 "결과를 크게 확인"이 목적이라 최대 높이를 뷰포트 기준으로
   더 넉넉하게 잡는다. */
.tp-lightbox-img {
  max-height: 70vh;
}
.tp-intro-row-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.tp-intro-row-thumb .tp-asset-badge {
  font-size: 9px;
  padding: 0 4px;
  top: 2px;
  left: 2px;
}
.tp-intro-row-name {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tp-intro-row-dims {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
}
.tp-intro-row-size {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
}
.tp-intro-row-actions {
  display: flex;
  gap: 4px;
  flex: 0 0 auto;
}
.tp-intro-row-actions .btn {
  padding: 4px 8px;
  font-size: 12px;
}
