/*
 * Design tokens (color/spacing/radius/shadow/motion/type) live in
 * /shared/design-tokens/tokens.css — loaded BEFORE this file via <link> in
 * index.html. Do NOT redeclare `--ds-*` or theme overrides here.
 */

* {
  box-sizing: border-box;
}

/* ---- Commerce price comparison card v1 -----------------------------------------------
   판매처별 확정 배송비와 같은 규격이 결속된 행만 순위를 갖는다. 데스크톱은 비교표처럼
   읽히는 행, 520px 이하는 순위·총액 중심 세로 목록이다. 색은 기존 테마 토큰만 쓴다. */
/* 2026-08-26 공용 위젯 카드 셸로 이관(차별화 축① 1차 슬라이스).
   컨테이너·표면·실선·모서리·그림자·본문색·상단 여백은 이제
   .wiz-card[data-wiz-density="comfortable"] 가 소유한다(wizon-widget-card-shell.css).
   여기 남는 건 이 카드«만»의 것 — 이름 붙은 컨테이너 하나다. */
.commerce-price-card {
  container-name: commerce-price;
}

.commerce-price-qa-body {
  min-height: 100%;
  overflow-x: hidden;
  background: var(--wizon-ai-canvas);
  color: var(--wizon-ai-text-primary);
  font-family: var(--wizon-ai-font-family);
}

.commerce-price-qa-shell {
  width: min(calc(100% - (var(--wds-space-5) * 2)), 1180px);
  margin: 0 auto;
  padding: var(--wds-space-8) 0 var(--wds-space-12);
}

.commerce-price-qa-intro {
  margin-bottom: var(--wds-space-5);
}

.commerce-price-qa-intro span,
.commerce-price-qa-intro p,
.commerce-price-qa-answer > p {
  color: var(--wizon-ai-text-secondary);
}

.commerce-price-qa-intro h1,
.commerce-price-qa-intro p,
.commerce-price-qa-answer > p {
  margin: var(--wds-space-2) 0 0;
}

.commerce-price-state {
  display: grid;
  min-width: 0;
  gap: var(--wds-space-3);
  margin-top: var(--wds-space-4);
  padding: var(--wds-space-4) var(--wds-space-5);
  border: 1px solid var(--wizon-ai-popover-border);
  border-left: 3px solid var(--wds-color-wizon-accent);
  border-radius: var(--wizon-ai-radius-popover);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
}

.commerce-price-state h3,
.commerce-price-state p {
  overflow-wrap: anywhere;
}

.commerce-price-state h3 {
  margin: 0;
  font-size: var(--wds-font-size-title-l);
  line-height: var(--wds-line-height-tight);
}

.commerce-price-state p {
  margin: var(--wds-space-1) 0 0;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-body-2);
  line-height: var(--wds-line-height-label);
}

.commerce-state-retry {
  justify-self: start;
  min-height: var(--wds-control-height-md);
  padding: var(--wds-space-2) var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  font: inherit;
  font-weight: var(--wds-font-weight-medium);
}

.commerce-state-retry:hover,
.commerce-state-retry:focus-visible {
  background: var(--wizon-ai-hover-soft-bg);
}

.commerce-state-retry:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

/* .commerce-card-head 는 이 묶음에서 빠졌다 — 배치(display/align/justify/gap/padding)와
   아래쪽 실선을 공용 셸의 .wiz-card__head[data-wiz-rule="on"] 가 소유한다. */
.commerce-toolbar,
.commerce-section-head,
.commerce-condition-row,
.commerce-ads,
.commerce-offer {
  display: flex;
  align-items: center;
}

/* .commerce-example-label 은 이 묶음에서 빠졌다 — 셸의 .wiz-card__eyebrow 가 같은
   값을 소유한다(muted · caption · label leading). 나머지 캡션들은 카드 헤더 밖이라
   이번 슬라이스 범위가 아니다. */
.commerce-why span,
.commerce-offer-price > span,
.commerce-section-head span {
  display: block;
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-label);
}

/* ★ .commerce-card-head h3 / .commerce-card-head p 규칙을 지웠다. 그냥 중복이라서가
   아니다: 자손 선택자(0,1,1)는 셸의 .wiz-card__title(0,1,0)을 «특이도로 이긴다».
   남겨 두면 셸을 입혀도 제목·부제는 계속 옛 규칙이 그리고, 화면은 안 바뀌는데
   토큰만 바꾼 듯한 착시가 생긴다. 지워야 셸이 실제로 소유한다.
   제목 위 4px 여백은 이제 __headings 의 gap 이 낸다. */
.commerce-toolbar p,
.commerce-notes p {
  margin: var(--wds-space-1) 0 0;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-body-2);
  line-height: var(--wds-line-height-label);
}

.commerce-why {
  flex: 0 0 auto;
  padding-left: var(--wds-space-4);
  border-left: 2px solid var(--wds-color-wizon-accent);
  text-align: right;
}

.commerce-why strong {
  display: block;
  margin-top: var(--wds-space-1);
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-1);
}

.commerce-toolbar {
  flex-wrap: wrap;
  gap: var(--wds-space-3);
  justify-content: space-between;
  padding: var(--wds-space-3) var(--wds-space-5);
  background: var(--wizon-ai-canvas);
  border-bottom: 1px solid var(--wizon-ai-popover-border);
}

.commerce-toolbar p {
  margin: 0;
}

.commerce-sort {
  display: inline-flex;
  flex: 0 0 auto;
  padding: var(--wds-space-1);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface);
}

.commerce-sort button,
.commerce-question-chip {
  min-height: var(--wds-control-height-sm);
  padding: var(--wds-space-2) var(--wds-space-3);
  border: 0;
  border-radius: calc(var(--wizon-ai-radius-control) - var(--wds-space-1));
  background: transparent;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-medium);
}

.commerce-sort button[aria-pressed="true"] {
  background: var(--wizon-ai-text-primary);
  color: var(--wds-color-text-on-primary);
}

.commerce-sort button:focus-visible,
.commerce-question-chip:focus-visible,
.commerce-price-card a:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

.commerce-offers {
  display: grid;
}

.commerce-price-card > [data-commerce-live] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.commerce-offer {
  display: grid;
  grid-template-columns: 68px minmax(128px, 0.65fr) minmax(390px, 2.2fr) minmax(150px, 0.8fr);
  grid-template-areas: "rank seller prices actions";
  gap: var(--wds-space-3);
  min-width: 0;
  padding: var(--wds-space-4) var(--wds-space-5);
  border-bottom: 1px solid var(--wizon-ai-popover-border);
}

.commerce-offer[data-commerce-active-rank="1"] {
  background: color-mix(in srgb, var(--wds-color-wizon-accent) 7%, var(--wizon-ai-surface));
}

.commerce-offer-rank {
  grid-area: rank;
  display: flex;
  align-items: flex-start;
  gap: var(--wds-space-1);
  color: var(--wizon-ai-text-primary);
}

.commerce-offer-rank strong {
  padding: var(--wds-space-1) var(--wds-space-2);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-surface);
  font-size: var(--wds-font-size-body-2);
  font-variant-numeric: tabular-nums;
  line-height: var(--wds-line-height-label);
  white-space: nowrap;
}

.commerce-offer-seller span,
.commerce-offer-price small,
.commerce-offer-checked,
.commerce-condition-row small {
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-label);
}

.commerce-offer-seller,
.commerce-offer-price,
.commerce-offer-actions,
.commerce-condition-row > span {
  display: grid;
  min-width: 0;
  gap: var(--wds-space-1);
}

.commerce-offer-seller { grid-area: seller; }

.commerce-offer-seller strong,
.commerce-offer-price strong {
  overflow-wrap: anywhere;
  font-size: var(--wds-font-size-body-1);
  font-variant-numeric: tabular-nums;
}

.commerce-offer-prices {
  grid-area: prices;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--wds-space-2);
  min-width: 0;
}

.commerce-offer-price {
  align-content: start;
  min-width: 0;
  padding: var(--wds-space-2) var(--wds-space-3);
  border: 1px solid transparent;
  border-radius: var(--wizon-ai-radius-control);
}

.commerce-price-card[data-commerce-basis="product"] [data-commerce-measure="product"],
.commerce-price-card[data-commerce-basis="total"] [data-commerce-measure="total"],
.commerce-price-card[data-commerce-basis="unit"] [data-commerce-measure="unit"] {
  border-color: var(--wds-color-wizon-accent);
  background: color-mix(in srgb, var(--wds-color-wizon-accent) 10%, var(--wizon-ai-surface));
}

.commerce-price-card[data-commerce-basis="product"] [data-commerce-measure="product"] strong,
.commerce-price-card[data-commerce-basis="total"] [data-commerce-measure="total"] strong,
.commerce-price-card[data-commerce-basis="unit"] [data-commerce-measure="unit"] strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-title-l);
}

.commerce-offer-actions {
  grid-area: actions;
  align-content: start;
  justify-items: start;
}

.commerce-source-link,
.commerce-final-check,
.commerce-offer-checked {
  max-width: 100%;
  overflow-wrap: anywhere;
}

.commerce-price-card a {
  color: var(--wds-color-link);
  font-size: var(--wds-font-size-caption);
  text-decoration: none;
}

.commerce-price-card a:hover {
  text-decoration: underline;
}

.commerce-final-check {
  display: inline-flex;
  align-items: center;
  min-height: var(--wds-control-height-sm);
  padding: var(--wds-space-2) var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  font-weight: var(--wds-font-weight-medium);
  white-space: nowrap;
}

.commerce-notes,
.commerce-conditions,
.commerce-ads,
.commerce-question-chips {
  margin: var(--wds-space-4) var(--wds-space-5) 0;
}

.commerce-notes {
  padding: var(--wds-space-3) var(--wds-space-4);
  border-left: 2px solid var(--wds-color-wizon-accent);
  background: var(--wizon-ai-canvas);
}

.commerce-notes p {
  margin-top: var(--wds-space-1);
}

.commerce-conditions {
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
}

.commerce-section-head {
  justify-content: space-between;
  gap: var(--wds-space-3);
  padding: var(--wds-space-3) var(--wds-space-4);
  border-bottom: 1px solid var(--wizon-ai-popover-border);
  background: var(--wizon-ai-canvas);
}

.commerce-condition-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: var(--wds-space-3);
  padding: var(--wds-space-3) var(--wds-space-4);
}

.commerce-condition-row > strong {
  font-variant-numeric: tabular-nums;
}

.commerce-ads {
  flex-wrap: wrap;
  gap: var(--wds-space-2) var(--wds-space-4);
  padding-top: var(--wds-space-3);
  border-top: 1px solid var(--wizon-ai-popover-border);
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-caption);
}

.commerce-ads a {
  display: inline-flex;
  gap: var(--wds-space-2);
}

.commerce-ads a span {
  padding-inline: var(--wds-space-2);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
}

.commerce-question-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wds-space-2);
  padding-bottom: var(--wds-space-5);
}

.commerce-question-chip {
  border: 1px solid var(--wizon-ai-popover-border);
  background: var(--wizon-ai-surface);
}

.commerce-question-chip:hover,
.commerce-question-chip:focus-visible {
  background: var(--wizon-ai-hover-soft-bg);
  color: var(--wizon-ai-text-primary);
}

/* Category intelligence stays separate from the exact-product price table: it
   presents verified observations and server-owned display sequences, never a
   browser-calculated cross-product rank. */
.commerce-category-head,
.commerce-category-toolbar,
.commerce-category-product-main,
.commerce-category-product,
.commerce-category-detail li {
  display: flex;
  min-width: 0;
}

.commerce-category-head,
.commerce-category-toolbar {
  justify-content: space-between;
  gap: var(--wds-space-4);
  padding: var(--wds-space-5);
  border-bottom: 1px solid var(--wizon-ai-popover-border);
}

.commerce-category-head h3 {
  margin: var(--wds-space-1) 0;
  font-size: var(--wds-font-size-title-l);
  line-height: var(--wds-line-height-tight);
}

/* The portal prose renderer numbers generic answer headings. Card-local labels
   are product names, not answer sections, so the category card owns them. */
.assistant .message-body .commerce-category-card h3::before,
.assistant .message-body .commerce-category-card h4::before,
.assistant .message-body .commerce-category-card h5::before {
  content: none !important;
}

/* md-h2 추가(v11, C19): 답변 헤딩 카운터의 최상위 이름이 md-h3→md-h2 로 바뀌었다
   (styles.css §섹션 자동 번호 v11). 위젯은 h2 태그를 쓰지 않아 원래도 영향이 없지만,
   방어적으로 새 최상위 카운터도 이 스코프에서 리셋해 둔다. */
.commerce-category-card { counter-reset: md-h2 md-h3 md-h4 md-h5; }

.commerce-category-head p,
.commerce-category-toolbar p,
.commerce-category-note {
  margin: 0;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-body-2);
  line-height: var(--wds-line-height-label);
}

.commerce-category-checked {
  flex: 0 0 auto;
  text-align: right;
}

.commerce-category-toolbar {
  align-items: center;
  flex-wrap: wrap;
  padding-block: var(--wds-space-3);
  background: var(--wizon-ai-canvas);
}

.commerce-category-products {
  display: grid;
}

.commerce-category-product {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(176px, .45fr) minmax(120px, .24fr);
  gap: var(--wds-space-4);
  align-items: start;
  padding: var(--wds-space-5);
  border-bottom: 1px solid var(--wizon-ai-popover-border);
}

.commerce-category-product[data-expanded="true"] {
  background: color-mix(in srgb, var(--wds-color-wizon-accent) 5%, var(--wizon-ai-surface));
}

.commerce-category-product-main {
  gap: var(--wds-space-3);
}

.commerce-category-media {
  flex: 0 0 88px;
  width: 88px;
  margin: 0;
}

.commerce-category-media img {
  display: block;
  width: 88px;
  height: 88px;
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  object-fit: contain;
  background: var(--wizon-ai-canvas);
}

.commerce-category-media figcaption,
.commerce-category-price span,
.commerce-category-price small,
.commerce-category-detail small {
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-label);
}

.commerce-category-media figcaption {
  margin-top: var(--wds-space-1);
}

.commerce-category-copy {
  min-width: 0;
}

.commerce-category-brand {
  margin: 0 0 var(--wds-space-1);
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
}

.commerce-category-copy h4 {
  margin: 0;
  overflow-wrap: anywhere;
  font-size: var(--wds-font-size-body-1);
  line-height: var(--wds-line-height-tight);
}

.commerce-category-advantages,
.commerce-category-detail ul {
  display: grid;
  gap: var(--wds-space-1);
  margin: var(--wds-space-2) 0 0;
  padding: 0;
  list-style: none;
}

.commerce-category-advantages {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.commerce-category-advantages li::before {
  content: "•";
  margin-right: var(--wds-space-1);
  color: var(--wds-color-wizon-accent);
}

.commerce-category-specs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wds-space-1) var(--wds-space-2);
  margin: var(--wds-space-2) 0 0;
}

.commerce-category-specs div {
  display: inline-flex;
  gap: var(--wds-space-1);
  padding: var(--wds-space-1) var(--wds-space-2);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
  font-size: var(--wds-font-size-caption);
}

.commerce-category-specs dt { color: var(--wizon-ai-text-muted); }
.commerce-category-specs dd { margin: 0; color: var(--wizon-ai-text-primary); }

.commerce-category-price {
  display: grid;
  min-width: 0;
  gap: var(--wds-space-1);
  text-align: right;
}

.commerce-category-price strong {
  overflow-wrap: anywhere;
  font-size: var(--wds-font-size-title-l);
  font-variant-numeric: tabular-nums;
}

.commerce-category-price b {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
  font-variant-numeric: tabular-nums;
}

.commerce-category-expand {
  min-height: var(--wds-control-height-md);
  padding: var(--wds-space-2) var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
}

.commerce-category-expand[aria-expanded="true"],
.commerce-category-expand:hover {
  background: var(--wizon-ai-hover-soft-bg);
}

.commerce-category-expand:focus-visible,
.commerce-category-detail a:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

.commerce-category-detail {
  grid-column: 1 / -1;
  min-width: 0;
  padding-top: var(--wds-space-3);
  border-top: 1px dashed var(--wizon-ai-popover-border);
}

.commerce-category-detail h5 {
  margin: 0;
  font-size: var(--wds-font-size-body-2);
}

.commerce-category-detail li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: var(--wds-space-2);
  align-items: baseline;
  padding: var(--wds-space-2) 0;
  border-bottom: 1px solid var(--wizon-ai-popover-border);
  font-size: var(--wds-font-size-caption);
}

.commerce-category-detail li small,
.commerce-category-detail li a {
  grid-column: 1 / -1;
}

.commerce-category-detail b { font-variant-numeric: tabular-nums; }

.commerce-category-detail > p {
  margin: var(--wds-space-3) 0 0;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-label);
}

.commerce-category-detail > p a {
  display: inline;
  margin-left: var(--wds-space-1);
}

.commerce-category-caution {
  padding-left: var(--wds-space-2);
  border-left: 2px solid var(--wds-color-wizon-accent);
}

.commerce-category-note {
  margin: var(--wds-space-4) var(--wds-space-5) var(--wds-space-5);
  padding: var(--wds-space-3) var(--wds-space-4);
  border-left: 2px solid var(--wds-color-wizon-accent);
  background: var(--wizon-ai-canvas);
}

/* The category surface is intentionally editorial and dense: a product row has
   one identity/spec rail, one verified-price rail and one controlled disclosure,
   instead of repeating equal generic cards. */
.commerce-category-card {
  overflow: hidden;
  box-shadow: 0 10px 28px color-mix(in srgb, var(--wizon-ai-text-primary) 8%, transparent);
}

.commerce-category-head {
  align-items: flex-end;
  background: linear-gradient(120deg, color-mix(in srgb, var(--wds-color-wizon-accent) 7%, var(--wizon-ai-surface)), var(--wizon-ai-surface) 56%);
}

.commerce-category-head h3 {
  max-width: 26ch;
  font-size: var(--wds-font-size-display-s);
}

.commerce-category-toolbar {
  border-bottom-color: color-mix(in srgb, var(--wizon-ai-popover-border) 72%, var(--wds-color-wizon-accent));
}

.commerce-category-product {
  grid-template-columns: minmax(0, 1fr) minmax(204px, .42fr) minmax(126px, .22fr);
  gap: var(--wds-space-5);
  padding-block: calc(var(--wds-space-5) + var(--wds-space-1));
  transition: background-color 160ms ease;
}

.commerce-category-product[data-expanded="true"] {
  background: color-mix(in srgb, var(--wds-color-wizon-accent) 6%, var(--wizon-ai-surface));
}

.commerce-category-product-main { gap: var(--wds-space-4); }

.commerce-category-media,
.commerce-category-media img {
  width: 96px;
}

.commerce-category-media { flex-basis: 96px; }

.commerce-category-media img {
  height: 96px;
  border-color: color-mix(in srgb, var(--wizon-ai-popover-border) 72%, var(--wds-color-wizon-accent));
  background: color-mix(in srgb, var(--wds-color-wizon-accent) 3%, var(--wizon-ai-canvas));
}

.commerce-category-media--missing {
  display: grid;
  min-height: 96px;
  place-items: center;
  border: 1px dashed var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-label);
  text-align: center;
}

.commerce-category-brand {
  color: var(--wds-color-wizon-accent);
  font-weight: var(--wds-font-weight-semibold);
  letter-spacing: .02em;
}

.commerce-category-copy h4 {
  font-size: var(--wds-font-size-title-l);
  line-height: 1.38;
}

.commerce-category-advantages {
  gap: 2px;
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
  line-height: var(--wds-line-height-label);
}

.commerce-category-advantages li::before {
  content: none;
}

.commerce-category-advantages li {
  padding-left: var(--wds-space-2);
  border-left: 2px solid color-mix(in srgb, var(--wds-color-wizon-accent) 62%, transparent);
}

.commerce-category-specs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--wds-space-3);
  margin-top: var(--wds-space-3);
  padding-top: var(--wds-space-2);
  border-top: 1px solid var(--wizon-ai-popover-border);
}

.commerce-category-specs div,
.commerce-category-all-specs div {
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: var(--wds-space-2);
  padding: var(--wds-space-1) 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.commerce-category-specs dd,
.commerce-category-all-specs dd { overflow-wrap: anywhere; }

.commerce-category-price {
  align-content: start;
  gap: var(--wds-space-2);
  padding-left: var(--wds-space-4);
  border-left: 1px solid var(--wizon-ai-popover-border);
  text-align: left;
}

.commerce-category-price strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-display-s);
  letter-spacing: -.025em;
  line-height: 1;
}

.commerce-category-price b {
  color: var(--wds-color-wizon-accent);
  font-weight: var(--wds-font-weight-semibold);
}

.commerce-category-price-provenance {
  margin: 0;
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-label);
}

.commerce-category-price > a {
  display: inline-flex;
  width: fit-content;
  min-height: var(--wds-control-height-sm);
  align-items: center;
  margin-top: var(--wds-space-1);
  padding: 0 var(--wds-space-2);
  border-bottom: 1px solid color-mix(in srgb, var(--wds-color-link) 45%, transparent);
  color: var(--wds-color-link);
  font-weight: var(--wds-font-weight-medium);
}

.commerce-category-expand {
  align-self: center;
  border-color: color-mix(in srgb, var(--wds-color-wizon-accent) 38%, var(--wizon-ai-popover-border));
  font-weight: var(--wds-font-weight-semibold);
}

.commerce-category-expand[aria-expanded="true"] {
  border-color: var(--wds-color-wizon-accent);
  color: var(--wds-color-wizon-accent);
}

.commerce-category-detail {
  padding-top: var(--wds-space-4);
  border-top-style: solid;
}

.commerce-category-detail-inner {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(240px, .8fr);
  gap: var(--wds-space-4) var(--wds-space-6);
}

.commerce-category-detail[data-motion="open"] .commerce-category-detail-inner {
  animation: commerce-category-detail-in 180ms ease-out both;
}

@keyframes commerce-category-detail-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

.commerce-category-detail-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--wds-space-3);
  grid-column: 1 / -1;
}

.commerce-category-detail-heading p {
  margin: 0;
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-caption);
}

.commerce-category-detail ul { margin: 0; }

.commerce-category-detail li {
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--wds-space-2) var(--wds-space-4);
}

.commerce-category-detail li a {
  width: fit-content;
  font-weight: var(--wds-font-weight-medium);
}

.commerce-category-all-specs {
  display: grid;
  align-content: start;
  gap: 0 var(--wds-space-3);
  margin: 0;
  padding: var(--wds-space-3);
  border-left: 2px solid var(--wds-color-wizon-accent);
  background: color-mix(in srgb, var(--wds-color-wizon-accent) 3%, var(--wizon-ai-canvas));
}

.commerce-category-detail > .commerce-category-detail-inner > p { margin: 0; }

@media (prefers-reduced-motion: reduce) {
  .commerce-category-product { transition: none; }
  .commerce-category-detail[data-motion="open"] .commerce-category-detail-inner { animation: none; }
}

@container commerce-price (max-width: 900px) {
  .commerce-offer {
    grid-template-columns: 68px minmax(0, 1fr) minmax(150px, .55fr);
    grid-template-areas:
      "rank seller actions"
      "prices prices prices";
  }

  .commerce-category-product {
    grid-template-columns: minmax(0, 1fr) minmax(160px, .42fr);
  }

  .commerce-category-expand {
    grid-column: 1 / -1;
  }

  .commerce-category-detail-inner {
    grid-template-columns: 1fr;
  }
}

@container commerce-price (max-width: 520px) {
  .commerce-card-head {
    align-items: flex-start;
    padding: var(--wds-space-4);
  }

  .commerce-why {
    display: none;
  }

  .commerce-toolbar {
    align-items: stretch;
    padding: var(--wds-space-3) var(--wds-space-4);
  }

  .commerce-sort {
    width: 100%;
  }

  .commerce-sort button {
    flex: 1 1 50%;
    min-height: var(--wds-control-height-md);
  }

  .commerce-toolbar p {
    width: 100%;
  }

  .commerce-offer {
    grid-template-columns: 72px minmax(0, 1fr);
    grid-template-areas:
      "rank seller"
      "prices prices"
      "actions actions";
    gap: var(--wds-space-2) var(--wds-space-3);
    padding: var(--wds-space-4);
  }

  .commerce-offer-rank { grid-area: rank; }
  .commerce-offer-seller { grid-area: seller; }
  .commerce-offer-prices {
    grid-area: prices;
    grid-template-columns: 1fr;
  }
  .commerce-offer-actions {
    grid-area: actions;
    grid-template-columns: 1fr;
    width: 100%;
  }

  .commerce-offer-price {
    grid-template-columns: minmax(104px, .7fr) minmax(0, 1fr);
    align-items: baseline;
    gap: var(--wds-space-1) var(--wds-space-2);
  }

  .commerce-offer-price small {
    grid-column: 2;
  }

  .commerce-final-check {
    justify-content: center;
    min-height: var(--wds-control-height-md);
  }

  .commerce-notes,
  .commerce-conditions,
  .commerce-ads,
  .commerce-question-chips {
    margin-inline: var(--wds-space-4);
  }

  .commerce-condition-row {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .commerce-condition-row a {
    grid-column: 1 / -1;
  }

  .commerce-question-chip {
    min-height: var(--wds-control-height-md);
  }

  .commerce-price-state {
    padding: var(--wds-space-4);
  }

  .commerce-state-retry {
    width: 100%;
  }

  .commerce-category-head,
  .commerce-category-toolbar,
  .commerce-category-product {
    padding-inline: var(--wds-space-4);
  }

  .commerce-category-head,
  .commerce-category-toolbar {
    align-items: stretch;
  }

  .commerce-category-checked {
    text-align: left;
  }

  .commerce-category-toolbar .commerce-sort {
    width: 100%;
  }

  .commerce-category-toolbar .commerce-sort button {
    flex: 1 1 0;
    min-width: 0;
    padding-inline: var(--wds-space-1);
  }

  .commerce-category-product {
    grid-template-columns: minmax(0, 1fr);
  }

  .commerce-category-product-main {
    align-items: flex-start;
  }

  .commerce-category-media,
  .commerce-category-media img {
    width: 72px;
  }

  .commerce-category-media { flex-basis: 72px; }
  .commerce-category-media img { height: 72px; }
  .commerce-category-media--missing { min-height: 72px; }

  .commerce-category-copy h4 { font-size: var(--wds-font-size-body-1); }

  .commerce-category-specs { grid-template-columns: 1fr; }

  .commerce-category-price {
    padding: var(--wds-space-3);
    border: 1px solid var(--wizon-ai-popover-border);
    border-radius: var(--wizon-ai-radius-control);
    background: var(--wizon-ai-canvas);
    text-align: left;
  }

  .commerce-category-expand { width: 100%; }
  .commerce-category-detail li { grid-template-columns: minmax(0, 1fr) auto; }
  .commerce-category-detail li b { grid-column: 1 / -1; }
  .commerce-category-detail-heading { align-items: flex-start; flex-direction: column; }
  .commerce-category-detail-inner { gap: var(--wds-space-4); }
  .commerce-category-note { margin-inline: var(--wds-space-4); }
}

@media (max-width: 520px) {
  .commerce-price-qa-shell {
    width: calc(100% - (var(--wds-space-3) * 2));
    padding-top: var(--wds-space-5);
  }
}

@media (prefers-reduced-motion: reduce) {
  .commerce-price-card * {
    scroll-behavior: auto;
  }
}

html,
body {
  width: 100%;
  min-width: 320px;
  height: 100%;
  margin: 0;
}

/* WS-K2 — 한국어 어절 단위 줄바꿈 기본값 (lang 게이트·상속 기반, WS-K1 admin과 동일 패턴).
   keep-all: 한글 음절 중간 분리 방지(라틴 무영향). overflow-wrap 동반: 공백 없는 초장문
   토큰(URL/코드/ID)은 넘칠 때만 끊어 오버플로 방지. body에만 걸어 상속되므로 더 높은
   특이도의 기존 규칙(overflow-wrap: anywhere, .message-body pre의 overflow: auto 등)이
   언제나 이겨 코드/URL/mono 예외를 자동 존중. 근거: docs/product/218. */
body:lang(ko) {
  word-break: keep-all;
  overflow-wrap: break-word;
}

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

button {
  cursor: pointer;
}

button:disabled {
  cursor: not-allowed;
  opacity: 0.58;
}

.app-shell {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  width: 100%;
  min-height: 100vh;
}

.sidebar {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-height: 100vh;
  padding: 18px;
  background: #111827;
  color: #f8fafc;
  border-right: 1px solid #0f172a;
}

.brand-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 46px;
}

.brand-row strong {
  display: block;
  font-size: var(--ds-text-body-16);
  line-height: 1.2;
}

.brand-row span {
  display: block;
  margin-top: 3px;
  color: #b8c2cc;
  font-size: var(--ds-text-caption-12);
}
.sidebar-section {
  display: grid;
  gap: 8px;
}

.sidebar-section.grow {
  flex: 1;
  min-height: 0;
}

.section-label,
.eyebrow {
  margin: 0;
  color: #6b7785;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  line-height: 1.2;
  text-transform: uppercase;
}

.sidebar .section-label {
  color: #9aa7b4;
}

select,
input,
textarea {
  width: 100%;
  min-height: 42px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #ffffff;
  color: var(--ink);
  outline: none;
}

select,
input {
  padding: 0 12px;
}

textarea {
  display: block;
  padding: 11px 12px;
  line-height: 1.45;
  resize: vertical;
}

select:focus,
input:focus,
textarea:focus {
  border-color: var(--ds-input-border-focus);
  box-shadow: 0 0 0 2px var(--ds-input-ring-focus);
}

.recent-list {
  display: grid;
  align-content: start;
  gap: 6px;
  overflow-x: hidden;
  overflow-y: auto;
  min-height: 0;
}

.recent-item {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px;
  width: 100%;
  min-height: 50px;
  padding: 2px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: #f8fafc;
  text-align: left;
}

/* "답변 대기 중"/"작업공간 미지정" 배지 — 사이드바 최근 채팅 행. .ds-badge--dot 토큰을
   그대로 쓰고(71-component-rulebook.md §4.6) 위치만 이 행에 맞춘다. absolute라 base(grid
   2열)와 chatgpt-reference-v1(block) 두 레이아웃 모두에서 격자/흐름에 끼지 않고 떠 있으므로,
   테마별로 따로 자리를 잡을 필요가 없다 — .recent-item은 두 테마 모두 position:relative.
   두 배지는 같은 행에서 상호 배타적(app.js 주석 참고)이라 자리를 공유한다. */
.recent-item .recent-pending-badge,
.recent-item .recent-workspace-unresolved-badge {
  position: absolute;
  top: 6px;
  right: 6px;
  pointer-events: none;
  z-index: 1;
}

.recent-primary {
  display: grid;
  align-content: center;
  gap: 5px;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 7px 8px;
  border: 0;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  color: inherit;
  text-align: left;
}

@media (hover: hover) {
  .recent-item:hover {
    border-color: rgba(255, 255, 255, 0.15);
    background: rgba(255, 255, 255, 0.07);
  }
}

.recent-item span,
.recent-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.recent-item small {
  color: #9aa7b4;
  font-size: var(--ds-text-micro-11);
}

.recent-actions {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  opacity: 0;
  pointer-events: none;
}

/* Reveal on focus (tap) always; reveal on :hover only for hover-capable (non-touch)
   devices. On iOS Safari a :hover rule that reveals hidden content makes the FIRST tap a
   hover-only gesture (the click is swallowed → two taps needed). Base rule counterpart to
   the .chatgpt-reference-v1 guard (doc 143, 20260709-mobile-recent-hover-firsttap). */
.recent-item:focus-within .recent-actions {
  opacity: 1;
  pointer-events: auto;
}

@media (hover: hover) {
  .recent-item:hover .recent-actions {
    opacity: 1;
    pointer-events: auto;
  }
}

.recent-action {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: currentColor;
}

.main-pane {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 100vh;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 78px;
  padding: 18px 24px;
  background: rgba(255, 255, 255, 0.92);
  border-bottom: 1px solid var(--line);
}

.topbar h1 {
  margin: 3px 0 0;
  font-size: var(--ds-text-title-20);
  font-weight: 600;
  line-height: 1.25;
}

.status-stack {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}

.status-pill {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  max-width: 240px;
  padding: 0 10px;
  border-radius: 8px;
  background: var(--accent-soft);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.status-pill.soft {
  border-color: rgba(249, 115, 22, 0.22);
  background: #fff4eb;
  color: #9a4d00;
}

.workbench {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: 0;
  flex: 1;
  min-height: 0;
}

.chat-pane,
.context-pane {
  min-height: 0;
}

.chat-pane {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  /* Single implicit column MUST be minmax(0, 1fr): without it the auto column sizes to the widest
     child's max-content (~420px from the composer/card), which on a phone (<420px pane) overflows
     the pane and pushed the question card's right side — badge + close X — off-screen (doc 166 후속
     mobile fix). Identical to the current desktop rendering (content already fills the pane there). */
  grid-template-columns: minmax(0, 1fr);
  background: #fbfcfd;
}

.message-list {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-height: 0;
  overflow: auto;
  padding: 32px min(7vw, 72px);
}

/* "맨 아래로" jump button (ChatGPT/Claude parity). Nested INSIDE the composer form (already
   position:relative) and anchored with bottom:100% so it floats just above the ComposerBar and
   tracks its autogrowing height in pure CSS. It is deliberately NOT a child of the .chat-pane grid:
   an absolutely-positioned child there collapses the grid's minmax(0,1fr) row on iOS Safari, which
   shoved the composer to the middle (doc 170 regression). Hidden until the user scrolls up. */
.scroll-to-bottom {
  position: absolute;
  left: 50%;
  bottom: 100%; /* top edge of the composer */
  margin-bottom: 12px;
  z-index: 30;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-surface, #fff);
  color: var(--cg-text, var(--text));
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.14);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, 8px);
  transition: opacity 160ms ease, transform 160ms ease, background 120ms ease;
}
.scroll-to-bottom.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}
/* R-54 S1-F(F1): 스트리밍 중 사용자가 위로 올려 «따라가기» 가 풀렸을 때 — 같은 버튼이 «↓ 새 내용»
   알약이 된다(별도 컴포넌트 없음, 클릭하면 부드럽게 따라가기를 재개). */
.scroll-to-bottom .scroll-to-bottom-label {
  display: none;
}
.scroll-to-bottom.has-new-content {
  width: auto;
  gap: 6px;
  padding: 0 16px 0 12px;
  flex-direction: row;
  font: inherit;
  font-size: var(--ds-text-body-14, 14px);
  font-weight: 500;
  white-space: nowrap;
}
.scroll-to-bottom.has-new-content .scroll-to-bottom-label {
  display: inline;
}
.scroll-to-bottom.has-new-content .scroll-to-bottom-icon {
  order: -1; /* ↓ 새 내용 */
}
.scroll-to-bottom:hover {
  background: var(--cg-hover-soft, var(--surface-2));
}
.scroll-to-bottom:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 2px;
}
.scroll-to-bottom .scroll-to-bottom-icon,
.scroll-to-bottom [data-icon] {
  display: inline-flex;
}
.scroll-to-bottom .scroll-to-bottom-icon svg,
.scroll-to-bottom svg {
  width: 18px;
  height: 18px;
}
@media (prefers-reduced-motion: reduce) {
  .scroll-to-bottom {
    transition: opacity 160ms ease;
    transform: translate(-50%, 0);
  }
}

.welcome {
  display: grid;
  gap: 12px;
  max-width: 720px;
  margin: auto;
  padding: 8px;
}

.welcome h2 {
  margin: 0;
  font-size: var(--ds-text-heading-32);
  font-weight: 600;
  line-height: 1.12;
}

.welcome p {
  max-width: 620px;
  margin: 0;
  color: var(--muted);
  line-height: 1.6;
}

.prompt-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin-top: 8px;
}

/* 20260915-feedback-toolbar-legacy-drop (feedback-toolbar-picker-chip-contract): 예전엔 이
   일반 셀렉터가 `.feedback-row` 안의 아무 `<button>` 도 잡았다 — `.message-action-button`
   이 min-height:36 을 명시해서 지금은 안 이기지만, 셀렉터 자체는 남아 있었다(judge report
   §11 「38px 가 아직 살아 있다」). `.prompt-row` 만 이 일반 버튼 스타일을 쓴다. */
.prompt-row button {
  min-height: 38px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #ffffff;
  color: var(--ink);
  text-align: left;
}

.prompt-row button:hover,
.secondary-button:hover {
  border-color: var(--accent);
}

.message {
  display: grid;
  grid-template-columns: 42px minmax(0, 760px);
  gap: 12px;
  align-items: start;
}

.message.user {
  justify-content: end;
}

/* E-B1: 시맨틱 토큰 매핑(다크 적응 + 라이트 근사) — docs/product/218, .chat-failure 선례 */
.message-label {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--ds-color-ink) 8%, var(--ds-color-surface));
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.message.user .message-label {
  background: var(--ds-feedback-warn-bg);
  color: var(--ds-feedback-warn-fg);
}

.message-body {
  min-width: 0;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  box-shadow: var(--ds-shadow-xs);
}

.message.user .message-body {
  background: var(--accent-soft);
}

/* Question card — WDS AI-guidelines "되묻기" shell, docked above the composer.
   Themed entirely with WIZON portal tokens (--cg-* → --wizon-ai-*), monochrome to match
   the rest of the shell. The primary action reuses the composer send treatment (--cg-send-*).
   Placement: #orchestrationDock sits between the message list and the composer. */
.orchestration-dock {
  width: 100%;
  max-width: var(--cg-chat-max-width, 800px);
  margin: 0 auto;
  padding: 0 var(--cg-composer-gutter, 12px) 10px;
  box-sizing: border-box;
}

.orchestration-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  /* doc 167 §4.2: inline card sits one rank below the sheet (18) — popover-tier 16. */
  border-radius: var(--wizon-ai-radius-popover, 16px);
  background: var(--cg-menu-bg, var(--surface, #fff));
  box-shadow: inset 0 0 0 1px var(--cg-menu-border, var(--line)), var(--cg-menu-shadow);
}

.orchestration-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--cg-text-meta-size, 12px);
  color: var(--cg-muted, var(--muted));
}

.orchestration-card-status {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--cg-muted, var(--muted));
}

/* Filled variant — the collapsed "선택됨 · X" line (ⓐ′ cut 3) reuses the same status dot slot,
   just filled to read as "settled" instead of "waiting". */
.orchestration-card-status.is-filled {
  background: var(--cg-text, currentColor);
}

.orchestration-card-context {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Progress eyebrow ("1/2"/"2/2") — continuation-surfaces-design-judge §4.2 슬롯①, only rendered
   when the step total can be inferred honestly (see orchestrationCardStep in app.js). Shares the
   head's 12/400 muted type — no new type scale. */
.orchestration-card-progress {
  flex: none;
  font-variant-numeric: tabular-nums;
  color: var(--cg-muted, var(--muted));
}

/* "이전 답 요약" (카드 2 recap line) — 12px muted text + a text "바꾸기" button, no new radius or
   surface (plain row between the head and the title, same as the existing recap precedent in
   .reask-dismissed-prompt). */
.orchestration-card-recap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: var(--cg-text-meta-size, 12px);
  color: var(--cg-muted, var(--muted));
}

.orchestration-card-recap-text {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.orchestration-card-recap-change {
  flex: none;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--cg-text, inherit);
  font: inherit;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}

.orchestration-card-recap-change:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 2px;
}

/* Close (X) — cancel the card when asked wrong by mistake (doc 166 §3.4).
   36×36 (continuation-surfaces-design-judge §3.2 "취소·수정" — C 액션 36 통일, 현 32→36). */
.orchestration-card-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin: -8px -8px -8px 0;
  padding: 0;
  border: 0;
  border-radius: var(--wizon-ai-radius-pill, 999px);
  background: transparent;
  color: var(--cg-muted, var(--muted));
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.orchestration-card-close:hover {
  background: var(--cg-surface-secondary);
  color: var(--cg-text, var(--text));
}

.orchestration-card-close:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 1px;
}

/* Collapsed state left in the thread after a question card is closed — a "다시 선택" note plus
   the original prompt and re-selectable option chips (Claude AskUserQuestion parity). */
.reask-dismissed {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: 640px;
  padding: 12px 14px;
  /* doc 167 §4.2: collapsed remnant is the demoted rank — menu-tier 12. */
  border-radius: var(--wizon-ai-menu-radius, 12px);
  background: var(--cg-surface-secondary);
  box-shadow: inset 0 0 0 1px var(--cg-menu-border, var(--line));
}

.reask-dismissed-head {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: var(--cg-text-meta-size, 12px);
  color: var(--cg-muted, var(--muted));
}

/* Answered variant — the closed card transitions here once the user re-picks an option. Compact
   single line, visually distinct from the closed state (research: answered ≠ dismissed). */
.reask-dismissed.reask-resolved {
  display: inline-flex;
  padding: 8px 12px;
}

.reask-resolved .reask-dismissed-head {
  color: var(--cg-muted, var(--muted));
}

.chatgpt-reference-v1 .reask-resolved-icon .wds-inline-icon {
  color: var(--cg-success);
}

.reask-dismissed-icon {
  display: inline-flex;
  flex: none;
}

.reask-dismissed-prompt {
  margin: 0;
  font-size: var(--cg-text-body-size, 16px);
  color: var(--cg-text, var(--text));
}

.reask-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.reask-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 12px;
  border: 0;
  border-radius: var(--wizon-ai-radius-pill, 999px);
  background: var(--cg-surface, #fff);
  box-shadow: inset 0 0 0 1px var(--cg-menu-border, var(--line));
  font-size: var(--cg-text-meta-size, 12px);
  color: var(--cg-text, var(--text));
  cursor: pointer;
  transition: background-color 120ms ease, box-shadow 120ms ease;
}

.reask-chip:hover {
  background: var(--cg-surface-secondary);
  box-shadow: inset 0 0 0 1px var(--cg-muted, var(--muted));
}

.reask-chip:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 1px;
}

.reask-chip-pill {
  padding: 1px 6px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-hover);
  color: var(--cg-text, inherit);
  font-size: var(--ds-text-micro-11);
}

/* "다시 열기" — reopen a closed text-only card (no chips to re-select). */
.reask-reopen-row {
  display: flex;
}

.reask-reopen {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 0;
  border-radius: var(--wizon-ai-radius-pill, 999px);
  background: var(--cg-surface, #fff);
  box-shadow: inset 0 0 0 1px var(--cg-menu-border, var(--line));
  font-size: var(--cg-text-meta-size, 12px);
  color: var(--cg-text, var(--text));
  cursor: pointer;
  transition: background-color 120ms ease, box-shadow 120ms ease;
}

.reask-reopen:hover {
  background: var(--cg-surface-secondary);
  box-shadow: inset 0 0 0 1px var(--cg-muted, var(--muted));
}

.reask-reopen:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 1px;
}

.orchestration-card-title {
  margin: 0;
  font-size: var(--cg-text-title-size, 18px);
  font-weight: var(--cg-text-title-weight, 600);
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--cg-text, inherit);
}

.orchestration-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* 행 48(1행)/56(2행: 제목+설명) — continuation-surfaces-design-judge §3.2 D-7, settings-v2 실측
   기준점. data-orchestration-lines 는 app.js 가 그 행에 desc 텍스트가 있는지로 정한다. */
.orchestration-opt {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 48px;
  padding: 10px 12px;
  text-align: left;
  cursor: pointer;
  border: 0;
  border-radius: var(--wizon-ai-radius-menu-row, 10px);
  background: transparent;
  color: var(--cg-text, inherit);
  transition: background 140ms ease, box-shadow 140ms ease, transform 90ms ease, opacity 140ms ease;
}

.orchestration-opt[data-orchestration-lines="2"] {
  min-height: 56px;
}

/* Hairline divider between consecutive options — a grouped list, not stacked boxes. */
.orchestration-opt + .orchestration-opt {
  border-top: 1px solid var(--cg-hover);
}

.orchestration-opt:hover,
.orchestration-opt:focus-visible {
  outline: none;
  background: var(--cg-hover-soft, var(--surface));
  box-shadow: inset 0 0 0 1.5px var(--cg-focus-visible-ring, var(--cg-focus-ring));
}

/* Hide the dividers touching a filled row so its rounded highlight isn't cut by a hairline. */
.orchestration-opt:hover,
.orchestration-opt:focus-visible,
.orchestration-opt:hover + .orchestration-opt,
.orchestration-opt:focus-visible + .orchestration-opt {
  border-top-color: transparent;
}

.orchestration-opt:active {
  transform: scale(0.99);
}

/* ⓐ′ 눌린 상태(cut 2) — 즉시 제출은 유지하되 탭한 행을 검정 면 + 체크로 채운다
   (multi-slot-clarification-flow-judge §5 M1c). 반경은 행 반경 그대로(10px), 새 반경 0.
   가르는 값(중앙 반려 뒤 재실측, 09-15): 클릭한 행은 실사용자·Playwright 둘 다 마우스가
   그 위에 남아 :hover(+ :focus-visible)도 동시에 매치한다. 이 규칙을 그 두 셀렉터
   "뒤"에 두면(둘 다 특이성 0,2,0으로 동일, 소스 순서가 이김) 캐스케이드 자체는 옳게
   이기지만, `.orchestration-opt`의 `transition: background 140ms ease`가 옛 :hover
   면(#f4f4f4)에서 이 검정 면으로 보간되는 도중에 읽으면(클릭 직후 즉시 스크린샷/실측)
   중간 프레임이 잡힌다 — 실측 rgba(244,244,244,.118) 같은 값, 캡처 02 의 "흐린 흰 면"이
   그 프레임이었다(전환 완료 후 재실측 시 라이트 19.44:1 / 다크 17.78:1로 정상). 셀렉터에
   `.orchestration-card` 조상을 더해 특이성을 (0,3,0)으로 올린 것은 소스 순서 tie-break
   에 기대지 않기 위한 방어이고, 실전에서 필요한 수정은 "읽는 시점"이었다 — 시험·캡처는
   전환 완료(140ms) 뒤에 값을 읽는다. */
.orchestration-card .orchestration-opt.is-pressed {
  background: var(--cg-text);
  color: var(--cg-send-fg);
  box-shadow: none;
}

.orchestration-card .orchestration-opt.is-pressed .orchestration-opt-desc {
  color: var(--cg-send-fg);
  opacity: 0.72;
}

.orchestration-card .orchestration-opt.is-pressed .orchestration-opt-icon,
.orchestration-card .orchestration-opt.is-pressed .orchestration-opt-key {
  color: var(--cg-send-fg);
  background: transparent;
}

.orchestration-card .orchestration-opt.is-pressed .orchestration-opt-pill {
  background: color-mix(in srgb, var(--cg-send-fg) 22%, transparent);
  color: var(--cg-send-fg);
}

/* 형제 행 40% — 선택 진행 중임을 알림(aria-busy 는 app.js 가 form 에 건다). */
.orchestration-options.is-selecting .orchestration-opt:not(.is-pressed) {
  opacity: 0.4;
  pointer-events: none;
}

/* Plain leading glyph — the filled 34px tile per row added weight; a muted icon in a slim slot
   keeps scannability without the boxiness (doc 167 P1-B). */
.orchestration-opt-icon {
  flex: none;
  width: 22px;
  display: grid;
  place-items: center;
  color: var(--cg-muted-2, var(--muted));
}

.orchestration-opt-icon svg {
  width: 19px;
  height: 19px;
}

.orchestration-opt-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.orchestration-opt-title {
  font-size: var(--cg-text-label-size, 14px);
  font-weight: var(--cg-text-label-active-weight, 500);
  /* 행 48/56 목표 실측: 전역 상속 line-height(1.72)로는 title+desc 2줄이 66px 로 넘친다 —
     .orchestration-card-title 이 이미 쓰는 1.3 과 통일(실측 18.2px). */
  line-height: 1.3;
}

.orchestration-opt-desc {
  font-size: var(--cg-text-meta-size, 12px);
  color: var(--cg-muted, var(--muted));
  line-height: 1.3;
}

.orchestration-opt-pill {
  flex: none;
  padding: 2px 10px;
  border-radius: var(--wizon-ai-radius-pill, 999px);
  font-size: var(--ds-text-micro-11);
  font-weight: 500;
  color: var(--cg-text, inherit);
  background: var(--cg-hover);
}

.orchestration-opt-key {
  flex: none;
  padding: 2px 8px;
  border-radius: var(--ds-radius-xs, 6px);
  font-size: var(--ds-text-micro-11);
  font-variant-numeric: tabular-nums;
  color: var(--cg-muted, var(--muted));
  background: var(--cg-hover-soft, rgba(0, 0, 0, 0.04));
}

/* N필드 카드는 이 행을 필드마다 하나씩 쌓는다(.orchestration-fields) — 각 칸에 눈에 보이는
   입력 경계가 이미 있어(inset box-shadow) 별도 컴포넌트가 필요 없다(multi-slot-clarification-
   flow-judge §6 참고 노트). 반경은 행 반경과 통일(10px, ≤3종 게이트 — card 16/row 10/pill 999). */
.orchestration-fields {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.orchestration-other {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 14px;
  border-radius: var(--wizon-ai-radius-menu-row, 10px);
  box-shadow: inset 0 0 0 1px var(--cg-menu-border, var(--line));
  transition: box-shadow 140ms ease;
}

.orchestration-other:focus-within {
  box-shadow: inset 0 0 0 1.5px var(--cg-text, var(--muted));
}

.orchestration-other-label {
  flex: none;
  font-size: var(--cg-text-meta-size, 12px);
  color: var(--cg-muted, var(--muted));
}

.orchestration-other-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 9px 0;
  border: 0;
  background: transparent;
  color: var(--cg-text, inherit);
  font: inherit;
  font-size: var(--ds-text-body-14);
}

.orchestration-other-input:focus {
  /* Suppress the global input:focus blue ring (--ds-input-ring-focus); the .orchestration-other
     container already shows a monochrome focus-within border. */
  outline: none;
  border-color: transparent;
  box-shadow: none;
}

.orchestration-other-input::placeholder {
  color: var(--cg-input-placeholder, var(--muted));
}

.orchestration-card-foot {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
}

/* 하단 컨트롤 = 36 pill(continuation-surfaces-design-judge §3.2 "하단 컨트롤" — 확인/건너뛰기
   높이 종류 1개로 통일, 현 35→36). */
.orchestration-card-submit {
  height: 36px;
  padding: 0 18px;
  border: 0;
  border-radius: var(--wizon-ai-radius-pill, 999px);
  cursor: pointer;
  background: var(--cg-send-bg, #0d0d0d);
  color: var(--cg-send-fg, #fff);
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
  transition: opacity 140ms ease, transform 90ms ease;
}

.orchestration-card-submit:hover {
  opacity: 0.9;
}

.orchestration-card-submit:active {
  transform: scale(0.98);
  opacity: 0.85;
}

/* Skip ("건너뛰기") — ghost secondary, pushed to the left of the submit button. Same 36 height
   as the confirm pill (높이 종류 1개). */
.orchestration-card-skip {
  margin-right: auto;
  height: 36px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--wizon-ai-radius-pill, 999px);
  cursor: pointer;
  background: transparent;
  color: var(--cg-muted, #8f8f8f);
  font-size: var(--ds-text-caption-13);
  font-weight: 500;
  transition: background 140ms ease, color 140ms ease;
}
.orchestration-card-skip:hover {
  background: var(--cg-hover-soft, rgba(13, 13, 13, 0.04));
  color: var(--cg-text, #0d0d0d);
}
.orchestration-card-skip:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, #0d0d0d);
  outline-offset: 2px;
}

/* 접힘 상태(ⓐ′ cut 3) — «● 선택됨 · X» 1줄, ≈56px(card padding 16×2 + head line). 카드 셸을
   그대로 재사용(반경 16, 면·테두리 동일) — 새 표면 0. */
.orchestration-card-collapsed {
  gap: 0;
}

.message.pending .message-body {
  color: var(--muted);
}

.message.failed .message-body {
  color: var(--cg-text, #0d0d0d);
}

/* 20260802-notice-surface (북극성 ③, doc 217 §11.3): 세 번째 상태 — 답변도 실패도 아닌 알림.
   ★카드로 만들지 않는다. doc 143 §1.5가 카드 남발을 금하고, 실패 카드(.chat-failure)와 같은
   무게를 주면 «읽고 넘어갈 것»과 «대응해야 할 것»의 구분이 사라진다. 면·테두리 없이 아이콘
   하나와 조용한 한 줄로 둔다. 색은 muted — 답변 본문보다 뒤로 물러나야 한다. */
/* ★특이도: 셸 규칙 `.chatgpt-reference-v1 .message-body p`(0,2,1)가 이긴다. 알림은
   .message-body 안에 있으므로 무접두 `.chat-notice`(0,1,0)로는 color/font-size 가 절대 안
   먹는다 — 배포본 실측에서 muted 대신 #0d0d0d, caption-13 대신 16px 이었다. 계약은 «선언»만
   읽어 GREEN 이었다. 이 파일의 다른 규칙들과 같은 셸 접두 + 요소 한정으로 (0,3,1)을 만든다. */
.chatgpt-reference-v1 .message-body p.chat-notice {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  align-items: start;
  gap: 8px;
  width: min(620px, 100%);
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  line-height: 1.6;
}

.chatgpt-reference-v1 .message-body .chat-notice-icon {
  display: grid;
  place-items: center;
  color: var(--cg-muted);
}

.chat-failure {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  align-items: start;
  gap: 12px;
  /* 20260911-chat-failure-width-unify (C12): 620px 고정 폭을 지우고 부모(.message-list,
     이미 --cg-chat-max-width/--cg-thread-gutter 로 본문·컴포저와 같은 열 폭을 계산해
     둔 컨테이너)의 100%를 그대로 물려받는다 — 새 리터럴을 추가하지 않고 실패 카드를
     본문 폭(757)과 같은 값으로 맞춘다(한 열에 3폭이던 것을 1폭으로). */
  width: 100%;
  padding: 14px 16px;
  /* 다크모드 리터럴 금지 (retry 버튼 준거, 아래 .chat-failure-retry 주석): 오류 카드
     표면·테두리를 시맨틱 토큰으로 플립. 라이트=거의 무변화(bad-fg 4% + surface ≈ 원본 은은한 톤),
     다크=near-black 표면 + 붉은 테두리로 flip → 검은 캔버스 위 연분홍 카드/안 보이는 제목 해소.
     4%는 격리 하네스 라이트 렌더에서 원본 근사 최적값(6/8%는 과채도)으로 확정. */
  border: 1px solid color-mix(in srgb, var(--ds-feedback-bad-fg) 24%, transparent);
  border-radius: 8px;
  background: color-mix(in srgb, var(--ds-feedback-bad-fg) 4%, var(--ds-color-surface));
}

.chat-failure-icon {
  color: var(--ds-feedback-bad-fg);
}

.chat-failure-copy {
  min-width: 0;
}

.chat-failure-copy strong {
  display: block;
  margin-bottom: 4px;
  font-size: var(--ds-text-body-14);
  font-weight: 600;
}

.chat-failure-copy p {
  margin: 0;
  color: var(--ds-color-muted);
  font-size: var(--ds-text-body-14);
  line-height: 1.5;
}

.chat-failure-copy small {
  display: block;
  margin-top: 6px;
  color: var(--ds-color-muted);
  font-size: var(--ds-text-micro-11);
}

/* 217: 원시 오류 코드를 기본 접힌 "기술 정보"로 강등(관측성/UX 분리). 토큰만 사용해 라이트/다크
   모두 muted 톤 유지, 기본 마커는 숨기고 요약을 muted 캡션처럼. */
.chat-failure-detail {
  margin-top: 6px;
}

.chat-failure-detail > summary {
  display: flex;
  /* ★터치 타깃 게이트(workspace-picker-sheet-build 추가 09-12 22:2x): v2 목업 실측
     18.69px 는 애플 최소 28pt 미달 — 이 시트가 만드는 유일한 (재)복구 진입점 화면이라
     같은 후보에서 함께 맞춘다(≤520 은 아래 44px 로 다시 올림). */
  align-items: center;
  min-height: 28px;
  color: var(--ds-color-muted);
  font-size: var(--ds-text-micro-11);
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.chat-failure-detail > summary::-webkit-details-marker {
  display: none;
}

.chat-failure-detail > small {
  margin-top: 4px;
}

/* D2b(fix-c, W3 QA §Q5-b): 재승격 완료 카드 — .chat-failure 의 해부(아이콘+본문 그리드,
   14px/16px 패딩, 8px 반경)는 그대로 물려받되 의미색(빨강)을 지운다. 무채색·버튼 0·
   기술 정보 0(옮긴 뒤엔 뜻이 없다) — 아트디렉션 규칙(오너 09-08 무채색 테마)의 「완료」
   상태는 강조색이 아니라 잉크 톤 하나로 충분하다는 원칙을 그대로 따른다. 새 반경/그림자
   값 0 — 기존 .chat-failure 토큰만 재사용. */
.chat-workspace-moved {
  border-color: var(--line);
  background: var(--ds-color-surface);
}

.chat-workspace-moved-icon {
  color: var(--ds-color-muted);
}

@media (max-width: 520px) {
  .chat-failure-detail > summary {
    min-height: 44px;
  }
}

.chat-failure-retry {
  min-height: 32px;
  padding: 0 11px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 7px;
  /* P1-6 다크모드 리터럴 금지 (doc art-direction, 2026-07-17): 표면·텍스트·호버를
     함께 토큰화해 라이트=무변화(#fff/#0d0d0d/#f4f4f4), 다크=surface #000·text #ececec·
     hover #212121 로 flip → 두 테마 모두 대비 유지. 패턴은 styles.css:11492-11493 준거. */
  background: var(--cg-surface, #fff);
  color: var(--cg-text, #0d0d0d);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  cursor: pointer;
  white-space: nowrap;
}

.chat-failure-retry:hover {
  background: var(--cg-hover-soft, #f4f4f4);
}

@media (max-width: 520px) {
  .chat-failure {
    grid-template-columns: 22px minmax(0, 1fr);
  }

  .chat-failure-retry {
    grid-column: 2;
    justify-self: start;
  }
}

/* ============================================================================
   작업공간 선택 시트 (workspace-picker-sheet-build, 오너 승인 09-12 「시트ⓐ」)
   정본: workspace-picker-sheet-design 레인 v2 judge 산출물
   (harness/workspace-picker.css·mockup.html·mockup.js) 를 그대로 이식.
   raw 색 0 · raw 그림자 0 · 새 토큰 0 — 전부 이미 살아 있는 var() 재사용.
   ★fix-b(judge 09-12) 실측 교정: 폰트 크기 {11, 13, 14, 22} 4종(13 = 상태줄/스피너 문구,
   앞 주석이 빠뜨렸었다) · 카드 반경 {18 시트·hero, 14 그룹·웰·상태·빈결과} 2종
   세로 리듬 실측 값 집합 {8, 12, 24} 3종(D5·상태줄 리듬 수리 뒤, rhythm.json 재실측)
   ============================================================================ */

/* ── 1. 실패 카드의 복구 액션 ────────────────────────────────────────────────
   ★클래스 이름이 `chat-failure-retry` 가 아니다 — CI 계약
   (apps/ai-portal/tests/test_workspace_access_safe_recovery_surface.py 가
   assertNotIn("chat-failure-retry", html)). `failure.action` 도 건드리지 않는다
   (같은 시험의 assertIsNone). 이 버튼은 failure.kind 로만 켜진다. */
.chat-failure-recovery {
  height: 36px; /* 위계 게이트(추가 09-12 22:2x): 웹 최소 36 — v2 시연값 34 보다 넉넉히 */
  padding: 0 16px;
  border: 1px solid transparent;
  border-radius: var(--ds-radius-pill);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

.chat-failure-recovery:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 2px;
}

/* ★배포된 실패 카드는 본문이 제목보다 크다(실측: p 16px vs strong 14px).
   `.chatgpt-reference-v1 .message-body p`(0,2,1) 가 `.chat-failure-copy p`(0,1,1) 를 이겨
   카드 저자가 적어 둔 14px 이 한 번도 화면에 닿은 적이 없다. 이 시트의 «트리거 표면» 이므로
   같은 후보에서 특이도만 올려 되돌린다 — 값은 원저자가 쓴 그대로다(새 값 0).
   ★1차 수리(`.chatgpt-reference-v1 .chat-failure-copy p`, (0,2,1))는 실측 컨테이너 QA 에서
   여전히 16px 로 나왔다 — 이 리포의 `.chatgpt-reference-v1 .message-body p` 규칙 자체가 이미
   `.chatgpt-reference-v1` 접두를 달고 있어 특이도가 (0,2,1) 로 **동률**이었고, 파일 뒤쪽에
   있는 그 규칙이 캐스케이드로 이겼다. `.chat-failure` 컨테이너 클래스를 한 단 더 넣어
   (0,3,1) 로 확실히 앞선다(원저자 값 그대로, 파일 위치 무관하게 이김). */
.chatgpt-reference-v1 .chat-failure .chat-failure-copy p {
  font-size: var(--ds-text-body-14);
}

@media (max-width: 520px) {
  /* 실패 카드는 ≤520 에서 2열이 되고 액션이 2열의 새 줄로 내려간다(기존
     .chat-failure-retry 와 같은 해부 — 행 해부 1종). 폰에서는 막다른 화면의
     유일한 출구이므로 AAA 44 정책선까지 올린다. */
  .chat-failure-recovery {
    grid-column: 2;
    justify-self: start;
    height: 44px;
  }
}

/* ── 2. 시트 컨테이너 ──────────────────────────────────────────────────────
   네이티브 <dialog>.showModal() — top layer · ::backdrop 포인터 차단 · 포커스 트랩 ·
   Esc · iOS 스크롤 락 · doc 667 오버레이 히스토리를 전부 상속한다.
   ★지면이 «회색» 이고 그 위에 흰 카드가 얹힌다(v1 은 반대였고, 그래서 목록이 배경과
     같은 평면이 되어 깊이가 0 이었다 — 중앙 반려 3번). */
.workspace-picker-dialog[open] {
  position: fixed;
  inset: 0;
  width: min(520px, calc(100vw - 24px));
  /* fix-e(09-13, QA2 실측 864x304): 뷰포트 단위를 dvh 로 — 모바일 브라우저 URL 바처럼
     레이아웃 뷰포트와 시각 뷰포트가 갈리는 환경에서 이전 단위 기준은 자체 잘림 여지를
     남긴다. 값 자체(680/48→16 은 아래 저높이 미디어쿼리와의 정합 때문에 16 으로 좁혔다
     — 640px 문턱 위에서는 어차피 hero-metrics 가 빠져 여유가 남는다)는 정본 지정값 그대로. */
  height: min(680px, calc(100dvh - 16px));
  max-height: calc(100dvh - 16px);
  margin: auto;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-lg);
  background: var(--wp-ground);
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-body-14);
  box-shadow: var(--ds-shadow-lg);
  opacity: 0;
  transform: translate3d(0, 10px, 0) scale(0.985);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
}

.workspace-picker-dialog[data-motion-state="open"] {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}

.workspace-picker-dialog[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(0, 10px, 0) scale(0.985);
}

@media (prefers-reduced-motion: reduce) {
  .workspace-picker-dialog[open] {
    transition: none;
    transform: none;
  }
}

.workspace-picker-dialog::backdrop {
  background: var(--cg-scrim, var(--wizon-scrim, var(--line)));
}

/* 폰의 초기 포커스 대상(컨테이너). 프로그램적 포커스이므로 링을 그리지 않는다 —
   ★이 한 줄은 «기존 outline:none 리셋을 지우는» 것이 아니라 새 컨테이너 한정 추가다
   (anti-drift 래칫 기준선 흔들림 주의: 후보가 래칫 천장을 1 올린다). */
.workspace-picker-shell:focus,
.workspace-picker-shell:focus-visible {
  outline: none;
}

/* ★층위 토큰 — 라이트와 다크에서 «지면과 카드의 밝기 순서가 서로 반대»다(실측 휘도:
   라이트 --cg-surface 1.00 > --cg-hover-soft 0.905 / 다크 --cg-surface 0.000 < --cg-hover-soft 0.015).
   한 토큰 쌍으로는 두 테마를 다 만족시킬 수 없어 여기서만 스왑한다. 그 위의 층(호버·선택·
   안쪽 패널)은 color-mix 로 «잉크를 몇 % 섞는가» 로 표현해 테마와 무관하게 단조가 된다. */
:root,
.workspace-picker-dialog {
  --wp-ground: var(--cg-hover-soft, var(--bg));
  --wp-card: var(--cg-surface, var(--bg));
}

html[data-theme="dark"] .workspace-picker-dialog {
  --wp-ground: var(--cg-surface, var(--bg));
  --wp-card: var(--cg-hover-soft, var(--bg));
}

.workspace-picker-shell {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

/* ── 3. 내비 머리 (앱 시트 문법: 타이틀은 작게, 주인공은 내용) ──────────── */
.workspace-picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 8px 8px 16px;
}

.workspace-picker-head h2 {
  margin: 0;
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* R-②(2026-09-14, 중앙 4f8733b3): 닫기 버튼 히트 44×44 — desktop-chrome-1440 실측(mouse
   컨텍스트)에서 `.chatgpt-icon-button` 시각 34×34 그대로 잡혔다. 그 클래스의 기존 터치
   히트슬롭 관례(styles.css ~L41645, 아트디렉션 P1-5)는 `@media (pointer: coarse)` 로만
   켜져 마우스 컨텍스트를 덮지 않는다 — «다른 화면에서도 널리 쓰이므로 기본은 그대로 둔다»
   (C8 주석, L14713)는 그 규칙 그대로이므로, 여기서도 전역 `.chatgpt-icon-button` 은 건드리지
   않고 이 시트의 헤더 하나에만 스코프한다(이 헤더에 아이콘버튼은 닫기 1개뿐, index.html
   L2104). 시각 박스 34×34 는 유지, ::after 로 히트 영역만 44×44 로 넓힌다 — pointer 종류와
   무관하게 상시 적용(모바일도 이 규칙이 덮는다, 별도 coarse 분기 불필요). */
.workspace-picker-head .chatgpt-icon-button {
  position: relative;
}

.workspace-picker-head .chatgpt-icon-button::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

/* ── 4. 상태 카드 — 위젯 «물체» ────────────────────────────────────────────
   사내 정본은 흰 카드가 아니다: 날씨 카드(styles.css:24069)가 radial 2겹 + linear 한 겹의
   «색 면» 위에 흰 글자를 얹는다. 여기서도 같은 레시피를 쓰되 경보 상태라 채도 대신 흑연으로
   간다(--ds-gray-950 = #09090b, 다크 셸 배경 styles.css:11536 이 쓰는 바로 그 값).
   흰 글자 대비 14.7~19.9:1. 깊이는 그 면 위의 «반투명 서브 카드» 가 만든다(회색 웰 아님). */
.workspace-picker-hero {
  margin: 0 16px 12px;
  padding: 16px;
  border-radius: var(--ds-radius-lg);
  background:
    radial-gradient(130% 100% at 84% -10%, color-mix(in srgb, var(--ds-color-hero-glow) 30%, transparent), transparent 58%),
    radial-gradient(120% 120% at 4% 0%, color-mix(in srgb, var(--ds-color-hero-glow-2) 22%, transparent), transparent 52%),
    linear-gradient(168deg, var(--ds-color-hero-surface), var(--ds-color-hero-surface-deep));
  color: var(--ds-color-on-hero);
  box-shadow: var(--ds-shadow-md);
}

.workspace-picker-hero-eyebrow {
  display: block;
  color: color-mix(in srgb, var(--ds-color-on-hero) 62%, transparent);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* 핵심 상태 1개 — 이 시트에서 가장 큰 글자. 면이 이미 경보를 말하므로 글자는 흰색이고
   의미색 한 점은 아래 «칩» 이 가져간다(SC 1.4.1: 색 외 단서 = 글자 그 자체). */
.workspace-picker-hero-status {
  display: flex;
  align-items: center;
  gap: 8px;
  /* D5(judge fix-b): 이 요소는 <p> 라 UA margin-block:1em(=22px, 자기 font-size 기준)이
     margin-bottom 에 남아 있었다 — margin-top 만 선언하면 아래쪽은 8px 이 아니라 22px
     로 상쇄돼(computed margin-bottom:22px) hero 내부 리듬이 8/8/12 대신 8/22/12 로 깨진다.
     margin 전체를 선언해 bottom 을 0 으로 고정한다. */
  margin: 8px 0 0;
  color: var(--ds-color-on-hero);
  font-size: var(--ds-text-title-22);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.workspace-picker-hero-status .wds-inline-icon {
  width: 22px;
  height: 22px;
  color: var(--ds-color-on-hero-alert);
}

/* D4(judge fix-b): 위 규칙의 `fill: currentColor`(특이도 0,1,1)는 실전에서 전 포털
   아이콘 규칙 `.chatgpt-reference-v1 .wds-inline-icon{fill:none;width:20px}`(0,2,0, 파일
   뒤쪽)에 져서 한 번도 렌더에 닿지 않았다(computed fill:none, 20×20 — 선언 22px 미적용).
   ★그리고 닿아도 더 나빴다: circle-alert glyph 는 원(circle)+느낌표 선(line) 2개라
   전체를 currentColor 로 채우면 느낌표 획 픽셀이 원과 같은 빨강이 되어 대비 1.0(캡처
   icon-B-declared-fill.png). 특이도를 확실히 올려(0,3,0) 원만 채우고 느낌표는 어두운
   면 위에서 읽히는 잉크 색으로 stroke 를 굵혀 knockout 한다(실측 대비 7.19:1). */
.chatgpt-reference-v1 .workspace-picker-hero-status .wds-inline-icon {
  width: 22px;
  height: 22px;
}

.chatgpt-reference-v1 .workspace-picker-hero-status .wds-inline-icon circle {
  fill: currentColor;
}

.chatgpt-reference-v1 .workspace-picker-hero-status .wds-inline-icon line {
  stroke: var(--ds-color-hero-surface-deep);
  stroke-width: 2.4;
}

.workspace-picker-hero-subject {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  min-width: 0;
}

.workspace-picker-hero-name {
  overflow: hidden;
  color: color-mix(in srgb, var(--ds-color-on-hero) 82%, transparent);
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 채워진 칩(외곽선 아님) — 애플 위젯의 컬러 배지 문법. */
.workspace-picker-hero-chip {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  height: 20px;
  padding: 0 8px;
  border-radius: var(--ds-radius-pill);
  background: var(--ds-color-hero-alert-fill);
  color: var(--ds-color-on-solid);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  white-space: nowrap;
}

/* 서브 카드(층 3) — 색 면 위 반투명 흰 판. 회색 웰이 아니다. */
.workspace-picker-hero-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin-top: 12px;
  padding: 8px;
  border-radius: var(--ds-radius-md);
  background: color-mix(in srgb, var(--ds-color-on-hero) 14%, transparent);
}

.workspace-picker-hero-metric {
  display: grid;
  gap: 0;
  min-width: 0;
  text-align: center;
}

/* D6(judge fix-b): 특이도 (0,1,1)로는 파일 뒤쪽 `.chatgpt-reference-v1 b`(0,1,1, 동률·후순위
   승)에 져서 선언한 값이 캐스케이드에서 밀린다(실패 카드 p 가 겪은 것과 같은 캐스케이드
   패턴 — 그 주석이 이 세 번째 사례를 놓쳤다). 컨테이너 클래스 한 단을 더 넣어 (0,2,1)로
   확실히 이긴다.
   D6 정정(fix-d, test_typography_contract RED): 처음 선언한 800은 웨이트 팔레트
   {300,400,500,600,700} 밖이라 래칫 위반이었고, 700 은 파일 전체에 1곳(모바일 워드마크)
   으로 이미 상한이 차 있어 대신 쓸 수도 없었다(썼다면 count(700)<=1 이 새로 RED). 계약이
   정의한 "strong" 시맨틱 값 --cg-text-strong-weight(=600)로 낮춘다 — 특이도 승리 목적은
   그대로 유지된다. */
.chatgpt-reference-v1 .workspace-picker-hero-metric b {
  color: var(--ds-color-on-hero);
  font-size: var(--ds-text-title-22);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.workspace-picker-hero-metric span {
  overflow: hidden;
  color: color-mix(in srgb, var(--ds-color-on-hero) 66%, transparent);
  font-size: var(--ds-text-micro-11);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── 5. 검색 ─────────────────────────────────────────────────────────────── */
.workspace-picker-controls {
  display: flex;
  align-items: center;
  margin: 0 16px 16px;
}

.workspace-picker-search {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  padding: 0 16px;
  border-radius: var(--ds-radius-sm);
  background: var(--wp-card);
  box-shadow: var(--ds-shadow-sm);
}

.workspace-picker-search .wds-inline-icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--cg-muted, var(--muted));
}

.workspace-picker-controls input[type="search"] {
  flex: 1 1 auto;
  min-width: 0;
  height: 40px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--ds-text-body-14);
}

/* 전역 `select:focus, input:focus { box-shadow: 0 0 0 2px … }` 가 테두리 없는 이 입력의
   «안쪽»에 알약을 하나 더 그려 이중 테두리가 된다 — 링은 알약 «전체» 가 받아야 한다. */
.workspace-picker-controls input[type="search"]:focus,
.workspace-picker-controls input[type="search"]:focus-visible {
  outline: none;
  box-shadow: none;
}

.workspace-picker-search:focus-within {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}

/* ── 6. 목록 — 그룹 인셋 리스트 ──────────────────────────────────────────── */
.workspace-picker-list {
  flex: 1 1 auto;
  min-height: 0;
  margin: 0;
  padding: 0 16px 16px;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
}

/* 그룹 헤더 — 오른쪽 메타 「업무」 52줄 반복(정보량 0)을 여기로 올린 자리.
   그룹 «안» 은 전부 같은 성격이므로 행마다 같은 캡션을 되풀이할 필요가 없다. */
.workspace-picker-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 0;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-micro-11);
  letter-spacing: 0.02em;
}

.workspace-picker-group {
  margin: 0 0 16px;
  padding: 0;
  overflow: hidden;
  border-radius: var(--ds-radius-md);
  background: var(--wp-card);
  box-shadow: var(--ds-shadow-sm);
  list-style: none;
}

.workspace-picker-group:last-child {
  margin-bottom: 0;
}

.workspace-picker-option {
  display: flex;
  align-items: center;
  gap: 12px;
  box-sizing: border-box;
  min-height: 44px;
  padding: 0 16px;
  cursor: pointer;
  transition: background-color var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease);
}

/* 행 구분선은 타일 오른쪽 끝에서 시작한다(앱 리스트 관례) — 패딩 12 + 타일 28 + 간격 12 = 52.
   ★inset box-shadow 로는 안 된다: offset-x 를 주면 왼쪽 52px «면» 이 통째로 칠해진다
     (1차 렌더에서 타일 뒤 회색 기둥으로 나왔다). 절대배치 1px 선이 정답. */
.workspace-picker-option {
  position: relative;
}

/* ★히트슬롭(::after inset:-2px 0)은 «연속 목록»에서 작동하지 않는다 — 실측: 위쪽 2px 는
   먹지만 아래쪽 2px 는 «다음 행의 슬롭» 이 차지해 상쇄되고 실효 높이가 42px 에 그쳤다
   (elementFromPoint 로 경계 ±1/±2/±3 을 찍어 확인). 작동하지 않는 장치를 남기면 다음
   사람이 «있으니 괜찮다» 고 오독하므로 넣지 않는다.
   ★R-②(2026-09-14, 중앙 4f8733b3) 수리: 위 40px 결정은 「데스크톱 AA 24 통과·AAA 44 는
   정책선 미달로 기록」이라 밝히고 남긴 것이었으나, COMPONENT-RULES 44 정책이 데스크톱
   시트에도 적용돼(qa 레인 실측 486×40) 정책선 위반으로 확정됐다. 히트슬롭이 아니라 실제
   상자 높이를 44 로 올린다(연속 목록 슬롭 함정과 무관 — 박스 자체가 커지므로 다음 행과
   상쇄될 슬롭이 없다). 폰(≤520, 아래 §11)은 이미 64px 로 44 를 웃돌아 그대로 둔다. 좌측
   정렬선(배지 28px+패딩, 구분선 left:56px)·세로 리듬은 패딩 `0 16px` 그대로라 회귀 0. */

.workspace-picker-option + .workspace-picker-option::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 56px;
  height: 1px;
  background: var(--cg-menu-border, var(--line));
}

.workspace-picker-option[aria-selected="true"]::before,
.workspace-picker-option[aria-selected="true"] + .workspace-picker-option::before {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .workspace-picker-option {
    transition: none;
  }
}

/* 컬러 원형 이니셜 배지 — Apple Reminders 의 «행마다 색 한 점» 문법.
   ★색은 6개 «기존» 토큰에서만 고른다(새 raw 색 0). 흰 글자 대비 실측 5.02~8.75:1 로 전부
     SC 1.4.3 통과. 배정은 이름 해시 → data-badge="1".."6" (구현에서 결정, CSS 는 매핑만). */
.workspace-picker-option-badge {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--ds-radius-pill);
  background: var(--ds-color-identity-neutral);
  color: var(--ds-color-on-solid);
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}

.workspace-picker-option[data-badge="1"] .workspace-picker-option-badge { background: var(--ds-color-identity-1); }
.workspace-picker-option[data-badge="2"] .workspace-picker-option-badge { background: var(--ds-color-identity-2); }
.workspace-picker-option[data-badge="3"] .workspace-picker-option-badge { background: var(--ds-color-identity-3); }
.workspace-picker-option[data-badge="4"] .workspace-picker-option-badge { background: var(--ds-color-identity-4); }
.workspace-picker-option[data-badge="5"] .workspace-picker-option-badge { background: var(--ds-color-identity-5); }
.workspace-picker-option[data-badge="6"] .workspace-picker-option-badge { background: var(--ds-color-identity-6); }

.workspace-picker-option-body {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex: 1 1 auto;
  min-width: 0;
}

.workspace-picker-option-name {
  flex: 0 1 auto;
  overflow: hidden;
  font-size: var(--ds-text-body-14);
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 스캔 단서 — 우선순위: ① 내가 마지막으로 여기서 대화한 시각 + 대화 수
   ② 작업공간 설명 ③ «아직 사용한 적 없음». (문서 수는 bootstrap 에 없다.) */
.workspace-picker-option-sub {
  flex: 0 0 auto;
  overflow: hidden;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-micro-11);
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.workspace-picker-option-mark {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--cg-text, var(--text));
  opacity: 0;
}

.workspace-picker-option[aria-selected="true"] {
  background: color-mix(in srgb, var(--cg-text) 10%, transparent);
}

.workspace-picker-option[aria-selected="true"] .workspace-picker-option-name {
  font-weight: 600;
}

.workspace-picker-option[aria-selected="true"] .workspace-picker-option-mark {
  opacity: 1;
}

@media (hover: hover) {
  .workspace-picker-option:hover {
    background: color-mix(in srgb, var(--cg-text) 5%, transparent);
  }
}

/* 눌림 — 터치에서 «닿았다» 를 즉시 돌려준다(위젯 문법 ③). */
.workspace-picker-option:active {
  background: color-mix(in srgb, var(--cg-text) 14%, transparent);
}

/* 키보드 활성 항목 — 포커스는 검색 입력에 남아 있어 :focus-visible 이 안 걸린다.
   호버(연한 면)와 구별되도록 «면 + 링» 을 같이 준다. */
.workspace-picker-option.is-active {
  background: color-mix(in srgb, var(--cg-text) 10%, transparent);
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: -2px;
}

.workspace-picker-option[aria-disabled="true"] {
  cursor: default;
  opacity: 0.4;
}

/* 재승격 중에는 나머지 행을 죽이되 «내가 고른 그 행» 은 또렷하게 남긴다 — 진행 표시가
   가리키는 대상이 화면에서 같이 흐려지면 무엇을 옮기는 중인지 읽을 수 없다. */
.workspace-picker-option[aria-disabled="true"][aria-selected="true"] {
  opacity: 1;
}

.workspace-picker-empty {
  margin: 0;
  padding: 24px 16px;
  border-radius: var(--ds-radius-md);
  background: var(--wp-card);
  box-shadow: var(--ds-shadow-sm);
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-body-14);
  line-height: 1.5;
  text-align: center;
}

/* ── 7. 시트 자체의 상태줄 (진행 중 / 오류) ───────────────────────────────
   ★토스트를 쓰지 않는 이유: <dialog> 는 top layer 라 body 에 붙는
   .permission-toast-host 를 통째로 덮는다(실측: elementFromPoint → dialog).
   시트가 열려 있는 동안의 메시지는 시트가 직접 맡고, 토스트는 «닫은 뒤»에만.
   ★진행 중 표시는 스스로 사라지지 않는다(표면 분담 규약). */
.workspace-picker-status {
  display: flex;
  align-items: center;
  gap: 8px;
  /* D10(judge fix-b, 선택): 위 0 / 아래 16 은 목록↔상태줄 간격이 0(붙음)인데 상태줄↔푸터는
     16+8(푸터 패딩) = 24 가 되어 비대칭이었다. 위 16 / 아래 8(+푸터 패딩 8 = 16)로 대칭. */
  margin: 16px 16px 8px;
  padding: 12px;
  border-radius: var(--ds-radius-md);
  background: var(--wp-card);
  box-shadow: var(--ds-shadow-sm);
  font-size: var(--ds-text-body-14);
}

.workspace-picker-status[data-state="idle"] {
  display: none;
}

.workspace-picker-status[data-state="error"] {
  color: var(--ds-feedback-bad-fg);
}

.workspace-picker-status-spinner {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  border: 2px solid var(--cg-menu-border, var(--line));
  border-top-color: var(--cg-text, var(--text));
  border-radius: var(--ds-radius-pill);
  animation: workspace-picker-spin 900ms linear infinite;
}

@keyframes workspace-picker-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .workspace-picker-status-spinner {
    animation: none;
  }
}

/* ── 8. 푸터(출처 줄 — 위젯 문법 ⑥) ─────────────────────────────────────── */
.workspace-picker-foot {
  padding: 8px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-micro-11);
  line-height: 1.5;
}

/* ── 9. ≤520 — 껍데기만이 아니라 «내용»이 달라지는 지점 ───────────────────
   ★fix-b(judge 09-12) 실측 교정: 아래 ①②는 앞 초안 그대로였고 렌더 실측과 달랐다.
   ① 상태 카드의 3열 지표는 2열이 아니라 **통째로 숨긴다**(display:none, 바로 아래
      `.workspace-picker-hero-metrics` 규칙 참고) — 「보존된 질문 · 첨부」는 안심시키는
      정보이지 고르는 데 쓰는 정보가 아니고, 그 65px 를 목록 한 행 이상으로 돌려준다.
   ② 행 높이 **40 → 64**, 배지 **28 → 32**(터치 정확도) — 이전 «52→60» 은 실측과 다른
      값이었다(타일 셀렉터도 죽어 있어 실제로는 적용된 적이 없었다, D7).
   ③ 공통 어댑터의 height:auto/overflow-y:auto 를 이 시트만 되돌린다 — 어댑터 주석이
      스스로 «예/아니오 + 입력 한둘 크기» 용이라고 밝히고, 64px 2줄 행 목록에서는 시트
      자체가 스크롤돼 검색·상태가 위로 사라진다(실측: scrollHeight 756 > clientHeight 717).
      특이도 (0,4,1) 이라야 어댑터의 (0,3,1) !important 를 이긴다.
   ④ 이미 살아 있는 --wizon-kb-inset(소프트 키보드 높이)만큼 바닥을 띄운다(새 JS 0).

   fix-g(09-13, 착지 43#8 반환 — 형제 계약 시험 5건 RED): 이 두 오버라이드 블록(여기와
   아래 fix-e)이 값 없는 `[data-mobile-bottom-sheet]`(존재 셀렉터)를 그대로 써서, 파일
   순서상 이 블록이 §11 공통 어댑터 원본(styles.css 24554, `body.chatgpt-reference-v1
   [data-mobile-bottom-sheet][open]`)보다 앞에 나오는 바람에 `test_mobile_bottom_sheet_
   adapter.py`(A/B/E)·`test_mobile_bottom_sheet_popover_adapter.py`(F)가 «`[data-mobile-
   bottom-sheet][open]` 리터럴은 정확히 1번, 그 첫 등장이 §11 원본」이라고 세는 불변식을
   깼다(첫 등장이 이 블록으로 바뀌고 등장 횟수도 3이 됨). §19 팝오버 어댑터가 이미 쓰는
   자세(`[data-mobile-bottom-sheet="popover"]`, 값 지정 셀렉터)를 그대로 따라 이 대화상자
   전용 옵트인 값 `"workspace-picker"`(index.html:2095)로 바꾼다 — 존재 셀렉터
   `[data-mobile-bottom-sheet]`는 값 지정 어트리뷰트에도 매치되므로 §11 공통 규칙(위치·
   모션)은 그대로 적용되고, 이 블록은 그 위에 얹는 대화상자 전용 오버라이드로만 남는다. */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 .workspace-picker-dialog[data-mobile-bottom-sheet="workspace-picker"][open] {
    bottom: var(--wizon-kb-inset, 0px) !important;
    /* 85vh → 88vh → (R-40 ②파도 C, 669 §M-5) 짧은 값: 시트 높이는 «짧은/긴» 두 종류뿐이다.
       선택형(picker)은 짧은 값 — 목록은 시트 안에서 스크롤한다. */
    height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px)) !important;
    /* 어댑터가 max-height 도 !important 로 묶는다 — 같이 풀지 않으면 height 만 바꿔도
       클램프돼 값이 안 움직인다(실측: 717 그대로였다). R-40 ②파도 C(M-5) — 선택형이라 짧은 값. */
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px)) !important;
    overflow: hidden !important;
  }

  /* ★폰에서는 지표 패널을 통째로 뺀다. 「보존된 질문 1 · 첨부 0」 은 «안심시키는» 정보이지
     «고르는 데 쓰는» 정보가 아니다 — 폰은 고르는 화면이고, 그 65px 를 목록 한 행 이상으로
     돌려주는 편이 낫다(실측: 목록 가시 4.9행 → 6.3행). 데스크톱은 지면이 남으므로 유지. */
  .workspace-picker-hero-metrics {
    display: none;
  }

  /* ★행 «내용»이 달라지는 지점: 폰은 2줄(이름 위, 단서 아래)로 세운다.
     한 줄에 몰면 390 폭에서 단서가 잘리고, 엄지로 찍을 면적도 줄어든다. */
  .workspace-picker-option {
    min-height: 64px;
    padding: 8px 16px;
  }

  .workspace-picker-option-body {
    display: grid;
    gap: 4px;
  }

  .workspace-picker-option-sub {
    text-align: left;
  }

  /* D7(judge fix-b): 이 셀렉터는 `-tile` 이었다 — 마크업은 `.workspace-picker-option-badge`
     (전 리포 사용처 0)라 죽은 셀렉터였고, 배지는 데스크톱 28px 그대로 남는데 행 구분선은
     아래 규칙대로 60px(=16+32+12)에서 시작해 +4px 어긋났다(390/375/430/320 전부). 이름을
     실제 마크업 클래스로 교정한다. */
  .workspace-picker-option-badge {
    width: 32px;
    height: 32px;
  }

  .workspace-picker-option + .workspace-picker-option::before {
    left: 60px;
  }

  /* ★터치 타깃 게이트(추가 09-12 22:2x): 「기술 정보」 summary 는 v2 목업 실측 18.69px 로
     애플 최소 28pt 미달 — 44 로(폰은 항상 이 브레이크포인트를 통과한다). 원저자 summary
     자체 CSS 는 이 시트 CSS 파일 밖(.chat-failure-detail summary, 위 §1 인접)이라 여기서는
     건드리지 않고, 이 시트에서 새로 만든 UI(=검색·목록·상태)에는 애초에 summary 가 없다. */
}

/* D8(judge fix-b): ≤520 폭 조건만으로는 200% 확대(=720×450 CSS px) 같은 "낮은" 뷰포트를
   못 잡는다 — 실측: 목록 가시 0.40행 + 시트 자체 scrollHeight 404 > clientHeight 400(4px
   잘림). 지표 패널을 높이 기준으로도 접어 목록에 그 자리를 돌려준다(실측: 2.42행, 자체
   잘림 0). 폭 조건과 별개 쿼리라 데스크톱 저높이 창(예 720×450)에도 똑같이 적용된다. */
@media (max-height: 640px) {
  .workspace-picker-hero-metrics {
    display: none;
  }
}

/* fix-c(D8-2, judge fix-b 뒤 QA 실측 09-13): 실기 가로(320x304~390x320, 물리 1080x2400
   URL바+탭바 뺀 CSS px)에서는 위 640px 문턱 하나로는 부족하다 — 지표 웰을 뺀 뒤에도
   hero(eyebrow+상태+대상칩) 128.8px + 머리 50 + 검색 58 + 목록 자체 padding 16 + 푸터 40.5
   가 264px 짜리 시트를 채워 목록이 16px(0.25행)만 남는다(수리 전 RED, `shortheight_probe.py`).
   더 낮은 문턱에서 ① 비필수 장식(eyebrow·«이 기기에만…» 칩)을 접고 ② hero 패딩을 줄이고
   ③ 출처 줄 푸터를 접고 ④ 시트 자체 높이를 8px 여백만 남기고 키운다 — 「연결 끊김」 상태
   문구(무엇이 문제인지)는 유지한다. 새 반경/그림자 값 도입 0(기존 레이아웃 속성만 조정).

   fix-e(09-13, QA2 실측 864x304): 이 문턱이 `(max-width: 520px) and` 로 폭에 묶여 있어
   폭이 520 을 넘는 가로 폰/저높이 창(864x304·1024x304)에서는 전혀 걸리지 않고 위
   데스크톱 모달 경로(§2, `.workspace-picker-dialog[open]`)로 빠져 자체 잘림 + 목록
   0행이 됐다(수리 전 RED). 시트/모달을 가르는 것은 여전히 폭(§11 의 `@media (max-width:
   520px)` — 바텀시트 위치·모션)이지만, 저높이 «보정」 자체는 폭과 무관해야 한다 — 폭
   조건을 떼고 높이 단독 400→420 으로 문턱만 살짝 낮춘다(640px 문턱과의 간격 확보).
   `[data-mobile-bottom-sheet][open]` 대상 `height: calc(100dvh - 8px) !important` 는
   폭이 520 을 넘어도 그대로 걸린다 — 그 폭에서는 시트가 중앙 모달로 남지만(§11 이 위치를
   폭으로 가른다) 높이만 이 규칙이 눌러 §2 의 dvh 상한보다 더 넉넉하게 확보한다. */
@media (max-height: 420px) {
  body.chatgpt-reference-v1 .workspace-picker-dialog[data-mobile-bottom-sheet="workspace-picker"][open] {
    height: calc(100dvh - 8px) !important;
    max-height: calc(100dvh - 8px) !important;
  }

  .workspace-picker-hero-eyebrow,
  .workspace-picker-hero-chip,
  .workspace-picker-hero-subject {
    display: none;
  }

  .workspace-picker-hero {
    margin: 0 16px 4px;
    padding: 2px 8px;
  }

  .workspace-picker-hero-status {
    margin-top: 0;
  }

  .workspace-picker-head {
    padding-top: 2px;
    padding-bottom: 2px;
  }

  .workspace-picker-controls {
    margin-bottom: 4px;
  }

  .workspace-picker-list {
    padding-bottom: 4px;
  }

  .workspace-picker-foot {
    display: none;
  }
}

.message-body p {
  margin: 0 0 10px;
  line-height: 1.65;
  /* WS-K2: 읽기 문단의 rag/고아줄 개선(Safari 26+/Chrome, Firefox는 우아 폴백). */
  text-wrap: pretty;
}

.message-body p:last-child {
  margin-bottom: 0;
}

.message-body pre,
.mini-card code {
  display: block;
  overflow: auto;
  max-width: 100%;
  margin: 12px 0 0;
  padding: 10px;
  border-radius: 8px;
  background: #111827;
  color: #e5e7eb;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--ds-text-caption-12);
  line-height: 1.5;
}

.feedback-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}

/* Model + elapsed time cluster (doc 164 §후속2): both are non-interactive "how it was made" meta,
   pushed together to the action row's end. */
.answer-meta-cluster {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}
/* Answer elapsed time (ChatGPT/Claude parity) — muted meta; the cluster handles right-alignment. */
.answer-elapsed {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--ds-text-caption-12);
  font-variant-numeric: tabular-nums;
  color: var(--cg-muted, #8f8f8f);
}
.answer-elapsed svg {
  width: 13px;
  height: 13px;
}

.feedback-note {
  margin-top: 12px;
  color: var(--success);
  font-size: var(--ds-text-caption-13);
}

/* P1-A: Inline feedback reason picker (doc 199 §1.3, §1.5) — replaces feedback-row in-place
   when user clicks dislike button. Single-select reason chips + optional correction textarea. */
/* 싫어요 이유 피커 — doc 344 시각 재설계.
   ★이 블록에는 하드코딩 색도 `var(--토큰, #hex)` 폴백도 없다. 폴백은 토큰이 죽어도
   조용히 통과하기 때문에, 이 패널이 디자인 시스템 밖에서 그려지던 것을 아무도 못 봤다
   (2026-08-01 실측: 미정의 토큰 26개를 참조했고 하드코딩 hex 50개가 화면을 칠했다).
   ★`[data-theme="dark"]` 대응 규칙이 없는 것은 누락이 아니다 — --cg-* 는 다크에서
   토큰 자체가 뒤집힌다(실측 12/14, --cg-send-bg #0d0d0d→#ffffff). 다크 규칙을 다시
   쓰면 그 반전을 두 번 적용하게 된다. */
.feedback-picker-row {
  display: flex;
  flex-direction: column;
  gap: var(--cg-composer-gutter);
  margin-top: var(--cg-composer-gutter);
  padding: var(--cg-composer-gutter);
  border: 1px solid var(--cg-menu-border);
  /* 종류 수 게이트(judge report §1.2): 이 패널은 메시지 목록 «안»의 중첩 패널이지 최상위
     카드가 아니다 — 공용 메뉴 반경(--cg-menu-radius, 12)이 아니라 사다리의 «안쪽 행»
     값(--wizon-ai-radius-menu-row, 10)을 쓴다. --cg-menu-radius 정의 자체는 다른 메뉴류가
     여전히 쓰므로 건드리지 않는다 — 이 규칙만 참조를 바꾼다. */
  border-radius: var(--wizon-ai-radius-menu-row);
  background: var(--cg-surface-secondary);
  box-shadow: var(--cg-shadow-float-xs);
}

/* D-10 (continuation-surfaces-design-judge) → R-40 공통 시트 부품으로 재표현(w6-feedback-toolbar-picker-chip-contract-r2):
   390(≤520)에서 답변을 덮던 인라인 피커(실측 431px)를 바텀시트로 올린다. 자체 grab 막대(::after)·자체 100vmax
   그림자 스크림은 걷었다 — 손잡이는 공통 DOM 손잡이(.wizon-sheet-handle, app.js ensureSheetHandle 이 data-sheet-kind="task"
   를 보고 깐다), 스크림은 시트 바로 앞 형제 [data-sheet-scrim](--wizon-scrim 1토큰, §M-11), 끌어 닫기는 문서 위임
   컨트롤러(bindMobileSheetKindDrag)와 SHEET_SELECTOR_DISMISSERS 의 `.feedback-picker-row` 등록, 열림·닫힘은 공용
   키프레임 wizon-sheet-up/-down 이다. 반경·그림자·최대 높이(작업형 «긴» 값 = 화면 − 위 안전영역 − 40)도 공통 값(--wizon-ai-radius-sheet ·
   --wizon-ai-sheet-shadow · --wizon-safe-*)이다. 이 블록은 var() 폴백 금지라 JS 가 채우는 --wizon-vvh 대신 100dvh 를 쓴다. [popover] 요소가 아니라 메시지 목록 안의 고정
   오버레이라 position:fixed 만 이 블록이 준다(will-change 수리는 아래 20260924-feedback-picker-escape). */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 .feedback-picker-row {
    position: fixed;
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    width: 100%;
    max-height: calc(100dvh - var(--wizon-safe-top) - 40px);
    overflow-y: auto;
    margin: 0;
    /* 위 여백은 손잡이(sticky 44px 첫 행)가 대신 채운다. */
    padding: 0 14px calc(16px + var(--wizon-safe-bottom));
    border-radius: var(--wizon-ai-radius-sheet) var(--wizon-ai-radius-sheet) 0 0;
    box-shadow: var(--wizon-ai-sheet-shadow);
    z-index: 200;
    translate: 0 0;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration)
      var(--wds-spring-effect-default) backwards;
  }

  body.chatgpt-reference-v1 .feedback-picker-row[data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms)
      var(--wizon-sheet-exit-ease) forwards !important;
  }
}

/* 모션 줄이기 — 다른 시트와 같은 문법(이동 0, 124ms 흐려짐, 새 키프레임 0). */
@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .feedback-picker-row {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration) !important;
    translate: 0 0 !important;
  }
  body.chatgpt-reference-v1 .feedback-picker-row[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration) linear forwards !important;
    translate: 0 0 !important;
  }
}

/* 공통 손잡이는 (max-width:520) 또는 굵은 포인터에서 보인다 — 인라인으로 남는 521+ 에서는 시트가 아니므로 숨긴다. */
@media (min-width: 521px) {
  .feedback-picker-row > .wizon-sheet-handle {
    display: none;
  }
}

/* 이 패널에서 유일한 질문 문장이라 본문색으로 올린다(예전엔 보조색이라 흐렸다). */
.feedback-picker-label {
  font-size: var(--cg-text-label-size);
  font-weight: var(--cg-text-label-active-weight);
  line-height: var(--cg-text-label-leading);
  color: var(--cg-text);
}

/* 2열 그리드. 한국어 라벨 4개는 한 줄에 안 들어가고, wrap flex는 3+1로 깨져
   마지막 칩이 고아가 된다 (doc 344 §2). */
.feedback-reason-chips {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--cg-menu-gutter);
}

/* 좁은 화면에선 1열. 실측(2026-08-01, 라이브 getComputedStyle): 컨테이너 319px에서
   칩 4개 중 2개가, 343px에서 「표현·형식 문제예요」가 2줄로 접혔다. 알약 칩이 2줄이
   되면 행 높이가 어긋난다. 520px는 정본 브레이크포인트다(breakpoints.md §2). */
@media (max-width: 520px) {
  .feedback-reason-chips {
    grid-template-columns: 1fr;
  }
}

.feedback-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--cg-menu-gutter);
  /* M3/M5 패키지 목업 --h-chip: 32px — 높이 1값(현행 38.3/42.6 두 값을 접는다). 패딩을
     세로 0 으로 낮추고 height 로 고정해야 폰트 크기가 달라져도 높이가 흔들리지 않는다. */
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill);
  background: var(--cg-surface);
  color: var(--cg-text-soft);
  font-size: var(--cg-text-label-size);
  font-family: inherit;
  line-height: var(--cg-text-label-leading);
  cursor: pointer;
  appearance: none;
  transition: background-color var(--cg-transition-basic-duration) var(--cg-ease-move),
              color var(--cg-transition-basic-duration) var(--cg-ease-move),
              border-color var(--cg-transition-basic-duration) var(--cg-ease-move);
}

.feedback-chip:hover {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

/* D-4: role=radio(APG Radio Group) — 상태는 aria-checked, aria-pressed(button 전용)가
   아니다. 선택은 «채움 반전» 하나로 말한다. 경계색 변화는 한국어 소자에서 잘 안 보인다. */
.feedback-chip[aria-checked="true"] {
  border-color: transparent;
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
  font-weight: var(--cg-text-label-active-weight);
}

/* 체크 아이콘은 색 외의 두 번째 단서다(색각 이상 대비). 항상 자리를 차지하게 두고
   보이기만 토글한다 — display 토글이면 선택할 때마다 칩 폭이 변해 라벨이 줄바꿈된다. */
.feedback-chip .wds-inline-icon {
  flex: none;
  opacity: 0;
  transition: opacity var(--cg-transition-basic-duration) var(--cg-ease-move);
}

.feedback-chip[aria-checked="true"] .wds-inline-icon {
  opacity: 1;
}

/* focus-visible은 hover와 다른 시각이어야 한다(예전엔 구분이 없었다).
   outline을 쓰는 이유: box-shadow와 달리 배경을 덮지 않아 선택 반전과 겹쳐도 보인다. */
.feedback-chip:focus-visible,
.feedback-action-button:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring);
  outline-offset: 2px;
}

/* doc 209 C-2: 평점은 동의 없이 수집되지만 교정문은 계정 동의가 있어야 저장된다.
   사용자가 텍스트를 쓰기 시작하는 자리 바로 위에 둔다. */
.feedback-picker-disclosure {
  margin: 0;
  font-size: var(--cg-text-meta-size);
  line-height: var(--cg-text-label-leading);
  color: var(--cg-text-soft);
}

.feedback-correction {
  padding: 8px 12px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-sm);
  background: var(--cg-surface);
  color: var(--cg-text);
  font-size: var(--cg-text-label-size);
  font-family: inherit;
  line-height: var(--cg-text-label-leading);
  resize: vertical;
  min-height: 60px;
  max-height: 120px;
}

.feedback-correction::placeholder {
  color: var(--cg-input-placeholder);
}

.feedback-correction:focus-visible {
  border-color: var(--cg-text);
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 1px;
}

.feedback-picker-actions {
  display: flex;
  gap: var(--cg-menu-gutter);
  justify-content: flex-end;
}

.feedback-action-button {
  padding: 8px 16px;
  border: 1px solid transparent;
  border-radius: var(--ds-radius-pill);
  background: transparent;
  color: var(--cg-text-soft);
  font-size: var(--cg-text-label-size);
  font-family: inherit;
  font-weight: var(--cg-text-label-active-weight);
  line-height: var(--cg-text-label-leading);
  cursor: pointer;
  appearance: none;
  transition: background-color var(--cg-transition-basic-duration) var(--cg-ease-move),
              color var(--cg-transition-basic-duration) var(--cg-ease-move);
}

/* ★주버튼을 :first-child로 잡지 않는다. DOM 순서가 바뀌면 조용히 뒤집혀서
   «건너뛰기»가 주액션처럼 보이게 된다. 의미(data 속성)에 건다. */
.feedback-action-button[data-feedback-submit="tagged"] {
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}

.feedback-action-button[data-feedback-submit="tagged"]:hover {
  background: var(--wds-color-wizon-accent-hover);
}

.feedback-action-button[data-feedback-submit="skip"]:hover {
  background: var(--cg-hover);
  color: var(--cg-text);
}

.composer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 88px;
  gap: 10px;
  padding: 16px min(7vw, 72px) 22px;
  background: linear-gradient(180deg, rgba(251, 252, 253, 0.1), #fbfcfd 34%);
}

.composer textarea {
  max-height: 180px;
  min-height: 48px;
  border-color: #cdd5de;
  box-shadow: var(--shadow);
}

.primary-button,
.secondary-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  padding: 0 14px;
  border-radius: 8px;
  font-weight: 600;
}

.primary-button {
  border: 1px solid var(--ds-button-primary-bg);
  background: var(--ds-button-primary-bg);
  color: var(--ds-button-primary-fg);
}

.primary-button:hover {
  background: var(--ds-button-primary-bg-hover);
  border-color: var(--ds-button-primary-bg-hover);
}

.secondary-button {
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.08);
  color: #ffffff;
}

.wide {
  width: 100%;
}

.context-pane {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  background: var(--surface);
  border-left: 1px solid var(--line);
}

.tabs {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 4px;
  padding: 12px;
  border-bottom: 1px solid var(--line);
}

.tab-button {
  min-height: 38px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}

.tab-button.active {
  border-color: var(--cg-text);
  background: var(--accent-soft);
}

.tab-content {
  display: grid;
  align-content: start;
  gap: 14px;
  min-height: 0;
  overflow: auto;
  padding: 16px;
}

.panel-block {
  display: grid;
  gap: 10px;
}

.panel-block h2 {
  margin: 0;
  font-size: var(--ds-text-body-16);
  line-height: 1.2;
}

.panel-block p {
  margin: 0;
  color: var(--muted);
  font-size: var(--ds-text-caption-13);
  line-height: 1.5;
}

.ops-status-panel {
  gap: 12px;
}

.ops-status-head,
.ops-status-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.ops-status-head h2 {
  margin-top: 3px;
}

.ops-status-summary {
  align-items: flex-start;
}

.ops-version-code {
  overflow: hidden;
  max-width: 190px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #f8fafc;
  color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--ds-text-caption-12);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ops-status-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.ops-status-card {
  display: grid;
  gap: 5px;
  min-width: 0;
  min-height: 94px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #ffffff;
}

.ops-status-card strong,
.ops-status-card span,
.ops-status-card small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ops-status-card strong {
  color: var(--ink);
  font-size: var(--ds-text-caption-12);
}

.ops-status-card span {
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}

.ops-status-card small {
  color: var(--muted);
  font-size: var(--ds-text-micro-11);
}

.ops-status-card.is-ok,
.status-pill.is-ok {
  border-color: rgba(22, 163, 74, 0.24);
  background: #ecfdf3;
  color: #166534;
}

.ops-status-card.is-warning,
.status-pill.is-warning {
  border-color: rgba(217, 119, 6, 0.24);
  background: #fffbeb;
  color: #92400e;
}

.ops-status-card.is-error,
.status-pill.is-error {
  border-color: rgba(220, 38, 38, 0.24);
  background: #fef2f2;
  color: #991b1b;
}

.ops-meta-list {
  margin-top: 0;
}

.account-management-panel {
  gap: var(--ds-space-4);
}

.account-head-actions,
.account-section-head,
.account-member-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-3);
  min-width: 0;
}

.account-head-actions {
  flex-wrap: wrap;
  justify-content: flex-end;
}

.account-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: var(--ds-space-3);
}

.account-section {
  display: grid;
  align-content: start;
  gap: var(--ds-space-3);
  min-width: 0;
  padding: var(--ds-space-4);
  border: 1px solid var(--ds-card-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-color-surface-solid);
}

.account-section-head {
  align-items: flex-start;
}

.account-section-head h3 {
  margin: 0;
  color: var(--ds-color-ink);
  font-size: var(--ds-text-body-14);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-tight);
}

.account-section-head p {
  margin-top: var(--ds-space-1);
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
}

.account-kv-list div {
  grid-template-columns: 76px minmax(0, 1fr);
  padding: var(--ds-space-2) 0;
}

.account-member-list,
.account-role-list,
.identity-control-list {
  display: grid;
  gap: var(--ds-space-2);
  min-width: 0;
}

.account-member-row,
.identity-control-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: var(--ds-space-3);
  min-width: 0;
  min-height: 42px;
  padding: var(--ds-space-2) 0;
  border-bottom: 1px solid var(--ds-color-line);
}

.account-member-row:last-child,
.identity-control-row:last-child {
  border-bottom: 0;
}

.account-member-row strong,
.account-member-row span,
.identity-control-row span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-member-row strong {
  display: block;
  color: var(--ds-color-ink);
  font-size: var(--ds-text-body-14);
}

.account-member-row span,
.identity-control-row span {
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
}

.account-permission-chip,
.account-status-badge {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  max-width: 100%;
  padding: 0 var(--ds-space-2);
  border: 1px solid var(--ds-chip-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-chip-bg);
  color: var(--ds-chip-fg);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
  white-space: nowrap;
}

.account-status-badge.is-active {
  border-color: var(--ds-badge-ok-bg);
  background: var(--ds-badge-ok-bg);
  color: var(--ds-badge-ok-fg);
}

.account-status-badge.is-pending {
  border-color: var(--ds-badge-warn-bg);
  background: var(--ds-badge-warn-bg);
  color: var(--ds-badge-warn-fg);
}

.account-status-badge.is-disabled {
  border-color: var(--ds-badge-bad-bg);
  background: var(--ds-badge-bad-bg);
  color: var(--ds-badge-bad-fg);
}

.account-admin-only {
  margin: 0;
  padding: var(--ds-space-3);
  border: 1px dashed var(--ds-card-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-color-surface-muted);
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-13);
  line-height: var(--ds-leading-body);
}

.account-action-feedback {
  margin: 0;
  padding: var(--ds-space-3);
  border: 1px solid var(--ds-badge-ok-bg);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-badge-ok-bg);
  color: var(--ds-badge-ok-fg);
  font-size: var(--ds-text-caption-13);
  line-height: var(--ds-leading-body);
}

.account-inline-form,
.account-member-actions {
  display: grid;
  gap: var(--ds-space-2);
  min-width: 0;
}

.account-inline-form {
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  align-items: end;
}

.account-inline-form label,
.account-action-row {
  display: grid;
  gap: var(--ds-space-1);
  min-width: 0;
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
}

.account-inline-form .btn {
  min-height: 32px;
}

.account-member-actions {
  grid-template-columns: minmax(0, 1fr) repeat(3, minmax(72px, auto));
  align-items: end;
}

.account-member-actions .btn {
  min-width: 0;
  min-height: 32px;
  white-space: normal;
}

.account-audit-toolbar,
.account-audit-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-2);
  min-width: 0;
}

.account-audit-toolbar {
  flex-wrap: wrap;
}

.account-audit-toolbar strong {
  display: block;
  color: var(--ds-color-ink);
  font-size: var(--ds-text-caption-13);
}

.account-audit-toolbar span {
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
}

.account-audit-list {
  display: grid;
  gap: var(--ds-space-2);
  min-width: 0;
}

.account-audit-row {
  display: grid;
  gap: var(--ds-space-2);
  min-width: 0;
  padding: var(--ds-space-2) 0;
  border-bottom: 1px solid var(--ds-color-line);
}

.account-audit-row:last-child {
  border-bottom: 0;
}

.account-audit-row strong,
.account-audit-row span,
.account-audit-row time {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-audit-row strong {
  display: block;
  color: var(--ds-color-ink);
  font-size: var(--ds-text-caption-13);
  font-weight: var(--ds-weight-semibold);
}

.account-audit-row span,
.account-audit-row time {
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
}

.account-audit-meta {
  justify-content: flex-start;
  flex-wrap: wrap;
}

.account-notification-list {
  display: grid;
  gap: var(--ds-space-2);
  min-width: 0;
}

.account-notification-row {
  display: grid;
  gap: var(--ds-space-2);
  min-width: 0;
  padding: var(--ds-space-2) 0;
  border-bottom: 1px solid var(--ds-color-line);
}

.account-notification-row > div {
  display: grid;
  gap: var(--ds-space-1);
  min-width: 0;
}

.account-notification-row:last-child {
  border-bottom: 0;
}

.account-notification-row strong,
.account-notification-row span,
.account-notification-row time {
  min-width: 0;
  overflow-wrap: anywhere;
  white-space: normal;
}

.account-notification-row strong {
  display: block;
  color: var(--ds-color-ink);
  font-size: var(--ds-text-caption-13);
  font-weight: var(--ds-weight-semibold);
}

.account-notification-row span,
.account-notification-row time {
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
}

.account-notification-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: var(--ds-space-2);
  min-width: 0;
}

.account-notification-action {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: flex-start;
  gap: var(--ds-space-2);
  min-width: 0;
}

.account-notification-action .btn {
  min-height: 30px;
}

.account-notification-note {
  display: grid;
  flex: 1 1 180px;
  gap: var(--ds-space-1);
  min-width: 0;
}

.account-notification-note span {
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
}

.account-notification-note .ds-input {
  min-width: 0;
}

.account-notification-history {
  display: grid;
  gap: var(--ds-space-2);
  min-width: 0;
  padding-top: var(--ds-space-3);
  border-top: 1px solid var(--ds-color-line);
}

.account-notification-history-filters {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(118px, 148px);
  align-items: end;
  gap: var(--ds-space-2);
  min-width: 0;
}

.account-notification-history-filters label {
  display: grid;
  gap: var(--ds-space-1);
  min-width: 0;
}

.account-notification-history-filters label span,
.account-notification-history-result {
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
}

.account-notification-history-filters .ds-input {
  min-width: 0;
  width: 100%;
}

.account-notification-history-filters .btn {
  justify-self: start;
  min-height: 32px;
}

.account-notification-history-result {
  align-self: center;
  grid-column: 1 / -1;
  justify-self: start;
  white-space: nowrap;
}

.account-notification-meta span,
.account-notification-meta time {
  display: inline-flex;
  align-items: center;
  min-height: 22px;
  padding: 0 var(--ds-space-2);
  border: 1px solid var(--ds-chip-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-chip-bg);
}

.upload-form,
.run-form {
  display: grid;
  gap: 8px;
}

.upload-form {
  grid-template-columns: minmax(0, 1fr) 104px 92px;
}

.upload-form .secondary-button {
  border-color: var(--line);
  background: #ffffff;
  color: var(--ink);
}

.app-menu-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.app-menu-chip {
  min-height: 34px;
  padding: 0 10px;
  border: 1px solid var(--cg-menu-border);
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.app-menu-chip:hover {
  border-color: var(--accent);
  background: #d9f0f5;
}

.app-detail-panel {
  border: 1px solid var(--cg-menu-border);
  display: grid;
  gap: 12px;
  padding: 14px;
  border-radius: 8px;
  background: #f8fbfc;
}

.app-detail-panel h3 {
  margin: 2px 0 0;
  font-size: 17px;
  line-height: 1.25;
}

.app-detail-panel p {
  margin: 8px 0 0;
  color: var(--muted);
  font-size: var(--ds-text-caption-13);
  line-height: 1.5;
}

.app-workflow-panel {
  border: 1px solid var(--cg-menu-border);
  display: grid;
  gap: 12px;
  padding: 14px;
  border-radius: 8px;
  background: #f8fbfc;
}

.app-workflow-head h3 {
  margin: 2px 0 0;
  font-size: var(--ds-text-body-16);
  line-height: 1.3;
}

.app-workflow-steps {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.app-workflow-steps li {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  gap: 8px;
  align-items: start;
}

.app-workflow-steps li > span {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill, 999px);
  background: #ffffff;
  color: var(--cg-text-soft);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}

.app-workflow-steps strong {
  display: block;
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  line-height: 1.35;
}

.app-workflow-steps small {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.45;
}

.app-workflow-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.app-workflow-actions span {
  min-height: 28px;
  padding: 6px 9px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill, 999px);
  background: rgba(255, 255, 255, 0.78);
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.app-input-fields {
  border: 1px solid var(--cg-menu-border);
  display: grid;
  gap: 10px;
  padding: 12px;
  border-radius: 8px;
  background: #f8fbfc;
}

.app-input-head {
  display: grid;
  gap: 3px;
}

.app-input-head span {
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.45;
}

.app-input-field {
  display: grid;
  gap: 5px;
  min-width: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}

.app-input-field small {
  color: var(--muted);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  line-height: 1.35;
}

.app-input-field--checkbox {
  grid-template-columns: 18px minmax(0, 1fr);
  align-items: center;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #ffffff;
}

.app-input-field--checkbox input {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
}

.app-detail-meta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin: 0;
}

.app-detail-meta div {
  min-width: 0;
  padding: 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #ffffff;
}

.app-detail-meta dt {
  color: var(--muted);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}

.app-detail-meta dd {
  min-width: 0;
  margin: 3px 0 0;
  overflow-wrap: anywhere;
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}

.app-result-card {
  border: 1px solid var(--cg-menu-border);
  display: grid;
  gap: 10px;
  padding: 12px;
  border-radius: 8px;
  background: #f8fbfc;
}

.app-result-card strong {
  font-size: var(--ds-text-body-14);
  line-height: 1.35;
}

.operator-progress-panel {
  display: grid;
  gap: 10px;
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px solid var(--cg-menu-border);
}

.operator-progress-current-step {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
}

.operator-progress-current-step div {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.operator-progress-current-step small,
.operator-progress-milestone small {
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.operator-progress-current-step strong,
.operator-progress-step strong,
.operator-progress-milestone strong {
  min-width: 0;
  overflow-wrap: anywhere;
}

.operator-progress-counts {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 6px;
}

.operator-progress-counts div {
  display: grid;
  gap: 1px;
  min-width: 0;
  padding: 7px 8px;
  border: 1px solid rgba(15, 23, 42, 0.08);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.64);
}

.operator-progress-counts b {
  color: var(--ink);
  font-size: var(--ds-text-body-14);
  line-height: 1.1;
}

.operator-progress-counts em {
  color: var(--muted);
  font-size: var(--ds-text-micro-11);
  font-style: normal;
  font-weight: 600;
}

.operator-progress-steps,
.operator-progress-milestones {
  display: grid;
  gap: 6px;
}

.operator-progress-step,
.operator-progress-milestone {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 7px;
  align-items: center;
  min-width: 0;
  padding: 7px 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.5);
  color: var(--ink);
  font-size: var(--ds-text-caption-12);
}

.operator-progress-step.is-current {
  border-color: var(--cg-text);
  background: rgba(236, 253, 245, 0.72);
}

.operator-progress-milestone {
  grid-template-columns: auto minmax(0, 1fr);
}

.operator-progress-milestone small {
  grid-column: 2;
}

/* E-B1: 시맨틱 토큰 매핑(다크 적응 + 라이트 근사) — docs/product/218, .chat-failure 선례 */
.operator-progress-status {
  display: inline-grid;
  place-items: center;
  min-width: 48px;
  min-height: 24px;
  padding: 4px 7px;
  border-radius: var(--ds-radius-pill, 999px);
  background: color-mix(in srgb, var(--ds-color-ink) 6%, var(--ds-color-surface));
  color: var(--ds-color-muted);
  font-size: var(--ds-text-micro-11);
  line-height: 1;
  white-space: nowrap;
}

.operator-progress-status--completed,
.operator-progress-status--done,
.operator-progress-status--available {
  background: var(--ds-feedback-ok-bg);
  color: var(--ds-feedback-ok-fg);
}

.operator-progress-status--waiting,
.operator-progress-status--waiting-input,
.operator-progress-status--running {
  background: var(--ds-feedback-warn-bg);
  color: var(--ds-feedback-warn-fg);
}

.operator-progress-status--failed {
  background: var(--ds-feedback-bad-bg);
  color: var(--ds-feedback-bad-fg);
}

.required-input-form {
  display: grid;
  gap: 10px;
  margin-top: 10px;
  padding: 12px;
  border: 1px solid var(--cg-menu-border);
  border-radius: 8px;
  background: #ffffff;
}

.required-input-head {
  display: grid;
  gap: 3px;
}

.required-input-head strong {
  font-size: var(--ds-text-body-14);
}

.required-input-head small,
.required-input-field small {
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.4;
}

.required-input-field {
  display: grid;
  gap: 5px;
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}

.required-input-actions {
  display: flex;
  justify-content: flex-end;
}

.workflow-action-panel {
  display: grid;
  gap: 10px;
  margin-top: 10px;
  padding: 12px;
  border: 1px solid var(--cg-menu-border);
  border-radius: 8px;
  background: rgba(248, 251, 252, 0.88);
}

.workflow-action-head {
  display: grid;
  gap: 2px;
}

.workflow-action-head small,
.workflow-action-head span,
.approved-manual-map-form small,
.submission-readiness-row small,
.required-document-row small,
.company-context-row small {
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.4;
}

.submission-readiness-panel,
.required-documents-panel,
.company-context-panel {
  display: grid;
  gap: 10px;
  margin-top: 10px;
  padding: 12px;
  border: 1px solid var(--cg-menu-border);
  border-radius: 8px;
  background: #ffffff;
}

.submission-readiness-list,
.required-documents-list,
.company-context-list {
  display: grid;
  gap: 8px;
}

.submission-readiness-group {
  display: grid;
  gap: 6px;
}

.submission-readiness-group > b {
  color: #334155;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.submission-readiness-row {
  display: grid;
  gap: 3px;
  min-width: 0;
  padding: 8px 9px;
  border: 1px solid rgba(148, 163, 184, 0.26);
  border-radius: 8px;
  background: #f8fafc;
}

.submission-readiness-row span {
  overflow-wrap: anywhere;
  font-size: var(--ds-text-caption-13);
  line-height: 1.35;
}

.submission-readiness-row.is-blocking {
  border-color: rgba(185, 28, 28, 0.28);
  background: #fff7f7;
}

.submission-readiness-row.is-renewal {
  border-color: rgba(180, 83, 9, 0.28);
  background: #fffbeb;
}

.submission-readiness-row.is-public-mydata {
  border-color: rgba(3, 105, 161, 0.24);
  background: #f0f9ff;
}

.submission-readiness-row.is-deferred {
  border-color: rgba(71, 85, 105, 0.22);
  background: #f8fafc;
}

.required-document-row,
.company-context-row {
  display: grid;
  gap: 4px;
  min-width: 0;
  padding: 9px;
  border: 1px solid rgba(148, 163, 184, 0.28);
  border-radius: 8px;
  background: #f8fafc;
}

.required-document-row b,
.company-context-row b {
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.required-document-row strong,
.company-context-row span {
  overflow-wrap: anywhere;
  font-size: var(--ds-text-caption-13);
  line-height: 1.35;
}

.required-document-row.is-blocking {
  border-color: rgba(185, 28, 28, 0.28);
  background: #fff7f7;
}

.required-document-row.is-blocking b {
  color: #b91c1c;
}

.company-context-row.is-muted {
  opacity: 0.68;
}

.workflow-action-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: stretch;
}

.workflow-action-button {
  min-height: 34px;
  padding: 7px 10px;
  border: 1px solid var(--cg-menu-border);
  border-radius: 8px;
  background: #ffffff;
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  line-height: 1.2;
}

.workflow-action-button:disabled {
  cursor: not-allowed;
  opacity: 0.52;
}

.approved-manual-map-form {
  display: grid;
  grid-template-columns: minmax(180px, 1.1fr) minmax(160px, 1fr) minmax(160px, 1fr) auto;
  gap: 8px;
  align-items: end;
  width: 100%;
  padding: 10px;
  border: 1px solid rgba(15, 23, 42, 0.08);
  border-radius: 8px;
  background: #ffffff;
}

.approved-manual-map-form > div {
  display: grid;
  gap: 3px;
  min-width: 0;
}

.approved-manual-map-form strong {
  overflow-wrap: anywhere;
  font-size: var(--ds-text-caption-13);
}

/* ============================================================================
 * 답변 신뢰 패널 = 파일 패널의 '플로팅 둥근 카드' 도킹(20260710-dock-card-redesign)을 그대로 재사용.
 * 헤더(.chatgpt-route-page-head)·탭(.chatgpt-route-tabs)·타이포·route 토큰은 route-page가 소유.
 * 여기서는 카드 위치/모양만: 둥근 카드는 항상 그 자리(inset 12·radius 16)에 뜨고, 대화 쪽이
 * 나란히 놓을 여유가 있으면 그만큼 비켜 선다(R-47, 아래 .chat-pane[data-trust-dock] 참고).
 * 여유가 없으면 지금처럼 OVERLAY(push 없음 → 대화 위치·폭 불변).
 * 값은 파일 패널 카드(#portalPage docked)와 100% 동일(inset 12·radius 16·풀보더·그림자·width 34%/480).
 * #trustPanel 은 .chat-pane 의 '마지막 자식'이라 thread-hide sibling 규칙에 안 걸려 대화가 유지된다.
 * ========================================================================== */
.chatgpt-reference-v1 {
  /* 패널 자신의 width 와 .chat-pane 의 도킹 padding 이 같은 값을 공유한다(한 곳에서만 바뀐다) —
   * R-47(중앙 09-28 결정 c). */
  --trust-dock-width: clamp(360px, 34%, 480px);
}
/* R-52-a — 링크 미리보기는 종류별 기본 폭(하한 400 · 42% · 상한 720)을 갖는다. 근거 폭을 물려받지 않도록
 * app.js syncSidePanelKind 가 body 에 data-side-panel-kind 를 얹고, 사용자가 고른 폭은 body 인라인
 * --trust-dock-width 가 이 값을 이긴다. 패널 안 속성이 아니라 body 인 이유: 대화 예약(.chat-pane 도킹 padding)이
 * 같은 변수를 읽으므로 패널 바깥에서 정해져야 한다. */
.chatgpt-reference-v1[data-side-panel-kind="link"] {
  --trust-dock-width: clamp(400px, 42%, 720px);
}
@media (min-width: 901px) {
  /* 지오메트리는 body.side-dock-open이 아니라 패널 자신에 건다: closeTrustPanel이 body
   * 클래스를 퇴장 애니메이션보다 먼저 제거해서, 닫히는 ~190ms 동안 패널이 절대배치를
   * 잃고 일반 route 블록으로 풀리며 대화 위로 풀폭 점프(화면 깜빡임)했다. 가시성은
   * [hidden] 규칙이 소유하므로 카드 모양은 닫힘이 끝날 때까지 유지된다. */
  .chatgpt-reference-v1 #trustPanel.chatgpt-route-page {
    position: absolute;
    inset: 12px 12px 12px auto;
    width: var(--trust-dock-width);
    margin: 0;
    max-width: none;
    padding: 20px 20px calc(20px + var(--wizon-safe-bottom, 0px));
    overflow-y: auto;
    overflow-x: hidden;
    background: var(--cg-menu-bg, var(--bg));
    border: 1px solid var(--line);
    border-radius: 16px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.16);
    z-index: 40;
  }
  /* R-46(오너 09-28) — #trustPanel 자신이 스크롤 컨테이너(위 overflow-y:auto)라 헤더가
   * .chatgpt-route-page-inner 그리드의 첫 행일 뿐이면 스크롤에 같이 밀려 올라가 제목·닫기가
   * 사라졌다(오너 캡처 owner-2-scrolled-process.webp — 헤더가 화면 밖). sticky로 패널의
   * 스크롤 축 상단에 고정하고, 배경(패널과 동일 색)+아래쪽 경계선으로 본문과 분리한다.
   * margin/padding -16px↔16px 는 그 아래 그리드 gap(16px, 아래 .trust-route-page 규칙)을
   * 헤더 배경으로 마저 덮어 스크롤된 카드가 gap 틈으로 비치지 않게 한다. */
  #trustPanel .trust-route-head {
    position: sticky;
    top: 0;
    z-index: 1;
    margin-bottom: -16px;
    padding-bottom: 16px;
    background: var(--cg-menu-bg, var(--bg));
    box-shadow: inset 0 -1px 0 var(--cg-menu-border, var(--line));
  }
  /* R-47(오너 결정 c, 중앙 09-28) — «측정 기반 배치 상태»(design-system/foundations/
   * breakpoints.md §8). 뷰포트 폭이 아니라 .chat-pane 실측 폭으로 나란히/덮기를 가른다 —
   * 컨테이너 쿼리는 §7 에서 이미 폐기됐다(이 패널 자신이 position:fixed 자손이라 containing
   * block 을 깨뜨림). app.js updateTrustDockState() 가 ResizeObserver 로 매 리사이즈마다
   * 계산해 이 속성 하나만 세팅한다 — 새 @media 리터럴 0개(design-system 래칫 통과).
   * 속성이 없으면(덮기) 위 overlay 그대로 — padding 규칙이 없다. */
  .chat-pane[data-trust-dock="side"] {
    padding-right: calc(var(--trust-dock-width) + 12px);
  }
  .chat-pane[data-trust-dock="side"] .chatgpt-conversation-actions {
    right: calc(var(--trust-dock-width) + 22px);
  }
  /* R-51(오너 결정 1ⓐ) — 오른쪽 작업 패널 공통 «분할 경계». #trustPanel 자신이 스크롤 컨테이너라
   * 표 편집 손잡이(absolute + top/bottom 0)를 그대로 쓰면 스크롤에 같이 밀려 올라간다 — 그래서
   * sticky + float(높이 = 패널 화면 높이)로 «스크롤해도 제자리» 를 만든다. 히트 영역은 패널 왼쪽 변
   * 안쪽 16px(패널이 overflow-x:hidden 이라 바깥쪽 절반은 잘린다), 굵은 포인터는 24px. 그립은 표 편집
   * 손잡이의 ::before 규칙과 선택자 묶음으로 «같은 선언» 을 쓴다(위 .artifact-panel-resize-handle). */
  .chatgpt-reference-v1 #trustPanel .side-panel-splitter {
    position: sticky;
    top: calc(var(--trust-panel-pad, 20px) * -1);
    float: left;
    box-sizing: border-box;
    width: 16px;
    height: calc(100% + var(--trust-panel-pad, 20px) + var(--trust-panel-pad, 20px) + var(--wizon-safe-bottom, 0px));
    margin: calc(var(--trust-panel-pad, 20px) * -1) 0 0 calc(var(--trust-panel-pad, 20px) * -1);
    cursor: col-resize;
    touch-action: none;
    z-index: 3;
  }
  .chatgpt-reference-v1 #trustPanel .side-panel-splitter[hidden] {
    display: none;
  }
  /* 끄는 동안 패널만 움직이고 대화는 그대로 — 패널 안 텍스트가 끌려 선택되지 않게 하고, 경계의
   * 툴팁(끌기 안내 — 열린 동안 <body> 로 옮겨져 경계의 자손이 아니다)은 끄는 순간 소임을 다했으므로 접는다. */
  body.side-panel-resizing #trustPanel {
    -webkit-user-select: none;
    user-select: none;
  }
  body.side-panel-resizing .chatgpt-tooltip[data-tooltip-open] {
    display: none;
  }
  /* 패널 머리 ⋮ (R-51 2ⓐ) — 닫기 버튼(32) 앞. 머리는 flex space-between 이라 자동 여백으로 우측에 붙인다. */
  #trustPanel .trust-panel-more {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    box-sizing: border-box;
    width: 32px;
    height: 32px;
    margin-left: auto;
    margin-right: 6px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--cg-text);
    cursor: pointer;
  }
  #trustPanel .trust-panel-more[hidden] {
    display: none;
  }
  #trustPanel .trust-panel-more:hover,
  #trustPanel .trust-panel-more[aria-expanded="true"] {
    background: var(--cg-hover-soft);
  }
  #trustPanel .trust-panel-more:focus-visible {
    outline: 2px solid var(--cg-focus-ring);
    outline-offset: 2px;
  }
  #trustPanel .trust-panel-more-icon {
    display: inline-flex;
  }
  #trustPanel .trust-panel-menu {
    top: 40px;
  }
}
@media (pointer: coarse) {
  .chatgpt-reference-v1 #trustPanel .side-panel-splitter {
    width: 24px;
  }
}
/* 모바일: 파일 패널의 풀스크린 route 와 동일하게 전체를 덮는다 */
@media (max-width: 900px) {
  /* 풀스크린 route: 스레드를 불투명하게 덮고(배경 없으면 뒤 대화가 비쳐 겹침 — 이전 버그),
     헤더+탭은 고정하고 본문만 스크롤한다(고정 헤더/스크롤 바디 패턴). z-index는 모바일
     top-bar(<65) 위로 올려 패널 자체 헤더만 보이게 한다(back 아이콘 간헐 노출 해소). */
  #trustPanel.chatgpt-route-page {
    position: fixed;
    inset: 0;
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    z-index: 100;
    background: var(--cg-bg, var(--wizon-ai-canvas, #fcfcfc));
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  #trustPanel .chatgpt-route-page-inner {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    gap: 0;
    overflow: hidden;
  }
  /* 컴팩트 앱바 헤더: [< back] 타이틀 인라인(좁은 화면 세로 공간 활용) — 고정. */
  #trustPanel .trust-route-head {
    flex: 0 0 auto;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    padding: calc(10px + var(--wizon-safe-top, 0px)) 15px 10px;
    background: var(--cg-bg, var(--wizon-ai-canvas, #fcfcfc));
  }
  #trustPanel .trust-route-head h1 {
    display: block;
    font-size: var(--ds-text-title-18);
    font-weight: 500;
    line-height: 1.25;
  }
  /* 탭(출처/처리 과정)도 헤더 아래 고정. */
  #trustPanel .chatgpt-route-tabs {
    flex: 0 0 auto;
    padding: 0 15px 10px;
    background: var(--cg-bg, var(--wizon-ai-canvas, #fcfcfc));
    box-shadow: inset 0 -1px 0 var(--cg-menu-border, #e3e3e3);
  }
  /* 본문(소스 카드)만 스크롤. */
  #trustPanel .trust-route-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 12px 15px calc(15px + var(--wizon-safe-bottom, 0px));
    scrollbar-width: none;
  }
  #trustPanel .trust-route-body::-webkit-scrollbar {
    display: none;
  }
}
#trustPanel.chatgpt-route-page[hidden] {
  display: none;
}
/* R-43 파도 1 재작업(중앙 09-26, 결함 3) — .chatgpt-reference-v1 .chatgpt-route-tabs 는
 * (스타일시트 뒤쪽) display:flex 를 건다. author 규칙은 UA [hidden]{display:none} 을
 * origin 만으로 이긴다(특이도 무관) — renderTrustPanelInto 가 이 모드에서 항상
 * trustPanelTabs.hidden = true 로 두는데도 빈 탭 레일이 display:flex 로 계속 자리를 차지해
 * .chatgpt-route-page-inner(grid, gap 16px)의 행 사이 간격이 두 번(제목↔탭·탭↔본문)
 * 붙어 1440 에서 제목↔부제 사이 ≈80px 빈 공간이 됐다(390 은 모바일 오버라이드가 gap:0
 * 이라 안 드러났다). ID+class+attribute 특이도로 되돌린다 — 새 클래스 0, 새 리터럴 px 0. */
#trustPanel .chatgpt-route-tabs[hidden] {
  display: none;
}
/* R-43 파도 1 재작업(중앙 09-26, 결함 2) — trustPanelTitle 은 상호작용 요소가 아니라
 * tabindex="-1" 로 프로그램 초점만 받는다(openTrustPanel). #shareDialog:focus 와 같은
 * 기존 관례(이 파일 위쪽) 그대로 outline 을 지운다 — 초점 이동(activeElement=제목) 자체는
 * 그대로 유지, 사각 테두리만 안 그린다. */
#trustPanelTitle:focus {
  outline: none;
}
/* 패널 내부 스크롤바 숨김(스크롤 기능은 유지): 스크롤바 트랙이 둥근 카드(radius 16)의
 * 모서리를 각지게 깨뜨림 — .trust-chips와 동일한 숨김 관례. */
#trustPanel.chatgpt-route-page {
  scrollbar-width: none;
}
#trustPanel.chatgpt-route-page::-webkit-scrollbar {
  display: none;
}
/* route-page-inner 는 gap-lg(40px)라 트러스트엔 과하다 -> 파일 패널 도킹 레일 톤으로 좁힌다 */
.trust-route-page .chatgpt-route-page-inner {
  gap: 16px;
}
/* route-page-head 는 이미 flex space-between(제목 좌 · 액션 우) -> 그대로 쓰고 닫기만 추가 */
.trust-panel-close {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: var(--cg-hover-soft, #f2f3f5);
  color: var(--cg-text, #1f2937);
  cursor: pointer;
}
.trust-panel-close:hover {
  background: var(--cg-hover, #ececec);
}
.trust-panel-close:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 2px;
}
.trust-panel-close svg {
  width: 18px;
  height: 18px;
}
/* 닫기 아이콘: 웹 도킹 = X / 모바일 풀 = 뒤로(파일 패널과 동일 위치=헤더 좌측) */
.trust-panel-close-x {
  display: inline-flex;
}
.trust-panel-close-back {
  display: none;
}
@media (max-width: 900px) {
  .trust-panel-close-x {
    display: none;
  }
  .trust-panel-close-back {
    display: inline-flex;
  }
  .trust-route-head .trust-panel-close {
    order: -1;
  }
}

/* ============================================================================
 * R-52-a(오너 결정 3ⓐ·4ⓐ) — 링크 미리보기. 같은 #trustPanel 의 새 종류(data-panel-kind="link").
 * 데스크톱 = 근거 패널과 같은 도킹 카드(폭만 종류별), 모바일 ≤900 = 하단 시트(하단 «원문 열기»·«확인»).
 * ========================================================================== */
#trustPanel .trust-panel-back {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  box-sizing: border-box;
  width: 32px;
  height: 32px;
  margin-right: 6px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}
#trustPanel .trust-panel-back[hidden] {
  display: none;
}
#trustPanel .trust-panel-back:hover {
  background: var(--cg-hover-soft);
}
#trustPanel .trust-panel-back:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 2px;
}
#trustPanel .trust-panel-back svg {
  width: 18px;
  height: 18px;
}
#trustPanel .trust-panel-menu .chatgpt-menu-separator {
  height: 1px;
  margin: var(--ds-space-1) var(--ds-space-3);
  background: var(--cg-menu-border, var(--line));
}
.trust-link-preview-hint {
  display: block;
  margin-top: var(--ds-space-2);
  color: var(--accent);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
}
/* 웹 출처 카드의 클릭은 이제 «이 패널에서 미리 보기» 라 카드 뒤에 붙던 외부 이동 아이콘(↗)은 거짓말이 된다 —
 * 미리보기 안 «원문 열기 ↗» 가 그 자리를 이어받는다. Cmd/Ctrl-클릭은 여전히 새 탭이지만 표시는 «미리 보기 ›» 하나만. */
a.trust-card[data-trust-link-preview][data-external="true"]::after {
  content: none;
}
.link-preview {
  display: grid;
  gap: var(--ds-space-3);
  min-width: 0;
}
.link-preview-hero {
  margin: 0;
  overflow: hidden;
  border-radius: var(--ds-radius-md, 12px);
  background: var(--cg-hover-soft);
}
.link-preview-hero img {
  display: block;
  width: 100%;
  max-height: 240px;
  object-fit: cover;
}
.link-preview-host {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ds-space-1) var(--ds-space-2);
  margin: 0;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-caption-13);
}
.link-preview-host-name {
  color: var(--cg-text);
  font-weight: var(--ds-weight-medium);
  overflow-wrap: anywhere;
}
.link-preview-title {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-title-18);
  font-weight: var(--ds-weight-medium);
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.link-preview-desc {
  margin: 0;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-body-14);
  line-height: 1.55;
  overflow-wrap: anywhere;
}
/* ★R-53 ②(오너 상시 규칙 10-02 «왼쪽 테두리 강조선은 최대한 쓰지 않는다») — 구획은 강조선이 아니라 «면 톤 + 라벨» 이
   만든다. 면 = L2 가라앉음(--cg-hover-soft, 671 §9.10-5), 라벨 = 무채 캡션(무엇인지 말로). 강조색 라벨도 함께 뺐다 —
   강조선 + 강조색 라벨 한 쌍이 «AI 가 만든 인용 상자» 인상의 원천이었다. */
.link-preview-quote {
  display: grid;
  gap: var(--ds-space-1);
  margin: 0;
  padding: var(--ds-space-3);
  border-radius: var(--ds-radius-sm, 10px);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  line-height: 1.55;
  overflow-wrap: anywhere;
}
.link-preview-quote-cap {
  color: var(--cg-text-soft, var(--cg-muted));
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
}
.link-preview-fail {
  display: grid;
  gap: var(--ds-space-1);
  padding: var(--ds-space-3);
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-sm, 10px);
  background: var(--surface);
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-body-14);
  line-height: 1.5;
}
.link-preview-fail strong {
  color: var(--cg-text);
  font-weight: var(--ds-weight-medium);
}
.link-preview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-2);
}
.link-preview-origin,
.link-preview-secondary {
  box-sizing: border-box;
  min-height: 40px;
  padding: var(--ds-space-2) var(--ds-space-4);
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-pill, 999px);
  font: inherit;
  font-size: var(--ds-text-body-14);
  font-weight: var(--ds-weight-medium);
  cursor: pointer;
}
.link-preview-origin {
  border-color: var(--cg-text);
  background: var(--cg-text);
  color: var(--cg-bg, var(--bg));
}
.link-preview-secondary {
  background: var(--surface);
  color: var(--cg-text);
}
.link-preview-origin:focus-visible,
.link-preview-secondary:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 2px;
}
.link-preview-dismiss {
  display: none;
}
.link-preview-meta,
.link-preview-loading-copy {
  margin: 0;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-caption-12);
  line-height: 1.5;
}
.link-preview-skeleton {
  display: grid;
  gap: var(--ds-space-2);
}
.link-preview-skeleton span {
  display: block;
  height: 14px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-hover-soft);
}
.link-preview-skeleton span:nth-child(1) {
  width: 40%;
}
.link-preview-skeleton span:nth-child(2) {
  width: 90%;
}
.link-preview-skeleton span:nth-child(3) {
  width: 75%;
}
/* R-43 파도 2(오너 R-43 4ⓐ · 671 §4.13 안내형) — 답변 근거(answer-trust)와 링크 미리보기(link)는 같은
 * #trustPanel 의 «안내형(notice)» 시트다: 종류 속성은 모드로 달리고(app.js syncTrustPanelKindMarkup), 모양은 ≤900
 * 에서 아래 한 벌이 정한다(옛 R-52-a 는 링크 전용 [data-panel-kind="link"] 로 85dvh · --ds-radius-xl ·
 * 페인트 전용 100vmax 그림자 = M-4/M-5/M-11 위반). 링크 «본문 배치» 규칙([data-panel-kind="link"] .link-preview*)만
 * 링크 전용으로 남는다. citation-* 3모드는 종류가 없어 전체화면 유지(긴 폼·다단계). */
.trust-sheet-actions {
  display: none;
}
@media (min-width: 901px) {
  /* 터치 태블릿(pointer: coarse)에서도 도킹 패널에는 손잡이가 보이지 않는다(끌기는 canStart 가 막는다). */
  #trustPanel > .wizon-sheet-handle {
    display: none;
  }
}
@media (max-width: 900px) {
  /* M-5 두 단계 — 짧은 높이 min(70vh, vvh − 96), 위로 끌어 놓으면(data-sheet-detent="long") vvh − 안전영역 − 40.
   * M-4 위 두 모서리만 --wizon-ai-radius-sheet. 배경 어둡게 + 뒤 화면 막기는 아래 body 규칙(스크림) + app.js inert. */
  #trustPanel.chatgpt-route-page[data-sheet-kind="notice"] {
    inset: auto 0 0 0;
    width: 100%;
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px));
    border: 1px solid var(--cg-menu-border, var(--line));
    border-bottom: 0;
    border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0;
    box-shadow: none;
  }
  #trustPanel.chatgpt-route-page[data-sheet-kind="notice"][data-sheet-detent="long"] {
    max-height: calc(var(--wizon-vvh, 100dvh) - env(safe-area-inset-top, 0px) - 40px);
  }
  /* 손잡이(44px 히트, .wizon-sheet-handle)가 위 여백을 대신하므로 헤더는 안전영역을 더하지 않는다. × 와 뒤로 버튼은
   * 없다 — 하단 «확인»(M-2)·끌어내림·스크림·뒤로가 닫는다. */
  #trustPanel[data-sheet-kind="notice"] .trust-route-head {
    padding: 0 16px 8px;
  }
  #trustPanel[data-sheet-kind="notice"] .trust-route-head .trust-panel-close,
  #trustPanel[data-sheet-kind="notice"] .trust-panel-back {
    display: none;
  }
  #trustPanel[data-sheet-kind="notice"] .trust-route-body {
    overscroll-behavior: contain;
  }
  /* 근거 모드의 하단 고정 «확인» — 본문(스크롤) 밖의 줄이라 마지막 출처 카드가 이 줄 밑으로 들어가지 않는다(M-7 ①).
   * 링크 모드는 자기 줄(.link-preview-actions)을 본문 안 sticky 로 갖는다(오너 R-52 결정 모양 유지). */
  #trustPanel[data-sheet-kind="notice"]:not([data-panel-kind="link"]) .trust-route-body {
    padding-bottom: 12px;
  }
  #trustPanel[data-sheet-kind="notice"]:not([data-panel-kind="link"]) .trust-sheet-actions {
    display: flex;
    flex: 0 0 auto;
    padding: 12px 16px calc(12px + var(--wizon-safe-bottom, env(safe-area-inset-bottom, 0px)));
    background: var(--cg-bg, var(--bg));
    box-shadow: inset 0 1px 0 var(--cg-menu-border, var(--line));
  }
  #trustPanel[data-sheet-kind="notice"] .trust-sheet-actions .link-preview-origin {
    flex: 1 1 auto;
    min-height: 48px;
  }
  /* 링크 모드 본문 배치(오너 R-52) — 모양 규칙은 위 한 벌로 흡수했고 배치만 남긴다. */
  #trustPanel[data-panel-kind="link"] .trust-route-body {
    display: flex;
    flex-direction: column;
  }
  #trustPanel[data-panel-kind="link"] .link-preview {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-3);
  }
  #trustPanel[data-panel-kind="link"] .link-preview-meta {
    order: 98;
  }
  #trustPanel[data-panel-kind="link"] .link-preview-actions {
    order: 99;
    position: sticky;
    bottom: calc(-1 * (15px + var(--wizon-safe-bottom, 0px)));
    margin-top: auto;
    padding: 12px 0 calc(12px + var(--wizon-safe-bottom, env(safe-area-inset-bottom, 0px)));
    background: var(--cg-bg, var(--bg));
    box-shadow: inset 0 1px 0 var(--cg-menu-border, var(--line));
    flex-wrap: nowrap;
  }
  #trustPanel[data-panel-kind="link"] .link-preview-origin,
  #trustPanel[data-panel-kind="link"] .link-preview-secondary {
    flex: 1 1 0;
    min-height: 48px;
  }
  #trustPanel[data-panel-kind="link"] .link-preview-dismiss {
    display: block;
  }
  #trustPanel[data-panel-kind="link"] .link-preview-origin-host {
    display: none;
  }

  /* M-13 열림 wizon-sheet-up 372 · 닫힘 wizon-sheet-down 300. 특이도를 일부러 낮게(:where) 둬 파일 끝의 공용 종류
   * 규칙(끌기 중 따라옴 · 복귀 · 복귀 뒤 재생 금지 — build A D1/D6)이 이긴다: 여기서 #trustPanel 의 id 특이도로
   * animation/translate 를 걸면 D1(끌기 중 안 따라옴)이 이 패널에서 그대로 재발한다. */
  body.chatgpt-reference-v1 :where(#trustPanel)[data-sheet-kind="notice"] {
    translate: 0 var(--wizon-sheet-drag-y, 0);
    will-change: translate;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }
  body.chatgpt-reference-v1 :where(#trustPanel)[data-sheet-kind="notice"][data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
  }

  /* M-11 스크림 — 시트가 떠 있는 동안 화면 전체를 --wizon-scrim 1토큰으로 어둡게 칠하고(패널 z-index 100 바로 밑 —
   * body 가 아니라 .chat-pane 의 ::after 인 이유: .app-shell(position:fixed)이 쌓임 맥락이라 body::after 는 패널까지 덮는다),
   * 이 면이 «탭을 받는다»(뒤 화면은 app.js 가 inert 로 막고, 이 면을 누르면 handleTrustSheetScrimClick 이 닫는다).
   * 페인트 전용 100vmax 그림자는 뒤 화면 스크롤·길게 누르기가 그대로 통과했다. */
  .chat-pane:has(> #trustPanel[data-sheet-kind="notice"]:not([hidden]))::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 99;
    background: var(--wizon-scrim);
    animation: wizon-scrim-in var(--wds-spring-effect-default-duration, 188ms) linear both;
  }
  .chat-pane:has(> #trustPanel[data-sheet-kind="notice"][data-motion-state="closing"])::after {
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms) linear forwards;
  }
  /* M-14 ① 끌기 중 스크림 불투명도 = 1 − 끈 거리/시트 높이 — 컨트롤러(scrimTarget)가 .chat-pane 에 is-dragging 과
   * 진행도 변수를 얹는다. 놓아 닫히면 위 wizon-scrim-out 이 그 옅어진 값에서 이어 사라진다(키프레임 from 이 같은 변수). */
  .chat-pane.is-dragging:has(> #trustPanel[data-sheet-kind="notice"]:not([hidden]))::after {
    animation: none;
    opacity: calc(1 - var(--wizon-sheet-drag-progress, 0));
  }
}
/* M-15 — 모션 줄이기: 이동 0, 열림·닫힘은 124ms 불투명도(새 키프레임 0 — wizon-scrim-in/-out 재사용). 끌기 중·복귀·
 * 복귀 뒤에는 건드리지 않는다(손가락을 따라오는 것은 직접 조작이라 줄이지 않는다). */
@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 :where(#trustPanel)[data-sheet-kind="notice"]:not([data-motion-state="closing"], .is-dragging, .is-sheet-snapping, [data-sheet-settled]) {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear backwards;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
    translate: 0 0 !important;
  }
  body.chatgpt-reference-v1 :where(#trustPanel)[data-sheet-kind="notice"][data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
    translate: 0 0 !important;
  }
  .chat-pane:has(> #trustPanel[data-sheet-kind="notice"]:not([hidden]))::after {
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }
  .chat-pane:has(> #trustPanel[data-sheet-kind="notice"][data-motion-state="closing"])::after {
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }
}

/* ============================================================================
 * 아티팩트 패널(표 → 시트 편집, doc 171 P1) — 배치 E2-a2: #trustPanel의 도킹 카드 모양(둥근
 * 카드·그림자·보더)은 그대로 쓰되, 데스크톱에서는 그리드 흐름에서 완전히 분리된 `position: fixed`
 * 오버레이로 뜬다(대화가 밀리지 않고 그 위에 얹힘 — non-modal, 배경 클릭으로 안 닫힘). z-index는
 * 새 계열을 만들지 않고 다른 fixed 최상위 오버레이(.user-msg-menu 등)와 같은 200 계열을 재사용한다.
 * 3단계 폭(default/wide/full) + 드래그 커스텀 폭은 data-panel-width 속성으로 전환되고, "custom"은
 * 어떤 폭 규칙과도 매치하지 않으므로 JS가 얹는 인라인 width가 그대로 이긴다. 헤더는 마크업에서
 * .trust-route-head 클래스를 함께 얹어 기존 모바일 뒤로가기 order 규칙(아래)을 그대로 상속한다.
 * ========================================================================== */
@media (min-width: 901px) {
  .chatgpt-reference-v1 #artifactPanel.chatgpt-route-page {
    position: fixed;
    inset: 12px 12px 12px auto;
    width: clamp(360px, 38%, 520px);
    margin: 0;
    max-width: none;
    padding: 20px 20px calc(20px + var(--wizon-safe-bottom, 0px));
    overflow-y: auto;
    overflow-x: hidden;
    background: var(--cg-menu-bg, var(--bg));
    border: 1px solid var(--line);
    border-radius: 16px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.16);
    z-index: 200;
    display: flex;
    flex-direction: column;
  }
  /* 넓게: 대화창 폭 절반 정도까지(최대 900px) — 여전히 우측 카드 도킹 모양 유지. */
  .chatgpt-reference-v1 #artifactPanel.chatgpt-route-page[data-panel-width="wide"] {
    width: min(50vw, 900px);
  }
  /* 전체: 화면을 완전히 덮는 전면 취득 — 대화가 시각적으로 가려지므로 카드 느낌(라운드·인셋)은
     내려놓고 모바일 풀스크린과 동일한 엣지투엣지 취급으로 전환한다. */
  .chatgpt-reference-v1 #artifactPanel.chatgpt-route-page[data-panel-width="full"] {
    inset: 0;
    width: 100vw;
    border-radius: 0;
  }
  /* 드래그 리사이즈 핸들: 패널 좌측 경계에 얇은 히트영역 + 호버/포커스 시 보이는 그립 바.
     데스크톱 전용(모바일은 풀스크린 시트 유지 — 아래 @media (max-width: 900px)에서 숨김). */
  .chatgpt-reference-v1 #artifactPanel .artifact-panel-resize-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 16px;
    cursor: col-resize;
    touch-action: none;
    z-index: 1;
  }
  /* R-51 B3 — 굵은 포인터(터치)는 근거 패널 경계(.side-panel-splitter)와 같은 24px. 폭 기반이 아니라 포인터 기반이라
     디자인 시스템 breakpoint 래칫과 무관하고, 위 기본 규칙 «뒤에» 있어야 이긴다(같은 특이도). */
  @media (pointer: coarse) {
    .chatgpt-reference-v1 #artifactPanel .artifact-panel-resize-handle {
      width: 24px;
    }
  }
  .chatgpt-reference-v1 #artifactPanel .artifact-panel-resize-handle::before,
  .chatgpt-reference-v1 #trustPanel .side-panel-splitter::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 3px;
    height: 48px;
    border-radius: var(--ds-radius-pill, 999px);
    background: var(--cg-menu-border, var(--line));
    opacity: 0;
    transition: opacity var(--wds-duration-fast) ease;
  }
  .chatgpt-reference-v1 #artifactPanel .artifact-panel-resize-handle:hover::before,
  .chatgpt-reference-v1 #artifactPanel .artifact-panel-resize-handle:focus-visible::before,
  .chatgpt-reference-v1 #artifactPanel.is-resizing .artifact-panel-resize-handle::before,
  .chatgpt-reference-v1 #trustPanel .side-panel-splitter:hover::before,
  .chatgpt-reference-v1 #trustPanel .side-panel-splitter:focus-visible::before,
  .chatgpt-reference-v1 #trustPanel.is-resizing .side-panel-splitter::before {
    opacity: 1;
  }
  .chatgpt-reference-v1 #artifactPanel .artifact-panel-resize-handle:focus-visible,
  .chatgpt-reference-v1 #trustPanel .side-panel-splitter:focus-visible {
    outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
    outline-offset: 2px;
  }
  /* 드래그 중에는 포인터가 16px 핸들을 벗어나는 게 정상 궤적이라, 전역 커서를 col-resize로 고정하고
     본문 텍스트가 끌려 선택되는 것을 막는다(클래스는 begin/endArtifactPanelResize가 토글). */
  body.artifact-panel-resizing,
  body.side-panel-resizing {
    cursor: col-resize;
    -webkit-user-select: none;
    user-select: none;
  }
  /* 헤더 우측 액션(폭 토글 + 닫기) 묶음. */
  #artifactPanel .artifact-route-head-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
  }
  .artifact-panel-width-toggle {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 50%;
    background: var(--cg-hover-soft, #f2f3f5);
    color: var(--cg-text, #1f2937);
    cursor: pointer;
  }
  .artifact-panel-width-toggle:hover {
    background: var(--cg-hover, #ececec);
  }
  .artifact-panel-width-toggle:focus-visible {
    outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
    outline-offset: 2px;
  }
  .artifact-panel-width-toggle svg {
    width: 18px;
    height: 18px;
  }
  /* 닫기 버튼 가시성: 아티팩트 패널만 32→40px로 키운 히트영역(트러스트 패널의 .trust-panel-close는
     건드리지 않음 — 회귀 방지를 위해 ID로 스코프). */
  #artifactPanel .trust-panel-close {
    width: 40px;
    height: 40px;
  }
  #artifactPanel .trust-panel-close svg {
    width: 20px;
    height: 20px;
  }
}

/* ============================================================================
 * 분할 화면 — 「시트 편집」을 열면 대화가 «가려지는» 대신 왼쪽으로 좁혀져 나란히 선다.
 * 오너 지시 2026-09-02, 정본 `docs/product/544-sheet-editor-excel-ribbon-plan.md` §22.
 *
 * 무엇이 깨져 있었나(실측, 1920px): 패널은 우측 도킹 오버레이라 대화 위에 얹혔고, 대화 열 폭은
 * 열기 전후 둘 다 1660px 로 «전혀 움직이지 않았다». 표의 마지막 열(금액) 셀 중앙을
 * elementFromPoint 로 찍으면 패널이 잡혔다(covered-by-panel, 겹침 532px).
 *
 * 왜 그리드 열이 아니라 «오른쪽 여백 예약»인가: 패널은 이미 position:fixed 도킹 카드이고 그 위에
 * 3단계 폭 토글 + 드래그 리사이즈 + 전체화면(full) + localStorage 복원이 얹혀 있다(바로 위 블록).
 * 패널을 그리드 열로 옮기면 그 상태 기계를 전부 다시 지어야 한다. 대신 대화 쪽(.main-pane)이
 * 패널이 실제로 차지하는 띠(패널 폭 + 우측 인셋 12px + 사이 여백 12px)만큼 오른쪽 안여백을
 * 예약하면 같은 화면이 나오면서 기존 토글·드래그·복원이 그대로 산다. .main-pane 은 grid item 이고
 * 패딩은 grid track 을 바꾸지 않으므로 순환 의존도 생기지 않는다(패널의 % 폭은 fixed 라 뷰포트
 * 기준이다).
 *
 * ★--artifact-panel-reserve 는 JS(syncArtifactPanelSplitReserve)가 패널의 **실측 폭**을 넣는다.
 *   같은 값을 CSS 에서 38vw 로 흉내 내면 안 된다 — vw 는 클래식 스크롤바를 포함하고 fixed 요소의
 *   %는 포함하지 않아 플랫폼에 따라 조용히 어긋난다.
 *
 * 업계 대조(doc 544 §22.1): ChatGPT Canvas · Claude Artifacts · Gemini Canvas · Copilot Pages
 * 넷 다 오버레이가 아니라 좌우 분할이고, 좁아진 대화를 되찾는 수단은 «구분선 드래그»가 아니라
 * «패널 전체화면 토글»이다. 그래서 full 단계에서는 예약을 끄고(=기존 전면 취득 그대로), 새 리사이즈
 * 핸들도 만들지 않는다 — 이미 있는 것을 그대로 쓴다.
 *
 * 901px 미만에서는 예약하지 않는다(doc 544 §24, 오너 결정 2026-09-02 — 이 값은 원래 1200px 였다).
 * 사이드바 260px 을 그대로 두면 1024px 에서 대화 360 + 패널 389 + 여백 24 = 773 > 764 로 9px
 * 모자랐다(§22 A1). `syncArtifactPanelSplitSidebarCollapse`(app.js) 가 분할이 켜질 때 사이드바를
 * 52px 레일로 접어(기존 `.chatgpt-reference-v1.sidebar-collapsed .app-shell` 그리드 열을 그대로
 * 재사용, 새 토글 없음) 208px 을 돌려주므로 1024px 에서도 여유가 생긴다(실측 §22.5/§24 표).
 * ★901px 를 골랐지 1024px 를 그대로 쓰지 않은 이유: `tests/test_design_system_contract.py` 의
 * breakpoint 래칫(`design-system/foundations/breakpoints.md` §2)이 폭 기반 @media 리터럴을
 * 정본 6값(520/521/900/901/1200/1201)으로 묶어 두고, ai-portal styles.css 는 이미 드리프트
 * 천장이 0이다 — 1024 는 그 여섯에 없어 새 드리프트가 된다. 901 은 `expanded-min` 토큰과 같은
 * 값이라 정본이면서, 위 계산의 여유가 더 크다(849 - 824 = 25px, 1024 의 9px 보다 낫다). 그래서
 * 구간(<901px)은 지금까지의 덮기가 유지되고, 900px 이하는 원래대로 풀스크린 시트다.
 * ========================================================================== */
@media (min-width: 901px) {
  .chatgpt-reference-v1 .main-pane {
    /* 대화가 이보다 좁아지느니 패널이 그 위를 덮는 편이 낫다 — 폭 하한. 드래그 상한(85vw)은
       그대로 두었으므로, 사용자가 패널을 극단적으로 키우면 아래 min()이 예약을 여기서 끊고
       예전의 부분 덮기로 «되돌아간다». 값이 사라지는 게 아니라 우아하게 퇴화한다. */
    --artifact-split-min-chat: 360px;
    /* 패널 개폐(MOTION_PANEL_MS = 372ms)와 같은 시간·같은 곡선으로 움직여야 «대화가 밀린다»로
       읽힌다. 서로 다른 시간을 쓰면 두 개가 따로 노는 것으로 보인다. */
    transition: padding-inline-end var(--wizon-motion-panel-duration, 372ms)
      var(--wizon-motion-spatial-ease, ease);
  }
  .chatgpt-reference-v1[data-artifact-panel-split="on"] .main-pane {
    padding-inline-end: min(
      calc(var(--artifact-panel-reserve, 0px) + 24px),
      max(0px, calc(100% - var(--artifact-split-min-chat)))
    );
  }
  /* 드래그 중에는 전환을 끈다 — 372ms 곡선이 매 프레임 다시 시작되면 대화가 포인터를 한참
     늦게 따라오는 «고무줄»이 된다. */
  body.artifact-panel-resizing .main-pane {
    transition: none;
  }
  /* doc 544 §26 (오너 실검수 2026-09-02): 「2단계(넓게) 크기가 1단계(기본) 크기하고 별 차이가
     안 난다」— 분할 켜졌을 때 기본=clamp(440,42vw,720), 넓게=min(50vw,900)(비분할 값 그대로,
     분할 전용 넓히기가 없었다)이라 1920px 에서 720 vs 900, 겨우 180px(9.4%) 차이였다. 1200px
     에서는 504 vs 600, 8%로 더 심했다.
     레퍼런스(ChatGPT Canvas ≈50/50+Expand, Claude Artifacts, 젠오피스 §6.1)를 비율로 옮겼다 —
     기본 ≈ 대화 60/시트 40, 넓게 ≈ 대화 35/시트 65. 두 단계 다 vw 식으로 새로 쓰고, 하한/상한을
     아래 5폭 실측표(901/1024/1200/1440/1920 — 사이드바 자동 접기 후보 doc 544 §24 와 같은 표를
     써서 다른 레인과 대조 가능하게 했다)가 전부 인접 단계 차이 ≥25%폭이 되도록 역산했다:
       기본 = clamp(440px, 34vw, 720px)   — 하한 440 은 드래그 하한 ARTIFACT_PANEL_MIN_WIDTH 와
                                              같은 값(기존 그대로). 상한 720 도 기존 값 그대로 —
                                              1920px 까지는 34vw(652.8px)가 이미 상한 아래라
                                              캡이 걸리지 않고, 4K 이상에서만 개입한다.
       넓게 = clamp(670px, 69vw, 1180px)  — "wide" 는 지금까지 분할 전용 규칙이 아예 없어 비분할
                                              값(min(50vw,900))을 그대로 물려받았다. 아래에 처음
                                              으로 분할 전용 규칙을 추가한다.
     901px 에서 마진이 가장 빡빡하다(실측 25.5%) — 기본이 하한 440 에 눌리고 넓게도 하한 670 에
     눌린 채로 "전체(100vw=901)" 와도 25%(231px) 이상 벌어져야 해서, 440 을 고정하는 한 수학적
     으로 더 벌리기 어려운 상한선이다(중점 최적값). 1024/1200/1440/1920 은 전부 여유가 26~36%.
     ★이 clamp() 은 문턱 값과 무관하게 유효하다(수식이 비율 기반이라 어느 문턱에서 켜지든 같은
     표를 낸다) — 분할 문턱을 901 로 낮추고 사이드바를 자동 접는 §22.7 후보 ①(커밋 8fdafc738,
     doc 544 §24)이 이 작업을 시작한 뒤에 main 에 착지해, 그 위로 리베이스하면서 901/1024 행도
     Playwright 실렌더로 확인했다(doc 544 §26 표 — 계산값이 아니라 라이브 값이다).
     "full"/"custom" 은 손대지 않는다. */
  .chatgpt-reference-v1[data-artifact-panel-split="on"]
    #artifactPanel.chatgpt-route-page[data-panel-width="default"] {
    width: clamp(440px, 34vw, 720px);
  }
  /* doc 544 §26 — 위 기본과 짝을 이루는 "넓게" 분할 전용 규칙. 지금까지 이 선택자가 없어서
     "넓게"가 비분할 폭(min(50vw,900))을 그대로 썼던 것이 차이가 안 나던 근본 원인이다.
     ★알려진 트레이드오프(실측, doc 544 §26 — §24 사이드바 자동 접기 착지 후 라이브 재확인):
     25% 차등 요구를 채우려면 "넓게"가 커져야 하는데, 사이드바가 52px 로 접힌 채로도 A4 퇴화
     (대화 하한 360px 밑에서 예약이 끊겨 패널이 대화 위를 다시 덮는 것)를 901px 에서 193px,
     1024px 에서 107px, 1200px 에서 52px 폭으로 밟는다(1440·1920 은 0). 440px 로 고정된 "기본"
     하한과 360px 대화 하한이 동시에 있는 한 수학적으로 더 줄일 여지가 거의 없다(§26 표) — 새
     상태가 아니라 기존 A4 퇴화 경로이므로 새 안내는 만들지 않았지만, 다음에 이 폭을 또 만지는
     레인은 이 트레이드오프를 알아야 한다. */
  .chatgpt-reference-v1[data-artifact-panel-split="on"]
    #artifactPanel.chatgpt-route-page[data-panel-width="wide"] {
    width: clamp(670px, 69vw, 1180px);
  }
}

/* ============================================================================
 * doc 544 §27 — 전체 화면(full)의 «AI 서랍» (오너 실검수 2026-09-02)
 *
 * 오너 문장: «3단계 전체일 때는 … 전체 크기일 때 ai 버튼 호출 눌리면 오른쪽에 모바일 크기의
 * ai 팝업을 띄워서 작업할수 있으면 좋겠네요». 레퍼런스(젠오피스)의 AI 패널은 «워크시트의 접히는
 * 동료»이지 오버레이가 아니고(doc 555 §6.1), 접어도 워크북 세션이 재생성되지 않는다. 우리도
 * 같다 — 다만 그들의 R10(즉시 자동 반영)은 doc 555 §6.3 에서 이미 기각했고, 서랍은 우리
 * «제안 경유 승인»(doc 544 §13.4) 을 태우는 «같은 대화»를 그대로 보여줄 뿐 새 승인 경로가 아니다.
 *
 * ★새 렌더러도, DOM 재부모화도 없다. 대화는 제자리에 그대로 있고 .main-pane 이 왼쪽 안여백을
 *   잡아 오른쪽 390px 띠만 남긴다 — §22 의 «오른쪽 예약»을 좌우만 뒤집은 같은 기법이다. 이유는
 *   app.js 의 syncArtifactAiDrawer 주석(패널이 .chat-pane 의 «자식»이라 .chat-pane 자체는 옮길
 *   수 없고, 자식만 옮기면 스크롤·포커스가 죽는다)에 있다.
 *
 * ★숫자는 CSS 에 없다. 폭은 JS 상수 ARTIFACT_AI_DRAWER_WIDTH 하나가 정하고
 *   --artifact-ai-drawer-width 로 내려온다. 폴백을 `100%` 로 둔 것은 «변수가 없으면 예약도
 *   없다»(padding 0)로 우아하게 퇴화시키기 위해서다 — 폴백에 390px 를 적으면 JS 와 두 벌이 된다.
 *
 * ★transition 을 여기서 «두 속성 모두» 다시 선언하는 이유: 위 §22 블록의
 *   `.chatgpt-reference-v1 .main-pane { transition: padding-inline-end … }` 와 특이도가 같고
 *   소스 순서상 이 규칙이 뒤이므로 통째로 이긴다. padding-inline-end 를 함께 적지 않으면 §22 의
 *   열림 애니메이션이 조용히 사라진다. 반대로 `body.artifact-panel-resizing`(0,2,1)과 바로 아래
 *   prefers-reduced-motion 규칙은 각각 특이도·순서로 여전히 이 선언을 이긴다 — 드래그 중 고무줄
 *   없음과 모션 축소 존중이 그대로 유지된다.
 * ========================================================================== */
@media (min-width: 901px) {
  .chatgpt-reference-v1 .main-pane {
    transition:
      padding-inline-start var(--wizon-motion-panel-duration, 372ms) var(--wizon-motion-spatial-ease, ease),
      padding-inline-end var(--wizon-motion-panel-duration, 372ms) var(--wizon-motion-spatial-ease, ease);
  }
  /* 대화가 오른쪽 «모바일 폭» 띠로 접힌다. 100% 는 .main-pane 자신의 인라인 폭이므로 사이드바
     폭(260/52)이 무엇이든 대화의 오른쪽 변은 뷰포트 오른쪽 변에 그대로 붙는다 — 패널의 오른쪽
     변도 같은 변수로 당기므로 둘은 «정의상» 맞닿고 겹치지 않는다. */
  .chatgpt-reference-v1[data-artifact-ai-drawer="on"] .main-pane {
    padding-inline-start: max(0px, calc(100% - var(--artifact-ai-drawer-width, 100%)));
  }
  .chatgpt-reference-v1[data-artifact-ai-drawer="on"]
    #artifactPanel.chatgpt-route-page[data-panel-width="full"] {
    inset: 0 var(--artifact-ai-drawer-width, 0px) 0 0;
    width: auto;
  }
  /* 서랍 «머리 띠». 닫기 버튼이 대화 글자 위에 얹히면 안 된다 — 처음엔 .message-list 의
     padding 으로 밀었지만, 목록은 스크롤 상자라 조금만 내리면 글자가 다시 버튼 밑으로
     지나갔다(실측 1920: 첫 문단의 첫 글자가 덮였다). .chat-pane 의 안여백은 «그리드 영역
     자체»를 줄이므로 스크롤과 무관하게 항상 비어 있다(전역 box-sizing:border-box 라 100dvh
     높이는 그대로다). 닫기 버튼은 이 띠 안에 놓인다. */
  .chatgpt-reference-v1[data-artifact-ai-drawer="on"] .chat-pane {
    padding-block-start: 40px;
  }
  /* 서랍 자체의 닫기. .chat-pane 은 position:relative 라 absolute 가 «서랍 띠»의 좌표계다
     (fixed 로 두면 조상의 transform 하나에 기준이 바뀐다). 대화 액션(z-index 40)보다 위. */
  .chatgpt-reference-v1 .artifact-ai-drawer-close {
    position: absolute;
    top: 10px;
    left: 12px;
    z-index: 41;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    /* 목록이 이 버튼 «밑으로» 스크롤해 지나가므로 투명 버튼은 글자와 겹쳐 읽힌다(실측 1920:
       첫 문단의 첫 글자를 덮었다). 그래서 .scroll-to-bottom 과 같은 «떠 있는 칩» 언어를
       그대로 쓴다 — 불투명 면 + 1px 테두리 + 얕은 그림자. 새 시각 언어를 만들지 않는다. */
    border: 1px solid var(--cg-menu-border, var(--line));
    border-radius: var(--ds-radius-pill, 999px);
    background: var(--cg-surface, var(--bg));
    color: var(--cg-text, var(--text));
    box-shadow: var(--ds-shadow-md, var(--cg-shadow-float-xs));
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
  }
  /* This component declares display:inline-flex above, which has author origin and therefore
     overrides the browser's bare [hidden] display:none rule. Keep the state guard adjacent to
     the visible rule so a home/route restore cannot leave only this close chip on screen. */
  .chatgpt-reference-v1 .artifact-ai-drawer-close[hidden] {
    display: none;
  }
  .chatgpt-reference-v1 .artifact-ai-drawer-close:hover {
    background: var(--cg-hover-soft, var(--surface-2));
  }
  .chatgpt-reference-v1 .artifact-ai-drawer-close:focus-visible {
    outline: 2px solid var(--cg-focus-ring, var(--accent));
    outline-offset: 2px;
  }
  .chatgpt-reference-v1 .artifact-ai-drawer-close svg,
  .chatgpt-reference-v1 .artifact-ai-drawer-close .wds-inline-icon {
    width: 16px;
    height: 16px;
  }
  /* 헤더의 «AI» 진입: 아이콘 + 라벨이라 34x34 정사각 아이콘 버튼보다 넓다. 나머지(색·호버·
     포커스링)는 .artifact-titlebar-button 을 그대로 상속한다 — 새 버튼 종류를 만들지 않는다. */
  .chatgpt-reference-v1 #artifactPanel .artifact-titlebar-ai-button {
    width: auto;
    gap: 6px;
    padding-inline: 8px;
  }
  .chatgpt-reference-v1 #artifactPanel .artifact-titlebar-ai-label {
    font-size: var(--wizon-ai-font-size-caption);
    font-weight: 600;
    letter-spacing: 0.02em;
  }
}
@media (max-width: 900px) {
  /* 모바일은 예전대로 «패널 풀스크린 + 대화 전환»이다 — 서랍을 주지 않는다(JS 도 같은
     브레이크포인트에서 사용 불가로 판정한다). 데스크톱에서 열어둔 채 창을 줄인 경우를 위해
     CSS 쪽에서도 닫기 버튼을 확실히 감춘다. */
  .artifact-ai-drawer-close {
    display: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .chatgpt-reference-v1 .main-pane {
    transition: none;
  }
}
@media (max-width: 900px) {
  #artifactPanel.chatgpt-route-page {
    position: fixed;
    inset: 0;
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    z-index: 100;
    background: var(--cg-bg, var(--wizon-ai-canvas, #fcfcfc));
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  /* flex/min-height/gap now live in the unconditional #artifactPanel .chatgpt-route-page-inner
     rule below (doc 171 A1 — desktop needs the exact same flex-column fill, see that rule's
     comment) — this mobile-only block keeps just the delta: clip the fullscreen sheet's own
     overflow so nothing scrolls past the window chrome. */
  #artifactPanel .chatgpt-route-page-inner {
    overflow: hidden;
  }
  /* doc 670 T1 — 3분할 그리드(44 / 1fr / 오른쪽 묶음). 이전(doc 635)의 `order:-1` 은
     `.artifact-route-head-actions` «그룹째» 앞으로 보내 닫기까지 함께 옮겨졌고, 결과로 제목이
     오른쪽 끝에 갇혔다(01-audit.md §1.2, 실측: 390 에서 제목 x=248, 47~56% 잘림). 그리드로
     바꾸고 각 자식에게 grid-column 을 직접 주면 닫기(1열)·제목(2열)·AI+더보기가 그룹 경계와
     무관하게 원하는 자리에 선다.
     ★오른쪽 묶음은 CSS 트랙 2개(3·4열, 둘 다 auto)다 — 처음에 3열 하나에 AI/더보기를 order
     로만 나눠 담았더니 «같은 셀에 둘» 이 되어 겹쳤다(실측: 두 rect 의 x 가 완전히 같았다,
     grid-column/row 가 같은 아이템은 order 와 무관하게 같은 칸에서 스택된다 — order 는 칸을
     만들지 않는다). 오른쪽 «묶음»이라는 개념(3분할의 3번째 자리)은 유지하되 실제로는 두 열로
     나눠 겹침을 없앤다. */
  #artifactPanel .artifact-route-head {
    flex: 0 0 auto;
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) auto auto;
    align-items: center;
    /* doc 670 T1 다듬기(judge) — 간격을 스케일(--ds-space-*)로 되돌린다. 이전 값
       (column-gap 6px · padding 10px)은 4/8/12/16 스케일 밖 손튜닝이었다.
       390 실측 산식: 44(닫기)+56(AI)+44(더보기)+간격 3×8+여백 2×8 = 184 → 제목
       390−184=206px(계약 하한 200px 이상, 승인 목업 205px 과 1px 안). 375 에서는 191px
       (목업 190px). 「8/12 로 두면 198px」이라던 최초 서술은 padding 12 를 함께 쓴 경우이고,
       padding 을 8 로 두면 하한을 넘는다(실측).
       ★하단 헤어라인: 승인 목업 `.titlebar` 의 border-bottom 1px solid var(--line). 이 선이
       없으면 타이틀바와 리본 탭 줄의 층위가 배경색 차이 하나에만 걸린다(다크에서 두 면이
       붙어 보인다 — 게이트 «깊이»). */
    column-gap: var(--ds-space-2);
    padding: calc(var(--ds-space-2) + var(--wizon-safe-top, 0px)) var(--ds-space-2) var(--ds-space-2);
    border-bottom: 1px solid var(--cg-menu-border, var(--line));
    background: var(--cg-bg, var(--wizon-ai-canvas, #fcfcfc));
  }
  #artifactPanel .artifact-route-head h1 {
    display: flex;
    justify-content: center;
    align-items: baseline;
    overflow: visible;
    white-space: nowrap;
    /* doc 670 T1 다듬기(judge) — 승인 목업 `.tb-name` 은 15px/650/20px 이다. 이전 값
       (18px/500)은 «크지만 약한» 제목이라 ⓐ 12px 보조줄과의 대비가 크기 하나로만 서고
       ⓑ 206px 슬롯에서 head 잘림이 커졌다(실측 390: 18px 에서 107px 잘림 → 15px 에서 60px).
       650 은 이 리포의 웨이트 팔레트 밖이라(test_static_contract T4: 300/400/500/600/700,
       700 은 워드마크 1곳 예약) 600 으로 내린다 — 굵기 대비는 보조줄 400 과의 차이로 선다. */
    font-size: var(--ds-text-body-15);
    font-weight: 600;
    line-height: 1.35;
  }
  /* head/tail 분리 유틸(setArtifactTitlebarFileName, app.js) — head 만 줄이고 tail(확장자)은
     항상 보인다. h1 의 «끝» 말줄임(아래 .artifact-titlebar-file h1, 무조건 규칙)은 여기서
     overflow:visible 로 무력화하고 head 쪽에서만 다시 자른다. */
  #artifactPanel .artifact-route-head .artifact-titlebar-title-head {
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
  }
  #artifactPanel .artifact-route-head .artifact-titlebar-title-tail {
    flex: 0 0 auto;
  }
  /* 승인 목업(mockup/635b-mobile-sheet-titlebar.html)의 `.tb-identity` 에는 파일 종류
     아이콘이 없다 — 가운데 자리는 제목 1줄 + 보조줄 1줄뿐이다(§1.1-2: 「가운데 = 문서
     정체성. 제목이 오른쪽 끝에 밀린 예는 0개」, 아이콘 언급도 0개). 데스크톱 아이콘
     (.artifact-titlebar-file-icon, 무조건 규칙)은 그대로 두고 폰에서만 감춘다. */
  #artifactPanel .artifact-titlebar-file-icon {
    display: none;
  }
  /* 아이콘을 감췄더니 짧은 파일명에서는 .artifact-titlebar-identity 가 shrink-to-fit
     너비(짧은 텍스트만큼)로 줄어 .artifact-titlebar-file 의 기본 justify-content:
     flex-start 때문에 «왼쪽» 붙어 보였다(긴 파일명은 우연히 208px 를 다 채워 안 보이는
     결함이었다 — 실측 QA 하네스 파일명이 24자라 매번 이 경로를 탔다). identity 를 항상
     가운데로 고정한다. */
  #artifactPanel .artifact-titlebar-file {
    justify-content: center;
  }
  #artifactPanel .artifact-route-body {
    padding: 12px 15px;
  }
  /* 폭 토글·드래그 리사이즈는 데스크톱 전용(모바일은 기존 풀스크린 시트 유지). */
  .artifact-panel-width-toggle,
  .artifact-panel-resize-handle {
    display: none;
  }
  #artifactPanel .artifact-route-head-actions {
    /* 20260904-titlebar-file-collapse (doc 635) — 실측: 이 선언 없이는 이 그룹(그 자체가 flex
       container 인 flex item)의 자동 최소 크기가 자기 내용 전체 폭(378px, 저장 버튼의 nowrap
       텍스트 포함)으로 굳어 «축소 예산»을 전혀 못 받는다. .artifact-titlebar-save-button 이
       overflow:hidden 으로 자기 최소 크기를 풀어도(아래 20260904 블록) 이 부모가 여전히
       min-width:auto 면 자식의 축소가 부모 폭에 반영되지 않는다 — 형제(.artifact-titlebar-file)
       쪽 min-width:0 과 대칭을 맞춘다. */
    min-width: 0;
    /* doc 670 T1 — 위 min-width:0 은 이 그룹이 flex 컨테이너였던 시절의 흔적이고, 문자열은
       apps/ai-portal/tests/test_artifact_titlebar_mobile_filename_collapse.py 가 pin 하므로
       남긴다(런타임에는 뜻이 없다). display:contents 로 이 div 의 박스를 지우고 자식(닫기·AI·
       더보기·숨긴 나머지 6개)을 위 그리드의 자식으로 «펼친다» — 그래야 닫기는 1열, AI/더보기는
       3열로 각각 grid-column 을 받을 수 있다(이 div 를 그대로 두면 하나의 그리드 아이템이라
       통째로 한 칸에만 놓인다). */
    display: contents;
  }
  /* doc 670 T1 다듬기(judge) — 승인 목업의 `.tb-btn` 은 «투명 배경 + 8px 라운드»다.
     실측 RED: 폰에서도 데스크톱 규칙의 `background: var(--cg-hover-soft)` + `border-radius:50%`
     가 그대로 걸려 좌상단에 상시 회색 원이 섰고, 그 원이 15px 제목보다 시각 무게가 컸다
     (위계 반전). 02-plan.md §1 의 레퍼런스 12장 중 «나가는 문»에 상시 채운 배경을 두는
     제품은 0개다. 색도 형제(더보기)와 같은 --cg-text-soft 로 맞춰 왼쪽/오른쪽 아이콘의
     잉크 무게를 한 단으로 통일한다. */
  /* doc 670 T1 polish2(build, §2.1) — r6→r10: 「나가는 문」은 면이 없다는 판정은 그대로
     두고 라운드만 오른쪽 둘(AI 알약·더보기 원)과 한 급 다르게 벌려, «면으로» 위계를
     가른다(오너 조정1 — 셋이 같은 반경이면 위계가 한 단으로 뭉친다). */
  #artifactPanel .artifact-route-head-actions .trust-panel-close {
    grid-column: 1;
    grid-row: 1;
    background: transparent;
    border-radius: var(--ds-radius-sm);
    color: var(--cg-text-soft, var(--muted));
  }
  #artifactPanel .artifact-route-head-actions .trust-panel-close:active {
    background: var(--wizon-ai-surface-press);
  }
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-ai-button {
    grid-column: 3;
    grid-row: 1;
  }
  #artifactPanel .artifact-route-head-actions [data-artifact-more-toggle] {
    display: flex;
    grid-column: 4;
    grid-row: 1;
  }
  /* 다운로드·저장·범위 질문·버전 기록·구분선 — «더보기» 임시 메뉴(artifactMoreMenu)로 같은
     명령이 그대로 닿으므로(handleArtifactMoreMenuActionClick, 01-audit.md §2.1 실측: 「파일」
     탭이 이미 같은 명령을 44×44 로 들고 있어 도달 손실 0) 폰 타이틀바에서는 감춘다. 1440
     데스크톱은 이 규칙 밖이라 무변화(test_mobile_sheet_titlebar_layout.py 음성 대조). */
  #artifactPanel .artifact-route-head-actions [data-artifact-range-ask],
  #artifactPanel .artifact-route-head-actions [data-workbook-history-toggle],
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-save-button,
  #artifactPanel .artifact-route-head-actions [data-artifact-download],
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-divider {
    display: none;
  }
}
#artifactPanel.chatgpt-route-page[hidden] {
  display: none;
}
#artifactPanel.chatgpt-route-page {
  scrollbar-width: none;
}
#artifactPanel.chatgpt-route-page::-webkit-scrollbar {
  display: none;
}
/* 엑셀 새창 골격(doc 171 A1): .chatgpt-route-page-inner의 공용 규칙(이 파일 아래쪽, 12862줄 근방)은
   display:grid로 콘텐츠 높이만큼만 차지한다 — 트러스트 패널처럼 스크롤되는 카드형 콘텐츠엔 맞지만,
   시트 창은 그리드(.artifact-route-body)가 창의 남은 세로 공간을 전부 채워야 한다(항목 4). 데스크톱
   에서도(모바일은 위 @media (max-width: 900px)에서 이미 flex-column이었다 — 이제 그 델타만 남김)
   flex 컬럼으로 바꿔 .artifact-route-body의 flex:1 1 auto가 실제로 작동하게 한다. ID 선택자라
   위쪽 공용 grid 규칙(클래스 2개, 이 파일 뒤쪽에서 선언되어 소스 순서상 이긴다)보다 특이도가 높아
   미디어 쿼리 안팎 어디서든 확실히 이긴다 — 실렌더로 검증됨(폭 토글/드래그 리사이즈 시 Univer 캔버스
   가 자체 ResizeObserver로 즉시 재도장하는 것도 함께 확인). */
#artifactPanel .chatgpt-route-page-inner {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  gap: 0;
}
/* 타이틀바 좌측: 파일 아이콘 + 파일명(h1) — 폭이 좁을 때 파일명이 잘리도록 min-width:0 필요. */
.artifact-titlebar-file {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
}
.artifact-titlebar-file-icon {
  display: inline-flex;
  flex: 0 0 auto;
}
/* doc 670 T1 — h1 + 폰 전용 보조줄을 한 세로 열로 묶는다. 데스크톱에서는 보조줄이
   display:none(아래)이라 자식이 h1 하나뿐 — 시각적으로 이전과 같다. */
.artifact-titlebar-identity {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.artifact-titlebar-file h1 {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 보조줄(시트명 · 저장 상태) — 폰 전용. 기본은 항상 display:none 이라(hidden 속성 유무와
   무관) 데스크톱은 무조건 그리지 않는다 — 아래 @media (max-width:900px) 의 ID-스코프 규칙만
   보인다. doc 635 §2.1 과 같은 함정(저자 스타일이 UA [hidden]{display:none}을 이긴다)을
   피하려고 «항상 그린다»가 아니라 «항상 감춘다»를 기본값으로 둔다. */
.artifact-titlebar-sub {
  display: none;
  margin: 2px 0 0;
}
@media (max-width: 900px) {
  #artifactPanel .artifact-titlebar-sub {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    max-width: 100%;
    overflow: hidden;
    color: var(--cg-muted, var(--cg-text-soft));
    font-size: var(--ds-text-caption-12);
    line-height: 16px;
    white-space: nowrap;
    /* ★실측 함정: 이 `<p>` 는 `.chatgpt-reference-v1 .chatgpt-route-page-head p` 공유 규칙
       (특이도 0,2,0, margin-top 12px = --chatgpt-route-gap-sm)에도 걸린다. 위 기본(무조건)
       .artifact-titlebar-sub 규칙(0,1,0)은 그 규칙보다 특이도가 낮아 margin 을 못 이긴다 —
       여기서 ID 스코프(1,1,0)로 다시 선언해야 이긴다(실측: 이 선언 없이 computed
       marginTop=12px, 목업은 margin-top:2px). */
    margin: 2px 0 0;
  }
  #artifactPanel .artifact-titlebar-sub-text {
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
  }
  /* doc 670 T1 다듬기(judge) — 상태가 없을 때 JS 가 `hidden` 을 세운다. 이 규칙은 display 를
     안 쓰므로 UA `[hidden]` 이 그대로 이기지만(특이도 0 vs 이 규칙), 같은 파일이 이미 겪은
     함정(저자 display 선언이 UA 를 이겨 «죽은 채로 그려짐»)을 다음 편집이 되풀이하지 않도록
     명시해 둔다. */
  #artifactPanel .artifact-titlebar-sub-dot[hidden] {
    display: none;
  }
  #artifactPanel .artifact-titlebar-sub-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex: 0 0 auto;
  }
  #artifactPanel .artifact-titlebar-sub-dot.is-saved {
    /* hex 폴백을 붙이지 않는다 — test_design_system_contract 의 raw-color 드리프트
       래칫이 var() 폴백 리터럴도 센다(이 파일 8659행 근처의 기존 주석과 같은 규칙).
       --cg-success 는 무조건 선언돼 있다. */
    background: var(--cg-success);
  }
  /* AI 알약 — 아이콘만이던 34/44 정사각을 라벨 있는 pill 로(§1.1-3 레퍼런스: Speechify 12번,
     아이콘+«AI» 강조). .chatgpt-reference-v1 의 데스크톱 pill 규칙(§27, min-width:901px)과
     같은 발상이되 값은 폰 터치 표적(44)에 맞춘다. */
  /* doc 670 T1 polish2(build, §2.1 + 오너 조정1) — 오너 지적 「라이트에서 테두리 회색
     네모로 읽힌다」: 흰 면 + 1px 테두리뿐인 알약은 «얹힌 컨트롤»이 아니라 «빈 칸»으로
     읽힌다(채도 없는 회색 네모). 테두리를 지우고 면(--wizon-ai-surface-raised) + 그림자
     (--wizon-ai-lift-1)로 바꾼다 — 다크 목업(mock-390-base-dark.png)이 이미 이 모양이었고
     라이트만 테두리 규칙이 남아 있었다. 너비 56→60(다크 목업 실측 60×44). */
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-ai-button {
    width: auto;
    min-width: 60px;
    height: 44px;
    padding: 0 12px;
    gap: 6px;
    border: 0;
    border-radius: var(--ds-radius-pill, 999px);
    background: var(--wizon-ai-surface-raised);
    box-shadow: var(--wizon-ai-lift-1);
    /* doc 670 T1 다듬기(judge) — 승인 목업 `.tb-ai` 의 color 는 잉크 1단(목업 토큰
       이름 `text-1`, 이 리포에서는 --cg-text)이다. 실측 RED: --cg-text-soft(#3f3f3f) 를
       상속해 «주요 행동»이 보조 아이콘과 같은 잉크였다.
       ★목업 토큰 이름을 `var(...)` 형태로 주석에 적지 않는다 —
       test_design_token_resolution 의 «폴백 없는 var() 가 없는 토큰을 가리킨다» 래칫이
       주석 안의 문자열도 그대로 센다(실측: 이 주석이 그 시험을 빨갛게 만들었다). */
    color: var(--cg-text, var(--text));
  }
  /* AI 눌림(닫힌 상태에서 누름) — ★신설(spec §2.1). 열린 상태([aria-expanded="true"])는
     이미 잉크 반전이라 이 규칙과 상태가 겹치지 않게 :not() 으로 가른다. */
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-ai-button:active:not([aria-expanded="true"]) {
    background: var(--wizon-ai-surface-press);
    box-shadow: var(--wizon-ai-sunk);
  }
  /* 승인 목업 `.tb-ai` 의 svg 는 16×16 — 실측 RED 18px. 라벨(13px)보다 큰
     아이콘은 «AI» 글자를 장식으로 밀어낸다(§1.1-3 은 라벨이 주인공인 알약을 요구한다). */
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-ai-button .artifact-titlebar-button-icon svg,
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-ai-button .artifact-titlebar-button-icon .wds-inline-icon {
    width: 16px;
    height: 16px;
  }
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-ai-button[aria-expanded="true"] {
    background: var(--cg-text, var(--text));
    /* doc 670 T1 다듬기(judge) — 승인 목업은 눌린 알약의 border-color 도 잉크로 바꾼다.
       실측 RED: 테두리만 --cg-menu-border(#e3e3e3) 로 남아 검은 알약 둘레에 밝은 링이
       한 겹 생겼다(깊이 층위가 두 겹으로 읽힌다). polish2(§2.1): border 자체는 0 이 됐지만
       (얹힌 면 대신 잉크 반전이라 테두리가 필요 없다) 이 선언은 남긴다 — border-color 만
       바뀌는 미래 회귀를 막는 안전망이고, `border:0` 아래에서는 렌더 영향이 없다. */
    border-color: var(--cg-text, var(--text));
    /* doc 670 T1 polish2(§2.1) — 얹힌 면의 그림자(--wizon-ai-lift-1)는 «떠 있는 알약» 신호다.
       열려서 잉크로 반전된 알약은 이미 배경 대비로 위계가 서므로 그림자까지 더하면
       두 신호가 겹친다(깊이 신호 중복). */
    box-shadow: none;
    /* hex 폴백을 붙이지 않는다(raw-color 래칫, 위 .is-saved 규칙과 같은 이유) —
       --wizon-ai-canvas 는 :root 에서 무조건 선언돼 있다. */
    color: var(--cg-bg, var(--wizon-ai-canvas));
  }
  /* 승인 목업 `.tb-ai` 는 13px/650 — 실측 RED 12px 였다(아이콘 18px 보다 작아 위계가
     뒤집혔다). 650 은 웨이트 팔레트 밖이라 600(위 제목과 같은 이유). */
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-ai-label {
    font-size: var(--ds-text-caption-13);
    font-weight: 600;
  }
  /* doc 670 T1 polish2(build, §2.1) — 더보기 «···» 히트영역은 44×44 그대로 두고, «보이는»
     면만 안쪽 36×36 원으로 만든다(마크업 신규 요소 0개 — 기존
     `[data-artifact-more-toggle] > .artifact-titlebar-button-icon` 를 그대로 원으로).
     AI 알약과 같은 --wizon-ai-lift-1 얹힌 면 문법을 써 둘이 «한 조»로 읽히게 한다. */
  #artifactPanel [data-artifact-more-toggle] .artifact-titlebar-button-icon {
    width: 36px;
    height: 36px;
    border-radius: var(--ds-radius-pill);
    background: var(--wizon-ai-surface-raised);
    box-shadow: var(--wizon-ai-lift-1);
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  #artifactPanel [data-artifact-more-toggle]:active .artifact-titlebar-button-icon {
    background: var(--wizon-ai-surface-press);
    box-shadow: var(--wizon-ai-sunk);
  }
}
/* doc 670 T1 — 더보기 임시 메뉴(#artifactMoreMenu) 콘텐츠. 팝오버 자체(위치·시트 승격·모션)는
   .chatgpt-conversation-menu 를 그대로 물려받는다(index.html 주석 참고) — 여기서는 이 메뉴만
   갖는 모양(식별 헤드·섹션 라벨·파일 정보 dl)만 새로 얹는다. */
.artifact-titlebar-more-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 14px 10px;
  /* doc 670 T1 polish2(build, §2.4) — 실측 RED: 저장 상태 칩을 붙인 뒤 이 요소가
     `#artifactMoreMenu`(display:grid) 의 그리드 아이템이라 자동 최소 크기가 자기 내용의
     max-content 폭(파일명 + 칩)으로 굳어 시트 오른쪽 밖으로 24px 새어 나갔다(head.right
     414.2 > 시트 width 390, 실측 390 라이트). min-width:0 으로 그리드 아이템의 자동
     최소 크기를 풀어야 아래 meta(flex column, 이미 min-width:0)와 name-row 로 축소
     예산이 전달된다 — 형제(.artifact-titlebar-file, 위 900px 블록)가 겪은 것과 같은
     함정. */
  min-width: 0;
}
/* doc 670 T1 polish2(build, §2.4) — 32→40, r10(--ds-radius-sm=10). 헤드 아이콘 타일도
   나머지 «면 문법»(--wizon-ai-surface-raised)에 맞춘다 — 예전 값(--cg-hover-soft)은 트랙과
   같은 색이라 이 타일도 눈에 덜 띄었다. */
.artifact-titlebar-more-head-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: var(--ds-radius-sm, 10px);
  background: var(--wizon-ai-surface-raised);
  color: var(--wizon-ai-text-secondary);
}
.artifact-titlebar-more-head-meta {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* polish2(§2.4) — 파일명 옆에 저장 상태 칩이 붙으므로 strong 을 한 줄로 묶는다. */
.artifact-titlebar-more-head-meta .artifact-titlebar-more-head-name-row {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  min-width: 0;
}
.artifact-titlebar-more-head-meta strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  color: var(--cg-text);
}
.artifact-titlebar-more-head-meta small {
  font-size: var(--ds-text-caption-12);
  color: var(--cg-muted);
}
/* polish2(§2.4) — ★저장 상태 칩(신설). 3상태 텍스트·클래스는 app.js
   artifactTitlebarSaveChipLabel()/artifactTitlebarSaveStateKind() 가 채운다(기존
   artifactWorkbookSaveState 를 그대로 재사용 — 새 상태 기계 0개). 배경은 --cg-success
   «톤»을 color-mix 로 얇게 깔아 새 색 리터럴 없이 하나의 의미색만 쓴다(T-8). */
.wizon-sheet-save-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  height: 22px;
  padding: 0 8px;
  border-radius: var(--ds-radius-pill);
  background: var(--wizon-ai-surface-raised);
  color: var(--wizon-ai-text-muted);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  white-space: nowrap;
}
.wizon-sheet-save-chip::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
.wizon-sheet-save-chip[data-state="editing"] {
  color: var(--wizon-ai-text-secondary);
}
.wizon-sheet-save-chip[data-state="saved"] {
  background: color-mix(in oklab, var(--cg-success) 16%, transparent);
  color: var(--cg-success);
}
/* polish2(§2.4) — 섹션 라벨 4→2(「내보내기」·「선택 범위」). 「이력과 저장」·「파일 정보」는
   라벨을 잃고 각각 무라벨 그룹 / 맨 아래 메타 한 줄이 된다(아래 .wizon-sheet-meta). 세로
   패딩을 12/6 으로 늘려 그룹 경계(32px)를 만든다 — why-document-like.md §1 후보⑤(섹션
   라벨과 행 간격이 정확히 0px 이던 문제)의 직접 수리다. */
.artifact-titlebar-more-sec {
  padding: 12px 20px 6px;
  color: var(--cg-muted);
  font-size: var(--ds-text-micro-11, 11px);
  /* 600 — test_typography_contract 의 웨이트 팔레트 래칫이 700 을 이미 1곳(모바일 워드마크)
     으로 못박아 두 번째를 못 받는다. 섹션 라벨은 600으로도 충분히 도드라진다. */
  font-weight: 600;
  letter-spacing: 0.04em;
}
.artifact-titlebar-more-hint {
  display: block;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  white-space: normal;
}
.artifact-titlebar-more-menu [aria-disabled="true"] {
  color: var(--cg-muted-2, var(--cg-muted));
  cursor: not-allowed;
}
/* polish2(§2.4) — 그룹 인셋 리스트. §6 이 재사용 가능하게 지은 이름
   (.wizon-sheet-group/-row/-meta) 그대로다 — 이 패스의 범위는 시트 상단뿐이지만 다음
   후보(질문 카드·생성 파일 «···»)가 그대로 물려받게 한다. */
.wizon-sheet-group {
  margin: 0 var(--ds-space-3);
  border-radius: var(--ds-radius-md);
  background: var(--wizon-ai-surface);
  box-shadow: var(--wizon-ai-lift-1);
  overflow: hidden;
}
.wizon-sheet-group + .wizon-sheet-group {
  margin-top: var(--ds-space-3);
}
/* polish2(§2.4) — 행 아이콘 컨테이너(신설). 예전엔 아이콘이 컨테이너 없이 라벨과 같은
   잉크(#0d0d0d)였다(why-document-like.md §1 후보③) — 타일 안에 담고 색을 한 단
   낮춘다(--wizon-ai-text-secondary). */
.wizon-sheet-row-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface-raised);
  color: var(--wizon-ai-text-secondary);
}
.wizon-sheet-row-icon svg,
.wizon-sheet-row-icon .wds-inline-icon {
  width: 16px;
  height: 16px;
}
[aria-disabled="true"] .wizon-sheet-row-icon {
  opacity: 0.6;
}
/* polish2(§2.4) — 우측 어포던스: 「버전 기록」의 `›`, 내려받기 두 행의 포맷 칩. */
.wizon-sheet-row-chevron {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin-left: auto;
  color: var(--wizon-ai-text-muted-2);
}
.wizon-sheet-format-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  margin-left: auto;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--ds-radius-xs);
  background: var(--wizon-ai-surface-raised);
  color: var(--wizon-ai-text-secondary);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  letter-spacing: 0.04em;
}
/* polish2(§2.4) — 파일 정보 dl → 맨 아래 메타 한 줄. 파일명은 h1 과 같은 head/tail 말줄임
   헬퍼(splitTitlebarFilename)를 재사용한다(app.js renderArtifactMoreMenuMeta). 오너
   결정(나): 「시트 N개 · N행 × N열」조각은 배선이 없어 넣지 않는다. */
.wizon-sheet-meta {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin: var(--ds-space-3) var(--ds-space-5) 0;
  overflow: hidden;
  color: var(--wizon-ai-text-muted);
  font-size: var(--ds-text-caption-12);
  /* 한국어 단어 중간 잘림 방지(실렌더로 확인) — spec §2.4. */
  word-break: keep-all;
}
.wizon-sheet-meta-name {
  display: inline-flex;
  overflow: hidden;
  min-width: 0;
}
.wizon-sheet-meta-head {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wizon-sheet-meta-tail,
.wizon-sheet-meta-status {
  flex: 0 0 auto;
  white-space: nowrap;
}
/* doc 670 T1 — «더보기» 진입은 폰 전용이라 기본값은 항상 숨김(hidden 속성이 아니라 display 로
   —doc 635 §2.1 과 같은 함정을 피한다: `hidden` 속성을 쓰면서 `.artifact-titlebar-button{
   display:inline-flex}` 처럼 특이도 낮은 저자 규칙이 UA `[hidden]{display:none}` 을 이겨
   버튼이 죽은 채로 그려질 수 있다). ≤900px 안의 ID-스코프 규칙(위)이 display:flex 로 되돌린다.
   ★실측(1440, stage=full): 처음엔 `[data-artifact-more-toggle]{display:none}`(특이도 0,1,0)
   로 적었더니 이 파일 뒤쪽의 `.artifact-titlebar-button{display:inline-flex}`(같은 특이도,
   소스 순서상 «뒤») 가 이겨 데스크톱에서도 버튼이 보였다. ID 스코프로 특이도를 올려
   소스 순서와 무관하게 이기게 한다. */
#artifactPanel [data-artifact-more-toggle] {
  display: none;
}
/* 타이틀바 우측 다운로드 버튼과 폭 토글/닫기 사이 구분선 — .chatgpt-library-view-divider와
   동일한 톤(1px, 얇은 세로 바)을 이 컨텍스트에 맞춰 재선언. */
.artifact-titlebar-divider {
  width: 1px;
  height: 20px;
  flex: 0 0 auto;
  background: var(--cg-menu-border, var(--line));
}
.artifact-titlebar-save-button {
  min-height: 34px;
  padding: 0 var(--ds-space-3);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-sm);
  background: var(--cg-surface, var(--ds-color-surface));
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.artifact-titlebar-save-button:hover:not(:disabled) {
  background: var(--cg-hover-soft);
}
.artifact-titlebar-save-button:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}
.artifact-titlebar-save-button:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}
/* 20260904-titlebar-file-collapse (doc 635) — 실측(390×844, 채팅 표를 sourceDocumentId 없이
   시트로 열면 [data-artifact-drive-save]="업무 드라이브에 저장"이 뜬다, doc 626 §8 S2): 이 규칙
   묶음 전(수리 전) .artifact-titlebar-file 은 min-width:0 인데 저장 버튼은 white-space:nowrap +
   overflow 기본값(visible)이라 자동 최소 크기가 «줄바꿈 없는 전체 텍스트 폭»(133px)으로 고정된다
   — 형제 사이의 축소 예산을 저장 버튼이 통째로 가져가 파일명 쪽 min-width:0 이 0 으로 끝까지
   밀린다(h1 clientWidth 0, 뷰포트 밖 x=403 로 밀려남). 데스크톱(>900px)은 폭이 넉넉해 이 경쟁이
   한 번도 안 일어나므로 이 델타를 모바일 미디어 쿼리 밖(위 900px 초과)에 두지 않는다 — 데스크톱
   1440 스크린샷은 그래서 픽셀 그대로다.
   수리: (1) 파일명 쪽에 최소폭을 준다(7ch ≈ 아이콘 18px+gap 8px 를 빼고도 자동 생성 파일명의
   첫 6~7자 + 말줄임을 보장). (2) 저장 버튼은 h1 과 «같은» 메커니즘(overflow: hidden +
   text-overflow:ellipsis)을 얻어 자동 최소 크기가 0 으로 풀리게 하고, 그 대신 min-width 를
   스스로 줘서 라벨이 아이콘 폭 아래로는 안 줄게 한다. 터치 표적은 44px 로 올린다
   (.artifact-titlebar-button 의 @media (pointer: coarse) 44px 관례와 같은 상수). */
@media (max-width: 900px) {
  .artifact-titlebar-file {
    min-width: 7ch;
  }
  .artifact-titlebar-save-button {
    flex: 0 1 auto;
    min-width: 44px;
    max-width: 42vw;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}
@media (max-width: 900px) and (pointer: coarse) {
  .artifact-titlebar-save-button {
    min-height: 44px;
  }
}
.artifact-workbook-risk {
  flex: 0 0 auto;
  margin: var(--ds-space-2) var(--ds-space-3) 0;
}
/* 값 목록(데이터 유효성) 폴백 안내 — doc 544 §15.
   ★이 규칙과 app.js 의 workbookV2DataValidationNoticeHtml 은 «반드시 함께» 읽어야 한다: 마크업만
   접어도 펼쳤을 때 다시 그리드를 밀어내고, 상한만 걸어도 접힌 요약이 없으면 처음부터 잘린 목록이
   보인다.
   실측(2026-08-30, 오너의 실제 견적서 6시트 · 73행 × 9열 · 규칙 24개, 1440×900): 수리 전 안내는
   평면 <p>+<ul> 이라 469px(패널 안쪽 834px 의 56.2%)를 먹고 그리드에 209px 만 남겼다. 규칙 200개
   에서는 안내 3285px, 그리드 **0px** — 시트 편집기에 시트가 아예 없었다.

   상한이 px 가 아니라 «패널 안쪽 대비 %» 인 이유: 이 결함의 본질은 «안내가 패널을 이긴다» 이므로
   기준은 패널이어야 한다. 고정 px 는 패널이 짧아지는 순간(폰 가로, 좁은 도킹 폭) 같은 결함으로
   되돌아간다. 이 자리에서 %가 실제로 해석되는 것은 확인된 사실이다 — #artifactPanel 이
   position:fixed + inset 으로 높이가 확정이고 .chatgpt-route-page-inner 가 flex:1 1 auto 라
   포함 블록 높이가 definite 다(실렌더 대조: 상한 30% → 안내 250px / 그리드 428px).
   산식: 그리드 = 100% − 크롬(타이틀바+리본+상태바, 실측 ≈19%) − 안내 상한(30%) ≳ 51%.
   접힌 기본 상태에서는 안내가 51px 고정이라 규칙 1개든 200개든 그리드가 627px(75.2%)로 «같다».

   ★스크롤 상자는 <details> 가 아니라 이 바깥 요소다. <details> 를 flex 컬럼으로 만들고 안쪽
   목록에 flex:1 1 auto + min-height:0 을 주는 «보통의» 방법은 여기서 조용히 안 먹는다: 크롬은
   summary 를 뺀 나머지를 UA 섀도의 ::details-content 블록에 담으므로 그 목록은 details 의 flex
   자식이 아니고, 따라서 줄지 않는다. 실측으로 잡았다 — 겉보기 높이는 상한대로 250px 인데
   scrollHeight 가 720px 이라 목록이 overflow:visible 로 그리드 «위에» 그려지고 있었다. 그래서
   상한과 overflow 를 같은 요소(이 바깥 상자)에 두고, 정직 고지가 스크롤에 딸려 사라지지 않게
   summary 를 sticky 로 붙인다. */
#artifactPanel [data-workbook-validation-notice] {
  max-height: 30%;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0;
  background: var(--cg-hover-soft);
}
.artifact-validation-notice-summary {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 12px;
  /* ★`background: inherit` 로 두면 안 된다: summary 의 부모는 배경을 가진 안내 슬롯이 아니라
     <details> 라서 투명을 물려받고, sticky 로 떠 있는 동안 목록이 글자 뒤로 비쳐 지나간다
     (실측 2026-08-30: computed background-color 가 rgba(0,0,0,0) 이었다). 같은 토큰을 직접
     쓴다 — 위 슬롯과 한 값이므로 테마가 바뀌어도 같이 움직인다. */
  background: var(--cg-hover-soft);
  cursor: pointer;
  list-style: none;
}
.artifact-validation-notice[open] .artifact-validation-notice-summary {
  border-bottom: 1px solid color-mix(in srgb, var(--cg-text) 8%, transparent);
}
/* Safari 는 ::marker 가 아니라 ::-webkit-details-marker 로 삼각형을 그린다 — 둘 다 지워야
   아래 자체 표식(▸/▾)과 이중으로 보이지 않는다. */
.artifact-validation-notice-summary::-webkit-details-marker {
  display: none;
}
.artifact-validation-notice-summary::marker {
  content: "";
}
.artifact-validation-notice-summary:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: -2px;
}
.artifact-validation-notice-headline {
  color: var(--cg-text, var(--fg));
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}
.artifact-validation-notice-headline::before {
  content: "▸ ";
  color: var(--cg-muted, var(--muted));
}
.artifact-validation-notice[open] .artifact-validation-notice-headline::before {
  content: "▾ ";
}
/* 펼침 안내를 «요약 문구»가 아니라 ::after 로 두는 이유: 열림 상태에 따라 문구가 뒤집혀야 하는데
   (닫힘 「어느 셀인지 보기」 / 열림 「접기」) 본문에 박아 두면 열린 뒤에도 「보기」라고 남는다.
   보조기술은 이 장식 문구가 아니라 <details>/<summary> 자체의 확장 상태를 읽는다. */
.artifact-validation-notice-headline::after {
  content: " — 어느 셀인지 보기";
  color: var(--cg-muted, var(--muted));
  font-weight: 400;
}
.artifact-validation-notice[open] .artifact-validation-notice-headline::after {
  content: " — 접기";
}
.artifact-validation-notice-hint {
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-caption-12);
}
.artifact-validation-notice-body {
  padding: 0 12px 8px;
}
.artifact-validation-notice-group + .artifact-validation-notice-group {
  margin-top: 8px;
}
.artifact-validation-notice-group h3 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 2px;
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}
.artifact-validation-notice-group h3 span {
  color: var(--cg-muted, var(--muted));
  font-weight: 400;
}
.artifact-validation-notice-group ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
.artifact-validation-notice-group li {
  padding: 2px 0;
  font-size: var(--ds-text-caption-12);
  line-height: 1.45;
}
.artifact-validation-notice-group li b {
  margin-right: 6px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
/* 커스텀 반응형 리본(doc 171 A3-a) — Univer 내장 리본을 숨기고(같은 자리에) 얹는 자체 리본.
   Univer(0.25.1) 내장 리본은 [data-u-comp="headerbar"] 하나에 탭 줄(시작/수식/데이터)과 아이콘
   툴바([data-u-comp="ribbon-toolbar"])가 함께 들어있다 — 실렌더로 확인: 이 anchor의 textContent가
   "시작수식데이터..."이고 자식이 정확히 이 둘 + 텍스트박스. 수식바([data-u-comp="formula-bar"])는
   헤더바 형제라 이 규칙에 걸리지 않고 그대로 유지된다.
   doc 555 §채택 A3(2026-08-27 재실측) — UniverSheetsCorePreset 은 `toolbar: false` 옵션이 실재한다
   (mountArtifactSheet() 의 createUniver 호출부 참조 — 이제 그 옵션도 같이 끈다). 이 규칙은 옵션이
   실패하거나(버전업으로 이름이 바뀌거나 사라지면) 렌더가 그 옵션을 무시하는 경우를 대비한 안전망으로
   남긴다 — 셀렉터가 안 맞아도 CSS 는 그대로 매칭 시도하고, 매칭 실패 시 내장 리본이 다시 보이는
   방향으로 fail-visible(레이아웃 파손 아님). (이 자리의 예전 메모는 "그런 옵션 자체가 존재하지
   않는다"고 적었는데, 이는 minified 번들에서 unquoted 객체 키를 quoted-string grep 으로 찾다 놓친
   오판이었다 — 실제로는 `{header:n=!0,toolbar:s=!0,footer:l=!0}` 형태로 존재한다.) */
#artifactSheetContainer [data-u-comp="headerbar"] {
  display: none;
}
/* 리본 자체: 홈 탭 핵심 그룹만(실행취소·재실행 / 굵게·기울임·밑줄 / 정렬 좌·중·우 / 표시형식 /
   자동합계) — 탭 줄(삽입·수식·데이터)과 색상(글꼴색·채우기)은 각각 A3-c/A3-b로 제외. container
   query로 자기 자신의 렌더 폭을 재서 3모드로 축약 — data-panel-width 속성(default/wide/full)이
   아니라 실제 px를 재는 이유: 드래그로 만든 "custom" 폭은 그 속성과 매치하는 CSS 규칙이 없어(E2-a2,
   인라인 style.width가 이김) 속성 셀렉터로는 드래그 중 반응형을 못 잡는다 — container query는 드래그
   폭 포함 항상 실제 렌더 폭을 본다. */
.artifact-ribbon {
  container-type: inline-size;
  container-name: artifact-ribbon;
  position: relative;
  flex: 0 0 auto;
  padding: 4px 8px;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
  background: var(--cg-hover-soft, #f8f8f8);
}
/* 컴팩트 팝오버가 열려 있을 때만 리본 전체(자신 + 절대배치 자식)를 그리드보다 위 스태킹 레이어로
   올린다 — 실렌더로 확인된 회귀: Univer 자체 그리드 <canvas>가 position:absolute; z-index:8로
   그려지고(인라인 스타일, 벤더 유틸리티 클래스 중 최대값도 univer-z-20 — 실측), 리본은 z-index
   없이 auto라 DOM 순서와 무관하게 캔버스가 항상 위에 그려져 팝오버가 그 밑에 깔려 클릭도 시각도
   막혔었다. 30 = Univer 쪽에서 실측된 최대치(20)보다 여유 있게 높음. 색상 팝오버(doc 171 A3-b)도
   같은 회귀에 노출되므로 동일 규칙에 편입. */
/* ★doc 597 P1-2 — 테두리 메뉴도 같은 목록에 붙는다. 단 이 리프트는 이제 팝오버를 «보이게»
   하는 장치가 아니다: doc 544 §20 이 잘림의 진짜 원인(행의 overflow/mask + 리본의
   container-type 조합)을 찾아 팝오버를 body 포털로 빼면서, 팝오버는 리본의 스태킹 컨텍스트
   밖에서 z-index:201 로 그려진다. 여기 남는 이유는 리본 «자신»(오버플로 그룹 등 포털되지 않는
   절대배치 자식)이 열린 동안 캔버스 위에 있어야 하기 때문이고, 세 컨트롤이 같은 규칙을 쓰는
   편이 규칙 둘로 갈라지는 것보다 낫다. §18.8 이 좌표까지 적어 둔 잘림은 §20 에서 닫혔다. */
.artifact-ribbon[data-ribbon-overflow-open="true"],
.artifact-ribbon[data-ribbon-color-popover-open="true"],
.artifact-ribbon[data-ribbon-border-popover-open="true"] {
  z-index: 30;
}
/* 리본 탭 줄 (doc 544 P1 → §12 시각 재설계, 목업 2층) — 파일·홈·삽입·수식·데이터·보기.
   ★P1 의 «카드형»(활성 탭만 표면색 + 테두리로 본문과 이어 붙이는 엑셀 2007 형태)은 §12 에서
     목업(sheet-concept-a `.rtab`)의 «밑줄형»으로 교체됐다: 배경은 어느 탭도 칠하지 않고,
     활성 탭만 잉크색 글자 + 아래 2px 잉크 밑줄로 선다. 카드형을 되살리면 리본 본문
     (--cg-hover-soft)과 활성 탭(--cg-surface)이 서로 다른 두 표면을 붙여 놓게 되고,
     목업의 «한 장의 종이» 인상이 깨진다.
   ★hover 배경(--cg-hover)은 그대로 둔다 — 상태(밑줄+잉크)와 hover(배경)의 «채널»이 다르므로
     §11.2-(b) 의 «상태색 == hover색» 결함이 구조적으로 재발할 수 없다.
   폰(≤520px)에서는 아래 미디어 블록이 이 줄을 가로 스크롤 스냅 «칩 행»으로 바꾼다(M1) —
   터치에서 밑줄은 히트영역을 못 그리므로 그 폭에서만 채워진 pill 로 남는다(§5.2-M1). */
.artifact-ribbon-tabs {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  padding: 0 8px;
  margin: -4px -8px 4px;
  border-bottom: 1px solid var(--cg-menu-border);
}
.artifact-ribbon-tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  height: 34px;
  padding: 0 16px;
  border: none;
  /* 밑줄은 «항상» 있고 색만 바뀐다 — 활성일 때만 border 를 만들면 탭이 2px 씩 들썩인다. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  background: transparent;
  color: var(--cg-muted);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  /* ★활성/비활성이 «같은» 무게다 — 목업의 600→800 대비를 여기서는 쓰지 않는다. 이유 두 가지,
     둘 다 실측이다.
     (1) 타이포 계약이 웨이트 팔레트를 {400,500,600} + 예외 2개(300 컴포저 글리프 1곳 ·
         700 모바일 워드마크 1곳)로 못박고 상한을 «각 1곳»으로 래칫한다
         (test_typography_contract T4). 800 은커녕 700 도 여기 둘 수 없어, 목업의 두 단계
         대비는 잘해야 500→600 한 단계로 줄어든다.
     (2) 무게가 상태를 말하면 활성 탭이 넓어져 뒤따르는 탭이 오른쪽으로 밀린다 — 탭을 누를
         때마다 줄 전체가 움찔한다. 보이지 않는 굵은 사본(::after)으로 폭을 미리 잡는 수를
         한 번 넣었다가 «양성 대조»에서 걷어냈다: 예약을 지워도 실렌더 오프셋이 소수점까지
         동일했다(937 / 993.75 / 1040.13 / 1096.88 / 1153.63 / 1220.75, 두 번 모두). 이 앱은
         font-synthesis-weight:none 이라 미로드 웨이트를 합성하지 않으므로, 게이트가 재는
         환경에서 500 과 600 은 «같은 폭»으로 그려진다 — 예약은 하는 일이 없었고 그것을
         지키는 검사도 재는 것이 없었다.
     활성 표시는 잉크색(--cg-muted → --cg-text)과 2px 밑줄이 함께 말한다 — 목업의 세 신호 중
     둘이고, 엑셀 웹·구글 시트도 이 둘만 쓴다. */
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
/* R1 (doc 544 §5.4): 탭의 hover 도 반드시 이 가드 안. 가드가 없으면 터치에서 «지나간» 탭의 배경이
   끈적여 사용자는 그 탭이 열린 줄 안다. */
@media (hover: hover) {
  .artifact-ribbon-tab:hover {
    background: var(--cg-hover);
    color: var(--cg-text);
  }
}
.artifact-ribbon-tab:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: -2px;
}
.artifact-ribbon-tab[aria-selected="true"] {
  border-bottom-color: var(--cg-text);
  background: transparent;
  color: var(--cg-text);
}
/* 배선 없는 탭(파일·삽입·데이터)의 한 줄 안내. 회색 더미 버튼 대신 «그 기능이 지금 어디 있는지»를
   말한다(doc 544 §3.3-4). 컨트롤이 아니므로 리본 활성/비활성과 무관하게 항상 읽힌다. */
.artifact-ribbon-panel-note {
  margin: 0;
  padding: 6px 4px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.5;
}
/* 그룹 상자 — 목업 3층 「각 그룹 하단에 그룹 라벨」. 라벨은 최대 폭(>901px)과 컴팩트 "⋯" 드롭다운
   에서만 보인다(중간 폭은 아이콘만 있는 1줄이어야 하므로). */
.artifact-ribbon-groupbox {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 0 0 auto;
  gap: 1px;
}
/* 파일 탭 「버전」 그룹 (doc 544 §11.1) — 저장/버전 기록은 워크북 문서에서만 보인다
   (openArtifactPanel 이 타이틀바 원본과 같은 hidden 을 쓴다). 둘 다 숨겨졌을 때 그룹 라벨과
   뒤 구분선만 남으면 «빈 그룹» 화면이 되므로 통째로 숨긴다. :has() 는 이 파일이 이미
   150여 곳에서 쓰는 관례다. 인접 형제(+)로 구분선까지 걷는 것이 요점 — 그룹만 숨기면
   패널 왼쪽에 구분선이 홀로 남는다. */
[data-ribbon-groupbox="file-version"]:not(:has(button:not([hidden]))),
[data-ribbon-groupbox="file-version"]:not(:has(button:not([hidden]))) + .artifact-ribbon-sep {
  display: none;
}
.artifact-ribbon-groupbox-body {
  display: flex;
  align-items: center;
  /* §12 — 목업 `.rgroup .items{gap:6px}`(환산 4px). 2px 는 버튼들이 한 덩어리로 붙어 보여
     «그룹 안»의 구분이 사라졌다. */
  gap: 4px;
}
.artifact-ribbon-groupbox-label {
  display: none;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  /* §12 — 목업 `.glabel{font-weight:600}`. 라벨이 400 이면 버튼 글자와 무게가 같아 «캡션»으로
     읽히지 않고 또 하나의 컨트롤처럼 보였다. */
  font-weight: 600;
  line-height: 1.2;
}
/* doc 544 §32: 세로 맞춤은 이제 wds-icons.js 의 전용 align-vertical-justify-* 글리프를 쓴다 —
   .artifact-ribbon-icon--vertical(가로 아이콘 90° 회전) 클래스는 index.html 어디서도 더는
   참조되지 않아 죽은 규칙이었다. 같은 커밋이 붓·테두리·줄바꿈·병합·행·열도 문자에서 아이콘으로
   옮겨, 아래 문자 버튼은 ₩ · % · , 뿐이다(엑셀도 표시 형식 프리셋은 문자다).
   .artifact-ribbon-label-text 가 아니라서 컴팩트 폭에서도 남는다 = 아이콘 없는 빈 버튼이 되지 않는다. */
/* R5 — 글자 버튼의 «표시»다. min-width 를 아이콘 밴드(16px)와 같게 두면 표시가 하나뿐인
   버튼은 아이콘이든 글자든 6+16+6 = 28px 정사각으로 같은 자리를 차지한다(R3). */
.artifact-ribbon-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  flex: 0 0 auto;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  line-height: 1;
}
/* ⓑ «자기 고지»(doc 544 P1 done_when 7 / doc 550 §6)의 규칙은 markup 과 함께 doc 550 S2-b 에서
   사라졌다 — 홈 탭의 서식 컨트롤이 전부 저장되므로 가리킬 문장이 없다. 되살릴 때는 index.html 의
   그 자리에 남긴 주석을 먼저 읽어라: 위험 알림이 아니라 사실 고지이므로 위험색이 아닌 상태바와 같은
   muted 잉크를 썼고, 모든 폭에서 보였다(폰 포함) — 컴팩트에서 사라지면 «저장되는 줄 알았다»가 바로
   그 폭에서 생긴다. */
/* ★가로로 «넘치면 스크롤»한다 — 모든 폭에서. 원래는 overflow:hidden 이었고, 컨트롤이 10개이던
   doc 171 A3-a 시절에는 넘칠 일이 없었다. P1 이 홈 탭을 18개로 늘리면서 중간 폭(561~900px)에서
   실제로 넘쳤는데 그 구간에는 "⋯" 오버플로 토글이 없어(그건 ≤560px 전용) 「표시 형식」 그룹이
   «보이지도 않고 닿을 수도 없게» 잘렸다(900px 실렌더로 확인: scrollWidth 900 / clientWidth 884,
   2026-08-27). 최대 폭에서도 같은 일이 있었다(1561 / 1382). hidden 은 그 사실을 «조용히» 숨긴다.
   스크롤바는 숨기고 좌우 페이드로 «더 있음»만 알린다 — 리본 안의 가로 스크롤바는 28px 줄에서
   내용을 반쯤 먹는다. ★아래 두 줄은 doc 544 §18.8/§20 실렌더로 더는 성립하지 않는 것으로
   정정됐다 — 색상 팝오버의 containing block 이 .artifact-ribbon(position:relative)인 것은
   맞지만, .artifact-ribbon 의 container-type:inline-size(§12, 컨테이너 쿼리용)가 암시하는
   layout containment 와 이 줄의 overflow/mask-image 가 함께 있을 때만(단독으론 재현 안 됨,
   실측) 그 escape 가 깨져 팝오버가 이 스크롤 상자 바닥에서 잘린다. 두 속성 다 기능상 못 지우므로
   팝오버는 이제 이 트릭에 기대지 않고 body 직속 포털(JS 의 openArtifactRibbonPopoverPortal)로
   조상 체인 자체를 벗어난다 — 아래 [data-ribbon-popover-portal] 규칙 참조. */
.artifact-ribbon-row {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  mask-image: linear-gradient(90deg, currentColor 0, currentColor calc(100% - 12px), transparent 100%);
}
/* 20260903-ribbon-scroll-hint: 오른쪽 페이드가 «항상» 떠 있어 실제로 끝까지 스크롤해도
   사라지지 않았다(정적 마스크는 스크롤 위치를 모른다) — 발주는 "오른쪽 끝 페이드 +
   스크롤 끝에서 사라짐"이었다.
   ★왼쪽 페이드도 같이 넣어 봤다가 걷어냈다: 이 리본(document-mode=on, 홈 탭, 1440px)은
   스크롤 위치 0에서도 이미 넘치지 않을 수 있는데, 좌우 대칭 마스크는 그 상태에서도 첫
   아이콘(클립보드)을 무조건 흐리게 그렸다 — 스크린샷 대조(before/after, 픽셀 diff bbox
   (38,30)-(465,121))로 잡았다. 발주 문구도 "오른쪽 끝"만 말했지 좌우가 아니었다 — 과잉
   구현이 새 결함을 만들었다.
   animation-timeline: scroll(self inline) 은 스크롤 컨테이너 자신의 가로 진행률(0~100%)에
   애니메이션을 직접 묶는다 — JS 없이 「끝까지 가면 사라진다」를 만드는 표준 메커니즘이다
   (Chrome/Firefox/Safari 모두 이 파일이 겨냥하는 cross-browser 최소선에서 지원).
   ★scroller 키워드를 반드시 self 로 적어야 한다 — 기본값(nearest)은 «조상» 스크롤러를
   찾고 자기 자신은 건너뛴다. .artifact-ribbon-row 는 자기 자신이 overflow-x:auto 스크롤러라
   scroll(inline) 만 쓰면 더 위의(대개 스크롤하지 않는) 조상을 타임라인 소스로 잡아
   애니메이션이 사실상 항상 비활성 상태로 멎는다 — 실측(700px, scrollWidth 1572 /
   clientWidth 684): row.scrollLeft 를 0→888(끝)로 옮겨도 computed mask-image 가 한 글자도
   안 바뀌었다. self 를 붙이자 같은 스크립트로 0%(정적 페이드와 동일)→100%(페이드 없음)
   전환을 확인했다.
   미지원 브라우저는 @supports 가 걸러 위 정적 페이드(고정, 항상 켜짐 — 오른쪽 페이드가
   수개월째 써 온 것과 같은 모양)만 본다 — 그것이 발주가 명시적으로 허용한 폴백이다. 넘치지
   않는 요소는 스크롤 타임라인이 «비활성»이라 애니메이션이 적용되지 않고 기존 정적값으로
   폴백한다 — 지원 브라우저에서도 이 한 경우는 위 문단의 폴백과 같은 모양이 되는데, 이는
   기존에도 있던 성질이라 이 커밋이 새로 만드는 회귀가 아니다(위 정적 규칙 자체를 건드리지
   않았다). */
@supports (animation-timeline: scroll(self inline)) {
  .artifact-ribbon-row {
    animation: 1ms linear forwards artifact-ribbon-row-fade-scroll;
    animation-timeline: scroll(self inline);
  }
}
@keyframes artifact-ribbon-row-fade-scroll {
  from {
    mask-image: linear-gradient(90deg, currentColor 0, currentColor calc(100% - 12px), transparent 100%);
  }
  to {
    mask-image: linear-gradient(90deg, currentColor 0, currentColor 100%);
  }
}
.artifact-ribbon-row::-webkit-scrollbar {
  display: none;
}
.artifact-ribbon-group {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}
/* §12 — 목업의 그룹 구분은 «짧은 칸막이»가 아니라 그룹 옆면을 세로로 가르는 선이다
   (`.rgroup{border-right:1px solid #ececec}` + 좌우 18px 여백). 20px 짜리 토막은 28px 버튼
   옆에서 위아래가 붕 떠 보였다. 28px = 버튼 높이와 같게 세우고 여백을 목업 리듬으로 벌린다.
   ★리본 한 줄은 «모든 폭에서» 가로 스크롤한다(위 .artifact-ribbon-row 주석) — 이 여백이
     늘어난 만큼 스크롤 폭이 늘어날 뿐, 잘려서 닿지 못하는 컨트롤은 생기지 않는다. */
.artifact-ribbon-sep {
  width: 1px;
  height: 28px;
  flex: 0 0 auto;
  margin: 0 8px;
  background: var(--cg-menu-border, var(--line));
}
/* ★리본 컨트롤 시각 규칙 (doc 544 §28, 오너 실검수 2026-09-02 «상단의 버튼들도 디자인
   들쭉들쭉하네요»). 홈 탭 31개 컨트롤을 실렌더로 전수 측정해 «무엇이» 어긋났는지부터 확정했다.
   높이는 이미 28px 로 분산 0 이었다 — 어긋난 것은 «크기 밴드»였다:
     (1) 아이콘이 20px 로 그려지고 있었다. 아래 R2 참조 — 규칙은 16px 라고 적혀 있었는데
         특이도 «동률»로 져서 한 번도 먹은 적이 없다.
     (2) 그래서 한 줄에 20px 아이콘과 12px 글자(붓·테두리·줄바꿈·병합·행·열·₩·%·,)라는
         두 밴드가 섞였고, 눈에는 그 «두 배 차이»가 들쭉날쭉으로 읽혔다.
     (3) 폭이 16 / 30 / 36 / 43 / 47 / 56px 로 흩어졌다 — 같은 «한 표시 한 클릭» 컨트롤이
         서로 다른 사각형이었다.
   규칙(엑셀 홈 탭 관례를 우리 토큰으로 옮긴 것):
     R1 높이 밴드 하나 = 28px (버튼·캐럿·select·구분선 전부. 이미 지켜지고 있었다)
     R2 아이콘 한 크기 = 16×16, 캐럿만 12×12 (주=본체, 종=캐럿의 위계를 크기로 말한다)
     R3 최소 폭 = 높이와 같은 28px → 표시가 하나뿐인 버튼은 «정사각»이 된다
     R4 좌우 패딩 = 6px 하나 (스플릿 본체만 캐럿 쪽 4px — 두 조각이 한 컨트롤로 붙는다)
     R5 글자 버튼은 아이콘 «자리»에 12px/600 글자를 넣는다(엑셀도 ₩ % , 는 문자다).
        아이콘화는 하지 않았다: shared/wds/wds-icons.js 에 붓/테두리/줄바꿈/병합/행/열이
        없고, 그 파일의 캐시 마커(?v=20260810-shared-icons-...)는 두 앱 세 HTML 에 손으로
        박혀 있으며 repin_portal_assets.py 가 «다루지 않는다» — 마커를 놓치면 브라우저가
        옛 아이콘 표를 계속 받아 새 버튼이 «빈 사각형»으로 뜬다. 그 위험을 리본 시각 정리와
        같은 커밋에 묶지 않는다(§28 「다음 후보」).
     R6 상태 3종은 모든 컨트롤에서 같은 규칙 — hover(--cg-hover) / 눌림(--ad-blue-tint +
        --ad-blue) / 비활성(opacity .45). 새 색은 만들지 않는다.
   ★gap 이 6→4 인 이유: 그룹 안 간격(.artifact-ribbon-groupbox-body{gap:4px})과 «버튼 안»
     간격이 서로 다르면 「행 + 」처럼 두 조각을 담은 버튼이 옆 버튼과의 간격보다 넓게 벌어져
     한 버튼으로 안 읽힌다(실측 56px, 리본에서 가장 넓었다). */
.artifact-ribbon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 28px;
  min-width: 28px;
  padding: 0 6px;
  border: none;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  color: var(--cg-text-soft, var(--muted));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  /* §12 — 목업 `.rbtn{font-weight:700}`. 12px 한글에서 700 은 뭉개지므로 600 으로 앉힌다
     (글리프 버튼 ₩ % , 병합 이 이미 쓰는 무게와 같아 한 줄 안에서 무게가 갈리지 않는다). */
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
/* R1 (doc 544 §5.4): 리본 버튼의 :hover는 전부 이 가드 안. doc 544 P0가 이 자리에 「보기」 눈금선
   토글을 넣으면서 이 공용 규칙이 그 토글의 hover도 소유하게 됐다 — 가드가 없으면 폰에서 한 번
   누른 토글의 배경이 «끈적여» 눌린 채로 남고, 사용자는 눈금선이 아직 그 상태인 줄 안다. */
@media (hover: hover) {
  .artifact-ribbon-btn:hover {
    background: var(--cg-hover, #ececec);
    color: var(--cg-text, var(--text));
  }
  .artifact-ribbon-btn:disabled:hover {
    background: transparent;
    color: var(--cg-text-soft, var(--muted));
  }
}
.artifact-ribbon-btn:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 1px;
}
.artifact-ribbon-btn:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
/* 토글 활성 상태(굵게/기울임/밑줄/정렬/세로 맞춤/표시 형식 프리셋).
   ★--ds-chip-bg-active + --accent 조합을 «쓰지 않는다». P0 레인이 상태바 토글에서 실렌더로 잡은
     것과 같은 결함이 이 공용 규칙에도 있었다(styles.css 의 눈금선 토글 주석 참조): 이 앱에서
     --ds-chip-bg-active → --ds-color-accent → --wizon-ai-action-primary-bg 이고 --accent 도 같은
     잉크로 접혀, 배경과 글자가 «같은 색»이 된다 — 굵게를 켜면 검은 사각형만 남고 B 가 사라졌다.
     주액션 쌍(--cg-send-bg / --cg-send-fg)은 배경과 글자가 «짝»으로 정의된 유일한 잉크 쌍이라
     라이트/다크 어느 쪽으로 뒤집혀도 대비가 유지되고, hover(--cg-hover)와도 확실히 구분된다.
   ★raw 색 리터럴도, var() «폴백» 리터럴도 쓰지 않는다 — 색상 부채 래칫(anti-drift-static-baseline
     .json raw-color)은 폴백 안의 리터럴까지 그대로 센다. 이 파일의 다수 관례가 폴백 없는 형태다. */
.artifact-ribbon-btn[aria-pressed="true"] {
  /* §12 — 목업 `.rbtn.on{background:#e8efff;color:#155eef}`: 눌림은 «반전된 검은 칩»이 아니라
     «연한 파랑 바닥 + 파랑 잉크»다. --cg-send-bg 쌍(검정/흰색)은 §11.3 이 hover 충돌을 피하려
     급히 잡은 자리였고, 리본 한 줄에 굵게·기울임·정렬이 동시에 켜지면 검은 사각형이 여러 개
     생겨 «주액션 버튼»처럼 읽혔다.
     ★--ad-blue-tint / --ad-blue 는 배경과 잉크가 «짝»으로 정의된 쌍이다: 틴트가
       color-mix(파랑, --wizon-ai-surface) 라 라이트(#fff)에서는 아주 연한 파랑, 다크(#000)
       에서는 아주 어두운 파랑이 되고, --ad-blue 자신은 다크에서 --wds-blue-400 으로 밝아진다
       (styles.css 「아트디렉션 v2」 블록). 어느 테마에서도 배경 != 글자가 유지된다 —
       --ds-chip-bg-active 가 두 값 모두 같은 잉크로 접혀 «검은 사각형»이 됐던 함정
       (P0/P1 주석)과 갈리는 지점이 바로 이 «짝으로 정의됐는가»다.
     ★hover 토큰(--cg-hover)과 겹치지 않는다 — §11.3 이 세운 계약이 그대로 지켜진다
       (§11.2-(b): 상태색 == hover색이면 클릭하는 순간 시각차가 0 이었다). */
  background: var(--ad-blue-tint);
  color: var(--ad-blue);
}
/* ★R2 — 이 규칙은 doc 171 A3-a 부터 «16px 라고 적혀 있었지만 한 번도 먹은 적이 없다».
   셸 기본 `.chatgpt-reference-v1 .wds-inline-icon`(width/height: var(--wds-icon-size-md, 20px))
   과 특이도가 (0,2,0) 으로 «동률»이고, 그 규칙이 이 파일에서 더 뒤(14541행)에 있어 소스 순서로
   졌다. 실측 2026-09-02: 리본 아이콘 전수 20×20. 같은 함정을 이 파일이 이미 두 번 겪었고
   (5798행·6066행 주석), 둘 다 «셸 기본을 이겨야 한다»고 적어 두고 (0,3,0)으로 올려 닫았다 —
   같은 처방을 쓴다. .chatgpt-reference-v1 은 앱 셸 루트라 리본은 항상 그 안에 있다(컴팩트
   "⋯" 팝오버로 접혀도 마찬가지 — 마크업이 옮겨가지 않는다). */
.chatgpt-reference-v1 .artifact-ribbon-btn .wds-inline-icon {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
}
/* 최대(>900px)에서만, 또는 컴팩트 팝오버가 열려있을 때만 보임 — 아래 @container 규칙. */
.artifact-ribbon-label-text {
  display: none;
}
.artifact-ribbon-select-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.artifact-ribbon-select {
  height: 28px;
  min-height: 0; /* 전역 select { min-height:42px } 누수 차단 — .artifact-sheet-container select와 동일 이유 */
  padding: 0 6px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-xs, 6px);
  background: var(--bg, #fff);
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  /* §12 — 목업 `.rsel{font-weight:600}`. 옆 버튼들과 같은 무게라야 한 그룹으로 읽힌다. */
  font-weight: 600;
  cursor: pointer;
}
/* R9 — 폭도 규칙으로 정한다. 그 전에는 둘 다 브라우저가 정한 94px 로 «같은 크기의 빈 상자
   두 개»였고(실측), 선택 셀의 글꼴이 목록 밖이면 둘 다 비어 있어 리본에서 시각적으로 가장
   무거운 요소가 «빈 사각형 두 개»가 됐다. 엑셀은 글꼴 이름을 넓게, 크기를 좁게 둔다 —
   폰(≤520px) 규칙이 이미 쓰는 max-width(108/68)와 같은 방향이고, 그 규칙이 더 뒤에 있어
   좁은 폭에서는 그쪽이 계속 이긴다. */
.artifact-ribbon-select--font-family {
  width: 116px;
}
.artifact-ribbon-select--font-size {
  width: 62px;
}
.artifact-ribbon-select:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
.artifact-ribbon-select:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 1px;
}
/* 글꼴색/채우기색 split-button (doc 171 A3-b) — 엑셀 그대로: 아이콘 본체(마지막 사용색 즉시 재적용)
   + ▾ 캐럿(팔레트 팝오버) 두 개의 독립 클릭 타겟이 한 몸처럼 붙어있다. 아이콘 하단 색상바
   (.artifact-ribbon-color-swatch)는 사용자가 실제로 고른 마지막 색을 인라인 style.background로
   반영 — 사용자 피드백 원문 "색상바는 색상이 변경에 따라 안바뀌고요"가 지적한 결함의 직접 수정. */
.artifact-ribbon-splitbtn {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
}
/* R4 — 본체와 캐럿은 «한 컨트롤»이다: 맞닿는 변의 라운드를 서로 지워 하나의 윤곽으로 만든다.
   그 전에는 본체가 네 귀 모두 6px, 캐럿이 오른쪽 두 귀만 6px 이라 둘 사이에 라운드 두 개가
   마주 보는 «홈»이 생겼고, 어느 한쪽에 hover 배경이 들어오는 순간 그 홈이 그대로 드러났다. */
.artifact-ribbon-splitbtn-main {
  padding-right: 4px;
  border-radius: var(--ds-radius-xs, 6px) 0 0 var(--ds-radius-xs, 6px);
}
/* ★padding:0 이 «새로 필요해진» 것이 아니라 처음부터 빠져 있었다: 이 규칙은 width/height 만
   정하고 패딩을 초기화하지 않아 UA 의 button 기본값 `1px 6px` 가 그대로 살아 있었다(실측).
   box-sizing:border-box 라 폭 16px 중 12px 를 패딩이 먹고 콘텐츠 상자가 4px 였고, 그 안에
   20px 짜리 ∨ 가 들어 있었다. R2b 로 아이콘을 12px 로 되돌리는 것과 «둘 다» 해야 캐럿이
   자기 히트영역 안에서 그려진다. */
.artifact-ribbon-splitbtn-caret {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: 0 var(--ds-radius-xs, 6px) var(--ds-radius-xs, 6px) 0;
  background: transparent;
  color: var(--cg-text-soft, var(--muted));
  cursor: pointer;
}
/* R1 (doc 544 §5.4 / P1 done_when 6): 리본의 «모든» :hover 는 이 가드 안. 아래 네 규칙은
   doc 171 A3-a/A3-b 때 가드 없이 착지했고, 터치에서 한 번 누른 캐럿/스와치/"⋯"가 눌린 채로
   남아 사용자가 그 색이 적용된 줄 아는 함정이 그대로 있었다. 폰(≤520px)에서 이 컨트롤들이
   실제로 손가락에 닿게 되는 P1 에서 함께 닫는다. ★var() 폴백 리터럴은 «그대로 둔다» —
   지우면 색상 부채 래칫 수가 움직여 이 작업의 델타가 리본 골격 밖으로 번진다. */
@media (hover: hover) {
  .artifact-ribbon-splitbtn-caret:hover {
    background: var(--cg-hover, #ececec);
    color: var(--cg-text, var(--text));
  }
}

.artifact-ribbon-splitbtn-caret:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 1px;
}
.artifact-ribbon-splitbtn-caret:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
/* ★R2b — 위와 같은 동률 패배. 실측상 캐럿의 ∨ 도 20×20 으로 그려져, 폭 16px 짜리 캐럿 버튼
   «밖으로» 4px 넘쳤고 본체 아이콘과 같은 크기라 주/종 위계가 없었다. */
.chatgpt-reference-v1 .artifact-ribbon-splitbtn-caret .wds-inline-icon {
  width: 12px;
  height: 12px;
}
.artifact-ribbon-color-icon-wrap {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.artifact-ribbon-color-swatch {
  width: 16px;
  height: 3px;
  border-radius: 1px;
  background: #000000; /* 초기값(글꼴색 검정) — background-color 컨트롤은 JS 마운트 시 #ffff00로 갱신 */
}
/* "채우기 없음"/"자동" 초기화 상태 — 리터럴 색 대신 대각선 체크 패턴으로 "색 없음"을 시각적으로
   구분(엑셀의 빗금 없음 스와치와 동일한 관례). */
.artifact-ribbon-color-swatch.is-empty {
  background: repeating-linear-gradient(-45deg, #e02020 0, #e02020 1px, transparent 1px, transparent 3px);
}
/* 리본 드롭다운 팝오버 공통 포털 (doc 544 §20) — .artifact-ribbon-color-popover 뿐 아니라 앞으로
   붙는 모든 리본 팝오버(테두리·붙여넣기 등)가 이 마커 하나만 쓰면 같은 방식으로 리본 밖까지
   온전히 그려진다: app.js 의 openArtifactRibbonPopoverPortal 이 팝오버 요소를 body 직속 호스트
   (#artifactRibbonPopoverPortalHost)로 옮기고 이 속성을 붙인다. position:absolute + "조상의
   position:relative 를 containing block 으로 쓴다"는 예전 트릭(§18.8 이 실렌더로 깨졌다고 적은
   바로 그것 — .artifact-ribbon-row 의 overflow/mask-image 와 .artifact-ribbon 의
   container-type:inline-size 가 «함께» 있을 때만 escape 가 막힌다, 둘 다 기능상 못 지운다)를
   대체한다. z-index 는 #artifactPanel/.user-msg-menu 등 body 직속 최상위 오버레이가 쓰는 200
   계열보다 위(201)로 — 포털된 뒤에는 더 이상 .artifact-ribbon(열렸을 때 z-index:30)의 스태킹
   컨텍스트 안이 아니라 body 최상위 계층에서 그 200 계열과 직접 경쟁하기 때문이다. */
[data-ribbon-popover-portal="true"] {
  position: fixed !important;
  z-index: 201 !important;
}
/* position:absolute 는 이제 «포털되기 전(hidden 인 동안)»의 기본값일 뿐이다 — 열릴 때마다 JS 가
   위 [data-ribbon-popover-portal] 로 바꿔치기하므로 실제 배치는 항상 포털 쪽이 이긴다. */
.artifact-ribbon-color-popover,
.artifact-ribbon-menu-popover {
  position: absolute;
  padding: 8px;
  width: max-content;
  background: var(--cg-menu-bg, var(--bg));
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-sm, 10px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  z-index: 5;
}
/* 테두리 메뉴 (doc 597 P1-2 · doc 544 §18). 상자 자체(배치·배경·테두리·그림자·반경)는
   색 팔레트 규칙에 «셀렉터를 하나 더 붙여» 그대로 쓴다 — 값을 복제하면 둘이 갈라진다. */
.artifact-ribbon-menu-popover {
  min-width: 168px;
  padding: 6px;
}
.artifact-ribbon-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 5px 8px;
  border: none;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  text-align: left;
  cursor: pointer;
}
@media (hover: hover) {
  .artifact-ribbon-menu-item:hover {
    background: var(--cg-hover, var(--line));
  }
}
.artifact-ribbon-menu-item:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: -2px;
}
/* 라벨만 있는 목록은 열 개가 되면 훑기 어렵다. 항목마다 «그 명령이 그리는 변»을 그대로 보여 주는
   16px 미리보기를 둔다 — 아이콘 표(shared/wds/wds-icons.js)는 관제 콘솔 사본과 바이트 동일성
   게이트로 묶여 있어 이 작업에서 늘리지 않는다.
   ★변은 box-shadow 가 아니라 «진짜 border» 로 그린다. 테두리를 미리 보이는 자리이므로 그것이
   맞는 속성이고, box-sizing:border-box 라 변이 늘어도 16px 상자가 흔들리지 않는다. 안쪽 가로·
   세로선만 의사요소로 얹는다. */
.artifact-ribbon-menu-mark {
  position: relative;
  flex: none;
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  background: transparent;
}
.artifact-ribbon-menu-mark--top { border-top: 2px solid currentColor; }
.artifact-ribbon-menu-mark--bottom { border-bottom: 2px solid currentColor; }
.artifact-ribbon-menu-mark--left { border-left: 2px solid currentColor; }
.artifact-ribbon-menu-mark--right { border-right: 2px solid currentColor; }
.artifact-ribbon-menu-mark--outside,
.artifact-ribbon-menu-mark--all { border: 2px solid currentColor; }
.artifact-ribbon-menu-mark--none { border: 1px dashed var(--cg-menu-border, var(--line)); }
.artifact-ribbon-menu-mark--all::before,
.artifact-ribbon-menu-mark--inside::before,
.artifact-ribbon-menu-mark--horizontal::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  border-top: 2px solid currentColor;
}
.artifact-ribbon-menu-mark--all::after,
.artifact-ribbon-menu-mark--inside::after,
.artifact-ribbon-menu-mark--vertical::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  border-left: 2px solid currentColor;
}
.artifact-ribbon-color-popover-reset {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 4px 6px;
  border: none;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  text-align: left;
  cursor: pointer;
}
@media (hover: hover) {
  .artifact-ribbon-color-popover-reset:hover,
  .artifact-ribbon-color-popover-custom:hover {
    background: var(--cg-hover, #ececec);
  }
}

.artifact-ribbon-color-popover-reset .artifact-ribbon-color-swatch {
  width: 14px;
  height: 14px;
  border-radius: var(--ds-radius-2xs, 3px);
  border: 1px solid var(--cg-menu-border, var(--line));
}
.artifact-ribbon-color-popover-label {
  margin: 6px 2px 4px;
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-micro-11);
}
.artifact-ribbon-color-popover-sep {
  height: 1px;
  margin: 4px 0;
  background: var(--cg-menu-border, var(--line));
}
.artifact-ribbon-color-theme-grid {
  display: grid;
  grid-template-columns: repeat(10, 18px);
  gap: 2px;
}
.artifact-ribbon-color-standard-row {
  display: grid;
  grid-template-columns: repeat(10, 18px);
  gap: 2px;
}
.artifact-ribbon-color-swatch-btn {
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: var(--ds-radius-2xs, 3px);
  cursor: pointer;
  position: relative;
}
@media (hover: hover) {
  .artifact-ribbon-color-swatch-btn:hover {
    outline: 2px solid var(--wizon-ai-focus-ring, var(--accent, #2f6bff));
    outline-offset: 1px;
  }
}

.artifact-ribbon-color-swatch-btn:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 1px;
}
/* 현재 선택 범위의 실제 색과 일치하는 스와치에 체크 표시 — 팝오버를 열 때마다 다시 계산(스펙 항목
   4 "현재 셀 색을 팔레트에서 체크 표시"). */
.artifact-ribbon-color-swatch-btn.is-current::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 2px solid var(--accent, #2f6bff);
  border-radius: var(--ds-radius-2xs, 3px);
}
.artifact-ribbon-color-popover-custom {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 4px 6px;
  border: none;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  text-align: left;
  cursor: pointer;
}
/* 실제 색상 선택은 절대 여기서 다시 그리지 않는다 — "다른 색…" 클릭은 네이티브
   <input type="color">.click()을 그대로 호출해 OS 컬러피커를 띄운다(사용자 지시: "다른색 클릭했을
   때는 컬러피커 컴포넌트 사용하여 띄워주시면 됩니다"). opacity:0 + 절대배치로 시각적으로만 숨기고
   display:none은 쓰지 않는다 — 일부 브라우저는 display:none 요소의 .click()으로 네이티브 다이얼로그를
   띄우지 못한다. */
.artifact-ribbon-color-native-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
/* doc 597 P1-14 · doc 544 §40 — 셀 스타일 갤러리. 상자(배치·배경·테두리·그림자·반경)는
   .artifact-ribbon-color-popover 를 그대로 물려받는다(HTML 이 두 클래스를 함께 붙인다) — 값을
   복제하면 둘이 갈라진다. 여기서는 내부 레이아웃(그리드)과 항목 자체만 새로 정의한다. 색/반경/
   글자 크기는 전부 var() 토큰이다 — anti-drift 래칫이 raw-color/raw-border-radius/
   raw-font-size 를 «파일 안 리터럴 등장 횟수»로 세므로, 새 리터럴을 쓰면 델타가 0이 아니게 된다.
   너비·간격(px)은 그 세 범주에 없어 리터럴로 둔다(box-shadow 래칫도 이 규칙이 새 box-shadow 를
   선언하지 않으므로 무관하다). */
.artifact-ribbon-style-popover {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  width: 246px;
  max-height: 320px;
  overflow-y: auto;
}
.artifact-ribbon-style-gallery-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 6px 4px;
  border: 1px solid transparent;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  cursor: pointer;
}
@media (hover: hover) {
  .artifact-ribbon-style-gallery-item:hover {
    border-color: var(--cg-menu-border, var(--line));
    background: var(--cg-hover, var(--line));
  }
}
.artifact-ribbon-style-gallery-item:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: -2px;
}
.artifact-ribbon-style-gallery-swatch {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 28px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-xs, 6px);
  font-size: var(--ds-text-caption-13);
}
.artifact-ribbon-style-gallery-label {
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-micro-11);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* doc 597 P1-8 · doc 544 §20.5 전환 — 「붙여넣기 ▾」 팝오버. ★정정: 처음엔 이 자리에 "position만
   여기서 정하고 static top/left는 offsetParent 기준으로 인라인 계산한다"고 적었으나, §20.2가
   그 containing-block escape 트릭이 .artifact-ribbon-row 의 overflow/mask-image 와
   .artifact-ribbon 의 container-type:inline-size 조합에서 깨진다는 것을 실측으로 확정했다(리본
   박스 아래끝에서 잘림). 지금은 openArtifactRibbonPopoverPortal/closeArtifactRibbonPopoverPortal
   (§20.4 공통 경로)이 열릴 때마다 이 요소를 body 직속 포털 호스트로 옮기고 position:fixed 로
   좌표를 인라인 지정한다 — 아래 position:absolute 는 그 전(hidden 인 동안)의 정적 기본값일
   뿐이고, [data-ribbon-popover-portal="true"] 규칙(4429행)이 !important 로 이긴다. */
.artifact-ribbon-paste-menu {
  position: absolute;
  padding: 4px;
  width: max-content;
  min-width: 168px;
  background: var(--cg-menu-bg, var(--bg));
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-sm, 10px);
  box-shadow: var(--ds-shadow-md);
  z-index: 5;
}
.artifact-ribbon-paste-menu-item {
  display: block;
  width: 100%;
  padding: 6px 8px;
  border: none;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  text-align: left;
  cursor: pointer;
}
@media (hover: hover) {
  .artifact-ribbon-paste-menu-item:hover {
    background: var(--cg-hover);
  }
}
.artifact-ribbon-paste-menu-item:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: -2px;
}
/* ============================================================================
 * 함수 삽입 대화상자 (doc 597 §4.2 P1-6 · doc 544 §25)
 *
 * 상자 자체는 리포의 기존 네이티브 <dialog> 관례(.chatgpt-search-dialog 계열)와 같은 자리·같은
 * 모션 토큰을 쓴다 — 새 표면 어휘를 만들지 않는다. 다른 것은 «안»이다: 505개 목록은 훑는
 * 화면이라 좌(목록)/우(설명) 두 판으로 나누고, 함수 이름만 등폭으로 세워 눈이 이름 열을 따라
 * 내려가게 한다(엑셀 「함수 마법사」와 구글 시트가 같은 판단).
 *
 * ★색·반경·그림자·글자 크기는 전부 --ds-* / --cg-* 토큰이다. anti-drift 래칫 6범주 델타 0 이
 *   이 레인의 게이트라, 여기서 원시 hex/rgba 를 한 번만 써도 빨개진다.
 * ========================================================================== */
.sheet-function-dialog[open] {
  position: fixed;
  inset: 0;
  width: min(860px, calc(100vw - 24px));
  height: min(560px, calc(100vh - 48px));
  max-height: calc(100vh - 48px);
  margin: auto;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-lg);
  background: var(--cg-menu-bg, var(--bg));
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-body-14);
  box-shadow: var(--ds-shadow-lg);
  opacity: 0;
  transform: translate3d(0, 10px, 0) scale(0.985);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
}
.sheet-function-dialog[data-motion-state="open"] {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}
.sheet-function-dialog[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(0, 10px, 0) scale(0.985);
}
@media (prefers-reduced-motion: reduce) {
  .sheet-function-dialog[open] {
    transition: none;
    transform: none;
  }
}
.sheet-function-dialog::backdrop {
  background: var(--cg-scrim, var(--wizon-scrim, var(--line)));
}
.sheet-function-shell {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}
.sheet-function-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 14px 10px 18px;
}
.sheet-function-head h2 {
  margin: 0;
  font-size: var(--ds-text-title-18);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.sheet-function-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 18px 12px;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}
.sheet-function-controls input[type="search"] {
  flex: 1 1 auto;
  min-width: 0;
  height: 36px;
  padding: 0 12px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-sm);
  background: var(--cg-composer-bg, var(--bg));
  color: inherit;
  font: inherit;
  font-size: var(--ds-text-body-14);
}
.sheet-function-controls input[type="search"]:focus-visible,
.sheet-function-controls select:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}
.sheet-function-controls select {
  flex: 0 0 auto;
  height: 36px;
  max-width: 172px;
  padding: 0 8px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-sm);
  background: var(--cg-composer-bg, var(--bg));
  color: inherit;
  font: inherit;
  font-size: var(--ds-text-caption-13);
}
.sheet-function-body {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  flex: 1 1 auto;
  min-height: 0;
}
.sheet-function-list-pane {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  border-right: 1px solid var(--cg-menu-border, var(--line));
}
.sheet-function-count {
  margin: 0;
  padding: 8px 18px 6px;
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-micro-11);
}
/* 가상 스크롤: 스크롤러가 전체 높이를 «가짜 상자»(sizer)로 갖고, 실제 <li> 는 보이는 창만큼만
   존재한다. 두 값 다 인라인 style 속성이 아니라 CSS 커스텀 속성으로 넘어온다(JS 가
   style.setProperty) — anti-drift 래칫의 inline-style 범주가 `style="` 리터럴을 세기 때문이고,
   값 자체도 «몇 번째 행부터/몇 행인가»라는 의미로 남아 읽힌다. */
.sheet-function-scroller {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.sheet-function-sizer {
  height: calc(var(--sheet-function-total, 0) * var(--sheet-function-row-height, 38px));
}
.sheet-function-list {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  transform: translateY(calc(var(--sheet-function-offset, 0) * var(--sheet-function-row-height, 38px)));
}
.sheet-function-option {
  display: flex;
  align-items: baseline;
  gap: 10px;
  box-sizing: border-box;
  width: 100%;
  height: var(--sheet-function-row-height, 38px);
  padding: 0 18px;
  border: none;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.sheet-function-option-name {
  flex: 0 0 auto;
  font-family: var(--wds-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}
.sheet-function-option-abstract {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-micro-11);
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (hover: hover) {
  .sheet-function-option:hover {
    background: var(--cg-hover, var(--line));
  }
}
.sheet-function-option[aria-selected="true"] {
  background: var(--cg-hover, var(--line));
}
.sheet-function-option[aria-selected="true"] .sheet-function-option-name {
  color: var(--accent);
}
.sheet-function-option:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: -2px;
}
.sheet-function-detail {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
  min-width: 0;
  padding: 16px 18px;
  overflow-y: auto;
}
.sheet-function-signature {
  margin: 0;
  font-family: var(--wds-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.sheet-function-category-chip {
  align-self: flex-start;
  padding: 2px 8px;
  border-radius: var(--ds-radius-pill);
  background: var(--cg-hover, var(--line));
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-micro-11);
}
.sheet-function-description {
  margin: 0;
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-caption-13);
  line-height: 1.55;
}
.sheet-function-params {
  margin: 0;
  padding: 0;
}
.sheet-function-params dt {
  margin-top: 8px;
  font-family: var(--wds-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}
.sheet-function-params dd {
  margin: 2px 0 0;
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-micro-11);
  line-height: 1.5;
}
.sheet-function-doc-link {
  align-self: flex-start;
  margin-top: auto;
  color: var(--accent);
  font-size: var(--ds-text-micro-11);
}
.sheet-function-empty {
  margin: 0;
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-caption-13);
}
.sheet-function-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  border-top: 1px solid var(--cg-menu-border, var(--line));
}
.sheet-function-hint {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-micro-11);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sheet-function-cancel,
.sheet-function-confirm {
  height: 34px;
  padding: 0 16px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-pill);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  cursor: pointer;
}
.sheet-function-cancel {
  background: transparent;
  color: var(--cg-text, var(--text));
}
.sheet-function-confirm {
  border-color: transparent;
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
  font-weight: 600;
}
.sheet-function-confirm[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}
@media (hover: hover) {
  .sheet-function-cancel:hover {
    background: var(--cg-hover, var(--line));
  }
}
.sheet-function-cancel:focus-visible,
.sheet-function-confirm:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 2px;
}
/* 좁은 폭 — 두 판을 세로로 쌓고 상자를 전면으로 편다. 설명 판이 목록 아래로 내려가면 선택이
   즉시 안 보이므로 목록 높이를 창의 절반 이하로 묶는다.
   ★분기점은 정본 900px 이다(design-system breakpoints.md §2, tests/test_design_system_contract.py
     가 비정본 폭을 막는다). 마침 이 값이 아티팩트 패널 자신이 도킹 카드 ↔ 풀스크린으로 갈리는
     지점과 같아서, 「패널이 전면일 때 모달도 전면」이 한 줄로 맞는다. */
@media (max-width: 900px) {
  .sheet-function-dialog[open] {
    width: 100vw;
    /* ★max-width 를 명시적으로 푸는 이유: UA 기본 스타일시트의 `dialog { max-width: calc((100% -
       6px) - 2em) }` 가 살아 있어 100vw 를 조용히 깎는다(실측 390px 뷰포트에서 상자 폭 356px —
       가장자리에 설명 없는 여백이 생겼다). 데스크톱은 상한이 860px 이라 이 UA 값에 안 닿는다. */
    max-width: none;
    height: 100dvh;
    max-height: 100dvh;
    border: 0;
    border-radius: 0;
  }
  .sheet-function-body {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
  }
  .sheet-function-list-pane {
    border-right: 0;
    border-bottom: 1px solid var(--cg-menu-border, var(--line));
  }
}
/* 정렬/표시형식/자동합계 묶음 — 기본(중간·최대 폭)은 그냥 행의 일부로 흐름, 컴팩트(≤560px)에서만
   아래 @container 규칙이 숨기고 "⋯" 팝오버로 대체한다(마크업 복제 없이 같은 DOM을 재배치). */
.artifact-ribbon-overflow-group {
  display: flex;
  align-items: center;
  gap: 2px;
}
.artifact-ribbon-overflow-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-left: auto;
  flex: 0 0 auto;
  border: none;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  color: var(--cg-text-soft, var(--muted));
  cursor: pointer;
}
@media (hover: hover) {
  .artifact-ribbon-overflow-toggle:hover {
    background: var(--cg-hover, #ececec);
    color: var(--cg-text, var(--text));
  }
}

.artifact-ribbon-overflow-toggle:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 1px;
}
/* ★R2 — 같은 동률 패배(실측 20×20, 28px 토글 안에서 여백이 2px 밖에 안 남았다). */
.chatgpt-reference-v1 .artifact-ribbon-overflow-toggle .wds-inline-icon {
  width: 16px;
  height: 16px;
}
/* 최대(>900px): 그룹 라벨 표시(스펙 항목 4 "최대: 그룹 라벨 표시"). */
@container artifact-ribbon (min-width: 901px) {
  /* 목업 3층 「각 그룹 하단에 그룹 라벨」 — 최대 폭에서만. 중간 폭(561~900px)은 아이콘만 있는
     1줄이어야 하므로 라벨을 켜면 리본이 두 줄이 되어 그리드가 그만큼 짧아진다.
     ★여기서 «버튼별» 라벨 텍스트(.artifact-ribbon-label-text)는 켜지 않는다. doc 171 A3-a 때는
       그룹 상자가 없어 버튼 텍스트가 「그룹 라벨 표시」의 대역이었지만, P1 이 진짜 그룹 라벨을
       들이면서 둘을 함께 켜면 홈 탭 한 줄이 1561px 이 된다 — 컨테이너는 1382px 이고
       .artifact-ribbon-row 는 overflow:hidden 이며 "⋯" 토글은 이 폭에서 display:none 이라,
       「표시 형식」과 「셀」 그룹이 «보이지도 않고 닿을 수도 없게» 잘렸다(1440px 실렌더로 확인,
       2026-08-27). 엑셀도 최대 폭에서 아이콘 버튼에 글자를 달지 않고 그룹 라벨만 단다.
       버튼 텍스트는 컴팩트 "⋯" 드롭다운에서만 남는다(그 자리에선 메뉴 항목 이름이라 필요하다).
       접근성 이름은 각 버튼의 aria-label 이 계속 들고 있다. */
  .artifact-ribbon-groupbox-label {
    display: block;
  }
}
/* 컴팩트(≤560px): 정렬/표시형식/자동합계를 "⋯" 팝오버 뒤로 감춘다(스펙 항목 4 "기본: 컴팩트 1줄 —
   핵심 아이콘만 + 오버플로 팝오버"). 열렸을 때는 같은 DOM을 세로 드롭다운으로 절대배치 전환 —
   버튼을 복제하지 않으므로 aria-pressed 등 상태 동기화가 저절로 하나만 존재한다. */
@container artifact-ribbon (max-width: 560px) {
  .artifact-ribbon-overflow-group {
    display: none;
  }
  .artifact-ribbon-overflow-toggle {
    display: inline-flex;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    position: absolute;
    top: 100%;
    right: 8px;
    margin-top: 4px;
    padding: 6px;
    gap: 2px;
    width: max-content;
    background: var(--cg-menu-bg, var(--bg));
    border: 1px solid var(--cg-menu-border, var(--line));
    border-radius: var(--ds-radius-sm, 10px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    z-index: 5;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-sep {
    display: none;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-label-text {
    display: inline;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-btn {
    justify-content: flex-start;
  }
  /* 드롭다운 안에서는 그룹 상자도 세로로 편다 — 가로 배치인 채로 절대배치 메뉴에 들어가면 메뉴가
     리본보다 넓어져 화면 밖으로 나간다. 그룹 라벨은 여기서 메뉴의 섹션 제목 노릇을 하므로 켠다. */
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-groupbox,
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-groupbox-body,
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-group {
    flex-direction: column;
    align-items: stretch;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-groupbox-label {
    display: block;
    order: -1;
    padding: 4px 8px 2px;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-splitbtn {
    align-items: stretch;
  }
}
/* 폰(≤520px) — doc 544 M1·M2. 520 은 이 앱이 이미 44곳에서 쓰는 폰 경계다(§5.1, 새 브레이크포인트
   발명 금지).
   ★이 블록은 위 @container 블록 «뒤»에 와야 한다. 미디어 쿼리는 특이도를 올리지 않으므로
     .artifact-ribbon-overflow-group{display:none}(컨테이너, 클래스 1개)과 아래
     .artifact-ribbon-overflow-group{display:flex}(미디어, 클래스 1개)는 소스 순서로 갈린다 —
     이 파일이 이미 상태바 토글 높이에서 같은 함정을 실측으로 겪었다(390px 히트영역 44×22).
   ★폰에서 "⋯" 오버플로를 «끈다»: 가로 스크롤이 오버플로를 대신하므로 두 메커니즘이 겹치면 같은
     명령이 두 군데 생긴다(§5.2-M2). 토글 버튼을 숨기는 것만으로는 부족하다 — 데스크톱 폭에서
     열어둔 채로 창을 좁히면 data-ribbon-overflow-open 이 남으므로 그 상태의 절대배치도 되돌린다. */
@media (max-width: 520px) {
  /* doc 670 T1 polish2(build, §2.2) — 리본 자체(`#f4f4f4`)가 트랙(`#ececec` 급)과 거의
     같은 밝기라 «오목»이 안 읽혔다(why-document-like.md §1 후보⑥). 리본을 캔버스 톤으로
     올려 트랙이 한 단 «아래」로 보이게 한다. 이 규칙은 ≤520px 안에서만 이겨 데스크톱
     1440 은 무변화다(T-9 음성 대조). */
  .artifact-ribbon {
    background: var(--wizon-ai-canvas);
  }
  /* M1 — 탭 줄을 가로 스크롤 스냅 칩 행으로. 활성 칩은 «채워진 pill»이다(밑줄이 아니다: 터치에서
     밑줄은 히트영역이 어디까지인지 안 보인다). 좌우 페이드 마스크로 «더 있음»을 알린다. */
  .artifact-ribbon-tabs {
    /* doc 670 T1 다듬기(judge) — 승인 목업 `.ribbon-tabs` 의 gap 은 4px(--s1) 이다. 실측
       RED 는 6px 로 4/8/12/16 스케일 밖이었다. 이 2px 가 탭 6개에 곱해져 scrollWidth 를
       430→420 으로 줄인다(390 실측: 넘침 40px → 30px).
       ★목업의 `scroll-snap-type: x mandatory` 는 **기각**했다. ⓐ doc 544 M1 계약
       (test_static_contract::test_ribbon_phone_rules_use_the_existing_520_breakpoint_and_kill_the_overflow)
       이 proximity 를 고정하고 있고, ⓑ 실측상 이 탭 수·폭에서는 둘의 정착 위치가 같다
       (mandatory/proximity 모두 끝까지 밀면 scrollLeft 30 에서 정착하고 「보기」가 완전히
       보인다 — 20px 만 민 경우도 둘 다 30). 차이가 0 인 변경으로 남의 계약을 흔들지 않는다. */
    gap: var(--ds-space-1);
    /* polish2(§2.2) — 좌우 8px 부모(.artifact-ribbon) 패딩을 더 이상 상쇄하지 않는다(아래
       margin 에서 좌우 값을 뺐다). 트랙 자체가 «면」이 되어야 하므로 부모 안쪽에 374×52
       @x=8 로 앉힌다(390 − 부모 좌우 패딩 16 = 374) — 예전엔 이 요소가 390 까지 번져
       배경이 없어 «트랙」이라는 개념이 없었다(리본 배경이 그대로 비쳤다). */
    padding: var(--ds-space-1);
    margin: -4px 0 4px;
    align-items: center;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    /* R6 — 이 스크롤러는 가로만 먹는다. 그리드 컨테이너의 touch-action 은 Univer 소유라 절대
       덮어쓰지 않는다(§8-7: 회귀 증상은 «셀이 안 눌린다»로 나타난다). */
    touch-action: pan-x;
    /* 마스크는 «알파»만 읽는다 — currentColor 를 쓰면 raw 색 리터럴(#000)을 새로 들이지 않고도
       같은 페이드가 나온다. 색상 부채 래칫은 이 자리의 리터럴도 그대로 센다. */
    mask-image: linear-gradient(90deg, transparent 0, currentColor 12px, currentColor calc(100% - 12px), transparent 100%);
    /* polish2(§2.2) — 트랙 자체의 면. 오목(--wizon-ai-sunk)이려면 부모(.artifact-ribbon,
       위에서 --wizon-ai-canvas 로 밝혔다)보다 트랙이 어두워야 한다 — --wizon-ai-surface-track
       은 라이트에서 --wizon-ai-hover-bg 별칭, 다크에서 --wizon-ai-hover-soft-bg 별칭(토큰
       정합 후속, tokens.md §2.1 — 다크 오버라이드 누락 함정 수정)이라 새 색 리터럴 0. */
    background: var(--wizon-ai-surface-track);
    border-radius: var(--wizon-ai-radius-popover);
    box-shadow: var(--wizon-ai-sunk);
  }
  .artifact-ribbon-tabs::-webkit-scrollbar {
    display: none;
  }
  .artifact-ribbon-tab {
    scroll-snap-align: start;
    height: 44px; /* R4 — 히트영역 44px */
    /* polish2(§2.2) — padding 14→10. min-width 를 64→56 으로 낮춰도(아래) 최장 라벨
       «데이터»(3자 × 12px ≈ 36 + 20 = 56) 가 들어간다 — 옆 주석의 「44+20=64 를 바닥」
       근거는 padding 14 기준이었다(padding 10 이면 56 으로 충분, 실측). */
    padding: 0 10px;
    border: none;
    border-radius: var(--wizon-ai-menu-radius);
    margin-bottom: 0;
    /* polish2(§2.2, why-document-like.md §1 후보⑥) — 비선택 배경을 지운다. 트랙과 같은
       색이던 예전 값(--cg-hover-soft)은 «칠해져 있는데 안 보인다»(트랙=비선택탭=동색)를
       만들었다 — 이제 트랙(--wizon-ai-surface-track)이 자기 면을 갖췄으니 비선택 탭은 투명해도
       읽힌다. 선택 탭만 아래서 --wizon-ai-surface 로 «얹힌 알약」이 된다. */
    background: transparent;
    /* polish2(§2.2) — radius 가 999px(pill) 에서 --wizon-ai-menu-radius(12px)로 바뀌어
       «폭≤높이 원 붕괴» 함정은 더 이상 해당하지 않는다(12px 라운드 사각은 폭이 좁아도
       원으로 붕괴하지 않는다). min-width 는 최장 라벨 «데이터»(3자 × 12px ≈ 36 + padding
       20 = 56) 를 담는 바닥으로만 남긴다. */
    min-width: 56px;
    /* polish2(§2.2) — 비선택 500 · 선택 600(아래) 두 단으로 위계를 준다. 팔레트
       (400/500/600) 안(test_typography_contract T4). */
    font-weight: 500;
    color: var(--wizon-ai-text-muted);
    transition: background 120ms ease, color 120ms ease, box-shadow 120ms ease;
  }
  .artifact-ribbon-tab:active:not([aria-selected="true"]) {
    background: var(--wizon-ai-surface-press);
  }
  /* polish2(§2.2, why-document-like.md §1 후보⑥) — 선택 탭을 «트랙 위에 얹힌 흰 알약」으로.
     예전 값(--cg-send-bg/-fg, `#0d0d0d`/`#fff`)은 트랙과 별개의 두 번째 잉크였다 — 이제
     트랙 자체가 어두운 면이므로 선택 탭은 반대로 밝은 면(--wizon-ai-surface)에 그림자로
     띄운다(R14 Apple Stocks 문법, why-document-like.md §2.1). */
  .artifact-ribbon-tab[aria-selected="true"] {
    background: var(--wizon-ai-surface);
    color: var(--wizon-ai-text-primary);
    box-shadow: var(--wizon-ai-lift-2);
    font-weight: 600;
  }
  /* polish2-fix(다크 세그먼트 반전, K1 k2-plan §5 위험#5 실측) — 다크에서
     --wizon-ai-surface 는 --wizon-ai-canvas 와 같은 #000000(셸 토큰 다크 블록,
     shared/wds/wizon-ai-shell-tokens.css:131-132)이다. 트랙(--wizon-ai-surface-track,
     당시 다크 오버라이드 없이 --wizon-ai-hover-bg 다크 #1f1f1f 로 캐스케이드 — 토큰 정합
     후속에서 --wizon-ai-hover-soft-bg #212121 명시 오버라이드로 바뀌었다, tokens.md §2.1)
     보다 선택 알약이 더 어두워져 «얹힌 면»이 아니라 «구멍」으로 읽혔다(빌드 캡처
     build-base-390-dark.png 실측 — 선택 탭이 거의 검정).
     새 토큰을 만들지 않고(에미터 범위 밖) 이미 존재하는 --wizon-ai-surface-raised(다크 #2a2a2a,
     트랙보다 밝음)을 다크 전용으로 얹는다 — box-shadow(--wizon-ai-lift-2)는 이미 테마별로
     갈라져 있어 그대로 둔다. 라이트는 --wizon-ai-surface(#ffffff, 트랙 #ececec보다
     밝음)가 이미 올바른 방향이라 손대지 않는다(라이트 diff 0). */
  [data-theme="dark"] .artifact-ribbon-tab[aria-selected="true"] {
    background: var(--wizon-ai-surface-raised);
  }
  /* M2 — 그룹 본문을 가로 스크롤 툴바 1행으로. 가로 스크롤 자체는 이제 «모든 폭»의 기본값이라
     (위 .artifact-ribbon-row 참조) 여기서는 터치에만 필요한 것만 더한다: R6 의 pan-x 와 관성
     스크롤. 그룹 라벨은 숨기고 그룹 사이 세로 구분선만 남긴다. */
  .artifact-ribbon-row {
    -webkit-overflow-scrolling: touch;
    touch-action: pan-x;
  }
  .artifact-ribbon-groupbox-label {
    display: none;
  }
  .artifact-ribbon-overflow-group {
    display: flex;
  }
  .artifact-ribbon-overflow-toggle {
    display: none;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group {
    position: static;
    flex-direction: row;
    align-items: center;
    margin-top: 0;
    padding: 0;
    width: auto;
    background: transparent;
    border: none;
    box-shadow: none;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-sep {
    display: block;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-label-text {
    display: none;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-groupbox,
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-groupbox-body,
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-group {
    flex-direction: row;
    align-items: center;
  }
  .artifact-ribbon[data-ribbon-overflow-open="true"] .artifact-ribbon-overflow-group .artifact-ribbon-groupbox-label {
    display: none;
  }
  /* R4 — 아이콘 전용이어도 터치 타깃은 44×44 이상. select 는 손가락으로 열어야 하므로 같은 높이. */
  .artifact-ribbon-btn,
  .artifact-ribbon-splitbtn-main {
    min-width: 44px;
    height: 44px;
    justify-content: center;
  }
  .artifact-ribbon-splitbtn-main {
    min-width: 36px;
  }
  .artifact-ribbon-splitbtn-caret {
    width: 24px;
    height: 44px;
  }
  .artifact-ribbon-select {
    height: 44px;
  }
  .artifact-ribbon-select--font-family {
    max-width: 108px;
  }
  .artifact-ribbon-select--font-size {
    max-width: 68px;
  }
}
/* 시트 본문(엑셀 새창 골격, doc 171 A1 — 항목 4): 고정 min-height 420px 대신 flex:1 1 auto +
   min-height:0로 창의 남은 세로 공간을 그대로 채운다. 부모 .chatgpt-route-page-inner가 이제
   flex-column(위 규칙)이라 이 flex:1 1 auto가 실제로 늘어날 공간을 받는다 — 그 전에는 inner가
   display:grid(콘텐츠 높이만큼만)라 420px 고정값 밑으로 빈 여백만 생겼었다(실렌더로 확인된
   회귀 원인). border/radius 제거 — 창 자체가 시트이므로 엣지투엣지, 카드 테두리를 이중으로
   두지 않는다. */
.artifact-route-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
/* 포털 전역 폼 스타일(select,input,textarea { min-height:42px })이 Univer 내부 입력에 새면 28px
   수식바 행 안의 이름상자가 42px로 부풀어 아래가 잘린다 — min-height만 넓게(모든 Univer 입력)
   국소 해제한다. 이건 무해하다: 어느 Univer 입력이든 42px 하한이 사라져도 자기 높이를 쓴다. */
.artifact-sheet-container input,
.artifact-sheet-container select,
.artifact-sheet-container textarea {
  min-height: 0;
}
/* 포털 전역 input의 border-radius(14px)·border·box-shadow(포커스 링)·background가 새면 Univer
   이름상자(name box)만 라운드+파란 포커스 링이 떠서 옆 수식창(Univer 기본 스타일 그대로)과 따로
   논다(실사용자 스크린샷 — doc 171 A2 fix). 이 시각 중화는 반드시 이름상자 하나로만 스코프한다:
   Univer의 다른 내부 입력(이름 정의 Insert/Edit 다이얼로그, 범위 선택기 등)까지 테두리·포커스
   표시를 지우면 WCAG 2.4.7(포커스 가시성) 위반이 된다(코드리뷰 HIGH). 이름상자 마커는 실브라우저
   DOM으로 확정: 수식바([data-u-comp="formula-bar"]) 안의 [data-u-comp="defined-name"] > input.
   오른쪽 구분선 테두리는 이 input이 아니라 defined-name 컨테이너 DIV에 있으므로 border:0으로
   지워지지 않는다. */
#artifactSheetContainer [data-u-comp="defined-name"] input {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
  outline: 0;
}
#artifactSheetContainer [data-u-comp="defined-name"] input:focus,
#artifactSheetContainer [data-u-comp="defined-name"] input:focus-visible {
  box-shadow: none;
  outline: 0;
}
/* 수식 입력줄 토글 (doc 544 §3.2 「보기」 탭 「표시」 그룹, P2) — Univer 네이티브 수식바(이름상자
   포함 한 행)를 CSS 층에서만 숨긴다. 벤더의 sheets-ui `formulaBar` 는 프리셋 «생성 시» 옵션이라
   런타임 토글 API 가 없다(app.js 수식바 토글 블록의 벤더 실측 주석). data-formula-bar-hidden 은
   refreshArtifactFormulaBarToggleState 가 세우는 패널 속성. display:none 뒤 그리드가 그 높이를
   되찾는지는 스모크 게이트(portal-sheet-engine-browser-smoke-gate)가 실렌더 px 로 잰다. 이
   셀렉터가 버전업에 늙어 매칭이 깨지면 수식바가 «다시 보이는» 안전 실패다(doc 544 §8-3 관례). */
#artifactPanel[data-formula-bar-hidden="true"] #artifactSheetContainer [data-u-comp="formula-bar"] {
  display: none;
}

/* 폰 수식바 축약형 (doc 544 §5.2-M3 / §10 구현 기록) — 벤더 DOM 은 옮기지 않는다(§5.2-M3:
   bottom sheet 명시 기각, §8-3 셀렉터 취약성). 축약(기본): 이름상자 좌측 고정 폭을 줄여
   fx 1줄 표시 폭을 최대화한다 — 폭의 정본은 벤더 유틸(.univer-w-24 = 96px 이름상자,
   .univer-w-\[100px\] 래퍼)이고 여기는 id 셀렉터 특이도로 이긴다. 탭하면 app.js 가 패널에
   data-formula-bar-expanded="true" 를 세우고 아래 규칙이 수식바를 3줄 높이로 늘린다 —
   벤더 자체 ResizeObserver 가 컨테이너 크기를 따라 doc 캔버스를 재배치한다(실측 2026-08-30,
   390×844 헤드리스 크롬: height 84px 오버라이드 → univer-doc-main-canvas 27→83px, 그리드
   무손상). 높이 전환은 벤더가 이미 갖고 있다(univer-transition-[height] 0.15s linear) —
   새 전환을 만들지 않는다. fx «말줄임(…)»은 불가: 값이 canvas 텍스트라(실측 136×27)
   CSS text-overflow 가 닿지 않는다 — 1줄 클리핑 + 탭 확장으로 대체(§10 편차 기록).
   셀렉터가 벤더 버전업으로 늙으면 아무 규칙도 안 붙고 내장 수식바가 원형 그대로 보인다
   — «내장 위젯이 다시 보이는» 안전 실패(§8-3 관례). touch-action 은 만지지 않는다(R6). */
@media (max-width: 520px) {
  /* 이름상자 축소 — 64px 는 «AA100» 급 셀 참조가 12px 폰트로 들어가는 폭. 래퍼(:has)와
     이름상자 본체를 함께 줄여야 한다: 래퍼만 줄이면 본체(96px)가 밖으로 넘친다(실측).
     transition: none 인 이유 — 이름상자는 벤더가 univer-transition-all(all 0.15s)을 달아
     두어, 데스크톱→폰 리사이즈 순간 96→64 width «전환»이 0.15s 걸린다. 화면은 이름상자가
     움찔 줄어드는 애니메이션이고, 스모크 게이트는 리사이즈 직후 판독에서 전환 중간값
     (실측 2026-08-30: 96/95.83/95.77px)을 읽어 빨개진다. 폰에는 hover 도 없어(R1) 이
     전환이 하는 일이 없다 — 끄면 리사이즈 즉시 최종 폭이 서고 판독이 결정적이 된다. */
  #artifactSheetContainer [data-u-comp="formula-bar"] > div:has([data-u-comp="defined-name"]),
  #artifactSheetContainer [data-u-comp="defined-name"] {
    width: 64px;
    transition: none;
  }
  /* 탭 확장 — 2~3줄. 84px 는 벤더 자체 확장 상태(.univer-h-20 = 80px)와 같은 급이다.
     84px 고정값이라 --wizon-vvh 불요(§8-8 은 100dvh 류 전면 배치에만 해당). */
  #artifactPanel[data-formula-bar-expanded="true"] [data-u-comp="formula-bar"] {
    height: 84px;
  }
  /* Univer 는 바의 height 전환을 먼저 칠하고 doc canvas 의 inline height 는 자체
     ResizeObserver 에서 다음 프레임에 갱신한다. 그 한 프레임 동안 캔버스 아래가 비는 것을
     막아, 실제 편집 면이 확장 바를 즉시 채우게 한다. min-height 만 두므로 벤더가 다음
     프레임에 쓰는 정확한 bitmap/client height 는 그대로 권위가 있고, 축약 상태에는 닿지 않는다. */
  #artifactPanel[data-formula-bar-expanded="true"]
    [data-u-comp="formula-bar"] canvas[data-u-comp="render-canvas"] {
    min-height: calc(100% - 1px);
  }
}

/* 폰 수식바 키보드 추종 (doc 635 §15 구현 기록, 3단계 조각 3) — data-formula-bar-docked 는
   위 data-formula-bar-expanded(수식바 자체를 탭해 펼치는 별개 제스처, §5.2-M3)와 상태를
   공유하지 않는다 — doc 635 §9.9 가 그 둘을 커플링했다가 실제 편집 상태와 상관을 보장 못 해
   되돌린 바로 그 함정을 피한다(app.js syncArtifactPhoneFormulaBarDock). #artifactPanel 은
   폰 폭에서 이미 position:fixed; inset:0 이라(§4.2 「컴포저 중앙 히트」 표) 그 자손의
   position:fixed 는 포털 없이도 사실상 뷰포트 기준으로 움직인다 — §9.2 의 컨테이너 쿼리
   containing-block 함정은 #artifactSheetContainer 조상 체인에 container-type 이 없어
   해당하지 않는다(코드 검토로 확인). --wizon-kb-inset 은 §4.4 가 이미 쓰는 키보드 높이
   출처(syncMobileViewportMetrics)를 그대로 재사용한다 — 새 CSS 변수를 만들지 않는다. */
@media (max-width: 520px) {
  #artifactPanel[data-formula-bar-docked="true"] #artifactSheetContainer [data-u-comp="formula-bar"] {
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--wizon-kb-inset, 0px);
    z-index: 121;
    background: var(--cg-bg);
    box-shadow: 0 -1px 0 var(--cg-menu-border, var(--line));
  }
}

.artifact-sheet-container {
  flex: 1 1 auto;
  min-height: 0;
  overflow-x: auto;
  overflow-y: auto;
}
.artifact-sheet-status {
  margin: 0;
  padding: 24px 4px;
  color: var(--cg-muted, var(--muted, #6b7280));
  font-size: var(--ds-text-caption-13);
  text-align: center;
}
.artifact-sheet-status--error {
  color: var(--ds-badge-warn-fg, #b45309);
}
/* 창 최하단 상태바 — 다운로드 버튼은 타이틀바로 옮겨갔으니(위) 여기는 "준비" + 셀 개수만 남는다.
   줌/시트탭은 Univer가 그리드 안에 자체적으로 그리므로(실렌더로 확인) 여기서 중복하지 않는다. */
.artifact-status-bar {
  display: flex;
  align-items: center;
  /* §12 — 목업 `.statusbar{gap:22px}`(환산 16px). 10px 는 「준비 / 선택 / 토글」이 한 덩어리로
     붙어 읽혀 어디까지가 한 항목인지 안 보였다. 폰(≤520px)은 아래 블록이 따로 좁힌다. */
  gap: 14px;
  height: 28px;
  flex: 0 0 auto;
  padding: 0 4px;
  border-top: 1px solid var(--cg-menu-border, var(--line));
  background: var(--cg-hover-soft, #f4f4f4);
  color: var(--cg-muted, var(--muted, #6b7280));
  font-size: var(--ds-text-caption-12);
}
.artifact-status-ready {
  color: var(--cg-text-soft, var(--muted));
  font-weight: 500;
}
.artifact-cell-count {
  color: var(--cg-muted, var(--muted, #6b7280));
  font-size: var(--ds-text-caption-12);
  /* 실측 회귀 (2026-08-29, iOS Safari 시뮬레이터 390pt · 크롬 375px): 선택 집계가 길어지면
     flex 가 이 칸을 18px 까지 줄였고, 기본값 `white-space: normal` 이라 "5행 × 4열" 이 세 줄로
     접혀 상태바가 28px → 63px 로 자랐다. 짧은 고정 라벨이므로 줄바꿈도 축소도 허용하지 않는다 —
     양보하는 쪽은 폰 블록에서 말줄임을 받는 선택 집계다(doc 544 M6). */
  flex: 0 0 auto;
  white-space: nowrap;
}
.artifact-workbook-save-status {
  min-width: 0;
  overflow: hidden;
  color: var(--cg-muted, var(--muted));
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 선택 집계(doc 171 A2) — 상태바 우측, 엑셀과 동일하게 평균/개수/합계. 빈 문자열이면(단일 셀·빈
   선택) 완전히 사라져 "준비"/셀개수 사이 남는 빈 여백이 안 생긴다. */
.artifact-selection-summary {
  margin-left: auto;
  color: var(--cg-muted, var(--muted, #6b7280));
  font-size: var(--ds-text-caption-12);
  white-space: nowrap;
}
.artifact-selection-summary:empty {
  display: none;
}
/* 상태바의 여유 공간을 저장상태 <output>이 흡수하게 해서 선택집계 + 눈금선 토글이 항상 오른쪽에
   붙게 한다(doc 544 M6 「좌측 요약 · 우측 토글」). margin-left:auto를 둘 다에 주면 남는 공간이
   두 auto 마진에 반씩 나뉘어 선택집계가 가운데로 떠 버린다 — 그래서 «미는 쪽»을 하나로 만든다.
   .artifact-status-bar로 스코프해 컴포저 쪽 같은 클래스에는 닿지 않는다. */
.artifact-status-bar .artifact-workbook-save-status {
  flex: 1 1 auto;
}
/* 눈금선 토글 (doc 544 P0, 목업 8층) — 리본 「보기」 그룹과 같은 명령을 상태바에서도 누른다.
   aria-pressed="true" = 눈금선이 보인다(= 문서 모드 OFF). 상태 표현은 버튼 배경이 아니라
   안의 스위치(.artifact-status-toggle-switch)가 맡는다 — §11.3. P0 의 «pressed=false 쪽
   배경 강조»는 오너 실화면 결함 보고로 폐기됐다(§11.2-(b)): 그 강조색이 hover 와 같은
   토큰(--cg-hover)이라 마우스가 올라간 채 클릭하는 순간에는 두 상태가 픽셀 하나도 다르지
   않았다. 상태(스위치/--cg-send-bg 쌍)와 hover(--cg-hover)의 토큰을 겹치지 않게 유지하라. */
.artifact-status-gridlines-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  flex: 0 0 auto;
  height: 22px;
  padding: 0 6px;
  border: none;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  /* ★--cg-* 토큰에 raw 폴백을 달지 않는다. 색상 부채 래칫(design-system/governance/
     anti-drift-static-baseline.json, raw-color 상한 961)은 var() «안»의 리터럴도 그대로 센다 —
     이 블록이 처음 착지했을 때 폴백 5개(#6b7280 ×2 · #ececec ×2 · #2f6bff ×1)만으로 상한을
     966 으로 넘겨 파이프라인 #5077 을 붉혔다. 토큰은 body 의 .chatgpt-reference-v1 에서
     정의되므로(styles.css:14876 근방) 이 패널에서 항상 해석된다 — 폴백은 값이 아니라 부채였다.
     이 파일의 다수 관례도 폴백 없는 형태다: var(--cg-muted) 167곳 · var(--cg-hover) 38곳. */
  color: var(--cg-muted);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  white-space: nowrap;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.artifact-status-gridlines-toggle .wds-inline-icon {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}
/* 스위치 (doc 544 §11.3 — 목업 sheet-concept-a 의 .tg 그대로): 트랙 + ::after 노브.
   켜짐(aria-pressed="true" = 눈금선 보임) = 트랙이 --cg-send-bg 로 차고 노브가 오른쪽으로
   이동. 노브 이동은 transform 만 쓴다(레이아웃 속성 애니메이션 금지 관례 — left 를 굴리면
   합성기 밖에서 프레임을 만든다). 색 토큰에 raw 폴백을 달지 않는 것은 위 토글 블록과 같은
   이유(색상 부채 래칫은 var() «안»의 리터럴도 그대로 센다).
   ★리본 쪽 눈금선/수식바 토글의 «반전 강조»는 여기서 삭제됐다(§11.2-(b) — hover 와 같은
     토큰이라 클릭 순간 시각차 0). 이제 공용 눌림 규칙(.artifact-ribbon-btn[aria-pressed=
     "true"] = --cg-send-bg 쌍)을 굵게·틀 고정과 똑같이 탄다 — 별도 규칙 자체가 없다. */
.artifact-status-toggle-switch {
  position: relative;
  width: 26px;
  height: 14px;
  flex: 0 0 auto;
  border-radius: var(--ds-radius-pill);
  background: var(--cg-menu-border, var(--line));
  transition: background 120ms ease;
}
.artifact-status-toggle-switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 10px;
  height: 10px;
  /* 10px 정사각 + pill 토큰 = 원. 50% 리터럴은 raw-border-radius 래칫을 +1 시킨다(실측:
     이 블록이 처음 착지했을 때 상한 168 → 169 로 게이트가 빨개졌다). */
  border-radius: var(--ds-radius-pill);
  background: var(--bg);
  transition: transform 120ms ease;
}
/* §12 — 켜짐 트랙을 목업 `.tg.on{background:#155eef}` 의 파랑으로. §11.3 이 --cg-send-bg 를
   고른 이유(«hover 와 겹치지 않는 짝 토큰»)는 그대로 유효하고, --ad-blue 도 같은 조건을
   만족한다: hover(--cg-hover)와 다르고, 다크에서 --wds-blue-400 으로 스스로 밝아지며,
   꺼짐 트랙(--cg-menu-border)과도 라이트/다크 양쪽에서 갈린다. 리본 눌림 강조와 «같은 파랑»을
   쓰는 것이 요점이다 — 한 화면에서 «켜짐»이 두 가지 색이면 사용자가 규칙을 못 세운다.
   ★치수(26×14 트랙 / 10px 노브 / translateX(12px))는 §11.3 그대로 둔다: 목업 34×18 은
     2000px 캔버스 기준이고 실화면 환산(×0.72)이 24.5×13 이라 이미 목업 값이다. */
.artifact-status-gridlines-toggle[aria-pressed="true"] .artifact-status-toggle-switch {
  background: var(--ad-blue);
}
.artifact-status-gridlines-toggle[aria-pressed="true"] .artifact-status-toggle-switch::after {
  transform: translateX(12px);
}
.artifact-status-gridlines-toggle:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 1px;
}
.artifact-status-gridlines-toggle:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
/* 페이지 업/다운 (doc 558 ⓒ) — 기본은 «없는 것»이다. 데스크톱 상태바에는 이 두 칸이 들어갈
   자리도 없고(M6 가 폰에서 컨트롤 5개를 2개로 줄인 것과 같은 이유) 넓은 화면에는 이미 휠과
   Univer 자체 스크롤바가 있다. 폰 규칙은 아래 @media (max-width: 520px) 블록이 켠다.
   ★이 기본 규칙이 그 미디어 블록보다 «앞»에 있어야 한다: 특이도가 같아(클래스 1개) 소스
   순서가 이기고, 뒤에 두면 display:none 이 폰 규칙을 덮어써 버튼이 영영 안 보인다 —
   아래 「★아래 두 블록은 «기본 규칙 뒤»에 와야 한다」 주석이 같은 함정을 height 로 이미
   실측해 두었다.
   색 토큰에 raw 폴백을 달지 않는 것은 바로 위 눈금선 토글과 같은 이유다(색상 부채 래칫은
   var() «안»의 리터럴도 그대로 센다). --ds-radius-xs 의 길이 폴백만 그 규칙과 동일하게 둔다. */
.artifact-status-page-scroll {
  display: none;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border: none;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  color: var(--cg-muted);
  font: inherit;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.artifact-status-page-scroll .wds-inline-icon {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
}
.artifact-status-page-scroll:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 1px;
}
.artifact-status-page-scroll:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
/* 줌 (doc 597 P1-3 · doc 544 §13) — 엑셀 상태줄 맨 오른쪽의 «− / 배율 / +». 슬라이더는 쓰지
   않는다: 28px 상태바에 슬라이더를 넣으면 트랙이 6px 로 눌려 조준이 안 되고, 엑셀에서도 실제로
   손이 가는 것은 양끝의 −/+ 다.
   색 토큰에 raw 폴백을 달지 않는 것은 위 눈금선 토글·페이지 스크롤과 같은 이유다(색상 부채
   래칫은 var() «안»의 리터럴도 그대로 센다). --ds-radius-xs 의 «길이» 폴백만 그 관례대로 둔다. */
.artifact-status-zoom {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
}
.artifact-status-zoom-btn,
.artifact-status-zoom-level {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  height: 22px;
  border: none;
  border-radius: var(--ds-radius-xs, 6px);
  background: transparent;
  color: var(--cg-muted);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.artifact-status-zoom-btn {
  width: 22px;
  padding: 0;
}
.artifact-status-zoom-btn .wds-inline-icon {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}
/* 배율 라벨은 «10%»~«400%» 사이를 오간다. 폭이 내용 따라 움직이면 −/+ 가 클릭할 때마다
   좌우로 흔들려 연타가 빗나간다 — 최대 문자열 기준으로 자리를 미리 잡고 숫자는 등폭으로 쓴다. */
.artifact-status-zoom-level {
  min-width: 42px;
  padding: 0 4px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.artifact-status-zoom-btn:focus-visible,
.artifact-status-zoom-level:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 1px;
}
.artifact-status-zoom-btn:disabled,
.artifact-status-zoom-level:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
/* R1 (doc 544 §5.4): hover는 반드시 이 안에서. 이 앱이 이미 실측으로 남긴 함정 — 가드 없이 쓴
   :hover는 터치에서 «끈적여» 배경이 눌린 채로 남고, 사용자는 그 서식이 적용된 줄 안다. */
@media (hover: hover) {
  .artifact-status-gridlines-toggle:hover {
    background: var(--cg-hover);
    color: var(--cg-text);
  }
  .artifact-status-gridlines-toggle:disabled:hover {
    background: transparent;
    color: var(--cg-muted);
  }
  /* doc 558 ⓒ 도 같은 가드 안에 둔다 — 이 버튼은 «폰 전용»이라 hover 가 걸릴 일이 거의
     없지만, 가드를 빼면 hover 를 흉내내는 터치 환경에서 배경이 눌린 채 남는다(R1). */
  .artifact-status-page-scroll:hover {
    background: var(--cg-hover);
    color: var(--cg-text);
  }
  .artifact-status-page-scroll:disabled:hover {
    background: transparent;
    color: var(--cg-muted);
  }
  /* doc 597 P1-3 — 줌도 같은 가드 안에. R1: 가드 없는 :hover 는 터치에서 «끈적여» 배경이
     눌린 채 남고, 사용자는 그 배율이 적용된 줄 안다. */
  .artifact-status-zoom-btn:hover,
  .artifact-status-zoom-level:hover {
    background: var(--cg-hover);
    color: var(--cg-text);
  }
  .artifact-status-zoom-btn:disabled:hover,
  .artifact-status-zoom-level:disabled:hover {
    background: transparent;
    color: var(--cg-muted);
  }
}

/* ★아래 두 블록은 «기본 규칙 뒤»에 와야 한다. 실측으로 잡힌 회귀(2026-08-27, 390px):
   .artifact-status-gridlines-toggle{height:22px} 와 미디어 블록의 height:44px 는 특이도가
   같아(클래스 1개) 소스 순서가 이긴다 — 미디어 블록을 이 파일 위쪽(패널 모바일 절)에 두었더니
   히트영역이 44×44 가 아니라 44×22 로 렌더됐다. 미디어 쿼리는 특이도를 «올리지 않는다». */
@media (max-width: 900px) {
  /* R3 (doc 544 §5.4) — 이 폭부터 패널이 풀스크린 시트라 상태바가 화면 «물리적» 최하단에 붙는다.
     홈 인디케이터/제스처 바가 먹는 만큼을 안전영역 변수로 비운다. height를 auto로 푸는 이유:
     28px 고정인 채로 padding-bottom을 더하면 내용이 그 패딩 밖으로 밀린다.
     ★여기에 100dvh를 쓰지 않는다 — 고정 배치에 뷰포트 높이가 필요하면 var(--wizon-vvh, 100dvh)
     여야 한다(R2, doc 544 §8-8). 이 규칙은 flex 흐름 안이라 뷰포트 높이 자체가 필요 없다. */
  .artifact-status-bar {
    height: auto;
    min-height: 28px;
    padding-bottom: var(--wizon-safe-bottom, 0px);
  }
}
/* 폰(≤520px) — doc 544 M6 「2단 축약: 좌측 선택 요약 · 우측 눈금선 토글 1개」.
   520은 이 앱이 이미 44곳에서 쓰는 폰 경계다(doc 544 §5.1 — 새 브레이크포인트 발명 금지).
   "준비" 라벨만 버린다(장식). 셀 개수·저장 상태·선택 집계는 좌측 요약으로 남고, 우측에는
   눈금선 토글 하나뿐이다 — 수식바 토글·줌은 P0에서 «만들지 않았다»(P2 「보기」 탭).
   ★리본 자체의 칩/가로 스크롤 변환(M1·M2)은 P0 범위가 아니다. 여기서 하지 마라. */
@media (max-width: 520px) {
  .artifact-status-bar {
    gap: 8px;
    padding-left: 8px;
    padding-right: 8px;
  }
  .artifact-status-ready {
    display: none;
  }
  /* R4 — 아이콘 전용이어도 터치 타깃은 44×44 이상. 라벨 텍스트를 숨겨 좁은 폭에서도 다른
     요약이 밀려나지 않게 하고, 크기는 히트영역으로 되돌려 준다. */
  .artifact-status-gridlines-toggle {
    min-width: 44px;
    height: 44px;
    padding: 0;
  }
  .artifact-status-gridlines-toggle-text {
    display: none;
  }
  .artifact-status-gridlines-toggle .wds-inline-icon {
    width: 18px;
    height: 18px;
  }
  /* doc 558 ⓒ — 폰에서만 나타난다. R4 대로 아이콘 전용이어도 터치 타깃은 44×44 이상.
     상태바가 이 폭에서 height:auto + min-height:28px 이므로(위 900px 블록) 44px 버튼이
     들어가면 상태바가 그만큼 자란다 — 잘리지 않는다. */
  .artifact-status-page-scroll {
    display: inline-flex;
    min-width: 44px;
    height: 44px;
    padding: 0;
  }
  /* 좌측 요약이 폭을 넘길 때 «양보하는 쪽»은 선택 집계 하나다. 셀 개수는 기본 규칙에서
     flex:0 0 auto + nowrap 이라 접히지 않으므로, 남는 압력을 여기서 말줄임으로 흡수한다.
     min-width:0 이 없으면 flex 아이템은 자기 내용 폭 아래로 안 줄어 말줄임이 영영 안 걸리고,
     nowrap 인 채 상태바를 가로로 밀어낸다(실측 2026-08-29: scrollWidth 399 > 375).
     ★.artifact-status-bar 로 한 겹 올려 쓴다 — 기본 규칙과 특이도가 같으면 소스 순서에
     매달리는데, 그 함정은 바로 위 눈금선 토글 height 에서 이미 한 번 났다. */
  .artifact-status-bar .artifact-selection-summary {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* 20261001-phone-sheet-status-full-reason — 저장 실패·경고 사유는 폰에서 한 줄 말줄임
     («저장하지 못했습니다. 파일이 …»)이면 끝까지 안 읽혀 낭독으로만 전달됐다(QA 364b2f9bd, 126–156px).
     짧은 상태(저장됨 등)는 그대로 한 줄이고, 길어진 문구만 최대 2줄까지 줄바꿈 + 넘치면 2줄 말줄임.
     말줄임이면 탭(app.js handleArtifactStatusMessageToggleClick)으로 data-expanded 를 켜 상태바
     전체 폭 한 줄 위(order:-1)로 펼친다 — 시트 본문은 flex 로 그만큼 줄어 겹치지 않는다. */
  .artifact-status-bar .artifact-workbook-save-status {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    white-space: normal;
    overflow-wrap: anywhere;
    line-height: 1.35;
  }
  .artifact-status-bar[data-status-expanded="true"] {
    flex-wrap: wrap;
  }
  .artifact-status-bar .artifact-workbook-save-status[data-expanded="true"] {
    display: block;
    flex: 1 0 100%;
    order: -1;
    padding-top: 6px;
    -webkit-line-clamp: unset;
    line-clamp: unset;
    overflow: visible;
  }
  /* doc 597 P1-3 — 줌은 폰에서 숨긴다. M6 이 하단에 허용한 것은 「좌측 선택 요약 · 우측 눈금선
     토글 1개」이고, 44px 히트영역 3개를 더 넣으면 그 문장이 깨진다. M6 이 말한 「보기」 탭 이관은
     아직 안 했다(doc 544 §13 「남은 것」) — 폰에는 지금도 줌이 없으므로 이 숨김은 후퇴가 아니라
     현상 유지다. ★.artifact-status-bar 로 한 겹 올려 쓴다: 기본 규칙과 특이도가 같으면 소스
     순서에 매달리고, 그 함정은 눈금선 토글 height 에서 이미 한 번 났다(위 주석). */
  .artifact-status-bar .artifact-status-zoom {
    display: none;
  }
}
/* P2 「클릭형 셀 출처 배지」 폴백: Univer 벤더 코어에 셀 단위 렌더 훅이 없어(app.js의
   cellSourceBarHtml 주석 참고) 캔버스 위 마커 대신 이 하단 바로 대체 — 셀 하나를 선택했을 때만
   보이고, hidden 속성이 innerHTML 비움과 함께 토글된다(renderArtifactCellSourceBar). */
.artifact-cell-source-bar {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  flex: 0 0 auto;
  padding: 6px var(--ds-space-3);
  border-top: 1px solid var(--cg-menu-border, var(--line));
  background: var(--cg-hover-soft);
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-caption-12);
}
.artifact-cell-source-bar[hidden] {
  display: none;
}
.artifact-cell-source-label {
  overflow: hidden;
  color: var(--cg-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.artifact-cell-source-action {
  flex: 0 0 auto;
  margin-left: auto;
  padding: 3px var(--ds-space-2);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-sm);
  background: var(--cg-surface, var(--ds-color-surface));
  color: var(--accent, var(--cg-text, var(--text)));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.artifact-cell-source-action:hover {
  background: var(--cg-hover-soft);
}
.artifact-cell-source-action:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}
/* P0 후속 「시트 생성 직후 다음작업 카드」: 셀 출처 바(위)와 같은 하단 자리, 상태바(아래) 바로
   위. innerHTML 비움과 함께 hidden이 토글된다(renderSheetNextActionCard/clearSheetNextActionCard,
   cellSourceBarHtml과 같은 컨벤션). 새 색 토큰 없이 기존 var()만 재사용한다. */
.artifact-next-action-card {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
  flex: 0 0 auto;
  padding: var(--ds-space-2) var(--ds-space-3);
  border-top: 1px solid var(--cg-menu-border, var(--line));
  background: var(--cg-hover-soft);
}
.artifact-next-action-card[hidden] {
  display: none;
}
.artifact-next-action-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-2);
}
.artifact-next-action-title {
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.artifact-next-action-dismiss {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  border-radius: var(--ds-radius-sm);
  background: transparent;
  color: var(--cg-muted);
  cursor: pointer;
}
.artifact-next-action-dismiss svg {
  width: 14px;
  height: 14px;
}
.artifact-next-action-dismiss:hover {
  background: var(--cg-surface, var(--ds-color-surface));
  color: var(--cg-text, var(--text));
}
.artifact-next-action-dismiss:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}
.artifact-next-action-items {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-2);
}
.artifact-next-action-item {
  flex: 0 0 auto;
  padding: 3px var(--ds-space-2);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-sm);
  background: var(--cg-surface, var(--ds-color-surface));
  color: var(--accent, var(--cg-text, var(--text)));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.artifact-next-action-item:hover {
  background: var(--cg-hover-soft);
}
.artifact-next-action-item:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}
/* 중복 통계바 제거(doc 171 A2 리뷰): Univer 내장 선택통계 위젯(영문 Max/Min/Sum/Count/Average)을
   숨겨 우리 한글 상태바 집계(.artifact-selection-summary — 평균·개수·합계)만 남긴다. 둘이 동시에
   보이면 "합계가 두 개"로 사용자 혼란이라 병합 전 해소.

   ⚠ 셀렉터 버전 취약성: 벤더된 Univer UMD(0.25.1)는 클래스를 유틸리티(univer-*, Tailwind류)로
   난독 생성해 시맨틱 클래스가 없다. 그래서 안정적 앵커 2겹으로 좁힌다 —
     (1) 상태바 루트의 data 속성 [data-range-selector] (선택 범위 통계 바에 Univer가 부여, 실렌더
         확인). 시트탭(1fr)·줌(100%)은 이 SECTION의 형제 그리드 칼럼이라 규칙에 안 걸린다.
     (2) 그 안에서 통계 블록만 감싸는 래퍼 .univer-max-w-fit — 실렌더에서 #artifactSheetContainer
         내 정확히 1개(통계 블록 전용, 시트탭/줌과 클래스 구분).
   Univer 버전업으로 이 속성/클래스가 바뀌면 규칙은 '매칭 실패 → 내장 위젯 다시 노출'로 안전하게
   fail-visible 된다(레이아웃 파손 아님). 그때 실렌더로 셀렉터만 갱신하면 된다. 통계 블록만
   display:none 하고 시트탭·줌·시트 그리드는 건드리지 않는다(실렌더로 형제 visible 확인). */
#artifactSheetContainer [data-range-selector] .univer-max-w-fit {
  display: none;
}

/* 중복 줌 제거 (doc 597 P1-3 후속 — GPT 독립 검수 보고, 2026-08-30 재현됨).
   바로 위 통계바 규칙의 주석은 「시트탭(1fr)·줌(100%)은 형제 그리드 칼럼이라 규칙에 안 걸린다」고
   적어 두었는데, 그때는 **우리 줌이 없어서** 벤더 줌 하나만 있는 것이 옳았다. 우리 상태바에 줌이
   생긴 지금 그 문장이 그대로 중복의 원인이 됐다 — 사용자에게 같은 기능이 두 개다.
   doc 544 §12 「리본/상태바는 우리가 소유한다」에 따라 우리 것을 남기고 벤더 것을 숨긴다.

   ⚠ 셀렉터 앵커 2겹 (실렌더로 확인한 0.25.1 구조):
     footer > SECTION[data-range-selector] (grid-cols-[1fr,auto,auto,auto])
       ├ DIV     ← 시트탭 (slide-tab-bar / sheet-bar-append-button)
       ├ DIV     ← 통계 블록 자리 (.univer-max-w-fit — 위 규칙이 담당)
       └ SECTION ← 줌 (버튼 3개, "100%")
   (1) `footer` 로 그리드 «영역»의 동명 요소를 배제하고, (2) 직계 `> section` 으로 좁힌다 —
   이 그리드에서 SECTION 은 줌 칼럼 하나뿐이고 시트탭·통계는 DIV 다.
   ★벤더 DOM 을 «옮기지» 않는다(doc 544 §8-3). 버전업으로 태그/속성이 바뀌면 규칙은
   '매칭 실패 → 벤더 줌 다시 노출'로 fail-visible 된다 — 최악이 «줌이 두 개»이지 결코
   «시트탭이 사라짐»이 아니다. 그 방향을 지키는 것이 이 셀렉터의 계약이다. */
#artifactSheetContainer footer [data-range-selector] > section {
  display: none;
}

/* 시트 탭 바 엑셀식 리스타일 + ◀▶ 네비/≡ 전체 시트 메뉴 (doc 171 A2.1, 사용자 피드백: "하단의 시트
   추가하는 부분을 엑셀처럼"). 실렌더로 확인한 Univer(0.25.1) 구조:
     grid(=[data-range-selector]) 1열(1fr, flex) 안에 두 형제 DIV —
       (a) 시트 추가 버튼 wrapper: [data-u-comp="sheet-bar-append-button"] × 2(+/변환)의 부모
       (b) 탭 바 wrapper: [data-u-comp="slide-tab-bar"](role=tablist)의 부모
     둘 다 시맨틱 클래스가 없는 Univer 유틸리티 클래스라 앵커는 오직 [data-u-comp] 속성만 신뢰.
   버튼/탭 자체는 손대지 않고(클릭 핸들러 유지) 색만 엑셀처럼 덮어쓴다 — 활성 탭은 Univer가 붙이는
   유틸리티 클래스(univer-bg-white univer-font-bold univer-text-primary-700 univer-shadow, 파랑)를
   흰 배경+초록 텍스트로, 비활성 탭은 연회색 배경으로. 다크모드 변형(dark:!univer-*)은 이 앱에서
   Univer 컨테이너에 .dark 클래스를 붙이는 배선이 없어 도달 불가 — 라이트만 다룬다. */
#artifactSheetContainer [data-u-comp="slide-tab-item"] {
  background: transparent;
  color: var(--cg-muted, #6b7280);
  font-weight: 500;
  border-radius: 4px 4px 0 0 !important; /* univer-rounded = 전체 라운드; 엑셀은 위쪽만 */
  box-shadow: none !important; /* 비활성 탭엔 그림자 없음 */
}
#artifactSheetContainer [data-u-comp="slide-tab-item"]:hover {
  background: var(--cg-hover-soft, #f4f4f4);
}
#artifactSheetContainer [data-u-comp="slide-tab-item"][aria-selected="true"] {
  background: var(--cg-surface, #fff) !important;
  color: var(--success, #12805c);
  font-weight: 600; /* doc 160 T4 weight palette caps at 600 — no 700 in this app */
  box-shadow: inset 0 0 0 1px var(--cg-menu-border, var(--line)) !important;
}
/* 시트 추가(+)·변환 버튼 — 이미 정사각형에 가까운 Univer 기본 스타일을 24×24 고정 + 우리 hover 톤으로
   엑셀 느낌만 더한다. 클릭 커맨드는 Univer 것 그대로(CSS만). */
#artifactSheetContainer [data-u-comp="sheet-bar-append-button"] {
  width: 24px !important;
  height: 24px !important;
  border-radius: 4px !important;
}
#artifactSheetContainer [data-u-comp="sheet-bar-append-button"]:hover {
  background: var(--cg-hover-soft, #f4f4f4) !important;
}
/* Univer가 탭 바 오버플로우 시 자체적으로 붙이는 ‹›(아이콘 univerjs-icon-more-icon, 하나는
   univer-rotate-180) 네이티브 스크롤 버튼 — 실렌더로 뒤늦게 발견(시트 1~2개일 땐 존재하지 않다가
   overflow가 실제로 생겨야 나타남 — data-u-comp="sheet-bar-append-button"을 +/변환 버튼과 공유해
   처음엔 놓쳤다). 이미 스크롤 동작 + disabled 판정까지 정확히 구현돼 있지만, "시트 1개=present+
   disabled"(doc 171 A2.1 스펙)를 못 만족(개수 부족이면 아예 DOM에 없음). 그래서 항상 present인
   우리 ◀▶(.artifact-sheet-tabnav-btn, 아래)를 유지하고 Univer 자체 버튼은 중복 표시되지 않도록
   숨긴다 — 우리 버튼과 별개 세트라 켜 두면 화살표가 두 벌 보인다. */
#artifactSheetContainer [data-u-comp="sheet-bar-append-button"]:has(.univerjs-icon-more-icon) {
  display: none;
}
/* ◀▶ 네비 + ≡ 전체 시트 메뉴 — Univer가 만들지 않는, 우리가 직접 주입하는 크롬(app.js
   mountArtifactSheetTabBarChrome). 시트가 많아 탭 바가 넘칠 때(overflow-hidden이라 넘친 탭은
   네이티브 스크롤바 없이 그냥 안 보임 — 실측 확인) 좌우로 스크롤하고, ≡는 전체 시트 목록 팝오버를
   연다. flex `order`로 탭 바 앞(엑셀 순서: 네비/≡ → 탭 → +)에 오도록 시각적으로만 재배치 — DOM
   이동·커맨드 배선 변경 없음(inline style로 Univer 소유 wrapper 두 개에 order만 부여). */
.artifact-sheet-tabnav {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 1px;
  flex: 0 0 auto;
  margin-right: 2px;
}
.artifact-sheet-tabnav-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--cg-muted, #6b7280);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.artifact-sheet-tabnav-btn .wds-inline-icon {
  width: 15px;
  height: 15px;
}
.artifact-sheet-tabnav-btn:hover:not(:disabled) {
  background: var(--cg-hover-soft, #f4f4f4);
  color: var(--cg-text, var(--text));
}
.artifact-sheet-tabnav-btn:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}
/* ◀▶는 탭 바가 실제로 넘칠 때만 활성 — 시트 1개(넘침 없음)면 여기 걸려 진짜 disabled(장식 아님,
   doc 171 A2.1 스펙). ≡는 시트가 하나뿐이어도 항상 활성(목록에 1개라도 뜬다). */
.artifact-sheet-tabnav-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.artifact-sheet-tabnav-menu {
  display: flex;
  flex-direction: column;
  position: absolute;
  left: 0;
  bottom: calc(100% + 6px);
  min-width: 168px;
  max-height: 240px;
  overflow-y: auto;
  padding: 4px;
  border-radius: 8px;
  background: var(--cg-menu-bg, var(--cg-surface, #fff));
  box-shadow: inset 0 0 0 1px var(--cg-menu-border, var(--line)), var(--cg-menu-shadow);
  z-index: 20;
}
.artifact-sheet-tabnav-menu[hidden] {
  display: none;
}
.artifact-sheet-tabnav-menu-item {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 6px 8px;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-caption-13); /* doc 160 T4 bans hand-tuned half-pixel font-size values */
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.artifact-sheet-tabnav-menu-item:hover {
  background: var(--cg-hover-soft, #f4f4f4);
}
.artifact-sheet-tabnav-menu-item.is-active {
  color: var(--success, #12805c);
  font-weight: 600;
}
.artifact-sheet-tabnav-menu-item .wds-inline-icon {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}

/* P0-B: 답변 신뢰 chip 행(하단) + 통일 패널(신뢰도·출처·처리 과정). 배지를 답변 위→아래로 내려
   출처와 한 줄에 묶고, chip 탭 시 #trustPanel(파일 패널 크롬)을 연다(doc 161 §7 — 단일 패널).
   --conf-accent = vivid 도트, --conf-fg = 어두운 텍스트(WCAG 1.4.3, tint 위 ~6.5:1). */
.trust-chip[data-tier="high"],
.trust-tier-badge[data-tier="high"] {
  --conf-accent: var(--success, #12805c);
  --conf-fg: #0a5f43;
}
.trust-chip[data-tier="medium"],
.trust-tier-badge[data-tier="medium"] {
  --conf-accent: var(--ds-badge-warn-fg, #b45309);
  --conf-fg: #8a3f07;
}
.trust-chip[data-tier="low"],
.trust-tier-badge[data-tier="low"] {
  --conf-accent: var(--muted, #6b7280);
  --conf-fg: #4b5563;
}

/* --- bottom chip row --- */
.trust-chips {
  display: flex;
  gap: 7px;
  margin-top: 12px;
  overflow-x: auto;
  overflow-y: visible;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 2px;
}
.trust-chips::-webkit-scrollbar {
  display: none;
}
/* R-26(오너 09-25 결정 ⓑ, 중앙 09-25 재작업 ②) — 위젯 근거 칩(날씨·금융·금융 그룹·뉴스)의
   바로 앞 형제가 위젯 카드 div(자체 하단 마진 0)라, 공유 .trust-chips margin-top:12px가
   그대로 보이는 간격이 됐다. 일반 답변은 문단의 더 큰 하단 마진과 마진 병합(collapse)돼
   20px로 보이므로, 실측표로 보면 같은 역할(카드→근거 칩)이 12px vs 20px 로 갈라져
   있었다(중앙 재작업 지적). 칩 마크업·톤은 .trust-chips 를 그대로 공유하고 간격만
   override한다 — 새 컴포넌트 0개. */
.trust-chips--widget {
  margin-top: 20px;
}
.trust-chip {
  position: relative;
  scroll-snap-align: start;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  /* Matched to the web source tag (.citation-tag, 20px) so every answer tag is one size family.
     Below the 24px WCAG 2.5.8 comfort target, but the source tag it mirrors is itself an
     interactive 20px control and the 7px inter-chip gap gives the spacing exception headroom.
     portal-chip-touch-target-44 실측(경쟁 원인 확인): 이 20px 시각 박스는 이미 44px 히트
     영역을 갖는다 — 아래 20260803-touch-target-44-trust-chip(doc 143 배치 6, styles.css
     이 파일 뒤쪽 body.chatgpt-reference-v1 스코프) 가 그 적용처이고, index.html 의 <body> 는
     그 클래스를 상시 고정으로 갖는다(토글되지 않음). 실 index.html/app.js/styles.css 로 렌더해
     elementFromPoint(칩 상하 ±7px) 로 재확인: 390/375/430 coarse 전부 true(report.md 표).
     ui3-326-328-333-qa-matrix 의 «h=20px<44 FAIL» 은 getBoundingClientRect 원시 높이만 본 것
     — 그 배치 6 규칙이 이미 특이도 우위로 이겨 살아 있는 히트 영역을 안 잡았다(하네스가 body
     클래스를 안 실었거나 raw-height 기준). position: relative 는 그 클래스가 없는 이론상
     맥락에서도 안전하게 두는 방어값이고, 이 자리에 별도 ::after 를 또 선언하지 않는다 —
     같은 가상요소를 두 규칙이 동시에 못 갖고 특이도 낮은 쪽은 죽은 코드가 된다(anti-DRY).
     .trust-chips 의 overflow-y: visible(위)은 배치 6 의 padding-block/margin-block 상쇄
     기법과 별개로, 오버플로 레일의 y 클립을 애초에 없애는 보강이다(회귀 없음, 실측 확인). */
  min-height: 20px;
  padding: 0 9px;
  border-radius: var(--ds-radius-pill, 999px);
  /* Unified with the web source tag (.citation-tag, doc 164 §후속): soft-fill pill + soft
     border so every answer-footer tag reads as one family. The filled surface (not white)
     carries the control boundary; the whole row shares the source-tag surface language. */
  border: 1px solid var(--cg-menu-border, var(--line));
  background: var(--cg-hover-soft, var(--surface-2));
  color: var(--cg-text, #1f2937);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.trust-chip:hover {
  background: var(--cg-hover, var(--surface-2));
  border-color: var(--cg-muted, var(--muted));
}
.trust-chip:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 2px;
}
.trust-chip--confidence {
  color: var(--conf-fg);
}
.trust-chip-text {
  font-weight: 600;
}
.trust-chip-cnt {
  display: inline-grid;
  place-items: center;
  min-width: 16px;
  height: 16px;
  padding: 0 5px;
  border-radius: var(--ds-radius-pill, 999px);
  /* White inset on the soft-fill chip so the count stays legible as a nested badge. */
  background: var(--surface, #fff);
  color: var(--muted, #475569);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}
.trust-chip-ic {
  display: inline-flex;
  /* Colored symbol like the source tag's favicon (doc 164 §후속3). Functional-color axes
     (styles.css 아트디렉션 v2): Blue = 문서·출처. 처리 과정 overrides to Violet (AI) below. */
  color: var(--ad-blue, #155eef);
}
.trust-chip--process .trust-chip-ic {
  color: var(--ad-violet, #7c5cff);
}
.trust-chip--verification .trust-chip-ic {
  color: var(--ad-green, #12805c);
}
.trust-chip--role-trace .trust-chip-ic {
  color: var(--ad-blue, #155eef);
}
/* Specificity must beat the shell default (.chatgpt-reference-v1 .wds-inline-icon = 20px), or the
   icon stays 20px and inflates the 20px chip. */
.chatgpt-reference-v1 .trust-chip-ic svg {
  width: 13px;
  height: 13px;
}
/* G-1(근거 칩, portal-evidence-chip-and-stream-marker): 무채색 — judge report.md §2 안 B.
   색으로 근거 층을 나누지 않는다(app.js 자체 규범 「색 단독 금지」 + WCAG 2.2 SC 1.4.1).
   g-key(「근거」 접두어)만 굵게 해 훑어도 자리를 찾게 하고, g-val(실제 값)은 본문 톤
   그대로 무채색 — mockup.html 의 .trust-chip--ground 사본. 크기·반경·패딩은 기존
   .trust-chip 그대로(높이·반경 종류 수를 늘리지 않는다). */
.trust-chip--ground .trust-chip-ic {
  color: inherit;
  opacity: 0.55;
}
.trust-chip-g-key {
  font-weight: 600;
}
.trust-chip-g-val {
  font-weight: 500;
  color: var(--cg-muted, var(--muted));
}
/* R-26 ⓒ(오너 확정 09-25) — 업무 모드 · 출처 0 · 사실 주장이 있는 모델 지식 답이면 게이트웨이가
   answer_grounding 을 싣고, 같은 근거 칩이 «근거 · 모델 지식 · 확인 필요» 로 바뀐다(새 배너 0).
   ★색 단독이 아니다 — 문구·아이콘(info)이 먼저 바뀌고 톤은 그 보강이다(WCAG 2.2 SC 1.4.1).
   토큰은 기존 경고 전경(--cg-warning-fg, 고지 레일·doc-summary 가 쓰는 것) 하나만 — 새 리터럴 0.
   크기·반경·패딩은 .trust-chip 그대로(높이·반경 종류를 늘리지 않는다). */
.trust-chip--ground[data-ground-state="verify"] {
  border-color: color-mix(in oklab, var(--cg-warning-fg) 55%, transparent);
}
.trust-chip--ground[data-ground-state="verify"] .trust-chip-ic,
.trust-chip--ground[data-ground-state="verify"] .trust-chip-g-val {
  color: var(--cg-warning-fg);
  opacity: 1;
}
/* R-43 파도 1 재작업(중앙 09-26, 결함 1) — .trust-empty[data-ground-state="verify"] 는
 * renderTrustSourcesSection 이 grounding.text 두 번째 문단을 더 이상 렌더하지 않아 죽었다
 * (요약 카드가 이미 같은 문구를 말한다 — 위 renderTrustSourcesSection 주석). 죽은 선택자
 * 제거. */
/* doc 164 §후속2: model info as a non-interactive meta badge (doc 161 §7 B2), now clustered with
   the elapsed time at the action row's end — muted, borderless, no cursor. Model + duration read
   as one "how it was made" group; the trust-chip row above stays all-interactive. */
/* Identical metrics to .answer-elapsed (same size/weight/color) so the model + duration cluster
   reads as one uniform meta pair (doc 164 §후속3). */
.answer-model-meta {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--cg-muted, #8f8f8f);
  font-size: var(--ds-text-caption-12);
  font-weight: 400;
  line-height: 1.2;
  white-space: nowrap;
}
.answer-model-meta-ic {
  display: inline-flex;
}
.answer-model-meta-ic svg {
  width: 13px;
  height: 13px;
}

/* --- shared meter (chip + panel tier badge) --- */
.trust-meter {
  display: inline-flex;
  gap: 3px;
}
.trust-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--line, #d7dbe1);
}
.trust-dot.filled {
  background: var(--conf-accent);
}

/* --- unified panel: file-panel chrome + tabs + cards (doc 158 → #trustPanel, doc 161 §7) --- */
/* tabs reuse .chatgpt-route-tabs base; this adds a sticky rail below the header */
.trust-panel-tabs {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 2px 0 10px;
  margin-bottom: 4px;
  background: var(--surface, #fff);
  border-bottom: 1px solid var(--line);
}
.trust-panel-tabs button:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 2px;
}
.trust-panel-tabs button.is-active {
  font-weight: 600;
}
.trust-tab-cnt {
  display: inline-grid;
  place-items: center;
  min-width: 17px;
  height: 17px;
  margin-left: 4px;
  padding: 0 5px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-hover-soft, #eef1f5);
  color: var(--muted, #475569);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}
.trust-panel-body {
  display: grid;
  gap: 10px;
}
.trust-panel-tier {
  padding: 4px 0 2px;
}
.trust-tier-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: var(--ds-radius-pill, 999px);
  background: color-mix(in srgb, var(--conf-accent) 12%, transparent);
  color: var(--conf-fg);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}
.trust-why {
  display: grid;
  gap: 5px;
}
.trust-why-row {
  margin: 0;
  font-size: var(--ds-text-caption-13);
  line-height: 1.55;
  color: var(--muted, #4b5563);
}
.trust-why-k {
  font-weight: 600;
  color: var(--cg-text, #374151);
  margin-right: 4px;
}

/* R-43 파도 1(결정 1ⓐ, 오너 09-26) — 패널 한 장 구조: 부제 → 요약 카드 → 출처 절 →
   접힘 절들. 새 클래스는 발주서가 허용한 두 개(.trust-summary/.trust-disclosure)뿐 —
   글자 사다리는 항목 10(패널 본문 14/15·보조 12/13·제목 17)을 --ds-text-* 토큰으로만
   낸다. ★이 스케일에 정확히 17 단계 토큰이 없다(styles.css 전체에 title-18 이 그
   위 최소 단): 새 리터럴 px 를 짓는 대신 사다리에서 가장 가까운 기존 단 title-18 을
   쓴다 — report.md 에 이 대체를 명시.  */
/* trust-panel-r4(중앙 재작업, 09-27) — 위 블록이 anti-drift 래칫 천장을 4건 넘겼다
   (raw-color/raw-rgb/raw-border-radius/raw-box-shadow, 새 리터럴 값이 var() 폴백
   두 번째 인자로 들어가 있었다). 새 토큰은 만들지 않고 기존 토큰으로 치환:
   - border/background/outline 폴백(#e5e7eb·#fff·#2f6bff 등)은 --line/--surface/
     --accent/--cg-text 가 body 셸 클래스(.chatgpt-reference-v1, index.html 에 항상
     붙는다)에서 항상 정의돼 폴백이 죽은 코드였다 — var(--line) 처럼 리터럴만 뺐다
     (계산값 실측 동일, report.md 참고).
   - border-radius: 12px → var(--ds-radius-md, 12px), 4px → var(--ds-radius-xs, 4px).
     이 스케일에 정확히 12px/4px 단이 없다(기존 39353·39732행이 이미 같은 12px 자리에
     --ds-radius-md 를 쓰는 선례) — 계산값 12→14px(+2px), 4→6px(+2px, 포커스 링
     보더반경만). 시각차 작고 QA 스크린샷(390/1440)에서 안 보임.
   - box-shadow: 두 겹 rgba(16,16,20,.05/.04) → var(--ds-shadow-xs)(단일 겹
     rgba(15,23,42,.04)). 카드 부양 음영이 얕아진다 — 가장 가까운 기존 섀도 토큰,
     차이는 report.md 에 표. */
.trust-panel-subtitle {
  margin: 0;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-caption-13);
  line-height: 1.4;
}
.trust-summary {
  display: grid;
  gap: 6px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-md, 12px);
  background: var(--surface);
  box-shadow: var(--ds-shadow-xs);
}
.trust-summary-title {
  margin: 0;
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-title-18);
  font-weight: 600;
  line-height: 1.3;
}
.trust-summary-body {
  margin: 0;
  color: var(--muted);
  font-size: var(--ds-text-body-15);
  line-height: 1.55;
}
.trust-summary[data-trust-summary-kind="verify"] .trust-summary-title {
  color: var(--cg-warning-fg);
}
.trust-summary-action {
  justify-self: start;
  margin-top: 2px;
  padding: 7px 14px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-hover-soft, var(--surface-2));
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  cursor: pointer;
  min-height: 32px;
}
.trust-summary-action:hover {
  background: var(--cg-hover, var(--surface-2));
}
.trust-summary-action:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 2px;
}
.trust-summary-hint {
  margin: 0;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-caption-12);
  line-height: 1.5;
}
.trust-summary-verify-line {
  margin: 0;
  color: var(--muted);
  font-size: var(--ds-text-caption-13);
  line-height: 1.5;
}
.trust-panel-section {
  display: grid;
  gap: 8px;
}
.trust-panel-section-title {
  margin: 6px 0 0;
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  text-transform: none;
}
.trust-panel-section-title:first-child {
  margin-top: 0;
}
.trust-disclosure {
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-md, 12px);
  background: var(--surface);
  overflow: hidden;
}
.trust-disclosure-toggle {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 14px;
  border: 0;
  background: transparent;
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.trust-disclosure-toggle:hover {
  background: var(--cg-hover-soft, var(--surface-2));
}
.trust-disclosure-toggle:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: -2px;
}
.trust-disclosure-summary {
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-caption-13);
  font-weight: 400;
}
.trust-disclosure-body {
  padding: 0 14px 14px;
  border-top: 1px solid var(--line);
  padding-top: 12px;
  display: grid;
  gap: 10px;
}
/* ★실측 결함(중앙 QA, 2026-09-26): [hidden] 은 UA 스타일시트가 display:none 을 주는데,
   위 .trust-disclosure-body { display: grid } 가 특이도가 같아 작성자 규칙이 이겨
   버려서 hidden 속성이 있어도 절이 항상 펼쳐진 채로 보였다(390px 실캡처로 발견) — 이
   override 로 hidden 이 다시 이긴다. */
.trust-disclosure-body[hidden] {
  display: none;
}
.trust-source-open-failed {
  margin: 6px 0 0;
  color: var(--cg-warning-fg);
  font-size: var(--ds-text-caption-12);
  line-height: 1.5;
}
/* R-43 파도 1(결정 2ⓐ) — 걸린 시간이 이제 버튼일 수 있다. .answer-elapsed 는 여전히
   무채색 메타처럼 보여야 하므로(모델 메타와 한 클러스터), 버튼 UA 기본값만 지운다. */
button.answer-elapsed {
  border: 0;
  background: transparent;
  padding: 0;
  cursor: pointer;
}
button.answer-elapsed:hover {
  color: var(--cg-text, var(--text));
}
button.answer-elapsed:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 2px;
  border-radius: var(--ds-radius-xs, 4px);
}
/* 결정 6ⓐ — 공유 사본의 근거 칩은 role="note" span(버튼 아님). .trust-chip 기본값의
   cursor: pointer 는 대화형 칩용이라 여기서만 되돌린다. */
.trust-chip[role="note"] {
  cursor: default;
}

.process-route-widget {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  padding: 14px;
  border: 1px solid var(--line, #e5e7eb);
  border-radius: 12px;
  background: var(--surface, #fff);
  /* 깊이 2단(아트디렉션 v2): 카드만 띄운다 */
  box-shadow: 0 1px 2px rgba(16, 16, 20, 0.05), 0 4px 14px rgba(16, 16, 20, 0.04);
}
.process-route-icon.md-chip {
  width: 30px;
  height: 30px;
  border-radius: 9px;
}
.process-route-icon.md-chip .wds-inline-icon {
  width: 16px;
  height: 16px;
}
.process-route-copy {
  display: grid;
  gap: 4px;
  min-width: 0;
}
.process-route-copy > small {
  color: var(--ad-blue, #155eef);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  letter-spacing: 0.04em;
}
.process-route-copy > strong {
  color: var(--cg-text, #1f2937);
  font-size: var(--ds-text-body-15);
}
.process-route-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 2px;
}
.process-route-badges span {
  padding: 3px 6px;
  border: 1px solid var(--line, #dfe3e8);
  border-radius: var(--ds-radius-xs, 6px);
  background: var(--cg-hover-soft, #f7f8fa);
  color: var(--muted, #596273);
  font-size: var(--ds-text-micro-11);
  line-height: 1.2;
}
.process-route-shadow {
  margin: 4px 0 0;
  padding-top: 7px;
  border-top: 1px solid var(--line);
  color: var(--muted, #667085);
  font-size: var(--ds-text-caption-12);
  line-height: 1.45;
}
.process-route-shadow strong {
  color: var(--cg-text, #374151);
}
/* 상태 콜아웃: 처리 완료=그린 / 자동 전환=앰버 — 본문 콜아웃과 동일 문법 */
.process-status {
  display: flex;
  gap: 10px;
  margin-top: 12px;
  padding: 11px 13px;
  border: 1px solid var(--line, #e5e7eb);
  border-radius: 12px;
}
.process-status[data-tone="green"] {
  border-color: color-mix(in srgb, var(--ad-green) 16%, transparent);
  background: var(--ad-green-tint);
}
.process-status[data-tone="amber"] {
  border-color: color-mix(in srgb, var(--ad-amber) 22%, transparent);
  background: var(--ad-amber-tint);
}
.process-status-copy {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.process-status-copy strong {
  font-size: var(--ds-text-caption-13);
  line-height: 1.45;
}
.process-status[data-tone="green"] .process-status-copy strong { color: var(--ad-green); }
.process-status[data-tone="amber"] .process-status-copy strong { color: var(--ad-amber); }
.process-status-copy span,
.process-status-copy small {
  color: var(--cg-text-soft, var(--muted, #667085));
  font-size: var(--ds-text-caption-13);
  line-height: 1.5;
}
.process-status-copy small {
  color: var(--cg-muted, var(--muted, #667085));
  font-size: var(--ds-text-caption-12);
}
/* One-line recap header of the process panel (doc 165) — the gist at a glance. */
.process-summary-head {
  margin: 0 0 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
  color: var(--cg-text, var(--ink));
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.process-sec-label {
  margin: 24px 0 10px;
  color: var(--cg-muted, var(--muted, #7b8492));
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.process-timeline {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0 0 0 2px;
  list-style: none;
}
.process-step {
  position: relative;
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  gap: 0 10px;
  padding-bottom: 18px;
  align-items: start;
}
.process-step:last-child {
  padding-bottom: 2px;
}
.process-step:not(:last-child)::before {
  content: "";
  position: absolute;
  top: 28px;
  bottom: 2px;
  left: 11px;
  width: 2px;
  border-radius: 2px;
  background: var(--line, #dfe3e8);
}
/* 상태 도트: 단계 의미색 틴트 원 + 아이콘 (아트디렉션 v2) */
.process-dot {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--ad-green-tint);
  color: var(--ad-green);
}
.process-dot[data-tone] .wds-inline-icon {
  /* 특이도 (0,3,0): 전역 .chatgpt-reference-v1 .wds-inline-icon(20px)을 이겨야 틴트 원이 산다 */
  width: 13px;
  height: 13px;
}
.process-dot[data-tone="blue"] { background: var(--ad-blue-tint); color: var(--ad-blue); }
.process-dot[data-tone="amber"] { background: var(--ad-amber-tint); color: var(--ad-amber); }
.process-dot[data-tone="violet"] { background: var(--ad-violet-tint); color: var(--ad-violet); }
.process-step-copy {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.process-step-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  min-height: 24px;
}
.process-step-row strong {
  color: var(--cg-text, #1f2937);
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  line-height: 24px;
  letter-spacing: -0.01em;
}
.process-step-copy small,
.process-step time {
  color: var(--cg-muted, var(--muted, #7b8492));
  font-size: var(--ds-text-caption-12);
  line-height: 1.5;
}
.process-files {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 6px;
}
/* 모델 단계의 라이선스 칩. 문서 조회 단계의 .process-files와 같은 자리·같은 리듬이라 간격
   값을 그대로 따른다(새 spacing 토큰을 만들지 않는다). 색은 기존 아트디렉션 축의 green/amber만
   쓰고 중립은 --line/--muted로 — 새 색 토큰을 만들면 형제 선택자가 옛 토큰에 남는 문제가
   그대로 재현된다. */
.process-model-governance {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 6px;
}
.model-gov-chip {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-pill);
  font-size: var(--ds-text-micro-11);
  line-height: 1.5;
  white-space: nowrap;
  color: var(--muted);
}
.model-gov-chip[data-tone="green"] {
  border-color: color-mix(in srgb, var(--ad-green) 30%, transparent);
  background: var(--ad-green-tint);
  color: var(--ad-green);
}
.model-gov-chip[data-tone="amber"] {
  border-color: color-mix(in srgb, var(--ad-amber) 34%, transparent);
  background: var(--ad-amber-tint);
  color: var(--ad-amber);
}
.process-step time {
  flex: none;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* source cards — mockup anatomy: [n] · type · 관련도{ko} / title / snippet */
.trust-card-list {
  display: grid;
  gap: 8px;
}
.trust-card {
  display: block;
  padding: 11px;
  border: 1px solid var(--line, #e5e7eb);
  border-radius: 11px;
  text-decoration: none;
  color: inherit;
}
.trust-card:hover {
  background: var(--cg-hover-soft, #f7f8fa);
}
.trust-source-location-open {
  display: inline-flex;
  min-height: 32px;
  align-self: flex-start;
  align-items: center;
  gap: var(--ds-space-1);
  margin-top: 2px;
  padding: var(--ds-space-1) var(--ds-space-2);
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--ds-color-surface);
  color: var(--ds-color-muted);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
  cursor: pointer;
}
.trust-source-location-open:hover {
  border-color: var(--accent);
  color: var(--ink);
}
.trust-source-location-open:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.trust-source-location-open .wds-inline-icon {
  width: 14px;
  height: 14px;
}
@media (pointer: coarse) {
  .trust-source-location-open {
    min-height: 44px;
  }
}
.trust-empty {
  padding: 12px 0;
  font-size: var(--ds-text-caption-13);
  color: var(--muted, #6b7280);
}

/* Public verification and role-trace summaries intentionally expose aggregates and mapped
   labels only. Identifiers, claim text, scope digests, and internal payload paths stay server-side. */
.verification-summary {
  display: grid;
  gap: 12px;
}
.verification-status {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 12px;
  border: 1px solid color-mix(in srgb, var(--ad-green, #12805c) 22%, var(--line, #e5e7eb));
  border-radius: 11px;
  background: color-mix(in srgb, var(--ad-green-tint, #e8f7f0) 70%, transparent);
  color: var(--cg-text, #1f2937);
}
.verification-status[data-status="unsupported"],
.verification-status[data-status="mixed"] {
  border-color: color-mix(in srgb, var(--ad-amber, #b45309) 24%, var(--line, #e5e7eb));
  background: color-mix(in srgb, var(--ad-amber-tint, #fff4d6) 72%, transparent);
}
.verification-status-icon {
  display: inline-flex;
  color: var(--ad-green, #12805c);
}
.verification-status[data-status="unsupported"] .verification-status-icon,
.verification-status[data-status="mixed"] .verification-status-icon {
  color: var(--ad-amber, #b45309);
}
.verification-status-icon svg {
  width: 18px;
  height: 18px;
}
.verification-status strong,
.verification-status small {
  display: block;
}
.verification-status strong {
  font-size: var(--ds-text-body-14);
  line-height: 20px;
}
.verification-status small {
  margin-top: 2px;
  color: var(--cg-muted, #7b8492);
  font-size: var(--ds-text-caption-12);
}
.verification-metrics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
.verification-metrics > div,
.verification-count-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-width: 0;
  padding: 9px 10px;
  border: 1px solid var(--line, #e5e7eb);
  border-radius: 9px;
  background: var(--cg-hover-soft, #f7f8fa);
}
.verification-metrics span,
.verification-count-row span {
  color: var(--cg-muted, #7b8492);
  font-size: var(--ds-text-caption-12);
}
.verification-metrics strong,
.verification-count-row strong {
  color: var(--cg-text, #1f2937);
  font-size: var(--ds-text-body-14);
  font-variant-numeric: tabular-nums;
}
.verification-counts {
  display: grid;
  gap: 6px;
}
.verification-note {
  margin: 0;
  color: var(--cg-muted, #7b8492);
  font-size: var(--ds-text-caption-12);
  line-height: 1.5;
}
.role-trace-widget {
  display: grid;
  gap: 7px;
  padding: 11px;
  border: 1px solid color-mix(in srgb, var(--ad-blue, #155eef) 20%, var(--line, #e5e7eb));
  border-radius: 11px;
  background: color-mix(in srgb, var(--ad-blue-tint, #edf4ff) 62%, transparent);
}
.role-trace-widget-head,
.role-trace-checks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
}
.role-trace-widget-head strong {
  color: var(--cg-text, #1f2937);
  font-size: var(--ds-text-caption-13);
}
.role-trace-widget-head > span:last-child,
.role-trace-widget p,
.role-trace-checks {
  color: var(--cg-muted, #7b8492);
  font-size: var(--ds-text-caption-12);
}
.role-trace-widget p {
  margin: 0;
  line-height: 1.5;
}
.role-trace-checks span {
  padding: 3px 7px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-surface, #fff);
}

/* follow-up questions row — action nudges below the answer, distinct from the trust chips.
   D-6/D-8 (continuation-surfaces-design-judge §5): 30→32(M3 chipMinHeight 32dp), gap
   7→8(--wds-space-2), 면/선을 `--surface`/`--line-strong`(다크에서 면=캔버스, #000==#000
   실측 — «면이 사라진다») 에서 `--cg-*`(테마마다 자동 반전) 로 옮긴다. */
.followup-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wds-space-2);
  margin-top: 8px;
}
.followup-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  position: relative;
  min-height: 32px;
  padding: 0 12px;
  border-radius: var(--ds-radius-pill, 999px);
  border: 1px solid var(--cg-menu-border);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease;
}
/* portal-chip-touch-target-44(선례 d26648a84 관례): 시각 높이 30px 은 유지하고 히트 영역만
   coarse 포인터에서 44px 로 넓힌다. 이 칩은 ::after 를 쓰지 않으므로(캐럿은
   .followup-chip__caret 자식 span) 그대로 붙는다. .followup-chips 부모는 flex-wrap 만 선언해
   overflow 계산값이 visible 이라 09-18 트랙에서 본 overflow-y 강제 클립 부작용이 없다. */
@media (pointer: coarse) {
  .followup-chip::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }
}
.followup-chip:hover {
  background: var(--cg-hover-soft);
}
/* 390 coarse 히트슬롭 — 형제 컨트롤(액션 버튼·recent-action)은 이미 44 를 가졌는데 이
   칩만 없었다(judge report §11, content:none 실측). 20260803-touch-target-44 관례를
   그대로 재사용한다(폭은 그대로, 세로만 44 — 위아래 여유가 넓다, 좌우는 이웃 칩과 gap 8
   이라 중간선 규칙 대상이 아니다: 칩 높이 32 + 위아래 6 씩이면 44, 8px 거터의 절반보다
   넓어 겹치지 않는다). */
@media (pointer: coarse) {
  .followup-chip::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    height: 44px;
    transform: translateY(-50%);
  }
}
/* 라벨 + '›' 어포던스 (doc 361 §11, 소유자 요청 2026-08-07). 캐럿은 «고를 수 있다»는
   신호이므로 라벨보다 옅게 두고, 호버에서만 또렷해진다. 색·간격은 기존 칩 토큰을
   그대로 쓴다 — 새 토큰을 만들면 형제 선택자가 옛 값에 남는다(디자인 트랙 교훈). */
.followup-chip__label {
  min-width: 0;
}
.followup-chip__caret {
  margin-left: 6px;
  opacity: 0.45;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.followup-chip:hover .followup-chip__caret,
.followup-chip:focus-visible .followup-chip__caret {
  opacity: 0.9;
  transform: translateX(1px);
}
@media (prefers-reduced-motion: reduce) {
  .followup-chip__caret {
    transition: opacity 0.15s ease;
  }
  .followup-chip:hover .followup-chip__caret,
  .followup-chip:focus-visible .followup-chip__caret {
    transform: none;
  }
}
.followup-chip:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 2px;
}

.evidence-card {
  border: 1px solid var(--cg-menu-border);
  margin-top: 12px;
  border-radius: 8px;
  background: #f8fbfc;
}

.evidence-card summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 3px;
  padding: 11px 12px;
  color: var(--cg-text-soft);
  font-weight: 600;
}

.evidence-card summary small {
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  line-height: 1.4;
}

.evidence-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 12px 10px;
}

.evidence-meta span,
.evidence-note {
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.45;
}

.source-list {
  display: grid;
  gap: 8px;
  padding: 0 12px 12px;
}

.source-card {
  display: grid;
  gap: 6px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #ffffff;
}

.source-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.source-card-head strong,
.source-card-head span {
  overflow-wrap: anywhere;
  font-size: var(--ds-text-caption-12);
  line-height: 1.35;
}

.source-card-head span {
  color: var(--muted);
  white-space: nowrap;
}

.source-snippet {
  margin: 0;
  color: #334155;
  font-size: var(--ds-text-caption-13);
  line-height: 1.55;
}

.evidence-note {
  margin: 0;
  padding: 0 12px 12px;
}

.document-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}

.document-actions button {
  min-height: 30px;
  padding: 0 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #ffffff;
  color: var(--ink);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.document-actions button:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.run-form label,
.login-card label {
  display: grid;
  gap: 7px;
  color: #425160;
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}

.item-list {
  display: grid;
  gap: 8px;
}

.mini-card,
.app-card {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #ffffff;
}

.mini-card {
  display: grid;
  gap: 5px;
  padding: 12px;
}

.mini-card.selected-document {
  background: #f0fafc;
  box-shadow: inset 3px 0 0 var(--accent);
}

.mini-card strong,
.app-card strong {
  display: block;
  overflow-wrap: anywhere;
  font-size: var(--ds-text-body-14);
  line-height: 1.35;
}

.mini-card span,
.app-card small {
  display: block;
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.app-card {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 10px;
  align-items: start;
  padding: 11px;
  color: var(--ink);
  text-align: left;
}

.app-card:hover,
.app-card.selected {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.app-icon {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: #111827;
  color: #ffffff;
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
}

.context-list {
  display: grid;
  gap: 8px;
  margin: 0;
}

.context-list div {
  display: grid;
  grid-template-columns: 82px minmax(0, 1fr);
  gap: 8px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}

.context-list dt {
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.context-list dd {
  min-width: 0;
  margin: 0;
  overflow-wrap: anywhere;
  font-size: var(--ds-text-caption-13);
}

.empty-note {
  margin: 0;
  padding: 12px;
  border: 1px dashed var(--line);
  border-radius: 8px;
  color: var(--muted);
  font-size: var(--ds-text-caption-13);
  line-height: 1.5;
}

.noise-note {
  margin: 6px 0 0;
  padding: 0 4px;
  color: var(--muted);
  font-size: var(--ds-text-micro-11);
  line-height: 1.5;
}

/* .login-dialog uses ds-modal Tier 3 tokens (71 §4.2).
 * Aligned to ds-modal--sm (560px) per Phase 3.3 user confirm (2026-05-24). */
.login-dialog {
  width: min(560px, calc(100vw - 32px));
  padding: 0;
  border: 1px solid var(--ds-modal-border);
  border-radius: var(--ds-modal-radius);
  box-shadow: var(--ds-modal-shadow);
  background: var(--ds-modal-surface);
  outline: none;
}

.login-dialog::backdrop {
  background: var(--ds-modal-overlay);
}

.login-card {
  display: grid;
  gap: var(--ds-space-4);
  padding: var(--ds-space-6);
  background: var(--ds-modal-surface);
}

.login-card h2 {
  margin: 3px 0 0;
  font-size: 22px;
}

.subtle {
  margin: 8px 0 0;
  color: var(--muted);
  font-size: var(--ds-text-body-14);
  line-height: 1.5;
}

.error-text {
  min-height: 18px;
  margin: 0;
  color: var(--danger);
  font-size: var(--ds-text-caption-13);
}

/* Enterprise AI Workspace product refresh */
.app-shell {
  grid-template-columns: 308px minmax(0, 1fr);
}.theme-toggle {
  width: 100%;
  min-height: 40px;
  border: 1px solid rgba(226, 232, 240, 0.16);
  border-radius: var(--ds-radius-pill, 999px);
  background: rgba(255, 255, 255, 0.08);
  color: #f8fafc;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  transition: background var(--motion), border-color var(--motion), transform var(--motion);
}

.theme-toggle:hover,
.nav-item:hover,
.nav-item.active {
  border-color: rgba(34, 211, 238, 0.38);
  background: rgba(255, 255, 255, 0.1);
}

.theme-toggle:hover,
.nav-item:hover {
  transform: translateY(-1px);
}

.nav-section {
  gap: 7px;
}

.nav-item {
  display: grid;
  gap: 3px;
  width: 100%;
  min-height: 54px;
  padding: 10px 12px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: #e2e8f0;
  text-align: left;
  transition: background var(--motion), border-color var(--motion), transform var(--motion);
}

.nav-item span {
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  line-height: 1.25;
}

.nav-item small {
  color: #94a3b8;
  font-size: var(--ds-text-micro-11);
  line-height: 1.3;
}

select,
input,
textarea {
  border-color: var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
}

.main-pane {
  background: var(--bg);
}

.workspace-hero {
  min-height: 96px;
  padding: 22px 30px;
  background: var(--ds-color-surface);
  border-bottom: 1px solid var(--line);
  backdrop-filter: blur(18px);
}

.hero-copy {
  margin-top: 6px;
  color: var(--muted);
  font-size: var(--ds-text-body-14);
  line-height: 1.45;
}

.status-pill {
  min-height: 34px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--accent-soft);
  color: var(--accent);
}

.status-pill.soft {
  border-color: rgba(6, 182, 212, 0.24);
  background: var(--ds-color-cyan-soft);
  color: #0891b2;
}

.status-pill.glass {
  border-color: var(--line);
  background: rgba(255, 255, 255, 0.62);
  color: var(--ink-soft);
}

[data-theme="dark"] .status-pill.glass {
  background: rgba(255, 255, 255, 0.06);
}

.workbench {
  grid-template-columns: minmax(0, 1fr) minmax(340px, 430px);
}

.chat-pane {
  background:
    linear-gradient(180deg, rgba(6, 182, 212, 0.04), transparent 300px),
    var(--bg);
}

.message-list {
  gap: 20px;
  padding: 36px min(6vw, 72px);
}

.workspace-welcome {
  width: min(1120px, 100%);
  max-width: none;
  grid-template-columns: minmax(0, 1.05fr) minmax(320px, 0.95fr);
  align-items: center;
  margin: auto;
  gap: 22px;
}

.welcome-intro,
.artifact-workspace,
.app-command-grid button {
  border: 1px solid var(--line);
  border-radius: var(--radius-2xl);
  background: var(--ds-color-surface);
  box-shadow: var(--shadow-panel);
}

.welcome-intro {
  display: grid;
  gap: 14px;
  padding: clamp(22px, 4vw, 38px);
}

.welcome h2 {
  max-width: 760px;
  font-size: clamp(30px, 4vw, 48px);
  letter-spacing: 0;
}

.artifact-workspace {
  display: grid;
  gap: 16px;
  padding: 20px;
}

.artifact-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.artifact-head h3 {
  margin: 3px 0 0;
  font-size: 22px;
}

.artifact-head span {
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.artifact-preview-grid {
  display: grid;
  gap: 10px;
}

.artifact-preview-card {
  display: grid;
  gap: 5px;
  min-height: 72px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.artifact-preview-card strong {
  font-size: var(--ds-text-body-14);
}

.artifact-preview-card span {
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
}

.app-command-grid {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.app-command-grid button {
  display: grid;
  gap: 5px;
  min-height: 90px;
  padding: 16px;
  color: var(--ink);
  text-align: left;
  transition: border-color var(--motion), transform var(--motion), box-shadow var(--motion);
}

.app-command-grid button:hover,
.prompt-row button:hover,
.mini-card:hover,
.app-card:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow);
}

.app-command-grid strong {
  font-size: var(--ds-text-body-15);
}

.app-command-grid span {
  color: var(--muted);
  font-size: var(--ds-text-caption-13);
}

.section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.section-head h3 {
  margin: 2px 0 0;
  font-size: var(--ds-text-title-18);
  line-height: 1.25;
}

.prompt-row button,
.feedback-row button,
.message-label,
.message-body,
.mini-card,
.app-card,
.app-detail-panel,
.app-workflow-panel,
.app-input-fields,
.evidence-card,
.source-card,
.app-result-card,
.empty-note,
.document-actions button,
.app-menu-chip,
.login-card {
  border-radius: var(--radius-lg);
}

.message {
  grid-template-columns: 42px minmax(0, 820px);
}

.message-body {
  background: var(--ds-color-surface);
  box-shadow: var(--shadow-panel);
}

.message.user .message-body {
  background: var(--accent-soft);
}

.message-document-location-row {
  display: flex;
  margin-top: var(--ds-space-2);
}

.message.user .message-document-location-row {
  justify-content: flex-end;
}

.message-document-location-chip {
  display: inline-flex;
  min-height: 32px;
  max-width: min(100%, 420px);
  align-items: center;
  gap: var(--ds-space-1);
  padding: var(--ds-space-1) var(--ds-space-2);
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--ds-color-surface);
  color: var(--muted);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
  line-height: 1.35;
  cursor: pointer;
}

.message-document-location-chip:hover {
  border-color: var(--accent);
  color: var(--ink);
}

.message-document-location-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.message-document-location-chip .wds-inline-icon {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}

@media (pointer: coarse) {
  .message-document-location-chip {
    min-height: 44px;
  }
}

/* ── 제목 사다리 (v8, 2026-08-07) ──
   ★실측으로 잡은 결함: `##`(h4)와 `###`(h5)가 **둘 다 16px** 이라 화면에서 구분이 안 됐고,
   본문도 16px 이라 «굵은 문단»으로만 보였다. 마크다운 3단 구조가 화면에서 1단으로
   뭉개진 것이 「문서처럼 안 읽힌다」의 가장 큰 원인이다.
   연구 근거: 위계 신호 중 **크기가 가장 강하다**. 그래서 굵기·색이 아니라 크기로 세 칸을
   벌린다 — 20 / 18 / 16.
   ★여백도 문서 관례로 바꾼다: 제목은 **위로 넓고 아래로 좁아야** 아래 본문과 한 덩어리로
   묶인다(24 / 8). 기존 16/9 는 위아래가 거의 같아 제목이 어느 쪽에 속하는지 모호했다.
   ★굵기 (v10, 2026-08-07 — 소유자 승인). v8 은 UA 기본 굵기를 그대로 뒀는데, 그 값이
   타이포 계약(doc 160: 400·500·600)을 벗어나 있었다. 이제 semibold 토큰으로 내린다.
   ★정적 CSS 만 읽으면 이 규칙에 굵기 선언이 없어 «본문 400 을 물려받는다»고 읽힌다 —
     실측해야 UA 기본값이 이기고 있다는 게 보인다. 라이브 계산값으로 확인할 것.
   ★유예했던 이유였던 충돌은 실측으로 해소됐다: h5 는 본문과 같은 16px 이라 semibold 로
     내리면 `**강조**`(16px·semibold)와 값이 같아지지만, h5 에는 **섹션 번호가 항상
     앞에 붙는다**(h3 있는 문서 `1.1.1`, 없는 문서 `1.1` — 두 분기 모두 확인). 인라인
     강조에는 번호가 없으므로 화면에서 섞이지 않는다. */
.message-body h2,
.message-body h3,
.message-body h4,
.message-body h5 {
  margin: var(--ds-space-6) 0 var(--ds-space-2);
  color: var(--ink);
  font-weight: var(--ds-weight-semibold);
  line-height: 1.3;
}

.message-body h2 {
  font-size: var(--ds-text-title-20);
}

.message-body h3 {
  font-size: var(--ds-text-title-20);
}

.message-body h4 {
  font-size: var(--ds-text-title-18);
}

.message-body h5 {
  font-size: var(--ds-text-body-16);
}

/* ── 제목 크기는 태그가 아니라 마크다운 레벨로 고정 (v11, 2026-09-11, C19 doc 671) ──
   ★답변(`.message-body`, `renderMarkdownLite`의 `useAccessibleHeadingLevels=true` 경로)의
   헤딩 태그는 이제 한 단 얕다(#→h2, ##→h3, ###→h4 — 페이지에 h1~h3 가 전혀 없는 채팅
   화면에서 답변의 첫 헤딩이 h4 부터 시작해 SC 1.3.1 을 어기던 결함의 수리). 위 태그별
   규칙(h2=20 / h3=20 / h4=18 / h5=16)을 그대로 두면 옛 레벨2(`##`, 이제 h3 태그)가
   20px 로, 레벨3(`###`, 이제 h4 태그)가 18px 로 커져 버린다 — 태그가 아니라 렌더러가
   찍는 `data-level`(1/2/3, 원래 `#` 개수)로 다시 20/18/16 을 고정한다. 특이도
   (클래스 2 + 속성 1)가 위 태그 규칙(클래스 1 + 타입 1)을 이겨서, 이 파일 순서와 무관하게
   항상 적용된다. 위젯의 bare h3/h4/h5(`data-level` 없음)와 아티팩트/드라이브 미리보기
   (`useAccessibleHeadingLevels=false`, 태그가 그대로 h3/h4/h5)는 위 태그별 규칙만
   받으므로 픽셀 그대로다. */
.message-body .md-heading[data-level="1"] {
  font-size: var(--ds-text-title-20);
}

.message-body .md-heading[data-level="2"] {
  font-size: var(--ds-text-title-18);
}

.message-body .md-heading[data-level="3"] {
  font-size: var(--ds-text-body-16);
}

/* ── 문단 분리 (v8) ──
   ★실측: 문단 간격 10px 인데 행 높이는 27.2px — **0.37줄**밖에 안 벌어져 여러 문단이 한
   덩어리로 흘렀다. 「문맥·문단이 분류가 안 된다」는 인상의 직접 원인이다.
   0.74줄(20px)로 벌린다 — 한 줄을 통째로 비우면 목록·표와의 리듬이 무너지고, 0.5줄 아래면
   지금 문제가 그대로 남는다.
   ★범위를 좁게 잡는다. 전역 `.message-body p` 를 건드리면 사용자 말풍선과 위젯 내부
   문단(고지·근거 표기·타일 라벨)까지 벌어진다. **답변의 최상위 문단만** 겨냥한다 —
   마크다운 렌더러는 블록을 `.message-body` 의 직계 자식으로 넣는다.

   ★★특이도를 «일부러 낮게» 잡는다 — (0,2,1). 처음엔
   `.chatgpt-reference-v1 .message.assistant .message-body > p`(0,4,1)로 썼다가
   `test_table_card_prose_specificity` 에 걸렸다. 그 게이트는 카드가 만드는 <p>
   (`.md-table-provenance`·`.md-table-tile-label` 등, 0,3,0)가 **모든 산문 규칙을
   이겨야** 한다고 요구하는데, 0,4,1 은 그것들을 눌러 버린다. `> p` 라서 실제로는
   카드 문단에 닿지 않지만, 게이트는 «닿을 수 있는가»가 아니라 특이도만 보고 보수적으로
   막는다 — 그 보수성이 옳다. 여기서 예외를 뚫는 대신 특이도를 낮춘다.
   여백을 거는 규칙은 `.message-body p`(0,1,1) 하나뿐이라 (0,2,1)로 충분히 이긴다. */
.assistant .message-body > p {
  margin-bottom: var(--ds-space-5);
}

/* ── 섹션 자동 번호 (v9, 2026-08-07 / v11, 2026-09-11 — C19 doc 671) ──
   ★레퍼런스: Perplexity Research Report(1. / 1.1 / 1.2)·Dovetail(3.3). 번호가 붙는
   순간 «대화 답변»이 «문서»로 읽힌다 — 조사한 문서형 AI 답변이 예외 없이 쓰는 장치다.
   ★렌더러를 안 고친다. 번호는 «표현»이지 «내용»이 아니므로 CSS 카운터로 붙인다 —
   모델 출력에 번호를 넣으면 복사·내보내기한 원문이 오염되고, 답변마다 번호가 어긋난다.
   ★답변마다 1부터 다시 센다(카운터 리셋을 답변 뿌리에 둔다).
   ★번호는 본문보다 흐리게 — 문서의 «가구»이지 내용이 아니다.
   ★v11: 카운터가 h2/h3/h4 를 센다 — 답변 헤딩 자체가 `renderMarkdownLite`의
   `useAccessibleHeadingLevels` 매핑(#→h2, ##→h3, ###→h4)으로 한 단 얕아졌기 때문이다
   (SC 1.3.1: 페이지에 h1~h3 가 전혀 없는 채팅 화면에서 답변이 h4 부터 시작하던 결함).
   위젯의 bare h3/h4 태그(뉴스·상거래 카드 등, 아래 «위젯 제외» 블록)는 그대로다 —
   겹치는 태그 이름이 h4/h5 에서 h3/h4 로 옮겨졌을 뿐, 겹침 자체와 그 처리 방식은
   이전과 동일하다.
   ★`.md-table-title[data-promoted-level]` 도 같은 체인의 `counter-increment`/
   `counter-reset` 을 받는다(C2): 표 바로 위 헤딩이 캡션으로 승격되며 `blocks`에서
   pop 되면(extractTableTitleFromBlocks) 그 헤딩 엘리먼트 자체가 DOM에 없어
   `counter-increment`를 못 받고, 뒤따르는 진짜 헤딩의 번호가 하나 밀려 보였다
   (표 없는 답변 {1.,2.} vs 표 있는 답변 {(승격, 무번호),1.}).
   ★★캡션 자신의 `::before`는 건드리지 않는다 — 그 자리는 이미 «표 번호»(APA 스타일
   "표 1. 사대보험 요율", 아래 v9 블록, `:not([data-title-fallback])::before`)가 쓰고
   있다. 특이도가 우연히 같아(둘 다 클래스/속성 4개) 파일 순서로 승자가 갈리는 게
   드러났다 — 두 번호(«이 답변의 몇 번째 표인가»와 «이 답변의 몇 번째 절인가»)를 한
   `::before`에 같이 못 담고, 기존 표 번호는 헤딩과 무관하게 이미 올바르게 동작한다.
   그래서 이 수리는 카운터 «증가»만 얹는다 — 캡션 자체는 여전히 "표 N. 제목"으로 보이고
   (달라지지 않는다), 그 뒤에 오는 **진짜** 헤딩이 스킵 없이 이어서 세어진다는 것만
   고친다. `:has()` 분기에도 같은 캡션 선택자를 넣어야 한다 — 그 답변의 유일한 h2 가
   하필 표 바로 위에 있어서 캡션으로 승격됐다면, 실제 DOM 에는 h2 태그가 하나도 안
   남기 때문이다(폴백 "표 N"과 bold-lead 승격은 `data-promoted-level` 이 없으므로 이
   체인에 끼지 않는다 — 원래도 번호가 없었다). */
.assistant .message-body {
  counter-reset: md-h2 md-h3 md-h4;
}
.assistant .message-body h2,
.assistant .message-body .md-table-title[data-promoted-level="2"] {
  counter-increment: md-h2;
  counter-reset: md-h3 md-h4;
}
.assistant .message-body h3,
.assistant .message-body .md-table-title[data-promoted-level="3"] {
  counter-increment: md-h3;
  counter-reset: md-h4;
}
.assistant .message-body h4,
.assistant .message-body .md-table-title[data-promoted-level="4"] {
  counter-increment: md-h4;
}
.assistant .message-body h2::before,
.assistant .message-body h3::before,
.assistant .message-body h4::before {
  color: var(--cg-muted);
  font-variant-numeric: tabular-nums;
  font-weight: inherit;
}
.assistant .message-body h2::before {
  content: counter(md-h2) ".";
}
/* ★`#` 이 하나도 없는 답변에서는 `##` 가 최상위다. 그때 `0.1` 로 찍히면 안 된다 —
   실제 답변은 `##` 로 시작하는 경우가 흔하다. `:has()` 로 갈라 준다(이 리포에서 이미
   140곳 넘게 쓰는 확립된 패턴). 승격된 캡션(`.md-table-title[data-promoted-level="N"]`)
   도 :has() 조건에 넣는다 — 캡션은 번호를 «보이지» 않지만 여전히 «있었다»는 사실은
   뒤따르는 헤딩의 분기 판정에 반영돼야 한다. */
.assistant .message-body:has(h2, .md-table-title[data-promoted-level="2"]) h3::before {
  content: counter(md-h2) "." counter(md-h3);
}
.assistant .message-body:not(:has(h2, .md-table-title[data-promoted-level="2"])) h3::before {
  content: counter(md-h3) ".";
}
.assistant .message-body:has(h2, .md-table-title[data-promoted-level="2"]) h4::before {
  content: counter(md-h2) "." counter(md-h3) "." counter(md-h4);
}
.assistant .message-body:not(:has(h2, .md-table-title[data-promoted-level="2"])):has(h3, .md-table-title[data-promoted-level="3"]) h4::before {
  content: counter(md-h3) "." counter(md-h4);
}
.assistant .message-body:not(:has(h2, .md-table-title[data-promoted-level="2"])):not(:has(h3, .md-table-title[data-promoted-level="3"])) h4::before {
  content: counter(md-h4) ".";
}

/* ── 위젯 카드 제목은 산문 번호에서 제외 (v10, 2026-09-07) ──
   ★결함(오너 운영 스크린샷 09-07): 위 카운터는 `.message-body` 안의 **모든** h3/h4/h5
   를 센다. 뉴스 위젯이 같은 태그를 쓰면(`news-feed-title` h3·`news-section-name` h4)
   답변 안에 «1.연예 뉴스»·«0.1 | 함께 볼 기사»처럼 번호가 새어 든다 — 그 태그는
   «답변 문서의 섹션»이 아니라 카드 자신의 라벨이다.
   ★`content: none` 만으로는 부족하다 — 위젯 안 h3/h4 는 여전히 `counter-increment`
   를 받아서, 위젯 다음에 오는 **진짜 답변 헤딩**의 번호를 밀어 버린다(위젯이 조용히
   1개를 먹는다). `counter-reset` 으로 위젯을 새 카운터 스코프로 잘라내야 바깥 번호가
   안 밀린다 — 아래 `.commerce-category-card` 가 이미 쓰던 패턴을 넓힌다.
   ★★배치가 특이도만큼 중요하다: `:has(h3) h4::before`류 규칙(위, v9)과 특이도가
   같다(클래스 3 + 타입 2인 쪽끼리 동률) — **파일에서 뒤에 오는 쪽이 이긴다**. 그래서
   이 블록을 v9 뒤에 둔다. `.commerce-category-card`(L456 부근, v9 «앞»)는 이 순서
   문제 때문에 h3만 안전하고 h4/h5 는 `:has(h3)` 인 답변에서 여전히 샐 수 있었다(부분
   대응) — 같은 카드를 여기 selector 목록에도 넣어 완전히 닫는다.
   ★전수(2026-09-07 실측, app.js 렌더 함수 기준):
     - `.news-feed` — 뉴스(위 스크린샷의 직접 원인).
     - `.wiz-card` — 상거래 정가표 카드(`commerce-price-card`, 카테고리 비교와 다른
       변형)·문서 요약/드라이브 미리보기 카드(`doc-summary-card`)·유튜브 카드
       (`wizon-livewiki`, doc 665 §S2 의 bare `<h3>` 제목)가 공유하는 뿌리 클래스.
     - `.commerce-price-state` — 가격 비교 실패/일부 확인 상태 카드(49119행 h3).
     - `.commerce-category-card` — 위에서 설명한 부분 대응 완결.
     - `.finance-group-card` — 주가 비교 위젯(43865·43884·43886행의 bare h3/h4).
     - `.travel-itinerary-card` — 여행 가이드북 위젯 두 변형이 공유하는 뿌리 클래스
       (히어로·일차 개요·장소 상세 시트의 bare h3/h4/h5, 53230~53511행 등).
   ★영향 없음(실측, `.message-body` 밖이라 이 카운터가 원래 안 닿는다): CPQ 대시보드·
   Drive 인스펙터·설정 패널·AI 앱스·산출물 스튜디오는 `state.activeTab` 으로 갈리는
   **전용 화면**(app.js 21282행 주석: "CPQ 는 채팅 app-run 이 아니라 전용 화면")이고,
   뉴스 정기구독 시트는 `document.body` 에 직접 붙는 오버레이다. 날씨·여행 상세 패널
   (`travel-guidebook-heading` 등)은 `<header>`/`<span>` 만 쓰고 h3~h5 가 없다.
   `renderTravelCourseDecision` 의 h4(54034·54043·54056행)는 app.js 어디서도 호출되지
   않는 죽은 코드라 지금은 도달 불가 — 나중에 배선되면 같은 결함을 물려받을 잠재 위험. */
.assistant .message-body .news-feed h3::before,
.assistant .message-body .news-feed h4::before,
.assistant .message-body .news-feed h5::before,
.assistant .message-body .wiz-card h3::before,
.assistant .message-body .wiz-card h4::before,
.assistant .message-body .wiz-card h5::before,
.assistant .message-body .commerce-price-state h3::before,
.assistant .message-body .commerce-price-state h4::before,
.assistant .message-body .commerce-price-state h5::before,
.assistant .message-body .commerce-category-card h3::before,
.assistant .message-body .commerce-category-card h4::before,
.assistant .message-body .commerce-category-card h5::before,
.assistant .message-body .finance-group-card h3::before,
.assistant .message-body .finance-group-card h4::before,
.assistant .message-body .finance-group-card h5::before,
.assistant .message-body .travel-itinerary-card h3::before,
.assistant .message-body .travel-itinerary-card h4::before,
.assistant .message-body .travel-itinerary-card h5::before {
  /* ★`!important` 인 이유(10-02 news-title-number-prefix 실측): 위 v9 의 `:has(h2, .md-table-
     title[data-promoted-level])` 분기는 `:has()` 인자 특이도(0,2,0)를 통째로 물려받아
     (0,4,2)~(0,6,2) 다 — 이 가드(0,3,2)는 «파일 뒤에 있어도» 져서 뉴스 카드 제목에 «0.1»,
     유튜브 카드 h3 에 «1.» 이 붙었다(위 «동률이면 뒤가 이긴다» 설명은 `:has()` 가 없던 때의
     것). 특이도 경쟁을 끝내려고 가드만 `!important` 로 둔다. */
  content: none !important;
}

/* md-h2 추가(v11, C19): 위 `.commerce-category-card` 규칙과 같은 방어적 이유. */
.news-feed,
.wiz-card,
.commerce-price-state,
.finance-group-card,
.travel-itinerary-card {
  counter-reset: md-h2 md-h3 md-h4 md-h5;
}

.message-body code:not(pre code) {
  padding: 2px 5px;
  border-radius: var(--ds-radius-xs, 6px);
  background: var(--surface-2);
  color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9em;
}

.message-body pre,
.code-block,
.mini-card code {
  border-radius: var(--radius-lg);
  background: #0b1020;
}

/* Code-block card (doc 164): fenced code renders as a card — header bar (language label + copy
   button) over a horizontally scrolling body — matching ChatGPT/Claude. ★갭 #65(doc 669 ※4, 원 번호 #64 잉크
   규칙 예외, 오너 09-12 결정 3C): 카드는 더 이상 테마 무관 고정 다크가 아니다 — 라이트에서는
   밝은 카드, 다크에서는 어두운 카드로 `--wizon-ai-code-*` 토큰(shared/wds/wizon-ai-shell-
   tokens.css, DTCG 신설)을 따라간다. 구문 강조는 절제된 3색(키워드·문자열·숫자)만 유채색이고
   나머지는 잉크/뮤트로 접는다 — 무채색 원칙의 유일한 예외가 이 3색이다(대비 각 ≥4.5:1 실측). */
.code-card {
  /* ★v10 — 표와 같은 이유로 산문 리듬(20)에 맞춘다. 16 이면 이 블록 앞뒤에서만
     간격이 좁아져 문서의 세로 리듬이 끊긴다. */
  margin: var(--ds-space-5) 0;
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--wizon-ai-code-surface);
  /* ★judge 부수 발견(09-12): 이 font-family 를 카드 전체(헤더 언어 라벨·«복사»·
     «더보기/접기» 등 한글 산문까지)에 걸면 그 한글이 모노스페이스를 상속해 자간이
     넓어 보인다. 모노는 코드 본문(.code-card pre.code-block code)에만 명시하고,
     카드 컨테이너 자체는 본문과 같은 프로즈 폰트를 쓴다 — 헤더·푸터는 이 선언을
     그대로 상속한다. */
  font-family: var(--wizon-ai-font-family);
}
.code-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 8px 6px 14px;
  background: var(--wizon-ai-code-surface);
  border-bottom: 1px solid var(--wizon-ai-popover-border);
}
.code-card-lang {
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--wizon-ai-code-muted);
}
.code-card-copy {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--wizon-ai-code-muted);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  line-height: 1;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.code-card-copy:hover {
  background: var(--wizon-ai-hover-bg);
  color: var(--wizon-ai-code-ink);
}
.code-card-copy:focus-visible {
  outline: none;
  border-color: var(--cg-focus-ring, #6b8afd);
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}
.code-card-copy.is-copied {
  /* 성공 상태는 무채색 예외(의미색). 새 유채색을 늘리지 않도록 이미 대비 검증된
     구문 강조 «문자열» 초록 토큰을 재사용한다 — 카드 배경 대비 라이트 6.55:1/
     다크 8.05:1, 둘 다 이미 ≥4.5:1. */
  color: var(--wizon-ai-code-string);
}
.code-card-copy-icon {
  display: inline-flex;
}
.code-card-copy-icon svg,
.code-card-copy-icon .wds-inline-icon {
  width: 14px;
  height: 14px;
}
.code-card-scroll {
  overflow-x: auto;
}
.code-card pre.code-block {
  margin: 0;
  padding: 12px 14px;
  border-radius: 0;
  background: transparent;
  /* answer-code-fold-state-restore fix-b: `.message-body pre`의 overflow:auto 전역 규칙
     (2092줄)이 이 pre에도 적용돼 `.code-card-scroll`(overflow-x:auto, 8411줄)과 이중 스크롤
     컨테이너가 됐다 — 실 휠은 가장 안쪽인 이 pre로 가고, 캡처 코드는 `.code-card-scroll` 만
     듣는다. 스크롤 주체를 바깥 `.code-card-scroll` 하나로 좁힌다. */
  overflow: visible;
}
.code-card pre.code-block code {
  /* ★judge 부수 발견(09-12): .code-card 가 더 이상 모노를 강제하지 않으므로(헤더·
     푸터 한글 자간 수리) 코드 본문에는 여기서 직접 명시한다. */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--ds-text-caption-13);
  line-height: 1.6;
  color: var(--wizon-ai-code-ink);
  white-space: pre;
}
@media (prefers-reduced-motion: reduce) {
  .code-card-copy {
    transition: none;
  }
}

/* Long code blocks (>18 lines) start collapsed: a height-capped, bottom-faded preview with a
   더보기/접기 toggle (render2 — mobile scroll-bloat guard). Copy still grabs the full source. */
.code-card--collapsible.is-collapsed .code-card-scroll {
  max-height: 22rem;
  overflow-y: hidden;
  position: relative;
}
.code-card--collapsible.is-collapsed .code-card-scroll::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3.25rem;
  pointer-events: none;
  background: linear-gradient(180deg, transparent, var(--wizon-ai-code-surface));
}
.code-card-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 8px;
  border: 0;
  border-top: 1px solid var(--wizon-ai-popover-border);
  background: var(--wizon-ai-code-surface);
  color: var(--wizon-ai-code-muted);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.code-card-toggle:hover {
  background: var(--wizon-ai-hover-bg);
  color: var(--wizon-ai-code-ink);
}
.code-card-toggle:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}
@media (prefers-reduced-motion: reduce) {
  .code-card-toggle {
    transition: none;
  }
}

/* highlight.js token palette, scoped to the card so it never leaks. ★갭 #65(doc 669 ※4, 원 번호 #64 잉크 규칙
   예외, 오너 09-12 결정 3C): atom-one-dark 고정 7색 리터럴을 걷어내고 절제된 3색(키워드·문자열·
   숫자)만 남긴다 — 그 외 그룹(태그명·심볼·built_in 등)은 잉크로 접어 무채색 원칙을 지킨다. */
.code-card .hljs-comment,
.code-card .hljs-quote {
  color: var(--wizon-ai-code-muted);
  font-style: italic;
}
.code-card .hljs-doctag,
.code-card .hljs-keyword,
.code-card .hljs-formula,
.code-card .hljs-literal {
  color: var(--wizon-ai-code-keyword);
}
.code-card .hljs-section,
.code-card .hljs-name,
.code-card .hljs-selector-tag,
.code-card .hljs-deletion,
.code-card .hljs-subst,
.code-card .hljs-symbol,
.code-card .hljs-bullet,
.code-card .hljs-link,
.code-card .hljs-meta,
.code-card .hljs-selector-id,
.code-card .hljs-title {
  color: var(--wizon-ai-code-ink);
}
.code-card .hljs-string,
.code-card .hljs-regexp,
.code-card .hljs-addition,
.code-card .hljs-attribute,
.code-card .hljs-meta .hljs-string {
  color: var(--wizon-ai-code-string);
}
.code-card .hljs-attr,
.code-card .hljs-variable,
.code-card .hljs-template-variable,
.code-card .hljs-type,
.code-card .hljs-selector-class,
.code-card .hljs-selector-attr,
.code-card .hljs-selector-pseudo,
.code-card .hljs-number,
.code-card .hljs-built_in,
.code-card .hljs-title.class_,
.code-card .hljs-class .hljs-title {
  color: var(--wizon-ai-code-number);
}
.code-card .hljs-emphasis {
  font-style: italic;
}
.code-card .hljs-strong {
  font-weight: 600;
}
.code-card .hljs-link {
  text-decoration: underline;
}

/* Non-http markdown links (local paths) degrade to an inline code chip; break long paths so
   they wrap inside the bubble instead of forcing horizontal overflow. */
.message-body code.md-path {
  word-break: break-all;
}

/* Markdown renderer v2 (doc 161 P1): real lists/tables/quotes/callouts from renderMarkdownLite.
   Tokens only (--cg-* 와 --wds-* + legacy fallbacks) — no raw colors, no !important. */
.message-body .md-list {
  margin: var(--ds-space-2) 0 var(--ds-space-5);
  padding-left: 22px;
  display: grid;
  /* ★v11 — 항목 간격을 스케일로 올린다(5px → 12px). v8 이 산문 여백을 스케일로 접을 때
     목록 «안»은 손대지 않아 임의값이 남아 있었다.
     ★근거는 취향이 아니라 실측이다: 줄바꿈된 항목 «안»의 줄 사이가 7px 인데 항목 «사이»가
     5px 이었다 — 항목끼리가 줄끼리보다 «가까워서» 두 줄짜리 항목이 두 개 항목처럼 읽혔다.
     항목 경계가 보이려면 항목 사이 > 줄 사이여야 한다.
     ★8px(--ds-space-2) 도 조건은 넘지만 여유가 1px 뿐이라 폰트·폭·모바일에서 쉽게 뒤집힌다.
     문단 간격(20px)보다는 작아야 목록이 «한 묶음»으로 남는다 — 그 사이 값이 12px 이다. */
  gap: var(--ds-space-3);
}
.message-body .md-list li {
  line-height: 1.62;
}
.message-body .md-list li::marker {
  color: var(--cg-muted, var(--muted));
}
.message-body .md-list ul,
.message-body .md-list ol {
  /* ★중첩 목록은 부모 항목에 «붙어야» 한다 — 자기 항목 사이(8px)보다 부모와의 거리가
     가까워야 종속 관계로 읽힌다. 그래서 위 여백은 한 칸 더 작게 잡는다. */
  margin: var(--ds-space-1) 0 0;
  padding-left: 18px;
  display: grid;
  /* ★최상위(12px)보다 좁혀 위계를 준다. 다만 줄 사이(7px)는 넘어야 하므로 8px 이 하한이다. */
  gap: var(--ds-space-2);
}
/* ── 답변 속 차트 (차트 표준 규칙 5) ─────────────────────────────────────────────
   ★카드로 승격하지 않는다. 표를 카드에서 내린 v7 과 같은 판단이며, 차트는 본문의
   또 하나의 «형제 블록»이다 — 표의 다른 «뷰»가 아니다(토글하지 않는다).
   ★모든 값은 토큰이다. 안티드리프트 래칫이 여유 0이라 원시 hex·그림자·반경·크기를
   하나도 더 넣을 수 없다. 계열색은 `--ds-chart-slot-*`(두 표면에서 검증 통과). */
.md-chart {
  margin: var(--ds-space-5) 0;   /* 산문 리듬과 같은 값 — 차트에서만 리듬이 끊기지 않게 */
  position: relative;
}

/* ★타입 사다리 — 크기«와» 굵기 둘로 역할을 가른다. 크기만으로 나누면
   범례가 부제와 같은 층으로 읽힌다(목업 실측에서 둘 다 14/400 이었다). */
.md-chart-title {
  font-size: var(--ds-text-body-16);
  font-weight: var(--ds-weight-semibold);
  color: var(--ink);
}
.md-chart-subtitle {
  font-size: var(--ds-text-body-14);
  font-weight: var(--ds-weight-regular);
  color: var(--cg-text-soft, var(--muted));
  margin-top: var(--ds-space-1);
}
.md-chart-footnote {
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-regular);
  color: var(--cg-muted, var(--muted));
  margin-top: var(--ds-space-2);
}
.md-chart-area {
  margin-top: var(--ds-space-3);
  position: relative;
}
.md-chart-area svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  touch-action: none;
}
/* 글자 폭 측정용 — 화면에 나오지 않는다 */
.md-chart-measure {
  position: absolute;
  visibility: hidden;
  width: 0;
  height: 0;
}

/* 격자·축 — 1px «실선», 표면에서 한 단계. 점선은 눈에 띄어 데이터와 경쟁하고
   «임계값/추정»으로 오독된다. */
.md-chart-grid {
  stroke: var(--cg-menu-border, var(--line));
  stroke-width: 1;
}
.md-chart-tick {
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-regular);
  fill: var(--cg-text-soft, var(--muted));
  font-variant-numeric: tabular-nums;
}
/* 값은 이 표면에서 가장 굵다 — 축 라벨보다 위계가 높다 */
.md-chart-value {
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
  fill: var(--ink);
  font-variant-numeric: tabular-nums;
}

.md-chart-line {
  fill: none;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}
/* 마커·조각의 «2px 표면 링/간격» — 마크를 가르는 것은 테두리가 아니라 여백이다 */
.md-chart-dot,
.md-chart-slice {
  stroke: var(--bg);
  stroke-width: 2;
}
.md-chart-cross {
  stroke: var(--cg-menu-border, var(--line));
  stroke-width: 1;
  pointer-events: none;
}
.md-chart-lift {
  fill: var(--cg-hover-soft);
  pointer-events: none;
}
.md-chart-focus-dot {
  pointer-events: none;
}
/* 히트 영역은 «칠해진 픽셀»이 아니라 잡을 수 있는 넓이 */
.md-chart-hit {
  fill: transparent;
}
.md-chart-hit:focus-visible,
.md-chart-slice:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}

/* ★키 모양이 마크를 비춘다 — 범례와 툴팁이 «같은» 모양을 써야 한 계열에 인코딩이
   둘로 갈리지 않는다(Carbon: 범례는 색·모양·크기와 데이터의 연결을 정의한다). */
.md-chart-key {
  display: inline-block;
  flex: none;
}
.md-chart-key.line {
  width: var(--ds-space-3);
  height: 2px;
  border-radius: var(--ds-radius-2xs);
}
.md-chart-key.rect {
  width: var(--ds-space-2);
  height: var(--ds-space-2);
  border-radius: var(--ds-radius-2xs);
}

.md-chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-2) var(--ds-space-4);
  margin-top: var(--ds-space-3);
}
.md-chart-legend-item {
  /* ★아래 터치 슬롭(::after)의 기준 상자. 보이는 크기는 이 규칙이 그대로 소유한다. */
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  font-size: var(--ds-text-caption-13);
  font-weight: var(--ds-weight-medium);
  color: var(--ink);
  background: none;
  border: 0;
  padding: var(--ds-space-1) var(--ds-space-2);
  margin: calc(var(--ds-space-1) * -1) calc(var(--ds-space-2) * -1);
  border-radius: var(--ds-radius-xs);
  cursor: pointer;
}
/* ★터치에서 `:hover` 는 «다른 곳을 누를 때까지» 붙는다 — 마우스가 없으니 뗄 계기가 없다.
   범례는 `aria-pressed` 로 켜짐/꺼짐을 말하는데, 끈적이는 면이 그 신호와 겹쳐 «어느 계열이
   켜져 있나»를 거짓으로 읽히게 만든다. 2026-08-08 표 행에서 같은 병을 고쳤다(같은 가드). */
@media (hover: hover) {
  .md-chart-legend-item:hover:not([disabled]) {
    background: var(--cg-hover-soft);
  }
}
.md-chart-legend-item:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}
.md-chart-legend-item[disabled] {
  cursor: default;
}
/* ★숨김을 «흐림»만으로 표시하면 색약·저대비에서 안 보인다 — 취소선으로 형태도 준다 */
.md-chart-legend-item[aria-pressed="false"] {
  color: var(--cg-muted, var(--muted));
}
.md-chart-legend-item[aria-pressed="false"] .md-chart-legend-name {
  text-decoration: line-through;
}
.md-chart-legend-item[aria-pressed="false"] .md-chart-key {
  opacity: 0.35;
}

/* ★깊이: 차트는 평면이고 떠 있는 것은 툴팁 하나뿐이다. 그림자는 시스템 토큰을 쓴다. */
.md-chart-tip {
  position: absolute;
  z-index: 5;
  min-width: 128px;
  pointer-events: none;
  /* 반전 칩 — 정의된 잉크/바탕 쌍을 뒤집어 쓴다. 없는 토큰을 지어내면
     선언이 통째로 무효가 되어 배경이 투명해진다(뷰토글에서 겪은 결함). */
  background: var(--ink);
  color: var(--bg);
  border-radius: var(--ds-radius-xs);
  box-shadow: var(--ds-shadow-md);
  padding: var(--ds-space-2) var(--ds-space-3);
  font-size: var(--ds-text-caption-12);
  opacity: 0;
  transform: translateY(2px);
  transition: opacity 120ms ease, transform 120ms ease;
}
.md-chart-tip[data-open] {
  opacity: 1;
  transform: none;
}
/* 툴팁 안에서는 «값»이 앞장서고 이름이 따라온다 — 범례와 반대다.
   여기서는 이미 계열을 알고 숫자를 원한다. */
.md-chart-tip-head {
  font-weight: var(--ds-weight-semibold);
  margin-bottom: var(--ds-space-1);
}
.md-chart-tip-row {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
}
.md-chart-tip-row + .md-chart-tip-row {
  margin-top: var(--ds-space-1);
}
.md-chart-tip-name {
  flex: 1;
  opacity: 0.72;
}
.md-chart-tip-value {
  font-weight: var(--ds-weight-semibold);
  font-variant-numeric: tabular-nums;
}
/* 수화 전·검증 실패 시에는 소스가 그대로 남는다 — 깨진 차트를 그리지 않는다 */
.md-chart[data-chart-pending] .md-chart-source,
.md-chart:not([data-chart-type]) .md-chart-source {
  display: block;
}
.md-chart[data-chart-type] .md-chart-source {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .md-chart-tip {
    transition: none;
  }
}

/* Two-zone card (user request 2026-07-18, ref: 상세내역 report): the wrap is a tinted outer card
   holding a header band (toolbar) above a distinct white table block (.md-table-frame). No
   overflow clip here — the frame owns its own rounded clip; the wrap just pads the two zones. */
.md-table-wrap {
  /* ★v7 — 카드 승격을 되돌린다(사용자 승인, 2026-08-06 목업 B안).
     v6는 이 블록을 뉴스 위젯과 같은 «들어올린 카드»로 만들었다(반경 22px·리프트
     그림자·여백 24px·흰 면). 한 답변에 표가 둘 이상 나오면 산문 → 카드 → 산문이
     반복되며 답변이 «문서»가 아니라 «위젯 나열»로 읽혔다 — 사용자 지적의 원인이다.

     ★업계 실측(Mobbin 2026-08-06: Claude·ChatGPT·Grok·Perplexity·Mistral Le Chat)에서
     답변 속 표를 카드로 승격한 제품은 하나도 없었다. 전부 본문 흐름 안의 평면 표다.
     카드는 축·범례가 있어 경계가 필요한 «차트»에만 쓴다(Databricks Genie·Langdock).

     ★래퍼 자체는 남긴다 — 없애면 안 된다. 이 요소는 고지(risk notice) 마운트 앵커이자
     모바일 액션 알약의 위치 기준이고, 제목·종류칩·뷰토글이 여기 안에서 산문 규칙을
     이기는 특이도를 얻는다. «카드가 아니게» 만드는 것이지 «없애는» 것이 아니다. */
  position: relative;
  /* ★v10 — 산문 리듬에 맞춘다. v7 이 여기에 12/16 을 임의로 넣는 바람에 표 앞뒤만
     16px 이 되어, 문단·목록·인용이 모두 20px 인 흐름에서 표에서만 리듬이 끊겼다.
     ★선언값이 아니라 **실제 렌더 간격**으로 잡은 결함이다. 위쪽 12 는 앞 문단의
     20 과 상쇄돼 화면에는 20 으로 보이고, 아래쪽 16 만 튄다 — 선언만 읽으면
     «12/16 두 곳이 어긋난다»고 오진한다. 위아래 모두 산문과 같은 값으로 통일한다. */
  margin: var(--ds-space-5) 0;
}
/* The framed table block — a white surface with its own hairline + radius, sat inside the tinted
   card so it reads as a separate object from the header band above it. This is also the
   non-scrolling layer that anchors the edge-fade affordance. */
.md-table-frame {
  /* ★자기 테두리를 버린다. 카드가 곧 프레임이라 안쪽이 테두리를 유지하면 이중 테두리가
     된다 — 그 겹침이 «문서» 느낌의 절반이었다. 엣지 페이드는 여기 그대로 산다. */
  position: relative;
  overflow: hidden;
  background: var(--cg-surface, #fff);
}
/* The inner layer scrolls; the frame stays put so the edge fades can sit fixed at its edges. */
.md-table-scroll {
  overflow-x: auto;
  scrollbar-width: none;
}
.md-table-scroll::-webkit-scrollbar {
  display: none;
}
/* Desktop / mouse pointers can't flick a wide table sideways the way touch can, so show a real,
   always-visible horizontal scrollbar under the table there. Touch devices (coarse pointer) keep
   the hidden bar + edge fades — the drag gesture is the natural affordance. Styling the WebKit
   scrollbar also opts the bar out of macOS overlay auto-hide, so it stays visible while it's needed
   (overflow-x:auto still only paints it when the table actually overflows). */
@media (hover: hover) and (pointer: fine) {
  .md-table-scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--cg-scrollbar-thumb) var(--cg-scrollbar-track);
  }
  .md-table-scroll::-webkit-scrollbar {
    display: block;
    height: 10px;
  }
  /* Mirror the app's existing themed scrollbar (see .chatgpt-reference-v1 scrollbar block): the
     dark-mode-aware --cg-scrollbar-thumb token + inset thumb, not a hardcoded gray. */
  .md-table-scroll::-webkit-scrollbar-thumb {
    border: 3px solid transparent;
    border-radius: var(--ds-radius-pill, 999px);
    background: var(--cg-scrollbar-thumb);
    background-clip: content-box;
  }
}
.md-table-scroll:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: -2px;
}
/* Honest "more content" fades (doc 168): JS sets data-fade-left/right only while there is
   actually content beyond that edge, so the gradient never lies about scrollability. */
.md-table-frame::before,
.md-table-frame::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 26px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 140ms ease;
  z-index: 1;
}
.md-table-frame::before {
  left: 0;
  background: linear-gradient(90deg, var(--cg-surface, #fff), transparent);
}
.md-table-frame::after {
  right: 0;
  background: linear-gradient(270deg, var(--cg-surface, #fff), transparent);
}
.md-table-frame[data-fade-left="1"]::before {
  opacity: 1;
}
.md-table-frame[data-fade-right="1"]::after {
  opacity: 1;
}
/* ★T1(doc 626 §9, 2026-09-18): 안 B의 스크롤 셸 — `.md-table-frame`과 같은 «비-스크롤
   위치 기준»(overflow:hidden + 엣지 페이드)이지만 흰 면·테두리·반경이 없다("프레임·
   그림자 0", design-brief §8축). bindTableScrollAffordance는 `scroller.parentElement`를
   범용으로 읽으므로 JS 변경 없이 이 셸이 그대로 페이드 앵커가 된다. */
.md-table-shell {
  position: relative;
  overflow: hidden;
  /* design-brief §8축 «깊이: 층 1(헤어라인+머리행 배경), 프레임·그림자 0» — 표 블록
     전체를 위/아래 헤어라인 하나로만 감싼다(mockup.html .b-table과 동일 값). */
  border-top: 1px solid var(--cg-menu-border, var(--line));
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}
.md-table-shell::before,
.md-table-shell::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 26px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 140ms ease;
  z-index: 1;
}
.md-table-shell::before {
  left: 0;
  background: linear-gradient(90deg, var(--cg-surface), transparent);
}
.md-table-shell::after {
  right: 0;
  background: linear-gradient(270deg, var(--cg-surface), transparent);
}
.md-table-shell[data-fade-left="1"]::before {
  opacity: 1;
}
.md-table-shell[data-fade-right="1"]::after {
  opacity: 1;
}
.md-table {
  width: 100%;
  min-width: 340px;
  border-collapse: collapse;
  font-size: var(--cg-text-label-size, 14px);
}
.md-table th,
.md-table td {
  padding: var(--ds-space-2) var(--ds-space-3);
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
  text-align: left;
  vertical-align: top;
  line-height: 1.5;
}
/* 3+ columns = a comparison grid: give every column readable breathing room so a narrow
   viewport SCROLLS the grid instead of squeezing Korean text into letter ribbons (NN/g:
   wordy cells stay legible in at most 2 mobile columns — beyond that, preserve structure).
   ★132→120 (중앙 실측, answer-table-width-policy-judge/measured-probe.json 09-18): 768폭
   스레드 컬럼에서 132px × 6열 = 792px 로 정확히 24px 넘쳐 가로 스크롤이 켜졌다(운영 표
   47개 중 3.9%). 120px 는 6열까지 20/20 표본에서 스크롤 0(120×6=720≤768)이고 7열부터는
   여전히 스크롤(120×7=840>768) — 열 수 정책(2ⓒ, 열 ≥6 만 가로 스크롤)과 어긋나지 않는다.
   패딩(12→10)은 건드리지 않는다 — 타이포·밀도 유지, 폭만 좁힌다. */
.md-table--wide th,
.md-table--wide td {
  min-width: 120px;
}
/* ── 타이포 위계 (①, doc 234) ──
   ★실측: 헤더도 본문도 14px에 굵기는 500/600 두 칸뿐이었다. 100 차이는 눈에 거의 안
   보이므로 헤더와 데이터를 가르는 것이 사실상 회색 하나였다 — 「문서 같다」는 인상의 뿌리.
   조사 결론이 「글꼴을 바꾸지 말고 굵기로 위계를 세워라」인데 그 채널을 안 쓰고 있었다.
   사다리를 세 칸으로 벌린다: 본문 400 < 행 식별자 600 < 열 머리글 700.
   ★열 머리글은 작아지고 굵어진다 — 데이터 표의 관례이자, 크기·굵기 두 채널을 동시에 쓰는
   유일한 방법이다. 작아진 만큼 자간을 열어 판독성을 지킨다(조사: 작은 크기에서 자간 증가).
   ★모바일 kv 스택은 thead를 display:none으로 숨기므로 이 변경이 닿지 않는다
   (`test_table_typographic_hierarchy.py`가 그 전제를 지킨다). */
.md-table thead th {
  /* ★v7 — 머리글 행을 연회색 밴드로 세운다(사용자 요청 2026-08-06: 「진짜 표처럼 타이틀·
     내용 구분」). 카드를 걷어내며 흰 면이 사라지자 머리글과 본문을 가르는 채널이 글자
     색·크기·굵기뿐이 되었다 — 면(background)이 그 셋보다 먼저 읽힌다.
     ★hex 폴백을 붙이지 않는다. `var(--x, #fff)` 의 폴백도 드리프트 래칫의 raw-color 로
     세어지고, 그 카운터는 여유가 0이다. 토큰이 없으면 투명이 되는 편이 낫다 —
     폴백이 죽은 토큰을 숨기는 문제도 같이 피한다. */
  background: var(--cg-hover-soft);
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-bold);
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.md-table tbody th {
  font-weight: var(--ds-weight-semibold);
}
/* ★본문 굵기를 «명시»한다. 상속에 맡기면 사다리의 맨 아래 칸이 어디서 오는지 알 수 없고,
   위쪽 산문 규칙이 바뀌면 위계가 조용히 무너진다. */
.md-table tbody td {
  font-weight: var(--ds-weight-regular);
}
.md-table tbody tr:last-child th,
.md-table tbody tr:last-child td {
  border-bottom: 0;
}
/* Standard alignment (F1 table card redesign, doc 171): a numeric column right-aligns its
   header AND its data cells together — header/data alignment mismatch was the main legibility
   complaint in the B안 review. Tabular figures keep digit columns from jittering row to row. */
.md-table .is-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* Row hover only makes sense in the real grid — the kv mobile stack below repurposes th/td as
   block-level label/value pairs, where a row "highlight" would just be visual noise. */
/* ★`@media (hover: hover)` 가 없으면 터치 기기에서 이 배경이 «끈적인다».
   iOS 는 탭한 요소에 `:hover` 를 걸고 **다른 곳을 누를 때까지 유지**한다 — 마우스가 없으니
   뗄 계기가 없다. 2026-08-08 iPhone 17 실측에서 카드 줄을 탭하면 회색 띠가 남았고, 그
   띠에 «검은 사각형»이 뚫려 보였다: 이 규칙은 `tr` 이 아니라 **`th`/`td` 에** 칠하는데
   카드 리스트에서 행이 `display: grid` 라 셀 사이 `column-gap` 이 칠해지지 않고 캔버스가
   비친다. 원인을 찾기까지 캐럿·고정 열·탭 하이라이트·눌림 클래스·텍스트 선택을 차례로
   지웠다 — 전부 아니었고, 답은 «마우스 전용 피드백이 터치에 새어 나온 것»이었다.
   ★리포는 이 가드를 이미 30곳에서 쓴다. 이 표면만 빠져 있었다. */
@media (hover: hover) {
  .md-table tbody tr:hover th,
  .md-table tbody tr:hover td {
    background: var(--cg-hover-soft, #f7f7f8);
  }
}
/* Totals row (F1): the last body row is flagged when its row-header label matches a summary
   word (합계/총계/총합/계/소계/Total/Sum) — a heavier top rule + bold weight sets it apart from
   the data rows above it without a full background block. */
.md-table .md-table-total-row th,
.md-table .md-table-total-row td {
  border-top: 1.5px solid var(--cg-menu-border, #e3e3e3);
  /* ★토큰이어야 한다. 원래 `#fcfcfc` 였는데 원시 hex 라 테마를 못 따라갔다 — 다크에서
     글자만 밝아지고 면은 흰색에 남아 대비 **1.15:1**, 사실상 안 보였다(2026-08-08 실측.
     라이트에서는 18.94:1 이라 아무 신호가 없었다).
     ★지표 타일 띠·머리글 밴드가 이미 쓰는 «요약 면» 어휘를 그대로 쓴다. 셋 다 같은 성격의
     면이라 어휘를 나눌 이유가 없다. */
  background: var(--cg-hover-soft);
  font-weight: 600;
}
/* Formula-cell indicator (F1, visual only — evaluation lands in F2): a small dot after a cell
   whose raw text starts with "=" flags it as a spreadsheet-style formula reference. */
.md-table .md-formula-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: var(--ds-space-1);
  vertical-align: middle;
  border-radius: 50%;
  background: #16a34a;
}
/* 2-column key-value table → stacked label/value cards on a phone (doc 168, Codex-TUI record
   transform): rows are independent items (no comparison axis), so stacking loses nothing and
   reads like the list the narrow screen wants. The generic 2-col header adds no information
   once each label sits directly above its value, so it is dropped (CSS-Tricks canonical). */
@media (max-width: 520px) {
  .md-table--kv,
  .md-table--kv tbody,
  .md-table--kv tr,
  .md-table--kv th,
  .md-table--kv td {
    display: block;
  }
  .md-table--kv thead {
    display: none;
  }
  .md-table--kv tbody tr {
    padding: var(--ds-space-2) var(--ds-space-3);
  }
  .md-table--kv tbody tr + tr {
    border-top: 1px solid var(--cg-menu-border, var(--line));
  }
  .md-table--kv tbody th,
  .md-table--kv tbody td {
    padding: 0;
    border-bottom: 0;
  }
  .md-table--kv tbody th {
    margin-bottom: 2px;
    font-size: var(--cg-text-meta-size, 12px);
    color: var(--cg-muted, var(--muted));
  }
  /* The stacked card reads top-to-bottom (label above value) — a right-aligned numeric value
     against the card's full width would float away from its label, so realign left here. */
  .md-table--kv tbody .is-num {
    text-align: left;
  }
  /* ★kv 스택은 th/td를 block으로 바꾼다. 거기서 sticky는 의미가 없고 배경만 남아
     스택 카드의 라벨 줄에 엉뚱한 면이 생긴다. */
  .md-table-scroll .md-table--kv th:first-child,
  .md-table-scroll .md-table--kv td:first-child {
    position: static;
    background: transparent;
    box-shadow: none;
  }

  /* ── 3열 이상 표 = 카드 리스트 (B2 — 요약 줄 + 펼침, 소유자 결정 2026-08-08) ──
     ★2열 표는 위 `md-table--kv` 가 이미 라벨/값 카드로 스택한다. 여기서 다루는 것은 3열
     이상, 즉 «비교 축이 있는» 표다. 그래서 통째로 카드로 쪼개지 않는다 — 접힌 상태에서
     이름과 대표값이 한 줄로 남아 세로로 정렬되고, 그래서 열 방향 비교가 살아 있다.
     ★조사 경고를 그대로 받는다: 「카드는 훑기용, 표는 비교용」. 행을 통째로 카드로 쪼개는
     B1 안은 5행 표를 화면 4개 높이로 만들면서 「어느 분기가 가장 컸나」를 잃는다 —
     좁은 화면에서 «공간을 아껴 읽는다»는 목표와 반대로 간다.
     ★DOM 은 표 그대로다. 표시만 바꾼다 — 시맨틱·내보내기·수식 치환이 전부 그대로 산다. */
  .md-table--wide[data-card-list="1"],
  .md-table--wide[data-card-list="1"] tbody,
  .md-table--wide[data-card-list="1"] th,
  .md-table--wide[data-card-list="1"] td {
    display: block;
  }
  /* ★머리글을 지우지 않고 감춘다. 스크린리더는 여전히 열 이름을 듣고, 각 값 앞에는
     아래 `::before` 가 같은 이름을 다시 붙인다. */
  .md-table--wide[data-card-list="1"] thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .md-table--wide[data-card-list="1"] tbody tr {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    column-gap: var(--ds-space-3);
    padding: var(--ds-space-3) var(--ds-space-1);
    /* ★44px 는 스케일 단계가 아니라 터치 표적이다(같은 파일 알약 주석과 같은 상수). */
    min-height: var(--md-table-pill-target, 44px);
    border-top: 1px solid var(--cg-menu-border, var(--line));
    cursor: pointer;
  }
  .md-table--wide[data-card-list="1"] tbody th,
  .md-table--wide[data-card-list="1"] tbody td {
    padding: 0;
    border: 0;
  }
  .md-table--wide[data-card-list="1"] tbody tr:focus-visible {
    outline: 2px solid var(--cg-focus-ring, var(--accent));
    outline-offset: 2px;
  }
  /* ★탭한 행에 회색 면과 «검은 사각형»이 남았다(2026-08-08 iPhone 17 실측).
     추측을 두 번 하고 두 번 틀린 뒤에야 측정으로 좁혔다. 증거 셋:
       1. Chrome 에서 같은 DOM·같은 선언으로 재현하니 행 안에 면을 그리는 것은 초록
          수식 점 둘뿐이고, 행을 덮는 외부 요소는 **0개**였다 → 레이아웃이 아니다.
       2. 시뮬레이터에서 **다른 곳을 탭하자 회색 면과 검은 사각형이 함께 사라졌다**.
          행은 펼쳐진 채였다 → 상태가 아니라 «탭에 딸린 일시 표시»다.
       3. 리포 전역에 `-webkit-tap-highlight-color` 선언이 하나도 없다 → 이 표면은
          Safari 기본 탭 하이라이트를 그대로 쓰고 있었다.
     ★행이 `display: grid` 라 하이라이트가 칠하는 클라이언트 사각형이 셀별로 쪼개지고,
     그 사이에 네모난 조각이 남는다 — 그것이 «검은 사각형»으로 보였다.
     ★`user-select: none` 을 행 전체에 주지 **않는다**. 그러면 폰에서 숫자 하나를 길게
     눌러 복사하는 길이 막히고, 「표 전체를 내보내세요」는 그보다 나쁜 답이다. */
  .md-table--wide[data-card-list="1"] tbody tr[data-card-row] {
    -webkit-tap-highlight-color: transparent;
    /* ★진짜 원인은 **텍스트 선택**이었다. Chrome 에서 같은 행을 선택시켜 재보니 선택
       하이라이트가 클라이언트 사각형 **8개**로 쪼개졌다 — 첫 줄에서 이름 칸이
       `x 4→609`, 값 칸이 `x 621→753`. 그 사이 그리드 간격이 칠해지지 않고 남아
       **캔버스가 비쳐 보이는 검은 조각**이 된다. 회색 띠는 선택 하이라이트 자체다.
       탭이 선택을 만드는 이유: 이름 칸은 `1fr` 이라 짧은 글자 뒤로 빈 공간이 넓고,
       그 빈 곳을 누르면 Safari 가 선택 제스처로 읽는다.
       ★그래서 «누르는 상자»는 선택 불가로, «데이터»는 선택 가능으로 나눈다. 행 전체를
       막는 쉬운 답은 폰에서 숫자 하나 복사하는 길을 없앤다 — 아래에서 되돌린다. */
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
  }
  /* ★값과 상세는 다시 고를 수 있게 되돌린다. 카드 리스트를 만든 이유가 좁은 화면에서
     값을 «읽게» 하는 것인데, 읽은 값을 가져갈 수 없으면 반쯤만 한 것이다. */
  .md-table--wide[data-card-list="1"] tbody [data-card="value"],
  .md-table--wide[data-card-list="1"] tbody [data-card="detail"] {
    -webkit-user-select: text;
    user-select: text;
  }
  /* 요약 줄 — 이름 왼쪽, 대표값 오른쪽. 접혀 있어도 이 두 칸은 언제나 보인다. */
  .md-table--wide[data-card-list="1"] tbody [data-card="key"] {
    grid-column: 1;
    text-align: left;
    font-weight: var(--ds-weight-semibold);
  }
  .md-table--wide[data-card-list="1"] tbody [data-card="value"] {
    grid-column: 2;
    text-align: right;
    font-variant-numeric: tabular-nums;
  }
  /* ★펼침 표시는 생성 콘텐츠다. 셀에 텍스트 노드를 넣으면 복사·엑셀·CSV 가 전부
     「1,200 ▾」이 된다 — 내보내기는 `textContent` 를 읽고 복사는 raw `outerHTML` 을 싣는다. */
  .md-table--wide[data-card-list="1"] tbody tr[data-card-row] [data-card="value"]::after {
    content: "▾";
    margin-left: var(--ds-space-2);
    color: var(--cg-muted);
    font-size: var(--cg-text-meta-size);
  }
  .md-table--wide[data-card-list="1"] tbody tr[data-expanded="1"] [data-card="value"]::after {
    content: "▴";
  }
  /* 상세 — 펼쳤을 때만. 라벨:값 한 줄. */
  .md-table--wide[data-card-list="1"] tbody [data-card="detail"] {
    grid-column: 1 / -1;
    display: flex;
    align-items: baseline;
    /* ★`space-between` 이면 안 된다(2026-08-08 iOS 시뮬레이터 실측). 셀은 자식이 하나가
       아니다 — 수식 평가 셀은 「값 텍스트 + 평가 표시 점」 둘을 담고, flex 는 익명 텍스트
       노드까지 아이템으로 세므로 [라벨][값][점] **셋**이 고르게 벌어져 값이 한가운데로
       떠 버린다. 라벨만 밀어내면 값과 점은 붙은 채 오른쪽으로 간다. */
    justify-content: flex-start;
    gap: var(--ds-space-1);
    padding-top: var(--ds-space-1);
  }
  .md-table--wide[data-card-list="1"] tbody [data-card="detail"]::before {
    content: attr(data-label);
    margin-right: auto;
    padding-right: var(--ds-space-3);
    color: var(--cg-muted);
    font-size: var(--cg-text-meta-size);
  }
  .md-table--wide[data-card-list="1"] tbody tr:not([data-expanded="1"]) [data-card="detail"] {
    display: none;
  }
  /* ★스택에서 sticky 는 의미가 없고 배경만 남아 이름 줄에 엉뚱한 면이 생긴다
     (위 `md-table--kv` 가 같은 이유로 같은 것을 한다). */
  /* ★추이 열도 «오른쪽 고정»이다 — 첫 열 고정의 거울상이라 같은 병이 걸린다.
     2026-08-08 iPhone 17 실측: 카드를 펼치면 이름 오른쪽에 검은 사각형이 떴다. 스택에서
     `position: sticky` 는 붙을 축이 없어 아무 일도 안 하는데 `background`·`box-shadow`·
     `z-index: 2` 는 그대로 살아 남의 줄 «위»에 면을 그린다(다크 캔버스라 검정으로 보였다).
     ★캐럿인 줄 알고 `caret-color` 부터 손댔다가 틀렸다 — 화면만 보고 원인을 지어낸 것이다.
     선언을 읽으니 리포 주석이 이 실패 모드를 kv 변형에 대해 이미 적어 두고 있었다. */
  /* ★카드 리스트에서는 레일을 그리지 않는다. 레일의 일은 «행과 행을 세로로 잇는 것»인데,
     스택에서는 행 사이가 카드 경계로 갈라져 선이 토막난다 — 이으려던 것을 못 잇고
     조각만 남는다. 순서는 이미 카드가 위에서 아래로 읽히는 것이 말해 준다.
     ★`content: none` 으로 끈다. 규칙 자체를 좁은 폭 밖에 두면 넓은 화면과 어긋난다. */
  .md-table--wide[data-card-list="1"] th.md-table-timeline::before,
  .md-table--wide[data-card-list="1"] td.md-table-timeline::before,
  .md-table--wide[data-card-list="1"] th.md-table-timeline::after,
  .md-table--wide[data-card-list="1"] td.md-table-timeline::after {
    content: none;
  }
  .md-table--wide[data-card-list="1"] th.md-table-timeline,
  .md-table--wide[data-card-list="1"] td.md-table-timeline {
    padding-left: 0;
  }
  .md-table-scroll .md-table--wide[data-card-list="1"] .md-table-sparkline-head,
  .md-table-scroll .md-table--wide[data-card-list="1"] .md-table-sparkline-cell,
  .md-table-scroll .md-table--wide[data-card-list="1"] th:first-child,
  .md-table-scroll .md-table--wide[data-card-list="1"] td:first-child {
    position: static;
    background: transparent;
    box-shadow: none;
    /* ★`width` 도 되돌려야 한다. 추이 열은 넓은 화면에서 「내용만큼만」 차지하려고
       `width: 1%` 를 쓰는데, 스택에서는 그 선언이 `grid-column: 1 / -1` 을 **이겨**
       상세 줄 하나만 좁아진다(2026-08-08 실측: 다른 상세 749px vs 추이 132px).
       고정을 되돌릴 때 면·그림자만 챙기고 폭을 빠뜨린 것이 이 결함이었다 — 넓은 화면
       전용 선언은 «전부» 되돌려야 한다. */
    width: auto;
  }
  /* ★선도 오른쪽 끝에 앉아야 한다. 넓은 화면에서 선은 좁은 열 «가운데»에 놓이려고
     `margin: 0 auto` 를 쓰는데, 상세 줄이 전체 폭이 되자 그 auto 가 216px 로 풀려
     선이 허공 한가운데 떴다(2026-08-08 실기기 실측: 셀은 4→753 인데 선은 473→537).
     ★같은 카드 안에서 다른 상세 값은 전부 오른쪽 끝 정렬이다 — 선만 다른 축에 서면
     「이 줄은 다른 종류인가」로 읽힌다. 취향이 아니라 형제와의 일관성 문제다. */
  .md-table--wide[data-card-list="1"] tbody [data-card="detail"] .md-table-sparkline {
    margin: 0;
  }
  /* ★2026-09-03 doc 626 §7: 알약(절대배치 액션 바)을 걷어냈으므로 그 자리를 비우던
     padding-bottom 예약도 함께 걷어낸다 — 액션은 이제 항상 `.md-table-toolbar`의 flex
     흐름 안에 있어 카드가 별도로 바닥 여백을 만들 필요가 없다(desktop과 동일).
     ★44px 정의는 남긴다 — «스케일 단계»가 아니라 터치 표적 크기 상수이고, 아래
     `@media (any-pointer: coarse), (max-width: 520px)` 블록의 버튼 min-height 가
     여전히 이 값을 쓴다. */
  .md-table-wrap {
    --md-table-pill-target: 44px;
  }
  .md-table--kv {
    min-width: 0;
  }
}
/* ★T1(doc 626 §9, 2026-09-18): `.md-table-toolbar`/`.md-table-titles`(카드 툴바 제목 행)
   생성을 app.js가 더 이상 하지 않는다 — 안 B 구조에는 상주 액션 행이 없다(캡션은
   `<p class="md-table-caption md-table-title">` 하나로 충분하다). 옛 규칙은 걷어낸다;
   `.md-table-title` 자체(액센트 바·카운터·폴백 숨김)는 캡션이 그대로 상속하므로 아래에
   남긴다. */
.md-table-title {
  /* ★v7 — 22px 헤드라인에서 «표 제목»으로 내린다(사용자 요청 2026-08-06).
     22px/600은 카드 헤드라인 크기였다. 카드를 걷어낸 지금 그 크기는 본문 h3(20px)보다
     커서 표가 여전히 «독립 위젯»으로 읽힌다. 18px/700 은 본문 h3 아래·본문 16px 위에
     앉아 «이 표의 이름»으로 읽히는 자리다.
     ★굵기로는 올릴 수 없다 — 시도했다가 타이포 계약(T4)에 걸렸다. 원시 굵기 700은
     **모바일 상단바 WIZON 워드마크 한 곳**에만 허용된 브랜드 예외이고, 래칫이 그 예외가
     번지는 것을 막는다(그 게이트는 주석까지 세므로 여기에도 선언 형태로 적지 않는다).
     셸도 `--ds-weight-bold`를 600으로 덮으므로 토큰 경로로도 닿지 못한다.
     이 표면에서 굵기 사다리는 400/500/600 세 칸이 전부다.
     → 위계는 **크기(18 > 행 식별자 14 > 머리글 12)와 액센트 바**가 나눠 진다. 굵기는
     행 식별자와 같은 600이지만 4px 크기 차와 바가 «표의 이름»을 충분히 세운다.
     ★왼쪽 액센트 바(::before)가 «제목» 신호를 담당한다. 색을 글자에 칠하지 않는 이유 —
     매 답변에 채도색이 번지면 링크·강조와 경쟁한다(doc 345 잉크 규율). 바는 면적이
     좁아 그 예산 안에 들어가고, `--accent`라 사용자가 고른 강조색·다크모드를 따라간다. */
  /* ★flex 로 짜지 않는다. `applyTableCardTitle`(app.js)이 이 노드에 `textContent`를
     그대로 대입해 제목을 갱신하므로 자식 요소를 두면 조용히 지워진다. 또 flex 컨테이너의
     익명 텍스트 아이템에는 `text-overflow: ellipsis`가 걸리지 않는다.
     → 바는 절대배치 `::before`(DOM이 아니라 CSS라 textContent 대입에도 살아남는다),
       본문은 padding-left로 비켜 앉힌다. */
  /* ★v12 — `nowrap` + 말줄임을 **두 줄 줄바꿈**으로 바꾼다(2026-08-08).
     실측이 원인을 지목했다: 390px 에서 제목 196px 중 **87px(44%)이 잘렸고**, 같은 폭에서
     뷰 토글만 숨기니 87px → 7px 로 떨어졌다. 즉 잘림의 주범은 폭이 아니라 «같은 줄을
     쓰는 컨트롤»이었다. 토글은 A안에서 사라졌지만, 제목이 한 줄로 고정된 한 더 긴 제목은
     같은 자리에서 다시 잘린다 — 구조로 닫는다.
     ★업계도 제목을 컨트롤과 겨루게 두지 않는다(2026-08-08 실측): Claude 는 표 제목을
     산문 H3 18px/600 으로 표 위에 따로 놓고(우리와 같은 값·다른 자리), ChatGPT 는 제목
     없이 산문 한 문장이 그 역할을 한다. 어느 쪽도 잘리지 않는다.
     ★두 줄에서 멈춘다. 무한정 늘면 제목이 문단이 되어 표를 아래로 밀어낸다. */
  position: relative;
  padding-left: var(--ds-space-3);
  overflow: hidden;
  white-space: normal;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  font-size: var(--ds-text-title-18);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--cg-text, #0d0d0d);
}
.md-table-title::after {
  /* 제목 왼쪽 액센트 바. `height: 1em`이라 글자 크기를 따라간다.
     ★v9 에서 `::before` → `::after` 로 옮겼다. 바는 절대배치라 문서 순서와 무관하게
     같은 자리에 그려지고, 비워진 `::before` 는 지금 아무도 안 쓴다(v11, 09-12 표
     번호 제거로 비움 — doc 626 §1.1). */
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 4px;
  height: 1em;
  transform: translateY(-50%);
  border-radius: var(--ds-radius-xs, 6px);
  background: var(--accent);
}
/* ── 폴백 제목은 화면에서 감춘다 (v7-2, 실측 기반) ──
   ★실제 답변 3건이 모두 "표 1" 이었다 — 모델이 표부터 쓰기 시작해 승격할 헤딩이 없다.
   그건 «이 표의 이름»이 아니라 기계가 붙인 일련번호다. 카드 시절엔 헤더 안 헤드라인이라
   그러려니 했지만, 인라인에서는 액센트 바까지 달려 **의미 없는 라벨이 오히려 강조**된다.
   "N열 × M행" 을 화면에서 뺀 것과 정확히 같은 이유다.
   ★지우지 않고 감춘다: 표가 여럿일 때 스크린리더는 이름으로 구분하고, 아티팩트가 진짜
   제목을 주면 `applyTableCardTitle` 이 표식을 걷어내 그대로 드러난다. */
.md-table-title[data-title-fallback] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding-left: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.md-table-title[data-title-fallback]::before {
  content: none;
}
/* ★이 자리에 있던 «폴백 제목이면 툴바 padding-bottom:0» 규칙을 전역으로 두지 않는다
   (2026-09-02 오너 실화면 보고 수리: 업무 워크스페이스에서 표 위 버튼 행이 표 머리글에
   0px로 붙어 있었다 — doc 626 §6).
   이전 판단: 폴백 제목은 화면에서 감춰지니(위 규칙), 그 아래 8px 도 «제목과 표를
   띄우는» 간격이라 제목이 없으면 근거가 없다고 봤다. 근거가 그 8px 을 «제목의 몫»으로만
   읽은 것이 오류였다 — `.md-table-toolbar` 는 제목과 액션 버튼(XLSX 다운로드·시트로
   편집·⋯)이 **같은 flex 행**을 공유한다. 넓은 화면(데스크톱)에서는 액션이 이 흐름 안에
   그대로 남아 있으므로, padding-bottom 을 0으로 밀면 «제목»이 아니라 **여전히 보이는
   버튼 행**이 표 머리글에 바로 붙는다 — 캡션 있는 표와 없는 표에서 버튼 간격만 달라지는
   회귀다. 채팅에서 캡션 있는 표의 간격은 원래도 이 규칙이 적용된 적이 없으므로(이 셀렉터는
   `[data-title-fallback]`에만 걸린다) 그대로 유지된다.
   ★2026-09-03 doc 626 §7: 위 예외의 유일한 근거 — 좁은/터치 화면에서 액션이 알약으로
   절대배치되어 이 흐름을 떠난다 — 그 조건 자체를 걷어냈다. 오너 실화면 보고(2026-09-03
   16:45 KST, iPhone Safari 390px): 표 아래 떠 있는 둥근 바가 표 헤더·첫 행을 가렸다
   (실측: 라벨 두 개 + ⋯가 폭에 못 들어가 두 줄로 접히며 겹침이 커졌다). 이제 모바일도
   데스크톱처럼 액션이 항상 `.md-table-toolbar`의 flex 흐름 안에 있다(줄바꿈만 하고 흐름
   밖으로 나가지 않는다 — 아래 `@media (any-pointer: coarse), (max-width: 520px)` 블록
   참고). 그래서 폴백 제목이어도 툴바에는 여전히 보이는 버튼 행이 남고, 위 전역 규칙의
   전제가 모든 폭에서 성립한다. 조건부 예외는 더 이상 없다. */
/* ★T1: `.md-table-subtitle`(sr-only "N열×M행")·`.md-table-actions`·`.md-table-action`
   (상주 버튼 칩)은 markdownTableHtml이 더 이상 만들지 않는다 — 열×행 수는 이제
   `.md-table-foot .md-table-provenance` 보이는 텍스트에 있고("모델 지식 · N열 × M행"),
   상주 액션은 `.md-table-quiet`(«크게 보기»)와 `.md-table-more*`(··· 메뉴) 둘뿐이다. */
.md-table-more {
  position: relative;
}
.md-table-more-trigger {
  /* 20260911-table-toolbar-more-unify (C16): 44→36 폭으로 형제 버튼(다운로드·저장·시트로
     편집, 전부 h36)과 같은 폭을 쓰고, 좌우 패딩도 형제와 같은 --ds-space-3(12px)으로
     맞춘다(기존 --ds-space-2=8px 는 형제보다 좁았다). 반경·폰트는 이미 `.md-table-action`
     기본값(r8·12px/600)을 그대로 물려받는다.
     20260912-more-trigger-hitslop (main #6036 RED 수습): C16 이 시각 폭을 36으로 형제와
     맞추며 min-width:44px(구 44 폭)를 함께 지워 터치 타깃이 36으로 줄었다(K-4 회귀).
     시각 36은 형제 통일을 위해 유지하고, 히트 영역만 기존 20260729-touch-target-a2
     관례(.chatgpt-drive-context-trigger 등, 투명 ::after + min-width/min-height:44px +
     position:relative/absolute 중앙 정렬)를 그대로 따라 44×44로 되돌린다 — 새 규약이
     아니라 이 파일이 이미 쓰는 것과 같은 규약 1종. */
  width: 36px;
  padding-inline: var(--ds-space-3);
  font-size: var(--ds-text-caption-12);
  letter-spacing: 1px;
  position: relative;
}
.md-table-more-trigger::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
  transform: translate(-50%, -50%);
}
.md-table-more-menu {
  position: absolute;
  top: calc(100% + var(--ds-space-1));
  right: 0;
  z-index: 20;
  display: grid;
  min-width: 196px;
  padding: var(--ds-space-1);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-radius-menu-row);
  background: var(--cg-surface, var(--ds-color-surface));
  box-shadow: var(--ds-shadow-md);
}
.md-table-more-menu[hidden] {
  display: none;
}
.md-table-more-menu button {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  align-items: center;
  gap: var(--ds-space-2);
  min-height: 40px;
  padding: 0 var(--ds-space-2);
  border: 0;
  border-radius: var(--ds-radius-xs);
  background: transparent;
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  text-align: left;
  cursor: pointer;
}
.md-table-more-menu button:hover,
.md-table-more-menu button:focus-visible {
  background: var(--cg-hover-soft, var(--ds-color-surface-muted));
}
.md-table-copy-icon {
  display: inline-flex;
}
.md-table-copy-icon svg,
.md-table-copy-icon .wds-inline-icon {
  width: 18px;
  height: 18px;
}
@media (any-pointer: coarse), (max-width: 520px) {
  /* ── 차트 범례의 터치 슬롭 (2026-08-08 실기기) ──
     ★iPhone 17(402×874pt)에서 처음 재 봤다. 탭은 동작하지만 항목이 **67×30px** 이라
     WCAG 2.2 SC 2.5.8(24×24)은 넘어도 Apple HIG 의 44pt 는 못 넘는다.
     ★보이는 크기는 안 건드린다. 30px 을 44px 로 키우면 범례 줄이 데스크톱에서도 두꺼워져
     차트의 리듬이 바뀐다 — 이 파일이 이미 쓰는 방법(.citation-tag::after)대로 보이지 않는
     슬롭으로 히트 영역만 넓힌다. 폭은 이미 44 를 넘으므로 높이만.
     ★자기 칸 밖으로 나가지 않는다. 슬롭은 안 보이므로 겹치면 엉뚱한 계열이 꺼진다. */
  .md-chart-legend-item::after {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    width: 100%;
    height: 44px;
    transform: translateY(-50%);
  }
  /* ── 모바일 표 푸터 (T1, doc 626 §9 계승) ──
     옛 "모바일 액션 알약"(doc 626 §7)이 겪은 실패는 그대로 유효한 교훈이다: 흐름을
     떠나 절대배치되면 표 헤더 위에 겹친다. T1의 `.md-table-foot`은 처음부터 흐름
     안의 정적 행이고, 액션이 «크게 보기»+«···» 둘뿐이라 줄바꿈이 필요할 만큼 넘칠
     일이 없다 — `.md-table-foot`(위 정의, flex-wrap: wrap)이 극단 폭 대비를 이미 맡는다.
     `.md-table-more-menu`의 드롭다운은 여전히 `.md-table-foot`의 overflow:visible
     흐름 안에서 열린다(가로 스크롤을 주지 않는다 — 같은 실패를 반복하지 않는다). */
  .md-table-more-menu button {
    min-height: var(--md-table-pill-target);
  }
}
@media (prefers-reduced-motion: reduce) {
  .md-table-quiet {
    transition: none;
  }
  .artifact-titlebar-button {
    transition: none;
  }
}
/* Sheet-window titlebar download buttons (doc 171 A1) — same 34x34 icon-only convention as
   .md-table-copy above (F1), kept as its own ruleset (not a shared selector list) because
   test_table_action_buttons_are_icon_only_with_tooltips pins ".md-table-copy {" as a standalone
   selector string. */
.artifact-titlebar-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--cg-text-soft, var(--muted));
  font: inherit;
  line-height: 1;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.artifact-titlebar-button:hover {
  background: var(--cg-hover-soft, #f4f4f4);
  color: var(--cg-text, var(--text));
}
.artifact-titlebar-button:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}
.artifact-titlebar-button.is-copied {
  color: var(--accent, #2f6bff);
}
.artifact-titlebar-button:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
.artifact-titlebar-button:disabled:hover {
  background: transparent;
  color: var(--cg-text-soft, var(--muted));
}
.artifact-titlebar-button-icon {
  display: inline-flex;
}
.artifact-titlebar-button-icon svg,
.artifact-titlebar-button-icon .wds-inline-icon {
  width: 18px;
  height: 18px;
}

/* KaTeX math (render2, self-hosted). Inline math inherits answer color; display math centers and
   scrolls horizontally if an equation is wider than the bubble. */
.message-body .katex {
  font-size: 1.05em;
}
.message-body .md-math-block {
  margin: var(--ds-space-4) 0;
  padding: 4px 2px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}
.message-body .md-math-block .katex-display {
  margin: 0;
}
.message-body .md-math-fallback {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9em;
}

/* <kbd> keyboard keys (render2). */
.message-body .md-kbd {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-bottom-width: 2px;
  border-radius: var(--ds-radius-xs, 6px);
  background: var(--cg-hover-soft, var(--surface-2));
  color: var(--cg-text, var(--text));
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85em;
  line-height: 1.4;
  white-space: nowrap;
}

/* Inline emphasis (render2b): explicit so a cascaded font-style/text-decoration reset can't strip
   *italic* / ~~strikethrough~~. Struck text is de-emphasized (muted) like GitHub/Notion. */
.message-body em {
  font-style: italic;
}
.message-body del {
  text-decoration: line-through;
  color: var(--cg-text-soft, var(--muted));
}

/* Thematic break (render2b2): a quiet full-width hairline for "---" section dividers — no default
   inset border, tokenized so light/dark both read as a subtle rule. */
.message-body .md-hr {
  height: 0;
  margin: var(--ds-space-6) 0;
  border: 0;
  border-top: 1px solid var(--cg-menu-border, var(--line));
}

/* Mermaid diagrams (doc 169). Before hydration (or on load/parse failure) the source <pre> shows as
   the fallback; once rendered, the SVG shows and a toggle flips between diagram and source. */
.message-body .mermaid-block {
  margin: 12px 0 14px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: 12px;
  overflow: hidden;
  background: var(--cg-surface, #fff);
}
.message-body .mermaid-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 8px 6px 14px;
  background: var(--cg-hover-soft, var(--surface-2));
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}
.message-body .mermaid-label {
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  color: var(--cg-text-soft, var(--muted));
}
.message-body .mermaid-toggle {
  padding: 3px 9px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--cg-text-soft, var(--muted));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  line-height: 1;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.message-body .mermaid-toggle:hover {
  background: var(--cg-sidebar-active-bg);
  color: var(--cg-text, var(--text));
}
.message-body .mermaid-toggle:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent, #2f6bff));
  outline-offset: 1px;
}
/* Render area: hidden until the SVG is in; scrolls horizontally if the diagram is wide. */
.message-body .mermaid-render {
  display: none;
  padding: 16px;
  overflow-x: auto;
  text-align: center;
}
.message-body .mermaid-render svg {
  max-width: 100%;
  height: auto;
}
.message-body .mermaid-block.is-rendered .mermaid-render {
  display: block;
}
.message-body .mermaid-block.is-rendered.show-source .mermaid-render {
  display: none;
}
/* Source is the default/fallback view (pending or error) and the "소스 보기" view. It shows the raw
   mermaid code, so it uses the same --wizon-ai-code-{surface,ink} tokens as the code-block card
   (doc 164, R-10 reland 514fb0d2a) rather than a dark literal — this also overrides any global
   <pre> background that would clash. Was a fixed dark hex pair (#0d1017/#dfe3ea) until 09-25: with
   R-10 the code card itself became light-in-light/dark-in-dark, so a hardcoded dark source view
   went stale and disagreed with the card sitting next to it in the light theme. */
.message-body .mermaid-source {
  margin: 0;
  padding: 12px 14px;
  overflow-x: auto;
  background: var(--wizon-ai-code-surface);
  color: var(--wizon-ai-code-ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--ds-text-caption-13);
  line-height: 1.55;
  white-space: pre;
}
.message-body .mermaid-source code {
  color: inherit;
  background: none;
}
/* 렌더 실패(G-M2): 로딩 중 «소스만 보임» 과 구분되는 알림 줄 한 줄(mermaid·차트 블록 공용 규칙 — 이름만 블록별). 웰 면 + 1px 선 문법 — 상태 색을 면에 칠하지 않는다. */
.message-body .mermaid-error-note,
.message-body .md-chart-error-note {
  display: none;
  margin: 0;
  padding: 10px 14px;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
  background: var(--cg-hover-soft, var(--surface-2));
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-caption-13);
  line-height: 1.4;
}
.message-body .mermaid-block.is-error .mermaid-error-note,
.message-body .md-chart.is-error .md-chart-error-note {
  display: block;
}
.message-body .mermaid-block.is-rendered .mermaid-source {
  display: none;
}
.message-body .mermaid-block.is-rendered.show-source .mermaid-source {
  display: block;
}
@media (prefers-reduced-motion: reduce) {
  .message-body .mermaid-toggle {
    transition: none;
  }
}

/* GFM task list (render2): non-interactive checkbox glyph + label. Read-only — the model states
   status. The <li> drops its marker and aligns the box with the first text line. */
.message-body .md-task-item {
  list-style: none;
  margin-left: -22px;
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.message-body .md-task-box {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  margin-top: 3px;
  border: 1.5px solid var(--cg-menu-border, var(--line));
  border-radius: 5px;
  background: var(--cg-surface, #fff);
  color: #fff;
}
.message-body .md-task-box[data-checked="true"] {
  border-color: var(--accent, #2f6bff);
  background: var(--accent, #2f6bff);
}
.message-body .md-task-box svg,
.message-body .md-task-box .wds-inline-icon {
  width: 12px;
  height: 12px;
}
.message-body .md-task-label {
  flex: 1 1 auto;
}

.md-quote {
  /* ★v10 — 표와 같은 이유로 산문 리듬(20)에 맞춘다. 16 이면 이 블록 앞뒤에서만
     간격이 좁아져 문서의 세로 리듬이 끊긴다. */
  margin: var(--ds-space-5) 0;
  padding: 2px 0 2px 12px;
  border-left: 3px solid var(--cg-menu-border, var(--line));
  color: var(--cg-text-soft, var(--muted));
}
.md-callout {
  display: grid;
  gap: 6px;
  margin: var(--ds-space-4) 0;
  padding: 10px 12px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: 12px;
  background: var(--cg-hover-soft, var(--surface-2));
}
.md-callout-head {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: var(--cg-text-label-size, 14px);
  font-weight: 600;
}
.md-callout[data-callout="warning"] .md-callout-head {
  /* 의미색 통합 S2 — 이 선언은 :16855 의 `--ad-amber`(캐스케이드 후순위 승)에 덮여
     실제로 렌더되지 않는다(canvas 실측: getComputedStyle 이 항상 amber). 죽은
     값이라도 danger 리터럴 폴백 사본을 남기지 않는다 — WDS alias 로 정합만
     맞춘다(0px, 소비 안 되므로 회귀 없음). */
  color: var(--wds-color-error-text, var(--danger));
}
.message-body .md-callout-head .wds-inline-icon,
.md-callout-head .wds-inline-icon {
  width: 16px;
  height: 16px;
  flex: none;
}
.md-callout-body > p:last-child,
.md-callout-body > .md-list:last-child {
  margin-bottom: 0;
}
.md-heading {
  display: flex;
  align-items: center;
  gap: 7px;
}
.message-body .md-heading .wds-inline-icon,
.md-heading .wds-inline-icon {
  width: 18px;
  height: 18px;
  flex: none;
  color: var(--cg-text-soft, var(--muted));
}

/* in-place thinking (doc 139 제안 A): 답변 슬롯에 그대로, 최종 답변과 동일 타이포.
   회색↔잉크 모노크롬 shimmer(제품 토큰 --cg-*) + dots. 회전 오브/정적 진행칩 폐기. */
.thinking-inline {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: inherit;
}

/* R-54 S1-F(F5): 쉬머 = transform 띠. 옛 방식은 background-position 을 1.7s 무한 반복해 합성 레이어를
   타지 못했다. 이제 글자는 정적이고, 글자 위를 지나가는 반투명 띠 하나(::after)만 translateX 로 움직인다
   (합성 속성만). 띠 색은 «바탕색» 이라 바탕 위에서는 보이지 않고 글자 위에서만 글자를 잠깐 옅게 한다.
   위상은 app.js syncShimmerPhase 가 애니메이션 startTime 을 타임라인 0 에 고정해 맞춘다 —
   노드가 다시 만들어져도 띠가 되감기지 않는다. */
.thinking-shimmer {
  position: relative;
  display: inline-block;
  overflow: hidden;
  color: var(--cg-text, var(--ink));
}
.thinking-shimmer::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--shimmer-band-bg, var(--cg-bg)) 72%, transparent) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  animation: thinking-shimmer-sweep 1.7s linear infinite;
}

@keyframes thinking-shimmer-sweep {
  to {
    transform: translateX(100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .thinking-shimmer {
    color: var(--cg-muted, var(--muted));
  }
  .thinking-shimmer::after {
    animation: none;
    background: none;
  }

}

/* Live progressive status shown until the first answer token (doc 165). Reuses .thinking-shimmer
   for the sweep; the label crossfades (out-up / in-up) when the pipeline stage changes. */
.stream-status {
  /* R-54 S1-F(F3): 한 셀에 문구 노드를 겹쳐 얹어 180ms 교차한다(빈 칸 0). */
  display: grid;
  grid-template-areas: "label";
  align-items: center;
  justify-items: start;
  min-height: 1.6em;
  font-size: var(--ds-text-body-15);
}
.stream-status-label {
  grid-area: label;
  display: inline-block;
  font-weight: 500;
}
.status-fade-exit {
  animation: status-fade-out 180ms ease-out forwards;
}
.status-fade-enter {
  animation: status-fade-in 180ms ease-out both;
}
@keyframes status-fade-out {
  to {
    opacity: 0;
  }
}
@keyframes status-fade-in {
  from {
    opacity: 0;
  }
}
/* 첫 토큰: 상태줄 «유령» 이 답변 위에 얹힌 채 180ms 동안 옅어지고, 답변 블록은 같은 시간 동안 나타난다. */
.stream {
  position: relative;
}
stream-ghost {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  pointer-events: none;
  animation: status-fade-out 180ms ease-out forwards;
}
.stream.is-first-token > :not(stream-ghost) {
  animation: status-fade-in 180ms ease-out both;
}
@keyframes status-swap-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}
@media (prefers-reduced-motion: reduce) {
  .status-fade-exit,
  .status-fade-enter,
  stream-ghost,
  .stream.is-first-token > :not(stream-ghost) {
    animation-duration: 1ms;
  }
}

/* ---- 스트리밍 카드 스켈레톤 (T3, 스켈레톤 선행 트랙) ------------------------------------
   오케스트레이션 홀드가 카드 타입으로 확정되는 즉시 상태 라인 대신 마운트되고(app.js
   resolveOrchHold), finalize 패치가 실제 카드로 교체한다. 타입별 min-height는 실제 위젯의
   대략적 높이를 예약해 교체 시 레이아웃 점프(CLS)를 줄인다. 셸은 위젯과 같은 라운드
   (--ds-radius-lg)의 중립 표면, 내부 바/블록은 thinking-shimmer 키프레임을 재사용한
   저채도 스윕 — 잉크 모노톤 원칙(카드 본색은 완성본만 쓴다). */
.stream-card-skeleton {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 4px;
  padding: 20px 22px;
  border-radius: var(--ds-radius-lg);
  border: 1px solid var(--line);
  background: var(--cg-surface);
  animation: status-swap-in 200ms ease-out both;
}
/* R-54 S1-F(F5): 막대마다 background-position 을 돌리던 것을 카드 위 반투명 띠 «하나» 의 translateX 로
   바꿨다(합성 속성만). 막대는 정적 면이고 띠가 지나갈 때만 옅어 보인다. */
.stream-card-skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--sk-band, color-mix(in srgb, var(--cg-surface) 70%, transparent)) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  animation: sk-sweep 1.7s linear infinite;
}
@keyframes sk-sweep {
  to {
    transform: translateX(100%);
  }
}
.stream-card-skeleton .sk-label::after {
  display: none; /* 카드 띠가 이미 지나간다 — 라벨 자체 띠는 겹쳐 두 번 지나가지 않게 끈다 */
}
.stream-card-skeleton .sk-head {
  display: flex;
  align-items: center;
  min-height: 1.6em;
  font-size: var(--cg-text-label-size);
}
.stream-card-skeleton .sk-line,
.stream-card-skeleton .sk-block,
.stream-card-skeleton .sk-dot,
.stream-card-skeleton .sk-figure,
.stream-card-skeleton .sk-seg-pill {
  display: block;
  border-radius: var(--ds-radius-xs);
  background: var(--cg-hover-soft);
}
.stream-card-skeleton .sk-line {
  height: 12px;
}
.stream-card-skeleton .sk-block {
  width: 100%;
}
/* v3: 실위젯 내부 레이아웃 미러 — 헤더(제목/부제 + 우측 큰 수치), 본문 패널(시간대 셀 스트립 /
   차트+기간 세그먼트 / 리드+행). 데이터가 앉을 자리만 shimmer, 프레임은 정적. */
.stream-card-skeleton .sk-widget-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
.stream-card-skeleton .sk-ident {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
  min-width: 0;
}
.stream-card-skeleton .sk-title {
  height: 18px;
  width: 34%;
}
.stream-card-skeleton .sk-subtitle {
  width: 52%;
}
.stream-card-skeleton .sk-figure {
  width: 112px;
  height: 46px;
  border-radius: var(--ds-radius-sm);
  flex: none;
}
.stream-card-skeleton .sk-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 16px;
  border-radius: var(--ds-radius-sm);
  background: rgba(255, 255, 255, 0.08);
}
.stream-card-skeleton .sk-hourly {
  flex-direction: row;
  justify-content: space-between;
  gap: 10px;
}
.stream-card-skeleton .sk-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  flex: 1;
}
.stream-card-skeleton .sk-cell-label {
  width: 26px;
  height: 10px;
}
.stream-card-skeleton .sk-dot {
  width: 26px;
  height: 26px;
  border-radius: var(--ds-radius-pill);
}
.stream-card-skeleton .sk-cell-value {
  width: 20px;
}
.stream-card-skeleton .sk-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.stream-card-skeleton .sk-row-label {
  width: 22%;
}
.stream-card-skeleton .sk-row-value {
  width: 42%;
}
.stream-card-skeleton .sk-seg {
  display: flex;
  gap: 8px;
}
.stream-card-skeleton .sk-seg-pill {
  flex: 1;
  height: 34px;
  border-radius: var(--ds-radius-sm);
}
.stream-card-skeleton .sk-news {
  background: var(--cg-hover-soft);
}
.stream-card-skeleton .sk-news .sk-row {
  justify-content: flex-start;
}
.stream-card-skeleton .sk-thumb {
  width: 44px;
  height: 44px;
  border-radius: var(--ds-radius-sm);
  flex: none;
}
.stream-card-skeleton .sk-news .sk-row-value {
  width: 64%;
}
/* P2 (docs/53 Phase 2): 여행·비주얼 스켈레톤 — 두 카드 모두 라이트 표면(기본 셸 그대로),
   여행=헤더+히어로+day 칩+일정 행, 비주얼=이미지 모자이크 3타일+텍스트 행 미러. */
.stream-card-skeleton[data-card-skeleton="travel"] {
  min-height: 360px;
}
.stream-card-skeleton .sk-stops {
  background: var(--cg-hover-soft);
}
.stream-card-skeleton .sk-stops .sk-row {
  justify-content: flex-start;
}
.stream-card-skeleton[data-card-skeleton="visual"] {
  min-height: 300px;
}
.stream-card-skeleton .sk-mosaic {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 8px;
}
.stream-card-skeleton .sk-tile {
  display: block;
  height: 150px;
  border-radius: var(--ds-radius-xs);
  background: var(--cg-hover-soft);
}
/* 타입별 셸 = 실제 위젯의 배경 아이덴티티(사용자 결정 2026-07-22): 스켈레톤이 "그 카드가
   오는 중"임을 배경만으로 예고하고, finalize 교체 때 배경이 연속되어 스냅감이 사라진다.
   날씨=딥블루(#0d285e, .weather-widget, 다크 전용 콘텐츠 색 면 — 09-12 위젯 표면 규칙 예외).
   금융은 09-14 오너 규칙("주식 위젯 면 = 화이트/블랙 중립, 색은 데이터 마크만")으로
   .finance-surface 자체가 라이트/다크 테마 시트로 바뀌었다 — 스켈레톤도 같은 출처의 토큰을
   그대로 읽는다(라이트 = --wizon-ai-surface-sheet→--wizon-ai-surface, 다크 =
   --wizon-ai-surface-group→--wizon-ai-surface-sheet(R-3 v-E′ S1) — .finance-surface 의
   --fin-slab-surface 와 동일한 값, ΔE 0).
   옛 #101216 플랫 단색은 라이트에서 실카드(흰 시트)와 어긋나 제거(오너 운영 관찰 09-15).
   뉴스=라이트 표면(.news-feed). 다크 셸의 바/라벨은 화이트 계열 스윕으로 뒤집는다. */
.stream-card-skeleton[data-card-skeleton="finance"] {
  min-height: 300px;
  background: linear-gradient(180deg, var(--wizon-ai-surface-sheet) 0%, var(--wizon-ai-surface) 100%);
}
html[data-theme="dark"] .stream-card-skeleton[data-card-skeleton="finance"] {
  /* R-3 v-E′ S1 결정 7: .finance-surface 다크 슬랩과 같은 두 토큰(R=G=B) — ΔE 0 유지. */
  background: linear-gradient(180deg, var(--wizon-ai-surface-group) 0%, var(--wizon-ai-surface-sheet) 100%);
}
.stream-card-skeleton .sk-chart {
  height: 160px;
}
.stream-card-skeleton[data-card-skeleton="weather"] {
  min-height: 340px;
  background: #0d285e;
}
/* 주간 씬: 실위젯의 낮 하늘 그라디언트에 맞춘 밝은 블루 (라이브 실측 대비) */
.stream-card-skeleton[data-card-skeleton="weather"][data-sky="day"] {
  background: #4f97e0;
}
/* 다크 셸 보더 — weather 만(다크 전용 콘텐츠 색 면). finance 는 09-15 수리로 기본
   .stream-card-skeleton 보더(var(--line), 테마 추종)를 그대로 쓴다 — 더 이상 오버라이드 없음. */
.stream-card-skeleton[data-card-skeleton="weather"] {
  border-color: rgba(255, 255, 255, 0.09);
}
/* 뉴스 스켈레톤 — renderNewsFeed 의 6구획(헤더/레일/히어로/2단/카드그리드/푸터)을 그대로
   미러링한다(2026-08-20, newsFeedSkeletonHtml). 열 채움 셀은 전부 <i> 태그라 아래 태그
   선택자 하나로 채움+스윕을 준다 — 감싸개(div/span)는 shimmer를 받지 않는다.
   ★스윕은 기존 thinking-shimmer(background-position 애니메이션)와 톤·타이밍은 맞추되
   구현은 transform 전용 오버레이로 새로 짠다(웹 성능 규칙: 모션은 transform/opacity만).
   기존 thinking-shimmer 자체는 범위 밖이라 손대지 않는다. */
.stream-card-skeleton[data-card-skeleton="news"] {
  min-height: 900px;
}
.stream-card-skeleton .sk-news-body {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.stream-card-skeleton .sk-news-body > * {
  animation: status-swap-in 220ms ease-out both;
}
.stream-card-skeleton .sk-news-body > *:nth-child(1) { animation-delay: 0ms; }
.stream-card-skeleton .sk-news-body > *:nth-child(2) { animation-delay: 90ms; }
.stream-card-skeleton .sk-news-body > *:nth-child(3) { animation-delay: 180ms; }
.stream-card-skeleton .sk-news-body > *:nth-child(4) { animation-delay: 270ms; }
.stream-card-skeleton .sk-news-body > *:nth-child(5) { animation-delay: 360ms; }
.stream-card-skeleton .sk-news-body > *:nth-child(6) { animation-delay: 450ms; }
.stream-card-skeleton .sk-news-body i {
  position: relative;
  overflow: hidden;
  display: block;
  border-radius: var(--ds-radius-xs);
  background: var(--cg-hover-soft);
}
.stream-card-skeleton .sk-news-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.stream-card-skeleton .sk-news-title {
  width: 40%;
  height: 26px;
}
.stream-card-skeleton .sk-news-chips {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}
.stream-card-skeleton .sk-news-chip {
  width: 52px;
  height: 20px;
  border-radius: var(--ds-radius-xs);
}
.stream-card-skeleton .sk-news-count {
  width: 26px;
  height: 14px;
}
.stream-card-skeleton .sk-news-rail {
  display: flex;
  gap: 8px;
  overflow: hidden;
}
.stream-card-skeleton .sk-news-tab {
  flex: none;
  width: 92px;
  height: 44px;
  border-radius: var(--ds-radius-pill);
}
.stream-card-skeleton .sk-news-tab:nth-child(2) { width: 72px; }
.stream-card-skeleton .sk-news-tab:nth-child(3) { width: 108px; }
.stream-card-skeleton .sk-news-tab:nth-child(4) { width: 84px; }
.stream-card-skeleton .sk-news-tab:nth-child(5) { width: 96px; }
.stream-card-skeleton .sk-news-tab:nth-child(6) { width: 78px; }
.stream-card-skeleton .sk-news-hero {
  position: relative;
  aspect-ratio: 744 / 452;
  border-radius: var(--ds-radius-md);
  overflow: hidden;
  background: var(--cg-hover-soft);
}
.stream-card-skeleton .sk-news-hero-band {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 18px;
}
.stream-card-skeleton .sk-news-hero-top {
  display: flex;
  align-items: center;
  gap: 10px;
}
.stream-card-skeleton .sk-news-kicker {
  width: 64px;
  height: 19px;
  border-radius: var(--ds-radius-pill);
}
.stream-card-skeleton .sk-news-byline {
  width: 100px;
  height: 20px;
}
.stream-card-skeleton .sk-news-hero-title {
  height: 24px;
}
.stream-card-skeleton .sk-news-hero-title:nth-of-type(1) { width: 88%; }
.stream-card-skeleton .sk-news-hero-title:nth-of-type(2) { width: 58%; }
.stream-card-skeleton .sk-news-hero-excerpt {
  height: 19px;
}
/* i 형제 셈은 클래스가 아니라 태그 기준이라, 앞선 두 i.sk-news-hero-title 뒤이므로
   3/4번째가 이 두 줄이다 (1/2번째는 제목이 차지한다). */
.stream-card-skeleton .sk-news-hero-excerpt:nth-of-type(3) { width: 96%; }
.stream-card-skeleton .sk-news-hero-excerpt:nth-of-type(4) { width: 52%; }
.stream-card-skeleton .sk-news-second {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.stream-card-skeleton .sk-news-second-cell {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto auto;
  column-gap: 12px;
  row-gap: 6px;
}
.stream-card-skeleton .sk-news-second-title {
  grid-column: 1;
  height: 20px;
}
.stream-card-skeleton .sk-news-second-title:nth-of-type(2) { width: 70%; }
.stream-card-skeleton .sk-news-second-excerpt {
  grid-column: 1;
  height: 19px;
  width: 90%;
}
.stream-card-skeleton .sk-news-second-shot {
  grid-column: 2;
  grid-row: 1 / span 2;
  width: 64px;
  height: 64px;
  flex: none;
}
/* 실측(2026-08-20, 375px): .news-second-foot(출처+시각+북마크 버튼 행)이 34px — 스켈레톤이
   이 행을 아예 안 그려서 2단 칸 하나당 실물보다 짧게 예약되고 있었다. */
.stream-card-skeleton .sk-news-second-foot {
  grid-column: 1 / -1;
  width: 55%;
  height: 32px;
}
.stream-card-skeleton .sk-news-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.stream-card-skeleton .sk-news-section-title {
  width: 96px;
  height: 22px;
}
.stream-card-skeleton .sk-news-arrows {
  display: flex;
  gap: 6px;
}
.stream-card-skeleton .sk-news-arrow {
  width: 28px;
  height: 28px;
  border-radius: var(--ds-radius-sm);
}
.stream-card-skeleton .sk-news-cards {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, minmax(0, auto));
  grid-auto-columns: calc((100% - 32px) / 3);
  gap: 16px;
  overflow: hidden;
}
/* 정본 폭 분기점 래칫(520/521/900/901)의 대상이 아니다 — 폭 기반 앳룰이 아니라 컨테이너
   기반이고, 값도 실제 .news-cards 컨테이너 쿼리(styles.css)와 같은 620이다: 사이드 패널이
   열려 피드 자체 폭이 줄어드는 경우까지 잡기 위해 뷰포트가 아니라 이 요소 자신의 인라인
   크기를 쓴다. */
@container (max-width: 620px) {
  .stream-card-skeleton .sk-news-cards {
    grid-auto-columns: calc((100% - 16px) / 2);
  }
}
/* 실물(styles.css)도 이 폭에서 한 번 더 접는다 — 카드 1열, 2단 1열, 히어로 4:3(744/452보다
   같은 폭에 더 큰 키). 스켈레톤이 이 분기점을 안 따라가면 375px에서 정확히 CLS가 남는다
   (2026-08-20 실측: 380px 미포함 상태에서 실물 히어로/2단이 예약보다 커짐). */
@container (max-width: 380px) {
  .stream-card-skeleton .sk-news-hero {
    aspect-ratio: 4 / 3;
  }
  .stream-card-skeleton .sk-news-second {
    grid-template-columns: 1fr;
  }
  .stream-card-skeleton .sk-news-second-cell {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .stream-card-skeleton .sk-news-cards {
    grid-auto-columns: 100%;
    gap: 18px;
  }
}
.stream-card-skeleton .sk-news-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.stream-card-skeleton .sk-news-card-shot {
  width: 100%;
  aspect-ratio: 16 / 10;
  border-radius: var(--ds-radius-sm);
}
.stream-card-skeleton .sk-news-card-title {
  height: 20px;
}
/* i 형제 셈은 태그 기준이라 sk-news-card-shot(1번째 i)이 먼저다: 제목 두 줄은 2·3번째,
   요약 두 줄은 4·5번째. */
.stream-card-skeleton .sk-news-card-title:nth-of-type(3) { width: 65%; }
.stream-card-skeleton .sk-news-card-excerpt {
  height: 19px;
}
.stream-card-skeleton .sk-news-card-excerpt:nth-of-type(4) { width: 92%; }
.stream-card-skeleton .sk-news-card-excerpt:nth-of-type(5) { width: 55%; }
/* 실측(2026-08-20, 375px): .news-card-foot(출처+시각+북마크 버튼 행)이 36px — 12px 텍스트
   한 줄로만 예약했더니 카드마다 실물보다 24px씩 짧아, 카드가 5장 이상일 때 누적 오차가
   전체 스켈레톤 높이 격차의 가장 큰 몫이었다. */
.stream-card-skeleton .sk-news-card-byline {
  height: 36px;
  width: 50%;
}
.stream-card-skeleton .sk-news-foot {
  display: flex;
  align-items: center;
  gap: 12px;
}
.stream-card-skeleton .sk-news-source-chip {
  width: 88px;
  height: 32px;
  border-radius: var(--ds-radius-pill);
}
.stream-card-skeleton .sk-news-subscribe {
  width: 96px;
  height: 26px;
  border-radius: var(--ds-radius-pill);
  margin-inline-start: auto;
}
.stream-card-skeleton .sk-lead {
  height: 150px;
}
/* weather 만(다크 전용 콘텐츠 색 면). finance 는 09-15 수리로 기본 .sk-line/.sk-block/…
   그레이스케일 막대 + 카드 띠(::after)를 그대로 쓴다 — 화이트 스윕 제거. */
.stream-card-skeleton[data-card-skeleton="weather"] .sk-line,
.stream-card-skeleton[data-card-skeleton="weather"] .sk-block,
.stream-card-skeleton[data-card-skeleton="weather"] .sk-dot,
.stream-card-skeleton[data-card-skeleton="weather"] .sk-figure {
  background: rgba(255, 255, 255, 0.1);
}
.stream-card-skeleton[data-card-skeleton="weather"] {
  --sk-band: rgba(255, 255, 255, 0.16);
}
/* .thinking-shimmer의 텍스트 그라디언트는 테마 잉크 기반이라 다크 셸에서 안 보인다 — 화이트 스윕으로
   재정의. background 축약형은 background-clip까지 리셋하므로 텍스트 클립을 반드시 재선언한다
   (빠뜨리면 라벨이 글자 대신 회색 블록으로 칠해진다 — 하니스 실측). finance 는 09-15 수리로
   기본 테마 라벨 색(thinking-shimmer 텍스트 그라디언트)을 그대로 쓴다 — 흰 면 배경 위 화이트
   텍스트 스윕이 사라져야 라이트 테마에서 라벨이 읽힌다. */
.stream-card-skeleton[data-card-skeleton="weather"] .sk-label {
  /* R-54 S1-F(F5): 글자 그라디언트 스윕은 카드 띠(::after, --sk-band)로 대체됐다 — 라벨은 정적 화이트. */
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  color: rgba(255, 255, 255, 0.92);
}
@media (prefers-reduced-motion: reduce) {
  .stream-card-skeleton {
    animation-duration: 1ms;
  }
  .stream-card-skeleton .sk-line,
  .stream-card-skeleton .sk-block,
  .stream-card-skeleton .sk-dot,
  .stream-card-skeleton .sk-figure,
  .stream-card-skeleton .sk-seg-pill,
  .stream-card-skeleton .sk-tile {
    animation: none;
    background: var(--cg-hover-soft);
  }
  .stream-card-skeleton .sk-news-body > * {
    animation: none;
  }
  .stream-card-skeleton::after {
    animation: none;
    background: none;
  }
}

/* Step 2a: 완성 응답을 답변 슬롯에서 단어 단위 blur-in으로 재생한다.
   라이브 토큰 스트리밍이 아니라 이미 도착한 응답의 in-place 등장 연출이며,
   실제 SSE 스트리밍은 별도 백엔드 트랙(Step 2b)에서 소스만 교체한다.
   긴 답변(> REVEAL_MAX_WORDS)은 동시 blur 애니메이션 폭주를 피해 블록 페이드. */
.reveal-word {
  display: inline-block;
  animation: reveal-word-in 0.42s var(--cg-ease-enter, cubic-bezier(0.19, 1, 0.22, 1)) both;
  will-change: filter, transform, opacity;
}

@keyframes reveal-word-in {
  from {
    filter: blur(6px);
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    filter: blur(0);
    opacity: 1;
    transform: none;
  }
}

.message-body.is-revealing-block {
  animation: reveal-word-in 0.5s var(--cg-ease-enter, cubic-bezier(0.19, 1, 0.22, 1)) both;
}

@media (prefers-reduced-motion: reduce) {
  .reveal-word,
  .message-body.is-revealing-block {
    animation: none;
  }
}

.composer {
  grid-template-columns: minmax(0, 1fr) 96px;
  background: linear-gradient(180deg, transparent, var(--bg) 35%);
}

.composer textarea {
  border-radius: var(--radius-xl);
  background: var(--ds-color-surface);
  box-shadow: var(--shadow);
}

.primary-button,
.secondary-button {
  border-radius: var(--radius-md);
}

.primary-button {
  border-color: var(--accent);
  background: var(--ds-button-primary-bg);
}

.context-pane {
  background: var(--ds-color-surface);
  border-left: 1px solid var(--line);
}

.tabs {
  display: flex;
  overflow-x: auto;
  grid-template-columns: none;
  gap: 6px;
  padding: 13px;
}

.tab-button {
  flex: 0 0 auto;
  min-height: 36px;
  border-radius: var(--ds-radius-pill, 999px);
  padding: 0 12px;
}

.tab-button.active {
  border-color: var(--cg-text);
  background: var(--accent-soft);
  color: var(--accent);
}

.tab-content {
  gap: 16px;
  padding: 18px;
}

.panel-block h2 {
  font-size: 17px;
}

.tool-status-list,
.workflow-timeline,
.knowledge-grid {
  display: grid;
  gap: 10px;
}

.tool-status-card,
.workflow-step {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.tool-status-card small,
.workflow-step small {
  display: block;
  margin-top: 3px;
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
}

.status-dot,
.workflow-step > span {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}

.workflow-step.active {
  border-color: var(--cg-text);
  background: linear-gradient(180deg, var(--surface), var(--accent-soft));
}

.source-list.compact {
  padding: 0;
}

[data-theme="dark"] .sidebar .section-label,
[data-theme="dark"] .brand-row span {
  color: #94a3b8;
}

[data-theme="dark"] .upload-form .secondary-button,
[data-theme="dark"] .prompt-row button,
[data-theme="dark"] .feedback-row button,
[data-theme="dark"] .document-actions button {
  background: var(--surface);
  color: var(--ink);
}

[data-theme="dark"] .source-snippet {
  color: var(--ds-color-ink-soft);
}

@media (max-width: 1200px) {
  .workbench {
    grid-template-columns: minmax(0, 1fr);
  }

  .context-pane {
    min-height: 0;
    max-height: min(520px, 48vh);
    border-top: 1px solid var(--line);
    border-left: 0;
  }
}

@media (max-width: 900px) {
  .app-shell {
    grid-template-columns: 1fr;
  }

  .sidebar {
    min-height: auto;
    border-right: 0;
  }

  .nav-section {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .sidebar-section.grow {
    max-height: min(260px, 38vh);
  }

  .topbar {
    align-items: flex-start;
    flex-direction: column;
    padding: 16px;
  }

  .status-stack {
    justify-content: flex-start;
  }

  .message-list {
    padding: 22px 16px;
  }

  .message {
    grid-template-columns: 34px minmax(0, 1fr);
  }

  .message-label {
    width: 32px;
    height: 32px;
  }

  .prompt-row {
    grid-template-columns: 1fr;
  }

  .app-command-grid {
    grid-template-columns: 1fr;
  }

  .composer {
    grid-template-columns: 1fr;
    padding: 12px 16px 16px;
  }

  .upload-form {
    grid-template-columns: 1fr;
  }
}

/* AI Native reference adaptation: light-first product workspace */
.app-shell {
  grid-template-columns: 292px minmax(0, 1fr);
}

.sidebar {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.94)),
    var(--surface);
  color: var(--ink);
  border-right: 1px solid var(--line);
  box-shadow: 1px 0 0 rgba(255, 255, 255, 0.7);
}

.brand-row strong,
.nav-item span,
.recent-item span {
  color: var(--ink);
}

.brand-row span,
.nav-item small,
.recent-item small,
.sidebar .section-label {
  color: var(--muted);
}
.theme-toggle {
  border-color: var(--line);
  background: var(--surface);
  color: var(--ink-soft);
  box-shadow: var(--ds-shadow-xs);
}

.theme-toggle:hover,
.nav-item:hover,
.nav-item.active {
  background: linear-gradient(180deg, #ffffff, #f3f6ff);
}

.nav-item {
  color: var(--ink);
}

.recent-item {
  color: var(--ink);
}

.recent-item:hover {
  background: #f8faff;
}

.sidebar select {
  background: #ffffff;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
}

.sidebar .secondary-button {
  border-color: var(--line);
  background: #111827;
  color: #ffffff;
}
[data-theme="dark"] .brand-row strong,
[data-theme="dark"] .nav-item span,
[data-theme="dark"] .recent-item span {
  color: #f8fafc;
}

[data-theme="dark"] .theme-toggle {
  border-color: rgba(226, 232, 240, 0.16);
  background: rgba(255, 255, 255, 0.08);
  color: #f8fafc;
}

[data-theme="dark"] .theme-toggle:hover,
[data-theme="dark"] .nav-item:hover,
[data-theme="dark"] .nav-item.active {
  border-color: rgba(34, 211, 238, 0.38);
  background: rgba(255, 255, 255, 0.1);
}

[data-theme="dark"] .recent-item:hover {
  border-color: rgba(255, 255, 255, 0.15);
  background: rgba(255, 255, 255, 0.07);
}

[data-theme="dark"] .sidebar .secondary-button {
  border-color: rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.08);
  color: #ffffff;
}

.workspace-hero {
  position: sticky;
  top: 0;
  z-index: 20;
}

.context-pane {
  background: rgba(255, 255, 255, 0.84);
  backdrop-filter: blur(18px);
}

[data-theme="dark"] .context-pane {
  background: rgba(24, 24, 27, 0.86);
}

.context-panel-head {
  padding: 4px 2px 2px;
}

.context-panel-head h2 {
  margin-top: 3px;
  font-size: 19px;
}

/* Calm Portal v1: ChatGPT/Claude-style product surface */
.app-shell {
  grid-template-columns: 264px minmax(0, 1fr);
}

.sidebar {
  gap: 14px;
  padding: 16px;
  background: rgba(255, 255, 255, 0.96);
  color: var(--ink);
}

.brand-row {
  min-height: 42px;
}
.new-chat-button {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #111827;
  color: #ffffff;
  font-weight: 600;
}

.new-chat-button:hover {
  background: #020617;
}

.nav-section {
  gap: 5px;
}

.nav-item {
  min-height: 38px;
  padding: 0 12px;
  border-radius: var(--ds-radius-sm, 10px);
}

.nav-item span {
  font-size: var(--ds-text-body-14);
}

.nav-item small {
  display: none;
}

.recent-list {
  gap: 3px;
}

.recent-item {
  min-height: 38px;
  padding: 8px 10px;
}

.theme-toggle {
  min-height: 34px;
}

.workspace-hero {
  min-height: 76px;
  padding: 16px 28px;
  background: rgba(255, 255, 255, 0.86);
}

.topbar h1 {
  font-size: var(--ds-text-title-18);
}

.hero-copy {
  font-size: var(--ds-text-caption-13);
}

.status-stack {
  gap: 6px;
}

.status-pill {
  min-height: 30px;
  font-size: var(--ds-text-micro-11);
}

.workbench {
  grid-template-columns: minmax(0, 1fr) minmax(360px, 420px);
}

html[data-portal-tab="home"] .workbench {
  grid-template-columns: minmax(0, 1fr);
}

html[data-portal-tab="home"] .context-pane {
  display: none;
}

.chat-pane {
  background: #ffffff;
}

.message-list {
  padding: 36px clamp(22px, 7vw, 96px);
}

.calm-chat-home {
  width: min(760px, 100%);
  max-width: none;
  justify-items: center;
  align-self: center;
  margin: auto;
  gap: 18px;
  padding: 24px 8px 18px;
  text-align: center;
}

.home-project-pill {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  max-width: min(100%, 620px);
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-pill, 999px);
  background: rgba(255, 255, 255, 0.78);
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.calm-chat-home h2 {
  max-width: 720px;
  margin: 0;
  font-size: clamp(30px, 4vw, 42px);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: 0;
  word-break: keep-all;
}

.calm-chat-home p {
  max-width: 620px;
  margin: 0;
  color: var(--muted);
  font-size: var(--ds-text-body-15);
  line-height: 1.7;
  word-break: keep-all;
}

.quick-start-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  width: min(640px, 100%);
  margin-top: 8px;
}

.quick-start-grid button {
  min-height: 46px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-md, 14px);
  background: #ffffff;
  color: var(--ink);
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  text-align: left;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
  transition: border-color var(--motion), box-shadow var(--motion), transform var(--motion);
}

.quick-start-grid button:hover {
  box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
  transform: translateY(-1px);
}

.composer {
  width: min(760px, calc(100% - 44px));
  margin: 0 auto;
  padding: 0 0 28px;
  background: transparent;
}

.composer textarea {
  min-height: 58px;
  border-radius: var(--ds-radius-lg, 18px);
  background: #ffffff;
}

.composer .primary-button {
  min-height: 58px;
  border-radius: var(--ds-radius-lg, 18px);
  background: #111827;
}

.context-pane {
  background: rgba(255, 255, 255, 0.92);
}

.right-panel-empty {
  display: none;
}

[data-theme="dark"] .sidebar {
  background: rgba(9, 9, 11, 0.96);
}

[data-theme="dark"] .new-chat-button {
  background: #f8fafc;
  color: #09090b;
}

[data-theme="dark"] .chat-pane {
  background:
    radial-gradient(circle at 50% 12%, rgba(129, 140, 248, 0.11), transparent 32%),
    var(--bg);
}

[data-theme="dark"] .home-project-pill,
[data-theme="dark"] .quick-start-grid button,
[data-theme="dark"] .composer textarea {
  background: rgba(24, 24, 27, 0.88);
}

@media (max-width: 900px) {
  .app-shell {
    grid-template-columns: 1fr;
  }

  .sidebar {
    min-height: auto;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }

  .nav-section {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .nav-item {
    justify-content: start;
    text-align: left;
  }

  .workspace-hero {
    display: none;
  }

  .quick-start-grid {
    grid-template-columns: 1fr;
  }

  .composer {
    width: calc(100% - 28px);
    grid-template-columns: 1fr;
  }
}

.mini-card,
.app-card,
.app-detail-panel,
.app-workflow-panel,
.app-input-fields,
.source-card,
.tool-status-card,
.workflow-step {
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.9);
}

[data-theme="dark"] .mini-card,
[data-theme="dark"] .app-card,
[data-theme="dark"] .app-detail-panel,
[data-theme="dark"] .app-workflow-panel,
[data-theme="dark"] .app-input-fields,
[data-theme="dark"] .source-card,
[data-theme="dark"] .tool-status-card,
[data-theme="dark"] .workflow-step {
  background: rgba(24, 24, 27, 0.92);
}

.document-actions button {
  border-radius: var(--ds-radius-pill, 999px);
}

.message-body {
  border-radius: var(--radius-xl);
}

.message-label {
  border-radius: var(--ds-radius-md, 14px);
}

@media (max-width: 900px) {
  .app-shell {
    grid-template-columns: 1fr;
  }
}

/* Artifact Canvas v1 */
html[data-portal-tab="outputs"] .workbench {
  grid-template-columns: minmax(420px, 0.95fr) minmax(520px, 0.78fr);
}

.artifact-studio {
  display: grid;
  grid-template-columns: minmax(220px, 0.72fr) minmax(320px, 1.08fr);
  align-items: start;
  gap: 14px;
}

.artifact-library,
.artifact-canvas-shell {
  display: grid;
  gap: 12px;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  background: rgba(255, 255, 255, 0.88);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}

.artifact-library {
  padding: 14px;
  max-height: 70vh;
  overflow: auto;
}

.artifact-canvas-shell {
  padding: 16px;
  background: rgba(255, 255, 255, 0.92);
}

.empty-artifact-canvas {
  min-height: 220px;
  align-content: center;
}

.artifact-list-item {
  color: var(--ink);
  text-align: left;
}

.artifact-path-line {
  display: block;
  color: var(--muted);
  font-size: var(--ds-text-micro-11);
  line-height: 1.35;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.artifact-list-item.selected-artifact {
  border-color: var(--cg-text);
  background: linear-gradient(180deg, var(--cg-surface), var(--cg-hover-soft));
  box-shadow: inset 3px 0 0 var(--accent);
}

.artifact-preview-button {
  width: 100%;
  color: var(--ink);
  text-align: left;
}

.artifact-canvas-toolbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.artifact-canvas-toolbar h3 {
  margin: 3px 0 0;
  font-size: var(--ds-text-title-20);
  line-height: 1.25;
  word-break: keep-all;
}

.artifact-canvas-toolbar p:not(.eyebrow) {
  max-width: 100%;
  margin: 6px 0 0;
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.artifact-mode-switch {
  display: inline-flex;
  flex: 0 0 auto;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--surface-2);
}

.artifact-mode-switch button {
  min-height: 30px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: transparent;
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.artifact-mode-switch button.active {
  background: var(--surface);
  color: var(--accent);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
}

.artifact-meta-row,
.artifact-action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.artifact-meta-row span {
  min-height: 28px;
  padding: 6px 9px;
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--surface);
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.artifact-action-row button {
  min-height: 34px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.artifact-action-row button:hover {
  border-color: var(--cg-menu-border);
  background: var(--accent-soft);
}

.artifact-action-row .primary-inline {
  border-color: transparent;
  background: #111827;
  color: #ffffff;
}

.artifact-canvas-body {
  min-width: 0;
}

.artifact-document-preview,
.artifact-editor {
  width: 100%;
  min-height: 420px;
  max-height: 58vh;
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  background: var(--surface);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}

.artifact-document-preview {
  padding: 18px;
}

.artifact-document-preview p,

.artifact-document-preview h3,
.artifact-document-preview h4,
.artifact-document-preview h5 {
  margin: 18px 0 10px;
  color: var(--ink);
}

.artifact-editor {
  padding: 16px;
  color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--ds-text-caption-13);
  line-height: 1.6;
  resize: vertical;
}

.artifact-version-rail {
  display: grid;
  gap: 10px;
  padding-top: 2px;
}

.artifact-version-rail > div:first-child {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.artifact-version-rail strong {
  font-size: var(--ds-text-caption-13);
}

.artifact-version-list {
  display: grid;
  gap: 8px;
}

.artifact-version-list button {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 2px 10px;
  align-items: center;
  min-height: 54px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  text-align: left;
}

.artifact-version-list button.active {
  border-color: var(--cg-text);
  background: var(--accent-soft);
}

.artifact-version-list button > span {
  grid-row: span 2;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--surface-2);
  color: var(--accent);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}

.artifact-version-list small {
  color: var(--muted);
  font-size: var(--ds-text-micro-11);
}

[data-theme="dark"] .artifact-library,
[data-theme="dark"] .artifact-canvas-shell,
[data-theme="dark"] .artifact-document-preview,
[data-theme="dark"] .artifact-editor,
[data-theme="dark"] .artifact-list-item.selected-artifact {
  background: rgba(24, 24, 27, 0.9);
}

[data-theme="dark"] .artifact-action-row .primary-inline {
  background: #f8fafc;
  color: #09090b;
}

@media (max-width: 1200px) {
  html[data-portal-tab="outputs"] .workbench {
    grid-template-columns: 1fr;
  }

  .artifact-studio {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 900px) {
  .artifact-canvas-toolbar {
    display: grid;
  }

  .artifact-mode-switch {
    width: fit-content;
  }

  .artifact-document-preview,
  .artifact-editor {
    min-height: 320px;
  }
}

/* Premium Product Detail System v1 */
:root {
  --premium-surface: var(--ds-color-surface);
  --premium-surface-strong: var(--ds-color-surface-solid);
  --premium-border: var(--ds-color-line);
  --premium-shadow: var(--ds-shadow-xs);
}

/* 주식 비교 위젯 v-C(오너 09-14 결정 F9 ⓐ) — KRX 하락색을 잉크에서 파랑으로.
   C-4: 이 토큰은 전역(:root)에 정의해 스코프 밖 참조도 해결되게 하고, 카드 스코프
   (.fin-group-item.is-krx-market, .fin-group-direction--krx-down)는 이 값을 참조만
   한다(로컬 재정의 없음). 값은 목업 tokens.css --dn-1과 동일(라이트/다크) — 실제 리터럴은
   DTCG 소스(design-system/dtcg/tokens/wizon-ai*.tokens.json 의 stock-down)에 있고,
   여기서는 생성된 --wizon-ai-stock-down 을 참조만 한다(anti-drift raw-color 래칫 회피 —
   이 파일은 스캔 대상, shared/wds 는 아니다).
   S2i — 라이트 값은 #0a6ae0(S2b 도입)에서 #0a53b8 로 어둡게(F9ⓐ 파랑 유지, 명도만
   이동). #0a6ae0 은 라이트 카드 면(최악 배경 rgb(231,231,231)) 위 헤드라인/부분확인
   텍스트로 쓰일 때 4.09~4.40:1(S2h 재측정 report C1) 로 4.5 미달이었다. #0a53b8 은
   같은 배경 기준 5.77:1(WCAG 상대휘도 공식 실계산) — 이 파일의 다른 소비처(필 배경 위
   흰 글자, 차트 선)는 텍스트 대비 요건이 없거나 이미 더 어두워져 계속 통과한다. 다크
   값(#0a84ff)은 무변경(이미 R7 에서 헤드라인 텍스트만 --wds-blue-400 로 스코프 수리됨,
   아래 R7 주석 참고 — 다크는 손대지 않는다). */
:root {
  --fin-krx-down: var(--wizon-ai-stock-down);
  --fin-up: var(--wds-color-error-text);
  --fin-down: var(--fin-krx-down);
}

/* 강조 컬러(accent) — 개인 설정. app.js가 문서 루트에 data-accent를 세우면
   선택·강조 테두리, 폼 accent-color, --accent-soft 배경, 그라디언트가 이 값을 쓴다
   (이전엔 --accent가 정의된 적 없어 fallback #2f6bff로 고정됐다). focus ring은 별도
   토큰 --cg-focus-ring을 쓰므로 대상이 아니다. 블랙/그레이는 다크 배경에서 그대로면
   테두리가 안 보이므로 라이트·다크를 따로 정의한다. */
:root[data-accent="ink"] {
  --accent: #1a1a1a;
  --accent-2: #3d3d3d;
  --accent-soft: rgba(17, 17, 17, 0.06);
}
:root[data-accent="gray"] {
  --accent: #6b7280;
  --accent-2: #9ca3af;
  --accent-soft: rgba(107, 114, 128, 0.12);
}
:root[data-accent="blue"] {
  --accent: #155eef;
  --accent-2: #4f83ff;
  --accent-soft: rgba(21, 94, 239, 0.10);
}
:root[data-theme="dark"][data-accent="ink"] {
  --accent: #d4d4d4;
  --accent-2: #a3a3a3;
  --accent-soft: rgba(255, 255, 255, 0.10);
}
:root[data-theme="dark"][data-accent="gray"] {
  --accent: #9ca3af;
  --accent-2: #6b7280;
  --accent-soft: rgba(156, 163, 175, 0.15);
}
:root[data-theme="dark"][data-accent="blue"] {
  --accent: #4f83ff;
  --accent-2: #155eef;
  --accent-soft: rgba(79, 131, 255, 0.15);
}

/* 외부 링크 경고(safetyExternalLinkWarning). app.js가 포털 밖으로 나가는 링크에만
   data-external을 달면, 링크 라벨 뒤에 작은 외부 이동 아이콘(↗)을 붙여 사용자가
   눌러서 나가기 전에 알아채게 한다 — passive 표시라 클릭을 막지 않는다. */
a[data-external="true"]::after {
  content: "↗";
  display: inline-block;
  margin-inline-start: 0.15em;
  font-size: 0.85em;
  line-height: 1;
  vertical-align: baseline;
  opacity: 0.7;
}

.premium-product-shell-v1 {
  /* 2026-08-01 셸 층 중복 제거. body 에 셸 클래스 6개가 index.html 에서 항상 함께 붙어
     있어(토글 없음), 같은 특이도로 같은 속성을 거는 선언은 마지막 것만 이긴다. 여기서
     지던 선언은 화면에 한 번도 닿은 적이 없어 지웠다 — 계산 스타일 무변화로 검증. */
  line-height: var(--ds-leading-reading);
}

[data-theme="dark"] .premium-product-shell-v1 {
  background:
    radial-gradient(circle at 52% 8%, rgba(129, 140, 248, 0.12), transparent 28%),
    linear-gradient(180deg, #09090b 0%, #111113 100%);
}

.premium-product-shell-v1 .app-shell {
  grid-template-columns: 268px minmax(0, 1fr);
}

.premium-product-shell-v1 .sidebar {
  padding: 18px 14px;
  background: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(22px);
}

.premium-product-shell-v1 .brand-row strong {
  font-size: var(--ds-text-title-18);
  font-weight: var(--ds-weight-semibold);
}

.premium-product-shell-v1 .section-label,
.premium-product-shell-v1 .eyebrow {
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: 0;
}

.premium-product-shell-v1 .nav-item {
  grid-template-columns: 28px minmax(0, 1fr);
  align-items: center;
  min-height: 40px;
  padding: 7px 10px;
  border-radius: 12px;
  /* --ds-motion-hover 는 정의가 유실돼 이 선언이 통째로 무효였다(2026-08-01).
     원문은 `a52b7b910` design-system/tokens/motion/motion.css:5 에서 복원 —
     ★`all`이 아니라 명시적 속성 목록이다. 옛 --ds-motion-standard 는 값이 같은
     --ds-motion-default 로 옮겼다. 토큰으로 되살리지 않은 이유: ds.primitive 의
     motion 그룹은 $type:transition 이라 속성 목록을 담을 수 없고, 이 파일 머리말이
     --ds-* 재선언을 금지한다. */
  transition:
    transform var(--ds-motion-default),
    border-color var(--ds-motion-default),
    box-shadow var(--ds-motion-default),
    background var(--ds-motion-default);
}

.premium-product-shell-v1 .nav-icon {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--premium-border);
  border-radius: 8px;
  background: #ffffff;
  color: var(--accent);
  font-size: var(--ds-text-micro-11);
  font-weight: var(--ds-weight-semibold);
}

.premium-product-shell-v1 .nav-item.active {
  box-shadow: inset 3px 0 0 var(--accent);
}

.premium-product-shell-v1 .recent-item,
.premium-product-shell-v1 .new-chat-button,
.premium-product-shell-v1 .theme-toggle {
  transition:
    transform var(--ds-motion-default),
    border-color var(--ds-motion-default),
    box-shadow var(--ds-motion-default),
    background var(--ds-motion-default);
}

.workspace-identity {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
}

.workspace-cover {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 42px;
  height: 42px;
  border-radius: 16px;
  color: #ffffff;
  font-weight: var(--ds-weight-bold);
}

.premium-product-shell-v1 .workspace-hero {
  min-height: 82px;
  background: rgba(255, 255, 255, 0.78);
  box-shadow: var(--ds-shadow-hairline);
}

.premium-product-shell-v1 .topbar h1 {
  font-size: var(--ds-text-title-20);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-title);
}

.state-indicator {
  position: relative;
  gap: 7px;
  font-weight: var(--ds-weight-semibold);
}

.state-indicator::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: var(--ds-radius-pill, 999px);
  background: currentColor;
  box-shadow: 0 0 0 4px color-mix(in srgb, currentColor 14%, transparent);
}

.premium-product-shell-v1 .chat-pane {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(248, 250, 252, 0.98));
}

.premium-product-shell-v1 .calm-chat-home {
  animation: fade-in-up 420ms cubic-bezier(0.16, 1, 0.3, 1);
}

.premium-product-shell-v1 .calm-chat-home h2 {
  font-size: clamp(34px, 4.4vw, var(--ds-text-display-40));
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-tight);
}

.premium-product-shell-v1 .calm-chat-home p,
.premium-product-shell-v1 .message-body p,
.premium-product-shell-v1 .artifact-document-preview p {
  font-size: var(--ds-text-body-15);
  line-height: var(--ds-leading-reading);
  font-weight: var(--ds-weight-regular);
}

.premium-card,
.premium-product-shell-v1 .mini-card,
.premium-product-shell-v1 .app-card,
.premium-product-shell-v1 .app-detail-panel,
.premium-product-shell-v1 .app-workflow-panel,
.premium-product-shell-v1 .app-input-fields,
.premium-product-shell-v1 .artifact-library,
.premium-product-shell-v1 .artifact-canvas-shell,
.premium-product-shell-v1 .evidence-card,
.premium-product-shell-v1 .source-card {
  border-color: var(--premium-border);
  background: var(--premium-surface-strong);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}

.soft-hover-lift,
.premium-product-shell-v1 .quick-start-grid button,
.premium-product-shell-v1 .mini-card,
.premium-product-shell-v1 .app-card,
.premium-product-shell-v1 .artifact-preview-card,
.premium-product-shell-v1 .artifact-action-row button {
  transition:
    transform var(--ds-motion-default),
    border-color var(--ds-motion-default),
    box-shadow var(--ds-motion-default),
    background var(--ds-motion-default);
}

.soft-hover-lift:hover,
.premium-product-shell-v1 .quick-start-grid button:hover,
.premium-product-shell-v1 .mini-card:hover,
.premium-product-shell-v1 .app-card:hover,
.premium-product-shell-v1 .artifact-preview-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--premium-shadow);
}

.result-highlight {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 4px;
}

.result-highlight span {
  min-height: 30px;
  padding: 6px 10px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill, 999px);
  background: rgba(255, 255, 255, 0.72);
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
}

.app-result-card.result-highlight {
  display: grid;
  justify-content: stretch;
}
.premium-product-shell-v1 .message {
  grid-template-columns: 38px minmax(0, 780px);
}

.premium-product-shell-v1 .message-body {
  padding: 16px 18px;
  border-color: var(--premium-border);
  border-radius: 20px;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}

.streaming-cursor {
  display: inline-block;
  width: 7px;
  height: 1.12em;
  margin-left: 2px;
  border-radius: 2px;
  background: var(--cg-text, var(--ink));
  vertical-align: -2px;
  animation: streaming-cursor 900ms steps(2, jump-none) infinite;
}

.composer-surface {
  display: grid;
  gap: 8px;
  width: min(790px, calc(100% - 44px));
  padding: 10px;
  border: 1px solid var(--premium-border);
  border-radius: 26px;
  background: rgba(255, 255, 255, 0.88);
  box-shadow: 0 18px 56px rgba(15, 23, 42, 0.14);
  backdrop-filter: blur(18px);
}

.composer-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 4px;
}

.composer-tool-button {
  min-height: 28px;
  padding: 0 9px;
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-pill, 999px);
  background: rgba(248, 250, 252, 0.86);
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
}

.composer-tool-button:hover {
  background: var(--accent-soft);
  color: var(--accent);
}

.composer-input-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 92px;
  gap: 8px;
  align-items: end;
}

.composer-surface textarea {
  min-height: 52px;
  border: 0;
  border-radius: var(--ds-radius-lg, 18px);
  background: transparent;
  box-shadow: none;
  font-size: var(--ds-text-body-15);
  line-height: var(--ds-leading-body);
}

.composer-surface textarea:focus {
  box-shadow: none;
}

.artifact-live-badge {
  display: inline-flex;
  align-items: center;
  min-height: 20px;
  margin-left: 6px;
  padding: 0 7px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--ds-color-cyan-soft);
  color: #0891b2;
  font-size: 10px;
  font-weight: var(--ds-weight-bold);
  text-transform: none;
}

.premium-product-shell-v1 .artifact-document-preview {
  font-size: var(--ds-text-body-15);
  line-height: var(--ds-leading-document);
}

@keyframes fade-in-up {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes streaming-cursor {
  50% {
    opacity: 0;
  }
}

[data-theme="dark"] .premium-product-shell-v1 .sidebar,
[data-theme="dark"] .composer-surface,
[data-theme="dark"] .result-highlight span {
  background: rgba(24, 24, 27, 0.86);
}

[data-theme="dark"] .premium-product-shell-v1 .workspace-hero {
  background: rgba(9, 9, 11, 0.72);
}

@media (max-width: 900px) {
  .composer-surface {
    width: calc(100% - 28px);
  }

  .composer-input-row {
    grid-template-columns: 1fr;
  }
}

/* AI Workspace Product Renewal v28 */
:root {
  --ds-leading-body: 1.58;
}

/* 20260708-mobile-dialog-tokens: WIZON mobile dialog design tokens. On mobile,
   content-panel modals (settings, search, …) go full-screen edge-to-edge like
   ChatGPT; small confirmation alerts (delete, share) stay centered. Opt a <dialog>
   into the full-screen treatment with the [data-mobile-fullscreen] attribute — the
   rule that consumes these tokens lives at the end of this file
   (search "20260708-mobile-dialog-fullscreen-system").
   NOTE: --wizon-mobile-breakpoint is the single source of truth for the mobile
   breakpoint; CSS cannot use a custom property inside a media-query condition, so the
   520px literals in the media queries must be kept in sync with this token. */
:root {
  --wizon-mobile-breakpoint: 520px;
  --wizon-mobile-dialog-inline-size: 100vw;
  /* Track the live visual viewport so full-screen dialogs (search, settings) shrink
     with the iOS keyboard and their sticky header stays put — same fix as the shell
     (styles.css "20260709-mobile-keyboard-fix"). Falls back to 100dvh pre-JS. */
  --wizon-mobile-dialog-block-size: var(--wizon-vvh, 100dvh);
  --wizon-mobile-dialog-margin: 0;
  --wizon-mobile-dialog-radius: 0;
}

.service-product-v28 .app-shell {
  grid-template-columns: 260px minmax(0, 1fr);
}

.service-product-v28 .sidebar {
  padding: 18px 14px 16px;
  background: rgba(255, 255, 255, 0.96);
  border-right: 1px solid rgba(15, 23, 42, 0.08);
  box-shadow: none;
}

.service-product-v28 .brand-row {
  padding: 0 2px 8px;
}
.service-product-v28 .brand-row strong {
  font-size: var(--ds-text-body-16);
  font-weight: 600;
  line-height: 1.18;
}

.service-product-v28 .new-chat-button {
  min-height: 44px;
  border: 0;
  border-radius: 13px;
  background: #0f172a;
  box-shadow: 0 10px 28px rgba(15, 23, 42, 0.14);
}

.service-product-v28 .nav-section {
  gap: 3px;
}

.service-product-v28 .nav-item {
  grid-template-columns: 26px minmax(0, 1fr);
  min-height: 38px;
  padding: 7px 9px;
  border-radius: 11px;
  color: var(--ink-soft);
}

.service-product-v28 .nav-item.active {
  background: #f6f7ff;
  box-shadow: inset 3px 0 0 var(--accent);
}

.service-product-v28 .nav-icon {
  width: 22px;
  height: 22px;
  border-radius: 7px;
  background: #ffffff;
}

.service-product-v28 .recent-item {
  min-height: 36px;
  border-radius: var(--ds-radius-sm, 10px);
}

html[data-portal-tab="home"] .service-product-v28 .workspace-hero {
  display: none;
}

.service-product-v28 .chat-pane {
  min-height: 100vh;
  background: linear-gradient(180deg, #ffffff 0%, #fbfcfd 100%);
}

.service-product-v28 .message-list {
  padding: clamp(42px, 9vh, 92px) clamp(22px, 7vw, 104px) 28px;
}

.service-product-v28 .calm-chat-home {
  width: min(720px, 100%);
  gap: 17px;
  padding-top: 0;
}

.service-product-v28 .home-project-pill {
  min-height: 32px;
  border-color: rgba(15, 23, 42, 0.1);
  background: rgba(255, 255, 255, 0.86);
  color: #667085;
}

.service-product-v28 .calm-chat-home h2 {
  font-size: clamp(34px, 4.2vw, 44px);
  font-weight: 600;
  line-height: 1.16;
}

.service-product-v28 .quick-start-grid button {
  min-height: 48px;
  border-radius: 15px;
  font-weight: 600;
}

.service-product-v28 .result-highlight span {
  color: #667085;
}

.service-product-v28 .composer-surface {
  width: min(790px, calc(100% - 48px));
  margin-bottom: 22px;
  border-radius: var(--ds-radius-2xl, 28px);
  background: rgba(255, 255, 255, 0.94);
  box-shadow: 0 18px 56px rgba(15, 23, 42, 0.13);
}

.service-product-v28 .composer-toolbar {
  align-items: center;
}

.composer-model-pill {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  margin-left: auto;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--ds-radius-pill, 999px);
  background: #f8fafc;
  color: var(--muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
}

.service-product-v28 .composer-surface textarea {
  min-height: 54px;
}

.service-product-v28 .composer .primary-button {
  min-height: 52px;
  border: 0;
  border-radius: var(--ds-radius-lg, 18px);
  background: #0f172a;
}

.service-product-v28 .context-pane {
  background: rgba(255, 255, 255, 0.96);
}

[data-theme="dark"] .service-product-v28 {
  background:
    radial-gradient(circle at 50% 14%, rgba(129, 140, 248, 0.13), transparent 28%),
    linear-gradient(180deg, #09090b 0%, #101014 100%);
}

[data-theme="dark"] .service-product-v28 .sidebar,
[data-theme="dark"] .service-product-v28 .chat-pane,
[data-theme="dark"] .service-product-v28 .composer-surface {
  background: rgba(15, 15, 18, 0.96);
}

[data-theme="dark"] .service-product-v28 .nav-item.active {
  background: rgba(129, 140, 248, 0.14);
}

[data-theme="dark"] .composer-model-pill {
  background: rgba(255, 255, 255, 0.06);
}

@media (max-width: 900px) {
  .service-product-v28 .app-shell {
    grid-template-columns: 1fr;
  }

  .service-product-v28 .chat-pane {
    min-height: calc(100vh - 280px);
  }

  .service-product-v28 .message-list {
    padding: 38px 18px 24px;
  }

  .composer-model-pill {
    margin-left: 0;
  }

  .approved-manual-map-form {
    grid-template-columns: 1fr;
  }
}

/* ====================================================================== *
 * .btn — unified button system (71-component-rulebook.md §4.1)
 * 5 variants: primary, secondary, ghost, danger, link. No other variants.
 * Solid backgrounds are ONLY allowed for primary and danger.
 * Phase 3.1.E (2026-05-24): all JS/HTML callsites migrated to .btn.
 * Legacy .primary-button / .secondary-button / .primary-inline /
 * .composer-tool-button CSS rules below are orphaned and slated for
 * removal in Phase 4 lint (test_token_usage_lint.py).
 * ====================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-2);
  border: 1px solid transparent;
  border-radius: var(--ds-radius-xs);
  font: inherit;
  font-weight: var(--ds-weight-semibold);
  cursor: pointer;
  user-select: none;
  text-decoration: none;
  transition:
    background-color var(--ds-motion-fast),
    border-color var(--ds-motion-fast),
    color var(--ds-motion-fast),
    box-shadow var(--ds-motion-fast);
}

.btn:focus-visible {
  outline: 2px solid var(--ds-color-accent);
  outline-offset: 2px;
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
}

.btn.btn--sm { min-height: 28px; padding: 0 var(--ds-space-3); font-size: var(--ds-text-caption-13); }
.btn.btn--md { min-height: 36px; padding: 0 var(--ds-space-4); font-size: var(--ds-text-body-14); }
.btn.btn--lg { min-height: 44px; padding: 0 var(--ds-space-5); font-size: var(--ds-text-body-15); }

.btn.btn--primary {
  background: var(--ds-button-primary-bg);
  color: var(--ds-button-primary-fg);
  border-color: var(--ds-button-primary-bg);
}
.btn.btn--primary:hover:not(:disabled) {
  background: var(--ds-button-primary-bg-hover);
  border-color: var(--ds-button-primary-bg-hover);
}

.btn.btn--secondary {
  background: var(--ds-button-secondary-bg);
  color: var(--ds-button-secondary-fg);
  border-color: var(--ds-button-secondary-border);
}
.btn.btn--secondary:hover:not(:disabled) {
  background: var(--ds-button-secondary-bg-hover);
  border-color: var(--ds-color-line-strong);
}

.btn.btn--ghost {
  background: transparent;
  color: var(--ds-button-ghost-fg);
  border-color: transparent;
}
.btn.btn--ghost:hover:not(:disabled) {
  background: var(--ds-button-ghost-bg-hover);
}

.btn.btn--danger {
  background: var(--ds-button-danger-bg);
  color: var(--ds-button-danger-fg);
  border-color: var(--ds-button-danger-bg);
}
.btn.btn--danger:hover:not(:disabled) {
  background: var(--ds-button-danger-bg-hover);
  border-color: var(--ds-button-danger-bg-hover);
}

.btn.btn--link {
  background: transparent;
  color: var(--ds-button-link-fg);
  border-color: transparent;
  padding-left: 0;
  padding-right: 0;
  min-height: auto;
}
.btn.btn--link:hover:not(:disabled) {
  text-decoration: underline;
}

/* ====================================================================== *
 * .ds-input — unified input system (71-component-rulebook.md §4.4)
 * Sizes: sm 32px, md 40px (default), lg 48px.
 * States: idle / hover / focus / disabled / invalid.
 * Error state changes border only — never bg.
 * ====================================================================== */

.ds-input {
  width: 100%;
  border: 1px solid var(--ds-input-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-input-bg);
  color: var(--ds-input-fg);
  font: inherit;
  outline: none;
  transition:
    border-color var(--ds-motion-fast),
    box-shadow var(--ds-motion-fast);
}

.ds-input::placeholder { color: var(--ds-input-placeholder); }

.ds-input.ds-input--sm { min-height: 32px; padding: 0 12px; font-size: var(--ds-text-caption-13); }
.ds-input.ds-input--md { min-height: 40px; padding: 0 14px; font-size: var(--ds-text-body-14); }
.ds-input.ds-input--lg { min-height: 48px; padding: 0 16px; font-size: var(--ds-text-body-15); }

.ds-input:hover:not(:disabled):not([aria-invalid="true"]) {
  border-color: var(--ds-input-border-hover);
}

.ds-input:focus,
.ds-input:focus-visible {
  border-color: var(--ds-input-border-focus);
  box-shadow: 0 0 0 2px var(--ds-input-ring-focus);
}

.ds-input:disabled {
  background: var(--ds-input-bg-disabled);
  color: var(--ds-input-fg-muted);
  cursor: not-allowed;
}

/* Native <select> chevron (Phase 3.6) — replace browser-default arrow with
 * a token-driven inline SVG so the visual stays consistent across OS/UA. */
select.ds-input {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: var(--ds-input-chevron);
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px;
  padding-right: 32px;
}
select.ds-input::-ms-expand { display: none; }

/* 20260925-select-chevron-global: .ds-input.ds-input--sm/md/lg (특이도 0,2,0) 는
 * select.ds-input 의 padding-right (0,1,1) 를 이겨 화살표가 글자와 겹쳤다(전 화면).
 * 크기 변형과 같은 특이도(0,2,0 이상)로 padding-right 만 다시 명시해 되찾는다. */
select.ds-input.ds-input--sm,
select.ds-input.ds-input--md,
select.ds-input.ds-input--lg {
  padding-right: 32px;
}

.ds-input[aria-invalid="true"] {
  border-color: var(--ds-input-border-invalid);
}

/* Search wrapper: leading icon + optional trailing clear button */
.ds-search {
  position: relative;
  display: block;
}

.ds-search .ds-input {
  padding-left: 36px;
}

.ds-search__icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  color: var(--ds-input-fg-muted);
  pointer-events: none;
}

.ds-search__clear {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: var(--ds-radius-xs);
  color: var(--ds-input-fg-muted);
  font: inherit;
  line-height: 1;
}

.ds-search__clear:hover {
  background: var(--ds-color-surface-muted);
  color: var(--ds-color-ink);
}

/* ====================================================================== *
 * .ds-modal — unified modal system (71-component-rulebook.md §4.2)
 * 3-slot anatomy: .ds-modal__header / .ds-modal__body / .ds-modal__footer
 * Sizes: sm 560px, md 720px (default), lg 960px.
 * Light/dark via Tier 2 swap (no [data-theme="dark"] selectors here).
 * Coexists with legacy .login-dialog for incremental migration; new modals
 * MUST use .ds-modal*.
 * ====================================================================== */

.ds-modal__overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--ds-space-6);
  background: var(--ds-modal-overlay);
}
.ds-modal__overlay.is-open { display: flex; }

.ds-modal {
  background: var(--ds-modal-surface);
  border: 1px solid var(--ds-modal-border);
  border-radius: var(--ds-modal-radius);
  box-shadow: var(--ds-modal-shadow);
  display: flex;
  flex-direction: column;
  max-height: 90vh;
  width: 100%;
}

.ds-modal.ds-modal--sm { max-width: 560px; }
.ds-modal.ds-modal--md { max-width: 720px; }
.ds-modal.ds-modal--lg { max-width: 960px; }

.ds-modal__header {
  padding: var(--ds-space-6);
  border-bottom: 1px solid var(--ds-modal-border);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--ds-space-4);
  flex-shrink: 0;
}

.ds-modal__body {
  padding: var(--ds-space-6);
  overflow-y: auto;
  flex: 1 1 auto;
}

.ds-modal__footer {
  padding: var(--ds-space-6);
  border-top: 1px solid var(--ds-modal-border);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ds-space-3);
  flex-shrink: 0;
}

/* ====================================================================== *
 * .ds-chip — segmented + count (71-component-rulebook.md §4.5)
 * 2 variants only. Coexists with legacy .tab-button via selector grouping.
 * ====================================================================== */
.ds-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  background: var(--ds-chip-bg);
  color: var(--ds-chip-fg);
  border: 1px solid var(--ds-chip-border);
  font: inherit;
  font-size: var(--ds-text-body-14);
  cursor: pointer;
  transition: background var(--ds-motion-fast), color var(--ds-motion-fast), border-color var(--ds-motion-fast);
}
.ds-chip:hover:not([aria-disabled="true"]) {
  background: var(--ds-chip-bg-hover);
}
.ds-chip:focus-visible {
  outline: 2px solid var(--ds-color-accent);
  outline-offset: 2px;
}
.ds-chip[aria-pressed="true"],
.ds-chip.is-active {
  background: var(--ds-chip-bg-active);
  color: var(--ds-chip-fg-active);
  border-color: var(--ds-chip-bg-active);
}
.ds-chip.ds-chip--segmented {
  min-height: 32px;
  padding: 0 var(--ds-space-3);
  border-radius: var(--ds-radius-xs);
}
.ds-chip.ds-chip--count {
  min-height: 22px;
  padding: 0 var(--ds-space-2);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-chip-count-bg);
  color: var(--ds-chip-count-fg);
  border-color: transparent;
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
}

/* Security-events panel filter chips (period / category / view). Reuses
 * .ds-chip--segmented; this only lays the chips out in a wrapping row. */
.security-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-1);
  margin: var(--ds-space-2) 0;
}

/* ====================================================================== *
 * .ds-badge — status indicator (71-component-rulebook.md §4.6)
 * 5 variants: ok, warn, bad, info, neutral. Dot variant: 8px round, fg only.
 * ====================================================================== */
.ds-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  min-height: 22px;
  padding: 2px var(--ds-space-2);
  border-radius: var(--ds-radius-pill);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
  background: var(--ds-badge-neutral-bg);
  color: var(--ds-badge-neutral-fg);
}
.ds-badge.ds-badge--ok      { background: var(--ds-badge-ok-bg);   color: var(--ds-badge-ok-fg); }
.ds-badge.ds-badge--warn    { background: var(--ds-badge-warn-bg); color: var(--ds-badge-warn-fg); }
.ds-badge.ds-badge--bad     { background: var(--ds-badge-bad-bg);  color: var(--ds-badge-bad-fg); }
.ds-badge.ds-badge--info    { background: var(--ds-badge-info-bg); color: var(--ds-badge-info-fg); }
.ds-badge.ds-badge--neutral { background: var(--ds-badge-neutral-bg); color: var(--ds-badge-neutral-fg); }

.ds-badge.ds-badge--dot {
  width: 8px;
  height: 8px;
  min-height: 0;
  padding: 0;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
.ds-badge.ds-badge--dot.ds-badge--ok      { color: var(--ds-badge-ok-fg); }
.ds-badge.ds-badge--dot.ds-badge--warn    { color: var(--ds-badge-warn-fg); }
.ds-badge.ds-badge--dot.ds-badge--bad     { color: var(--ds-badge-bad-fg); }
.ds-badge.ds-badge--dot.ds-badge--info    { color: var(--ds-badge-info-fg); }
.ds-badge.ds-badge--dot.ds-badge--neutral { color: var(--ds-badge-neutral-fg); }

/* ====================================================================== *
 * .ds-tile — dashboard/hub stat tile (71-component-rulebook.md §4.7)
 * Anatomy: [dot? · number · label]. Same visual weight per row.
 * ====================================================================== */
.ds-tile {
  background: var(--ds-tile-bg);
  border: 1px solid var(--ds-tile-border);
  border-radius: var(--ds-tile-radius);
  padding: var(--ds-tile-padding);
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
  min-height: 84px;
}
.ds-tile__top {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  color: var(--ds-tile-label-fg);
  font-size: var(--ds-text-micro-11);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.ds-tile__num {
  font-size: var(--ds-text-heading-32);
  font-weight: var(--ds-weight-semibold);
  color: var(--ds-tile-num-fg);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.ds-tile.ds-tile--muted .ds-tile__num { color: var(--ds-color-ink-soft); }

/* ====================================================================== *
 * .ds-checkbox — minimal pattern (rulebook §4.8 not yet defined)
 * Native <input type="checkbox"> with custom appearance.
 * ====================================================================== */
.ds-checkbox {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--ds-checkbox-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-color-surface-solid);
  cursor: pointer;
  display: inline-grid;
  place-content: center;
  transition: border-color var(--ds-motion-fast), background var(--ds-motion-fast);
  vertical-align: middle;
  flex-shrink: 0;
}
.ds-checkbox:hover:not(:disabled) {
  border-color: var(--ds-checkbox-border-hover);
}
.ds-checkbox:checked {
  background: var(--ds-checkbox-bg-checked);
  border-color: var(--ds-checkbox-bg-checked);
}
.ds-checkbox:checked::before {
  content: "";
  width: 10px;
  height: 10px;
  background: var(--ds-checkbox-fg-checked);
  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0, 43% 62%);
}
.ds-checkbox:focus-visible {
  outline: 2px solid var(--ds-color-accent);
  outline-offset: 2px;
}

/* ====================================================================== *
 * .ds-card — generic card (71-component-rulebook.md §4.3)
 * 3 variants: default, interactive, static. Optional 3-slot anatomy.
 * ====================================================================== */
.ds-card {
  background: var(--ds-card-bg);
  border: 1px solid var(--ds-card-border);
  border-radius: var(--ds-card-radius);
  padding: var(--ds-card-padding);
  box-shadow: var(--ds-card-shadow);
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
  transition: border-color var(--ds-motion-fast), box-shadow var(--ds-motion-fast), transform var(--ds-motion-fast);
}

.ds-card.ds-card--interactive:hover {
  border-color: var(--ds-card-border-hover);
  box-shadow: var(--ds-card-shadow-hover);
  transform: translateY(-1px);
}

.ds-card.ds-card--static {
  transition: none;
  cursor: default;
}

.ds-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-3);
}
.ds-card__body { flex: 1 1 auto; min-width: 0; }
.ds-card__footer {
  display: flex;
  gap: var(--ds-space-2);
  justify-content: flex-end;
  margin-top: var(--ds-space-2);
}

/* WDS AI Portal alignment — conversation-first shell */
.wds-ai-portal-v1 {
  /* 2026-08-01 셸 층 중복 제거. body 에 셸 클래스 6개가 index.html 에서 항상 함께 붙어
     있어(토글 없음), 같은 특이도로 같은 속성을 거는 선언은 마지막 것만 이긴다. 여기서
     지던 선언은 화면에 한 번도 닿은 적이 없어 지웠다 — 계산 스타일 무변화로 검증. */
  font-size: var(--ds-text-body-15);
}

.wds-ai-portal-v1 .app-shell {
  grid-template-columns: 260px minmax(0, 1fr);
}
.wds-ai-portal-v1 .brand-row {
  padding: 0;
}
.wds-ai-portal-v1 .brand-row strong {
  color: var(--ds-color-on-accent);
  font-size: var(--ds-text-title-18);
  font-weight: var(--ds-weight-semibold);
}

.wds-ai-portal-v1 .brand-row span,
.wds-ai-portal-v1 .sidebar .section-label,
.wds-ai-portal-v1 .recent-item small {
  color: color-mix(in srgb, var(--ds-color-on-accent) 68%, transparent);
}

.wds-ai-portal-v1 .new-chat-button {
  min-height: 42px;
  border: 1px solid color-mix(in srgb, var(--ds-color-on-accent) 18%, transparent);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-button-secondary-bg);
  color: var(--ds-button-secondary-fg);
  box-shadow: none;
  font-weight: var(--ds-weight-semibold);
}

.wds-ai-portal-v1 .theme-toggle,
.wds-ai-portal-v1 .nav-item {
  border-radius: var(--ds-radius-xs);
  transform: none;
}

.wds-ai-portal-v1 .nav-item {
  grid-template-columns: 26px minmax(0, 1fr);
  min-height: 38px;
  padding: var(--ds-space-2) var(--ds-space-3);
  color: var(--ds-color-on-accent);
}

.wds-ai-portal-v1 .nav-item span:not(.nav-icon) {
  display: block;
  color: color-mix(in srgb, var(--ds-color-on-accent) 84%, transparent);
}

.wds-ai-portal-v1 .nav-item:hover,
.wds-ai-portal-v1 .nav-item.active {
  border-color: color-mix(in srgb, var(--ds-color-on-accent) 18%, transparent);
  background: color-mix(in srgb, var(--ds-color-on-accent) 9%, transparent);
  transform: none;
}

.wds-ai-portal-v1 .nav-item.active {
  box-shadow: inset 3px 0 0 var(--ds-color-accent-2);
}

.wds-ai-portal-v1 .nav-item:hover span:not(.nav-icon),
.wds-ai-portal-v1 .nav-item.active span:not(.nav-icon) {
  color: var(--ds-color-on-accent);
}

.wds-ai-portal-v1 .nav-icon {
  width: 22px;
  height: 22px;
  border: 1px solid color-mix(in srgb, var(--ds-color-on-accent) 16%, transparent);
  border-radius: var(--ds-radius-xs);
  background: color-mix(in srgb, var(--ds-color-on-accent) 8%, transparent);
  color: var(--ds-color-on-accent);
}

.wds-ai-portal-v1 .recent-item {
  min-height: 40px;
  border-radius: var(--ds-radius-xs);
  color: var(--ds-color-on-accent);
}

.wds-ai-portal-v1 .main-pane,
.wds-ai-portal-v1 .chat-pane {
  background: var(--ds-color-bg);
}

.wds-ai-portal-v1 .workspace-hero {
  min-height: 72px;
  padding: var(--ds-space-4) var(--ds-space-6);
  background: var(--ds-color-surface-solid);
  border-bottom: 1px solid var(--ds-color-line);
  box-shadow: none;
  backdrop-filter: none;
}

.wds-ai-portal-v1 .hero-copy {
  margin-top: var(--ds-space-1);
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-13);
  line-height: var(--ds-leading-body);
}

.wds-ai-portal-v1 .status-stack {
  gap: var(--ds-space-2);
}

.wds-ai-portal-v1 .status-pill {
  min-height: 28px;
  border: 1px solid var(--ds-badge-neutral-bg);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-badge-neutral-bg);
  color: var(--ds-badge-neutral-fg);
  box-shadow: none;
}

.wds-ai-portal-v1 .status-pill.soft {
  background: var(--ds-badge-info-bg);
  color: var(--ds-badge-info-fg);
  border-color: var(--ds-badge-info-bg);
}

.wds-ai-portal-v1 .workbench {
  grid-template-columns: minmax(0, 1fr) minmax(340px, 400px);
}

html[data-portal-tab="home"] .wds-ai-portal-v1 .workspace-hero,
html[data-portal-tab="home"] .wds-ai-portal-v1 .context-pane {
  display: none;
}

html[data-portal-tab="home"] .wds-ai-portal-v1 .workbench {
  grid-template-columns: minmax(0, 1fr);
}

.wds-ai-portal-v1 .message-list {
  gap: var(--ds-space-5);
  padding: clamp(var(--ds-space-8), 8vh, var(--ds-space-10)) clamp(var(--ds-space-5), 7vw, 104px) var(--ds-space-6);
}

.wds-ai-portal-v1 .calm-chat-home {
  width: min(760px, 100%);
  gap: var(--ds-space-5);
  text-align: center;
}

.wds-ai-portal-v1 .home-project-pill {
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-color-surface-solid);
  color: var(--ds-color-ink-soft);
  box-shadow: var(--ds-shadow-xs);
}

.wds-ai-portal-v1 .calm-chat-home h2 {
  margin: 0;
  color: var(--ds-color-ink);
  font-size: clamp(var(--ds-text-heading-32), 4vw, var(--ds-text-display-40));
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-tight);
}

.wds-ai-portal-v1 .calm-chat-home p {
  color: var(--ds-color-muted);
  font-size: var(--ds-text-body-15);
  line-height: var(--ds-leading-reading);
}

.wds-ai-portal-v1 .quick-start-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ds-space-2);
}

.wds-ai-portal-v1 .quick-start-grid button {
  min-height: 44px;
  border: 1px solid var(--ds-card-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-card-bg);
  color: var(--ds-color-ink);
  box-shadow: none;
  font-weight: var(--ds-weight-medium);
}

.wds-ai-portal-v1 .quick-start-grid button:hover {
  border-color: var(--ds-card-border-hover);
  box-shadow: var(--ds-card-shadow);
  transform: none;
}

.wds-ai-portal-v1 .portal-context-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--ds-space-2);
}

.wds-ai-portal-v1 .portal-context-strip span,
.wds-ai-portal-v1 .composer-chip {
  min-height: 26px;
  padding: var(--ds-space-1) var(--ds-space-2);
  border: 1px solid var(--ds-chip-border);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-chip-bg);
  color: var(--ds-chip-fg);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
}

.wds-ai-portal-v1 .message {
  grid-template-columns: 38px minmax(0, 780px);
}

.wds-ai-portal-v1 .message-body {
  padding: var(--ds-space-4) var(--ds-space-5);
  border-color: transparent;
  border-radius: var(--ds-radius-xs);
  background: transparent;
  box-shadow: none;
}

.wds-ai-portal-v1 .message.user {
  justify-content: end;
}

.wds-ai-portal-v1 .message.user .message-body {
  border: 1px solid var(--ds-color-line);
  background: var(--ds-color-accent-soft);
  box-shadow: var(--ds-shadow-xs);
}

.wds-ai-portal-v1 .message-label {
  border-radius: var(--ds-radius-xs);
  background: var(--ds-color-surface-muted);
  color: var(--ds-color-ink-soft);
}
.wds-ai-portal-v1 .answer-activity {
  margin-top: var(--ds-space-3);
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-color-surface-solid);
  box-shadow: var(--ds-shadow-xs);
}

.wds-ai-portal-v1 .answer-activity summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-3);
  min-height: 42px;
  padding: 0 var(--ds-space-4);
  cursor: pointer;
}

.wds-ai-portal-v1 .answer-activity summary span {
  color: var(--ds-color-ink);
  font-weight: var(--ds-weight-semibold);
}

.wds-ai-portal-v1 .answer-activity summary small,
.wds-ai-portal-v1 .evidence-note {
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
}

.wds-ai-portal-v1 .evidence-strip,
.wds-ai-portal-v1 .source-list {
  display: grid;
  gap: var(--ds-space-2);
  padding: 0 var(--ds-space-4) var(--ds-space-4);
}

.wds-ai-portal-v1 .source-card {
  border: 1px solid var(--ds-card-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-color-surface-solid);
  box-shadow: none;
}

.wds-ai-portal-v1 .composer-surface {
  grid-template-columns: 1fr;
  grid-template-rows: auto auto auto auto;
  align-content: start;
  width: min(860px, calc(100% - var(--ds-space-8)));
  gap: var(--ds-space-2);
  margin: 0 auto var(--ds-space-5);
  padding: var(--ds-space-3);
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-md);
  background: var(--ds-color-surface-solid);
  box-shadow: var(--ds-shadow-md);
  backdrop-filter: none;
}

.wds-ai-portal-v1 .composer-status-rail {
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
  gap: var(--ds-space-3);
  min-height: 20px;
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
  line-height: var(--ds-leading-body);
}

.wds-ai-portal-v1 .composer-attachment-lane,
.wds-ai-portal-v1 .composer-action-dock {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-2);
  min-height: 32px;
}

.wds-ai-portal-v1 .composer-prompt-body,
.wds-ai-portal-v1 .composer-input-row {
  grid-column: 1 / -1;
  display: block;
  width: 100%;
}

.wds-ai-portal-v1 .composer-surface textarea {
  width: 100%;
  min-height: 58px;
  max-height: 160px;
  padding: var(--ds-space-3);
  border: 1px solid var(--ds-input-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-input-bg);
  color: var(--ds-input-fg);
  box-shadow: none;
  resize: none;
}

.wds-ai-portal-v1 .composer-surface textarea:focus {
  border-color: var(--ds-input-border-focus);
  box-shadow: 0 0 0 2px var(--ds-input-ring-focus);
}

.wds-ai-portal-v1 .composer-tool-button,
.wds-ai-portal-v1 .composer-model-pill {
  min-height: 32px;
  border: 1px solid var(--ds-button-secondary-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-button-secondary-bg);
  color: var(--ds-button-secondary-fg);
  font-size: var(--ds-text-caption-13);
  font-weight: var(--ds-weight-medium);
}

.wds-ai-portal-v1 .composer-tool-button:hover {
  border-color: var(--ds-color-line-strong);
  background: var(--ds-button-secondary-bg-hover);
  color: var(--ds-button-secondary-fg);
}

.wds-ai-portal-v1 .composer-model-pill {
  margin-left: auto;
  color: var(--ds-color-muted);
}

.wds-ai-portal-v1 #sendButton {
  min-height: 36px;
  border-radius: var(--ds-radius-xs);
}

.wds-ai-portal-v1 .context-pane {
  background: var(--ds-color-surface-solid);
  border-left: 1px solid var(--ds-color-line);
}

.wds-ai-portal-v1 .tabs {
  padding: var(--ds-space-3);
  border-bottom: 1px solid var(--ds-color-line);
}

.wds-ai-portal-v1 .tab-button {
  border-radius: var(--ds-radius-xs);
}

.wds-ai-portal-v1 .panel-block,
.wds-ai-portal-v1 .app-detail-panel,
.wds-ai-portal-v1 .app-workflow-panel,
.wds-ai-portal-v1 .app-input-fields,
.wds-ai-portal-v1 .app-result-card,
.wds-ai-portal-v1 .mini-card,
.wds-ai-portal-v1 .app-card {
  border-radius: var(--ds-radius-xs);
  box-shadow: none;
}

@keyframes pulse {
  50% {
    opacity: 0.45;
  }
}

@media (max-width: 900px) {
  .wds-ai-portal-v1 .app-shell {
    grid-template-columns: 1fr;
  }

  .wds-ai-portal-v1 .sidebar {
    min-height: auto;
  }

  .wds-ai-portal-v1 .workbench {
    grid-template-columns: 1fr;
  }

  .wds-ai-portal-v1 .context-pane {
    display: block;
  }

  html[data-portal-tab="home"] .wds-ai-portal-v1 .context-pane {
    display: none;
  }

  .wds-ai-portal-v1 .quick-start-grid {
    grid-template-columns: 1fr;
  }

  .wds-ai-portal-v1 .composer-surface {
    width: calc(100% - var(--ds-space-5));
  }

  .wds-ai-portal-v1 .composer-status-rail,
  .wds-ai-portal-v1 .composer-action-dock {
    align-items: stretch;
    flex-direction: column;
  }

  .wds-ai-portal-v1 .composer-model-pill {
    margin-left: 0;
  }

  .ops-status-head,
  .ops-status-summary {
    align-items: stretch;
    flex-direction: column;
  }

  .ops-status-grid {
    grid-template-columns: 1fr;
  }

  .account-grid {
    grid-template-columns: 1fr;
  }

  .account-head-actions,
  .account-section-head,
  .account-member-meta {
    align-items: stretch;
    flex-direction: column;
  }

  .account-member-row,
  .identity-control-row {
    grid-template-columns: 1fr;
  }

  .account-inline-form,
  .account-member-actions,
  .account-audit-toolbar,
  .account-audit-meta,
  .account-notification-history-filters,
  .account-notification-meta {
    align-items: stretch;
    flex-direction: column;
  }

  .account-inline-form,
  .account-member-actions,
  .account-notification-history-filters {
    grid-template-columns: 1fr;
  }

  .account-notification-history-result {
    justify-self: start;
  }

  .ops-version-code {
    max-width: 100%;
  }
}

/* WDS pattern fidelity pass — Login / AI Chat / Light-first workspace */
.wds-pattern-fidelity-v2 {
  /* 2026-08-01 셸 층 중복 제거. body 에 셸 클래스 6개가 index.html 에서 항상 함께 붙어
     있어(토글 없음), 같은 특이도로 같은 속성을 거는 선언은 마지막 것만 이긴다. 여기서
     지던 선언은 화면에 한 번도 닿은 적이 없어 지웠다 — 계산 스타일 무변화로 검증. */
  /* 위즈온 accent = 잉크(WDS 블루 아님). WDS 색은 로고 브랜드 색이고, 위즈온의 accent는
     잉크 모노톤이다(관제 admin-wds-bridge.css가 이미 확립한 표준을 포털에 정렬).
     on-accent도 함께 잉크-fg로 잡아야 다크에서 accent가 흰색으로 뒤집힐 때 글자가
     검정으로 뒤집혀 대비가 유지된다(안 그러면 흰 위 흰 글자). 향후 WDS에 위즈온 색
     정의가 추가되면 그쪽으로 옮긴다. 근거: token-namespaces.md + 사용자 결정 2026-07-22. */
  --ds-color-accent: var(--wizon-ai-action-primary-bg);
  --ds-color-accent-soft: var(--wizon-ai-sidebar-active-bg);
  --ds-color-on-accent: var(--wizon-ai-action-primary-fg);
  /* 포커스 링/보더도 잉크로 확장(accent와 동일 근거). 보더선은 솔리드 잉크(accent를 따라감,
     라이트 #0d0d0d/다크 #fff — 다크에서 흰선으로 플립), 링은 settings-v2가 이미 쓰는
     반투명 잉크 halo(--wizon-ai-focus-ring)로 통일. shell-tokens가 --wds-color-focus-ring을
     잉크로 오버라이드하지만 wds.css 로드 순서에 밀려 라이브에선 블루가 이기므로 여기서 직접
     박는다. 근거: 사용자 결정 2026-07-22("포커스 링도 잉크로"). */
  --ds-input-border-focus: var(--wizon-ai-action-primary-bg);
  --ds-input-ring-focus: var(--wizon-ai-focus-ring);
  /* doc 345 §4 / doc 221 W-1 「색은 의미(액션·상태)에만」 — 주액션은 잉크다.
     ★여기가 --wds-button-bg(WDS 기본 파랑 #1252b2)를 가리키는 바람에 .btn--primary 가
     «기본 상태에서만» 파랑이었다. 바로 아래 hover 는 이미 잉크였으므로, 버튼이 쉬고
     있을 땐 파랑이다가 손을 올리면 검정으로 튀었다(2026-08-01 실측: 라이트
     rgb(18,82,178)→#262626, 다크 rgb(60,143,253)→#e3e3e3). 그 모순을 없앤다. */
  --ds-button-primary-bg: var(--wizon-ai-action-primary-bg);
  --ds-button-primary-bg-hover: var(--wizon-ai-action-primary-bg-hover); /* C-2: hover도 잉크 리프트(블루 잔재 제거) */
  --ds-button-link-fg: var(--wds-color-link);
  min-height: 100vh;
  letter-spacing: 0;
}

.wds-pattern-fidelity-v2 .app-shell {
  grid-template-columns: 252px minmax(0, 1fr);
  background: var(--ds-color-bg);
}

.wds-pattern-fidelity-v2 .sidebar {
  gap: var(--ds-space-4);
  padding: var(--ds-space-5) var(--ds-space-4);
  background: var(--ds-color-surface-muted);
  color: var(--ds-color-ink-soft);
  border-right: 1px solid var(--ds-color-line);
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .brand-row {
  min-height: 42px;
  padding: 0;
}

.wds-pattern-fidelity-v2 .workspace-cover {
  width: 34px;
  height: 34px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-color-accent-soft);
  color: var(--ds-color-accent);
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .brand-row strong {
  color: var(--ds-color-ink);
  font-size: var(--ds-text-title-18);
  font-weight: var(--ds-weight-semibold);
}

.wds-pattern-fidelity-v2 .brand-row span,
.wds-pattern-fidelity-v2 .sidebar .section-label,
.wds-pattern-fidelity-v2 .recent-item small {
  color: var(--ds-color-muted);
}

.wds-pattern-fidelity-v2 .new-chat-button,
.wds-pattern-fidelity-v2 .theme-toggle {
  min-height: 40px;
  border: 1px solid var(--ds-button-secondary-border);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-button-secondary-bg);
  color: var(--ds-button-secondary-fg);
  box-shadow: none;
  font-weight: var(--ds-weight-semibold);
}

.wds-pattern-fidelity-v2 .new-chat-button:hover,
.wds-pattern-fidelity-v2 .theme-toggle:hover {
  border-color: var(--ds-color-line-strong);
  background: var(--ds-button-secondary-bg-hover);
  transform: none;
}

.wds-pattern-fidelity-v2 .nav-item {
  grid-template-columns: 28px minmax(0, 1fr);
  min-height: 38px;
  padding: var(--ds-space-2) var(--ds-space-3);
  border: 1px solid transparent;
  border-radius: var(--ds-radius-xs);
  background: transparent;
  color: var(--ds-color-ink-soft);
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .nav-item span:not(.nav-icon) {
  color: var(--ds-color-ink-soft);
  font-weight: var(--ds-weight-medium);
}

.wds-pattern-fidelity-v2 .nav-icon {
  width: 24px;
  height: 24px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-xs);
  background: var(--ds-color-surface-solid);
  color: var(--ds-color-muted);
}

.wds-pattern-fidelity-v2 .nav-item:hover {
  border-color: var(--ds-color-line);
  background: var(--ds-color-surface-solid);
  transform: none;
}

.wds-pattern-fidelity-v2 .nav-item.active {
  border-color: var(--ds-color-accent);
  background: var(--ds-color-accent-soft);
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .nav-item.active span:not(.nav-icon),
.wds-pattern-fidelity-v2 .nav-item.active .nav-icon {
  color: var(--ds-color-accent);
}

.wds-pattern-fidelity-v2 .recent-item {
  min-height: 38px;
  border-radius: var(--ds-radius-xs);
  color: var(--ds-color-ink-soft);
}

.wds-pattern-fidelity-v2 .recent-item:hover {
  border-color: var(--ds-color-line);
  background: var(--ds-color-surface-solid);
}

.wds-pattern-fidelity-v2 .main-pane,
.wds-pattern-fidelity-v2 .chat-pane {
  background: var(--ds-color-bg);
}

.wds-pattern-fidelity-v2 .workspace-hero {
  min-height: 70px;
  padding: var(--ds-space-4) var(--ds-space-7);
  background: var(--ds-color-surface-solid);
  border-bottom: 1px solid var(--ds-color-line);
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .workbench {
  grid-template-columns: minmax(0, 1fr) minmax(320px, 380px);
}

.wds-pattern-fidelity-v2 .chat-pane {
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.wds-pattern-fidelity-v2 .ai-chat-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-2);
  width: min(760px, calc(100% - var(--ds-space-8)));
  margin: var(--ds-space-5) auto 0;
  color: var(--ds-color-ink);
}

.wds-pattern-fidelity-v2 .assistant-identity {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  min-width: 0;
}

.wds-pattern-fidelity-v2 .assistant-mark {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-color-surface-muted);
  color: var(--ds-color-ink-soft);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
}

.wds-pattern-fidelity-v2 .assistant-identity strong {
  display: block;
  color: var(--ds-color-ink);
  font-size: var(--ds-text-body-14);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-tight);
}

.wds-pattern-fidelity-v2 .assistant-identity span:not(.assistant-mark) {
  display: block;
  margin-top: 2px;
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
  line-height: var(--ds-leading-tight);
}

.wds-pattern-fidelity-v2 .assistant-state {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  min-height: 28px;
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-medium);
  white-space: nowrap;
}

.wds-pattern-fidelity-v2 .assistant-state-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--ds-radius-pill);
  background: var(--ds-color-accent);
}

.wds-pattern-fidelity-v2 .message-list {
  align-items: center;
  gap: var(--ds-space-4);
  padding: var(--ds-space-7) var(--ds-space-6) var(--ds-space-5);
}

.wds-pattern-fidelity-v2 .calm-chat-home {
  width: min(760px, 100%);
  margin: 0 auto;
  padding: var(--ds-space-7);
  border: 1px solid var(--ds-card-border);
  border-radius: var(--ds-radius-lg);
  background: var(--ds-card-bg);
  box-shadow: var(--ds-shadow-xs);
  text-align: left;
}

.wds-pattern-fidelity-v2 .home-project-pill {
  width: fit-content;
  min-height: 26px;
  border-color: var(--ds-color-line);
  background: var(--ds-color-surface-muted);
  color: var(--ds-color-muted);
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .calm-chat-home h2 {
  color: var(--ds-color-ink);
  font-size: var(--ds-text-heading-32);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-tight);
}

.wds-pattern-fidelity-v2 .calm-chat-home p {
  color: var(--ds-color-ink-soft);
  font-size: var(--ds-text-body-15);
  line-height: var(--ds-leading-body);
}

.wds-pattern-fidelity-v2 .quick-start-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.wds-pattern-fidelity-v2 .quick-start-grid button {
  min-height: 42px;
  border-radius: var(--ds-radius-xs);
  background: var(--ds-button-secondary-bg);
  color: var(--ds-button-secondary-fg);
  box-shadow: none;
  text-align: left;
}

.wds-pattern-fidelity-v2 .portal-context-strip {
  justify-content: flex-start;
}

.wds-ai-portal-v1 .wds-home-surface,
.wds-pattern-fidelity-v2 .wds-home-surface {
  width: min(720px, 100%);
  margin: auto;
  padding: 0 var(--ds-space-2);
  border: 0;
  background: transparent;
  box-shadow: none;
  text-align: center;
}

.wds-ai-portal-v1 .wds-home-kicker,
.wds-pattern-fidelity-v2 .wds-home-kicker {
  margin: 0;
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
  letter-spacing: 0;
}

.wds-ai-portal-v1 .wds-home-surface h2,
.wds-pattern-fidelity-v2 .wds-home-surface h2 {
  margin: 0;
  color: var(--ds-color-ink);
  font-size: var(--ds-text-title-20);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-tight);
}

.wds-ai-portal-v1 .wds-home-surface p,
.wds-pattern-fidelity-v2 .wds-home-surface p {
  max-width: none;
  margin: 0;
  color: var(--ds-color-ink-soft);
  font-size: var(--ds-text-body-14);
  line-height: var(--ds-leading-body);
}

.wds-ai-portal-v1 .prompt-chip-row,
.wds-pattern-fidelity-v2 .prompt-chip-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--ds-space-2);
  margin-top: var(--ds-space-3);
}

.wds-ai-portal-v1 .prompt-chip,
.wds-pattern-fidelity-v2 .prompt-chip {
  min-height: 34px;
  padding: 0 var(--ds-space-3);
  border: 1px solid var(--ds-chip-border);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-chip-bg);
  color: var(--ds-chip-fg);
  font-size: var(--ds-text-body-14);
  font-weight: var(--ds-weight-medium);
  cursor: pointer;
}

.wds-ai-portal-v1 .prompt-chip:hover,
.wds-pattern-fidelity-v2 .prompt-chip:hover {
  border-color: var(--ds-color-line-strong);
  background: var(--ds-color-surface-muted);
}

.wds-pattern-fidelity-v2 .message {
  width: min(760px, 100%);
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
}

.wds-pattern-fidelity-v2 .message-body {
  width: 100%;
  padding: var(--ds-space-3) 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .message.assistant .message-body,
.wds-pattern-fidelity-v2 .message.system .message-body {
  background: transparent;
}

.wds-pattern-fidelity-v2 .message.user {
  justify-items: end;
}

.wds-pattern-fidelity-v2 .message.user .message-body {
  width: fit-content;
  max-width: min(620px, 82%);
  padding: var(--ds-space-3) var(--ds-space-4);
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-lg);
  background: var(--ds-color-surface-muted);
  color: var(--ds-color-ink);
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .composer-surface,
.wds-pattern-fidelity-v2 .wds-composer-bar {
  width: min(760px, calc(100% - var(--ds-space-8)));
  margin: 0 auto var(--ds-space-6);
  padding: var(--ds-space-2);
  border: 1px solid var(--ds-card-border);
  border-radius: var(--ds-radius-lg);
  background: var(--ds-color-surface-solid);
  box-shadow: var(--ds-shadow-xs);
}

.wds-pattern-fidelity-v2 .composer-prompt-body {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--ds-space-2);
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .composer-surface textarea {
  min-height: 40px;
  max-height: 160px;
  padding: 10px 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  resize: none;
}

.wds-pattern-fidelity-v2 .composer-icon-button {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  min-width: 34px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-color-surface-solid);
  color: var(--ds-color-ink);
  font-size: var(--ds-text-body-16);
  font-weight: var(--ds-weight-semibold);
  line-height: 1;
  cursor: pointer;
}

.wds-pattern-fidelity-v2 .composer-icon-button:hover {
  border-color: var(--ds-color-line-strong);
  background: var(--ds-color-surface-muted);
}

.wds-pattern-fidelity-v2 .composer-action-dock {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
}

.wds-pattern-fidelity-v2 .composer-model-pill {
  min-height: 30px;
  padding: 0 var(--ds-space-2);
  border: 1px solid transparent;
  border-radius: var(--ds-radius-pill);
  color: var(--ds-color-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
}

.wds-pattern-fidelity-v2 #sendButton,
.wds-pattern-fidelity-v2 .composer-send-button {
  border-color: var(--ds-color-accent);
  background: var(--ds-color-accent);
  color: var(--ds-color-on-accent);
}

.wds-pattern-fidelity-v2 #sendButton:disabled {
  border-color: var(--ds-color-line);
  background: var(--ds-color-surface-muted);
  color: var(--ds-color-muted);
  cursor: not-allowed;
}

html[data-chat-state="empty"] .wds-ai-portal-v1 .chat-pane,
html[data-chat-state="empty"] .wds-pattern-fidelity-v2 .chat-pane {
  grid-template-rows: auto auto auto;
  align-content: center;
  padding-bottom: clamp(var(--ds-space-8), 8vh, var(--ds-space-10));
}

/* 20260726-route-page-not-empty-chat: the rule above centres the EMPTY CHAT HOME
   vertically, but data-chat-state="empty" only says "no messages" — it stays set while
   a route page (/drive, /library, ...) is open, so a route with short content floated
   to the middle of the viewport under the fixed topbar. Measured on /drive at 390px:
   0 items = 65px dead space, 2 items = 163px, 4 items = 101px, 8+ items = 20px
   (normal). The gap scales inversely with content, which is why an audit seeded with
   9 rows never saw it while a real, nearly empty drive shows it to every user.
   .chat-pane computes to display:block here, so grid-template-rows above is inert and
   only align-content lands. Specificity (0,3,2) beats that rule's (0,3,1), so no
   !important is needed, and the chat home keeps its centring because it is never
   portal-route-open. */
html[data-chat-state="empty"] body.portal-route-open .chat-pane {
  align-content: start;
}

html[data-chat-state="empty"] .wds-ai-portal-v1 .ai-chat-header,
html[data-chat-state="empty"] .wds-pattern-fidelity-v2 .ai-chat-header {
  margin-bottom: var(--ds-space-5);
}

html[data-chat-state="empty"] .wds-ai-portal-v1 .message-list,
html[data-chat-state="empty"] .wds-pattern-fidelity-v2 .message-list {
  justify-content: center;
  min-height: 0;
  overflow: visible;
  padding: 0 var(--ds-space-6) var(--ds-space-4);
}

html[data-chat-state="empty"] .wds-ai-portal-v1 .wds-home-surface,
html[data-chat-state="empty"] .wds-pattern-fidelity-v2 .wds-home-surface {
  margin: 0 auto;
  padding: 0;
}

html[data-chat-state="empty"] .wds-ai-portal-v1 .prompt-chip-row,
html[data-chat-state="empty"] .wds-pattern-fidelity-v2 .prompt-chip-row {
  justify-content: center;
}

html[data-chat-state="empty"] .wds-ai-portal-v1 .composer-surface,
html[data-chat-state="empty"] .wds-pattern-fidelity-v2 .composer-surface {
  margin: 0 auto;
  box-shadow: var(--ds-shadow-xs);
}

/* ChatGPT reference visual pass for the WDS AI chat surface.
   20260710-dead-base-rule-removed (P1-B): 여기 있던 `.chatgpt-reference-v1 { #ffffff/#0d0d0d }`
   리터럴 base 규칙은 `body.chatgpt-reference-v1 { background:var(--cg-bg); color:var(--cg-text) }`
   (L8893+, 특정성 0,1,1 > 0,1,0 + 후순위)에 가려 죽어 있었고, 이 클래스는 index.html의 <body>
   전용이라 비-body 매치도 없다 → 런타임 무변화. 다크모드 하드코딩 랜드마인 제거. */

.chatgpt-reference-v1 .app-shell {
  grid-template-columns: 260px minmax(0, 1fr);
  background: #ffffff;
}

.chatgpt-reference-v1.sidebar-collapsed .app-shell {
  grid-template-columns: 52px minmax(0, 1fr);
}

.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0 0 0 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* 20260926-sr-only-define: 전역 공용 시각-숨김 유틸. app.js 의 sr-only 사용처
   7곳(PDF 페이지 번호·상태·검색 라벨, 가격비교 live output 3, 여행 구간 요약)이
   이 규칙 없이 렌더되어 화면 낭독기용 텍스트가 그대로 노출됐다(qa pdf-mobile-
   layout 09-26). 설정 v2 페이지의 검증된 `.settings-v2-sr-only` 와 완전히 같은
   선언 세트를 재사용한다 — 새 값을 고르지 않는다. index.html 이 모든 화면에서
   항상 로드하는 전역 styles.css 에 둔 이유: settings-v2.css·travel-route-
   widget.css 는 해당 화면에서만 로드되지만 이 클래스는 PDF·가격비교·여행 등
   서로 다른 화면에서 공유돼야 한다. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* K-10(2.4.1 A, WCAG G1) — 건너뛰기 링크. .visually-hidden 처럼 clip 으로 숨기지 않는 이유는
   :focus 에서 되돌릴 때 레이아웃에 다시 끼워 넣어야 하는데, .visually-hidden 은 항상
   position:absolute 라 되돌릴 필요가 없다 — 대신 화면 밖(-9999px)에 뒀다가 포커스 시
   0,0 으로 되돌리는 표준 기법을 쓴다(레이아웃에 영향 없음, top layer 보다도 위). body 가
   항상 .chatgpt-reference-v1 을 갖고 있어(84행) --cg-text/--cg-surface 가 폴백 없이도
   상속으로 해석된다 — 새 원시색 0(anti-drift raw-color/raw-border-radius 래칫 순증 0,
   border-radius 는 var() 를 포함해 스캐너 조건상 통째로 면제된다). */
.skip-link {
  position: fixed;
  top: 0;
  left: -9999px;
  z-index: 10000;
  padding: 8px 16px;
  background: var(--cg-text);
  color: var(--cg-surface);
  border-radius: 0 0 var(--wizon-ai-radius-control, 8px) 0;
  font-size: var(--ds-text-body-14, 14px);
  text-decoration: none;
}

.skip-link:focus {
  left: 0;
}

.chatgpt-reference-v1 .sidebar {
  position: relative;
  z-index: 120;
  gap: 0;
  padding: 8px 6px 10px;
  overflow: visible;
  background: #f9f9f9;
  color: var(--cg-text);
  border-right: 1px solid #ececec;
  font-family: var(--wizon-ai-font-family); /* 20260711-typography-contract: 리터럴 시스템 스택 → 계약 토큰 */
}

.chatgpt-reference-v1 .sidebar[hidden] {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-sidebar-rail {
  display: none;
}

.chatgpt-reference-v1.sidebar-collapsed .chatgpt-sidebar-rail {
  position: relative;
  z-index: 140;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  min-height: 100vh;
  padding: 8px 6px;
  background: #f9f9f9;
  border-right: 1px solid #ececec;
  overflow: visible;
}

.chatgpt-reference-v1 .chatgpt-sidebar-rail[hidden] {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-rail-button {
  display: inline-grid;
  place-items: center;
  /* 20260704-sidebar-rail-chatgpt-measured: Chrome reference keeps 40px hit areas for collapsed rail items at x=6. */
  width: 40px;
  height: 36px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-rail-button:hover {
  background: #ececec;
}

.chatgpt-reference-v1 .chatgpt-tooltip-host {
  position: relative;
  overflow: visible;
}

.chatgpt-reference-v1 .chatgpt-tooltip {
  position: absolute;
  top: 50%;
  left: calc(100% + 16px);
  z-index: 1400;
  display: none;
  align-items: center;
  gap: 8px;
  min-height: 26px;
  padding: 4px 8px;
  border-radius: 8px;
  background: #1b1b1b;
  color: #ffffff;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.2);
  font-family: var(--wizon-ai-font-family); /* 20260711-typography-contract: 리터럴 시스템 스택 → 계약 토큰 */
  font-size: var(--ds-text-caption-12);
  font-weight: 400;
  line-height: 16px;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-50%);
  transition: opacity 120ms ease, transform 120ms ease, visibility 120ms ease;
  visibility: hidden;
  white-space: nowrap;
  -webkit-font-smoothing: antialiased;
}

.chatgpt-reference-v1 .chatgpt-tooltip-host[data-tooltip-placement="top"] > .chatgpt-tooltip {
  top: auto;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
}

.chatgpt-reference-v1 .chatgpt-tooltip-host[data-tooltip-open="true"] > .chatgpt-tooltip {
  display: inline-flex;
  opacity: 1;
  transform: translateY(-50%);
  visibility: visible;
}

/* 20260717-tooltip-fixed-1: the tooltip used to reveal two ways — this host-descendant rule
   (JS-set [data-tooltip-open]) AND a pure-CSS `:hover`/`:focus-visible` fallback. The fallback
   could never carry position:fixed viewport coordinates (CSS can't read
   getBoundingClientRect), so a host inside an `overflow: hidden` card (the table toolbar)
   clipped at the card edge instead of floating free. Opening is now unified on the JS path for
   BOTH triggers — pointerenter (mouse) and a `:focus-visible`-gated `focus` listener (keyboard)
   — so every reveal always gets a measured, viewport-fixed position (see
   setChatGptTooltipOpen / openChatGptTooltipFixedPosition in app.js). `:focus-visible` gating
   still excludes a plain mouse CLICK (which only matches bare `:focus`, dropped for the same
   reason as before — it fires on a click too and left the tooltip stuck after clicking the
   sidebar toggle), so that regression cannot return. Once reparented to <body> on open, the
   tooltip is no longer a DOM child of the host, so its OWN [data-tooltip-open] attribute (not
   the host's) drives visibility — see the .chatgpt-tooltip[data-tooltip-open="true"] rule
   below; the host-descendant rule above still covers the instant before JS reparents it. */
.chatgpt-reference-v1 .chatgpt-tooltip[data-tooltip-open="true"] {
  display: inline-flex;
  opacity: 1;
  visibility: visible;
}

.chatgpt-reference-v1 .chatgpt-tooltip-host[data-tooltip-placement="top"][data-tooltip-open="true"] > .chatgpt-tooltip {
  transform: translateX(-50%) translateY(0);
}

.chatgpt-reference-v1 .chatgpt-tooltip-label {
  color: #ffffff;
  font-weight: 600;
  letter-spacing: normal;
}

.chatgpt-reference-v1 .chatgpt-tooltip-shortcut {
  color: #afafaf;
  font-weight: 500;
  letter-spacing: normal;
}

/* Touch/coarse-pointer devices get no hover affordances: the icon tooltips and the citation
   hover-preview are pointer-only. Some mobile browsers mis-report (hover: hover), which let a
   TAPPED tooltip/preview stick and overlap the UI (reported mobile 겹침 bug). A coarse-pointer
   guard hides them on a NARROW screen (reliable) or any touch-capable device — on touch/mobile the
   tap-to-open panel and self-evident icons cover the intent. */
@media (max-width: 520px), (any-pointer: coarse) {
  .chatgpt-reference-v1 .chatgpt-tooltip,
  .citation-preview {
    display: none !important;
  }
}

/* 20260911-rail-button-x-unify (C17): `.chatgpt-rail-brand` 는 이전엔 width:36px +
   margin-bottom:8px 를 따로 줬다. width 36 은 형제 rail 버튼(기본 40px)보다 좁아
   place-items:center 컬럼 안에서 좌우 x 가 어긋났고(x{5.5,7.5} 실측), margin-bottom 8 은
   `.chatgpt-sidebar-rail`의 flex `gap:8px` 와 겹쳐 첫 간격만 16 이 됐다(vgap{16,52} 중
   16). 규칙 자체를 지워 기본 40px 폭·공통 gap 8px 을 그대로 물려받게 한다 — x·vgap 모두
   다른 rail 버튼과 1값이 된다. */

.chatgpt-reference-v1 .chatgpt-rail-account {
  align-self: flex-start;
  width: 38px;
  height: 40px;
  margin-top: auto;
  margin-bottom: 4px;
  margin-left: 2px;
  border-radius: var(--ds-radius-sm, 10px);
}

.chatgpt-reference-v1 .chatgpt-sidebar-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 44px;
  padding: 2px 4px 10px 8px;
  margin-bottom: 2px;
}

.chatgpt-reference-v1 .chatgpt-sidebar-head strong {
  color: var(--cg-text);
  font-size: var(--ds-text-body-16);
  font-weight: 500;
}

.chatgpt-product-mode-switcher {
  position: relative;
}

/* 20260725-sidebar-wordmark-only: the drawer's workspace switcher and the mobile top app bar's
   workspace chip are the same control, so only one may exist at a time. Desktop has no app bar →
   the button IS the switcher. Mobile has the chip → the drawer shows a plain wordmark, boxed like
   the button so the header height and the wordmark's x-position do not shift between the two.
   Swapping ELEMENTS (rather than hiding parts of the button) is what keeps the a11y tree honest:
   a display:none button leaves the tree entirely instead of announcing a menu it no longer opens. */
.chatgpt-reference-v1 .chatgpt-sidebar-wordmark {
  display: none;
  align-items: center;
  min-height: 44px;
  padding: 0 10px;
}

.chatgpt-product-mode-button {
  display: inline-flex;
  /* 20260911-sidebar-head-sibling-unify (C8): 44→36 으로 접기 버튼(34→36)과 높이를
     맞추고, 반경 12→var(--wizon-ai-radius-control)(8)로 두 형제를 1종으로 합친다
     (S3 실측 {34,44}/반경 2종 → 36/8 1값). */
  min-height: 36px;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--wizon-ai-radius-control);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}

.chatgpt-product-mode-button:hover,
.chatgpt-product-mode-button[aria-expanded="true"] {
  background: var(--wizon-ai-hover-bg, #ececec);
}

.chatgpt-product-mode-button > span:not([data-icon]) {
  color: var(--wizon-ai-text-muted, #6f6f6f);
  font-size: var(--ds-text-body-14);
}

.chatgpt-product-mode-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  width: 272px;
  padding: 8px;
  border: 1px solid var(--wizon-ai-popover-border, #e3e3e3);
  border-radius: var(--wizon-ai-radius-popover);
  background: var(--wizon-ai-popover-bg, #ffffff);
  box-shadow: var(--wizon-ai-popover-shadow, 0 8px 12px rgba(0, 0, 0, 0.08));
  z-index: var(--wizon-ai-popover-z, 80);
}

.chatgpt-product-mode-menu button {
  display: grid;
  width: 100%;
  min-height: 58px;
  grid-template-columns: 28px minmax(0, 1fr) 20px;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--ds-radius-sm);
  background: transparent;
  color: var(--wizon-ai-text-primary, #0d0d0d);
  text-align: left;
  cursor: pointer;
}

.chatgpt-product-mode-menu button:hover,
.chatgpt-product-mode-menu button.is-selected {
  background: var(--wizon-ai-hover-bg, #ececec);
}

.chatgpt-product-mode-menu button span:nth-child(2) {
  display: grid;
  min-width: 0;
  gap: 2px;
}

.chatgpt-product-mode-menu small {
  color: var(--wizon-ai-text-muted, #6f6f6f);
  font-size: var(--ds-text-caption-12);
}

.chatgpt-product-mode-menu button > span:last-child {
  opacity: 0;
}

.chatgpt-product-mode-menu button.is-selected > span:last-child {
  opacity: 1;
}

.chatgpt-reference-v1 [data-product-modes][hidden],
.chatgpt-reference-v1 [data-sidebar-destination][hidden],
.chatgpt-reference-v1 [data-sidebar-more-action][hidden] {
  display: none !important;
}

/* mobile-menu-row-touch-fix (doc 669 A-18, judge 판정 §5 ⑥): 좌우 마진이 8px 라
   아카이브·휴지통 아이콘이 나머지 내비 행보다 8px 안쪽에서 시작해 정렬선이 {10,18}
   2값이었다(실측). 0 으로 되돌려 사이드바 내비 행과 같은 x=10 에 맞춘다 — 구분선은
   그대로 유지(margin 만 변경, border-top 은 무변경). */
.chatgpt-reference-v1 .chatgpt-sidebar-utilities {
  display: grid;
  gap: 0;
  /* 20260911-sidebar-group-inset-unify (C1): 좌우 8px 인셋을 없애 주메뉴와 같은 행 격자
     (좌 6px)를 쓴다. 위 마진은 -6px→-4px 로 바꿔 「주메뉴→보관함」 간격을 16(그룹
     margin-bottom)-4=12 로 만들어 「보관함→최근 머리」 간격(12)과 합치고, 세로 리듬 값
     종류를 {0,2,10,12} 4종에서 {0,2,12} 3종으로 줄인다(§3, 기준 ≤3). 구분선은 border-top
     만 남긴다. */
  margin: -4px 0 12px;
  padding-top: 8px;
  border-top: 1px solid var(--wizon-ai-sidebar-border, #ececec);
}

.chatgpt-reference-v1 .chatgpt-sidebar-utilities .chatgpt-sidebar-link {
  width: 100%;
}

/* The product selector reuses reasoning-popover only for the mobile top-layer
   sheet machinery. On desktop it remains a sidebar-anchored 272px menu; the
   generic reasoning menu's later 124px rule must never collapse or offset it. */
@media (min-width: 521px) {
  body.chatgpt-reference-v1 .chatgpt-product-mode-menu.reasoning-popover {
    left: 0;
    right: auto;
    top: calc(100% + 8px);
    bottom: auto;
    width: 272px;
    min-width: 272px;
    max-height: min(340px, calc(100vh - 16px));
    padding: 8px;
    overflow-x: visible;
  }
}

.chatgpt-reference-v1 .chatgpt-icon-button {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #4b5563;
  cursor: pointer;
}

/* 20260911-sidebar-head-sibling-unify (C8): 사이드바 머리의 접기 버튼만 34→36 으로 올려
   워크스페이스 전환 버튼(36)과 형제 높이를 맞춘다. `.chatgpt-icon-button` 은 설정·검색·
   시트 함수·서식 다이얼로그 닫기 등 다른 화면에서도 널리 쓰이므로 기본 34px 은 그대로
   두고, 이 버튼(`data-sidebar-collapse`)만 좁혀 스코프한다. */
.chatgpt-reference-v1 .chatgpt-sidebar-head [data-sidebar-collapse].chatgpt-icon-button {
  width: 36px;
  height: 36px;
}

/* Only show a focus ring for keyboard navigation. Native <dialog>.showModal() autofocuses
   its first focusable child (e.g. the settings/close X), which painted the browser's default
   focus ring even when the dialog was opened by touch. :focus-visible scopes the ring to
   keyboard users so touch users see none (doc 143 mobile focus hardening; ref: MDN
   :focus-visible). */
.chatgpt-reference-v1 .chatgpt-icon-button:focus:not(:focus-visible) {
  outline: none;
}

@media (hover: hover) {
  .chatgpt-reference-v1 .chatgpt-icon-button:hover {
    background: #ececec;
    color: var(--cg-text);
  }
}

.chatgpt-reference-v1 .chatgpt-sidebar-group {
  display: grid;
  gap: 0;
  margin-bottom: 16px;
}

.chatgpt-reference-v1 .chatgpt-sidebar-link,
.chatgpt-reference-v1 .new-chat-button.chatgpt-sidebar-link {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  align-items: center;
  justify-content: start;
  column-gap: 12px;
  min-height: var(--cg-sidebar-item-height);
  width: var(--cg-sidebar-item-width);
  max-width: 100%;
  padding: 6px 10px;
  border: 0;
  border-radius: var(--ds-radius-sm, 10px);
  background: transparent;
  color: var(--cg-text);
  box-shadow: none;
  font-size: var(--cg-text-label-size, 14px); /* 20260711-typography-contract: 라벨 토큰(모바일 17px 전파) */
  font-weight: 400;
  line-height: var(--cg-text-label-leading, 1.45); /* 데스크톱 14×1.45≈20px 동등, 모바일 17px에 비례 */
  text-align: left;
  /* 20260802-sidebar-link-no-underline: 이 규칙은 <a>(프로젝트·드라이브·일정·라이브러리·아카이브·
     휴지통)와 <button>(새 채팅·검색)에 함께 걸린다. 리셋이 없으면 앵커만 브라우저 기본 밑줄을 받아
     같은 드로어에 두 종류의 행이 생긴다(iPhone 17 시뮬 실촬영, 배포 0583703ab). */
  text-decoration: none;
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-sidebar-link > [data-icon] {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
}

.chatgpt-reference-v1 .chatgpt-sidebar-link > span:not([data-icon]) {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-sidebar-shortcut {
  justify-self: end;
  margin-left: 8px;
  color: #8a8a8a;
  font-size: var(--ds-text-caption-12);
  font-weight: 500;
  letter-spacing: 0;
  line-height: 16px;
}

.chatgpt-reference-v1 .chatgpt-sidebar-shortcut[hidden] {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-sidebar-link.active {
  background: #ececec;
}
@media (hover: hover) {
  .chatgpt-reference-v1 .chatgpt-sidebar-link:hover {
    background: #ececec;
  }
}

.chatgpt-reference-v1 .chatgpt-sidebar-link svg,
.chatgpt-reference-v1 .chatgpt-icon-button svg,
.chatgpt-reference-v1 .chatgpt-account-menu svg,
.chatgpt-reference-v1 .chatgpt-more-menu svg,
.chatgpt-reference-v1 .chatgpt-rail-button svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--wds-icon-stroke, 1.7);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chatgpt-reference-v1 .chatgpt-more-menu {
  /* 20260704-sidebar-more-popover-chatgpt: Chrome-measured ChatGPT "더 보기" menu opens beside the sidebar (x 250, w 258, h 120). */
  position: absolute;
  left: 250px;
  top: var(--sidebar-more-menu-top, 271px);
  z-index: 70;
  display: block;
  width: 258px;
  height: 120px;
  padding: 6px 0;
  border: 0 solid rgba(0, 0, 0, 0.05);
  border-radius: 16px;
  background: #ffffff;
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.62);
}

.chatgpt-reference-v1 .chatgpt-more-menu[hidden] {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-more-menu button {
  display: flex;
  align-items: center;
  gap: 6px;
  width: calc(100% - 12px);
  height: 36px;
  min-height: 36px;
  margin: 0 6px;
  padding: 6px 10px;
  border: 0;
  border-radius: var(--ds-radius-sm, 10px);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-14);
  text-align: left;
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-more-menu button:hover {
  background: #f4f4f4;
}

.chatgpt-reference-v1 .chatgpt-more-menu button > [data-icon] {
  display: inline-grid;
  flex: 0 0 20px;
  place-items: center;
}

.chatgpt-reference-v1 .chatgpt-more-menu button svg {
  width: 20px;
  height: 20px;
}

.chatgpt-reference-v1 .chatgpt-account-area,
.chatgpt-reference-v1 .chatgpt-account-menu,
.chatgpt-reference-v1 .chatgpt-rail-account {
  --chatgpt-account-area-padding-top: calc(var(--wizon-ai-space-unit) * 2.25);
  --chatgpt-account-button-gap: var(--ds-space-2, 8px);
  --chatgpt-account-button-pad-y: var(--cg-menu-gutter);
  --chatgpt-account-button-pad-right: var(--cg-menu-gutter);
  --chatgpt-account-button-pad-left: var(--ds-space-2, 8px);
  --chatgpt-account-button-radius: var(--wizon-ai-radius-menu-row);
  --chatgpt-account-avatar-size: var(--ds-space-6, 24px);
  --chatgpt-account-avatar-bg: var(--wizon-ai-account-avatar-bg);
  --chatgpt-account-avatar-fg: var(--wizon-ai-account-avatar-fg);
  --chatgpt-account-avatar-font-size: var(--wds-font-size-caption);
  --chatgpt-account-text-size: var(--wds-font-size-body-2);
  --chatgpt-account-text-line-height: calc(var(--wizon-ai-space-unit) * 5);
  --chatgpt-account-caption-size: var(--wds-font-size-caption);
  --chatgpt-account-caption-line-height: var(--ds-space-4, 16px);
  --chatgpt-account-menu-width: var(--wizon-ai-account-menu-width);
  --chatgpt-account-menu-radius: var(--wizon-ai-radius-popover);
  --chatgpt-account-menu-gutter: var(--cg-menu-gutter);
  --chatgpt-account-menu-row-gap: var(--cg-menu-gutter);
  --chatgpt-account-menu-row-height: var(--wizon-ai-menu-row-height);
  --chatgpt-account-menu-row-radius: var(--wizon-ai-radius-menu-row);
  --chatgpt-account-menu-row-icon-size: var(--wizon-ai-icon-size-md);
  --chatgpt-account-menu-row-pad-y: var(--cg-menu-gutter);
  --chatgpt-account-menu-row-pad-left: calc(var(--wizon-ai-space-unit) * 2.5);
  --chatgpt-account-menu-row-pad-right: var(--ds-space-7, 32px);
  --chatgpt-account-menu-row-font-size: var(--wds-font-size-body-2);
  --chatgpt-account-menu-row-line-height: calc(var(--wizon-ai-space-unit) * 5);
  --chatgpt-account-menu-head-height: var(--wizon-ai-account-menu-head-height);
  --chatgpt-account-menu-head-gap: var(--ds-space-2, 8px);
  --chatgpt-account-menu-head-margin-bottom: calc(var(--wizon-ai-space-unit) * 2.25);
  --chatgpt-account-menu-separator-margin-block: var(--wizon-ai-space-unit);
  --chatgpt-account-menu-separator-margin-inline: var(--ds-space-4, 16px);
}

.chatgpt-reference-v1 .chatgpt-account-area {
  position: relative;
  margin-top: auto;
  padding-top: var(--chatgpt-account-area-padding-top);
}

.chatgpt-reference-v1 .chatgpt-account-area::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  height: 1px;
  background: var(--cg-sidebar-border);
}

.chatgpt-reference-v1 .chatgpt-account-button {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--chatgpt-account-button-gap);
  width: var(--cg-sidebar-account-width);
  max-width: 100%;
  height: var(--cg-sidebar-account-height);
  min-height: var(--cg-sidebar-account-height);
  padding: var(--chatgpt-account-button-pad-y) var(--chatgpt-account-button-pad-right) var(--chatgpt-account-button-pad-y) var(--chatgpt-account-button-pad-left);
  border: 0;
  border-radius: var(--chatgpt-account-button-radius);
  background: transparent;
  color: var(--cg-text);
  text-align: left;
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-account-button:hover,
.chatgpt-reference-v1 .chatgpt-account-button[aria-expanded="true"] {
  background: var(--cg-hover);
}

.chatgpt-reference-v1 .chatgpt-account-avatar {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: var(--chatgpt-account-avatar-size);
  height: var(--chatgpt-account-avatar-size);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--chatgpt-account-avatar-bg);
  color: var(--chatgpt-account-avatar-fg);
  font-size: var(--chatgpt-account-avatar-font-size);
  font-weight: 600;
}

/* 프로필 사진이 있으면 이니셜 대신 이미지가 배지를 채운다. 크기·모양은 배지가
   이미 정하므로 여기서 다시 정하지 않고 상속·꽉채움만 지시한다. */
.chatgpt-reference-v1 .chatgpt-account-avatar-image {
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
}

.chatgpt-reference-v1 .chatgpt-account-copy {
  display: grid;
  min-width: 0;
  gap: 0;
}

.chatgpt-reference-v1 .chatgpt-account-copy strong,
.chatgpt-reference-v1 .chatgpt-account-menu-head strong {
  overflow: hidden;
  color: var(--cg-text);
  font-size: var(--chatgpt-account-text-size);
  font-weight: 400;
  line-height: var(--chatgpt-account-text-line-height);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-account-copy small,
.chatgpt-reference-v1 .chatgpt-account-menu-head small {
  color: var(--cg-muted);
  font-size: var(--chatgpt-account-caption-size);
  line-height: var(--chatgpt-account-caption-line-height);
}

.chatgpt-reference-v1 .chatgpt-account-menu {
  position: absolute;
  left: var(--account-menu-left, 2px);
  top: var(--account-menu-top, auto);
  bottom: var(--account-menu-bottom, calc(100% - 5px));
  z-index: 80;
  display: block;
  width: var(--chatgpt-account-menu-width);
  max-height: var(--account-menu-max-height, min(360px, calc(100vh - 16px)));
  padding: var(--chatgpt-account-menu-gutter) 0;
  border: 0;
  border-radius: var(--chatgpt-account-menu-radius);
  background: var(--cg-menu-bg);
  box-shadow: var(--cg-menu-shadow);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.chatgpt-reference-v1 .chatgpt-account-menu[hidden] {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-rail-account-menu {
  left: var(--account-menu-left, calc(100% + 8px));
  top: var(--account-menu-top, auto);
  bottom: var(--account-menu-bottom, 8px);
  z-index: 120;
  transform-origin: left bottom;
}

.chatgpt-reference-v1 .chatgpt-account-menu-head {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-account-menu-head-gap);
  width: calc(100% - (var(--chatgpt-account-menu-gutter) * 2));
  min-height: var(--chatgpt-account-menu-head-height);
  margin: 0 var(--chatgpt-account-menu-gutter) var(--chatgpt-account-menu-head-margin-bottom);
  padding: var(--chatgpt-account-menu-row-pad-y) var(--chatgpt-account-menu-row-pad-left);
  border: 0;
  border-radius: var(--chatgpt-account-menu-row-radius);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-account-menu button {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-account-menu-row-gap);
  width: calc(100% - (var(--chatgpt-account-menu-gutter) * 2));
  height: var(--chatgpt-account-menu-row-height);
  min-height: var(--chatgpt-account-menu-row-height);
  margin: 0 var(--chatgpt-account-menu-gutter);
  padding: var(--chatgpt-account-menu-row-pad-y) var(--chatgpt-account-menu-row-pad-right) var(--chatgpt-account-menu-row-pad-y) var(--chatgpt-account-menu-row-pad-left);
  border: 0;
  border-radius: var(--chatgpt-account-menu-row-radius);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--chatgpt-account-menu-row-font-size);
  font-weight: 400;
  line-height: var(--chatgpt-account-menu-row-line-height);
  text-align: left;
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-account-menu .chatgpt-account-menu-head {
  gap: var(--chatgpt-account-menu-head-gap);
  height: var(--chatgpt-account-menu-head-height);
  min-height: var(--chatgpt-account-menu-head-height);
  margin: 0 var(--chatgpt-account-menu-gutter);
  padding: var(--chatgpt-account-menu-row-pad-y) var(--chatgpt-account-menu-row-pad-left);
}

.chatgpt-reference-v1 .chatgpt-account-menu button:not(.chatgpt-account-menu-head) [data-icon] {
  display: inline-grid;
  flex: 0 0 var(--chatgpt-account-menu-row-icon-size);
  place-items: center;
  width: var(--chatgpt-account-menu-row-icon-size);
  height: var(--chatgpt-account-menu-row-icon-size);
}

.chatgpt-reference-v1 .chatgpt-account-menu button:not(.chatgpt-account-menu-head) [data-icon] svg {
  width: var(--chatgpt-account-menu-row-icon-size);
  height: var(--chatgpt-account-menu-row-icon-size);
}

.chatgpt-reference-v1 .chatgpt-account-menu button:not(.chatgpt-account-menu-head) span:not([data-icon]) {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-account-menu button:hover,
.chatgpt-reference-v1 .chatgpt-account-menu-head:hover {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .chatgpt-menu-separator {
  height: 1px;
  margin: var(--chatgpt-account-menu-separator-margin-block) var(--chatgpt-account-menu-separator-margin-inline);
  background: var(--cg-sidebar-border);
}

.chatgpt-reference-v1 .chatgpt-settings-dialog {
  width: min(680px, calc(100vw - 32px));
  max-width: none;
  height: min(600px, calc(100vh - 48px));
  padding: 0;
  border: 0;
  border-radius: 16px;
  background: #ffffff;
  color: var(--cg-text);
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.62);
}

.chatgpt-reference-v1 .chatgpt-settings-dialog::backdrop {
  background: rgba(0, 0, 0, 0.28);
}

.chatgpt-reference-v1 .chatgpt-settings-shell {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  height: 100%;
}

.chatgpt-reference-v1 .chatgpt-settings-head {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  align-items: center;
  min-height: 56px;
  padding: 0;
  border-bottom: 1px solid #ececec;
}

.chatgpt-reference-v1 .chatgpt-settings-head .chatgpt-icon-button {
  width: 36px;
  height: 36px;
  margin-left: 15px;
  padding: 0;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-settings-head h2 {
  margin: 0;
  padding-left: 16px;
  font-size: var(--ds-text-title-18);
  line-height: 28px;
  font-weight: 400;
}

.chatgpt-reference-v1 .chatgpt-settings-body {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  min-height: 0;
}

.chatgpt-reference-v1 .chatgpt-settings-tabs {
  display: grid;
  align-content: start;
  gap: 0;
  padding: 4px 0;
  border-right: 1px solid #ececec;
  overflow: auto;
}

.chatgpt-reference-v1 .chatgpt-settings-tabs button {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 167px;
  min-height: 36px;
  margin: 0 6px;
  padding: 6px 10px;
  border: 0;
  border-radius: var(--ds-radius-sm, 10px);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-14);
  line-height: 20px;
  font-weight: 400;
  text-align: left;
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-settings-tabs button:hover,
.chatgpt-reference-v1 .chatgpt-settings-tabs button.is-active {
  background: rgba(0, 0, 0, 0.04);
}

.chatgpt-reference-v1 .chatgpt-settings-tabs button:focus:not(:focus-visible) {
  outline: none;
}

.chatgpt-reference-v1 .chatgpt-settings-panel {
  display: grid;
  align-content: start;
  gap: 0;
  padding: 0 16px;
  overflow: auto;
}

.chatgpt-reference-v1 .chatgpt-settings-panel h3 {
  margin: 0;
  font-size: var(--ds-text-title-18);
  line-height: 28px;
  font-weight: 400;
}

/* 셀 서식 대화상자 (doc 597 §4.2 P2-4 · doc 544 §61). #settingsDialog 와 같은
   chatgpt-settings-* 셸을 재사용하고(사이드바 탭·패널 레이아웃은 그대로), 이 대화상자에만
   필요한 3번째 grid row(취소/확인 footer)만 새로 추가한다. 6개 패널 내부 필드 레이아웃도
   여기서만 쓰는 이름이라 accessibility-checkbox/-radio/-field 접두어를 artifact-format-cells 로
   둔다 — 다른 화면과 이름이 겹치지 않는다. */
.chatgpt-reference-v1 .artifact-format-cells-shell {
  grid-template-rows: auto minmax(0, 1fr) auto;
}

/* ★:not([hidden]) 이 요점이다 — 이 셀렉터의 특이도(클래스+태그+속성)가 브라우저 기본
   `[hidden]{display:none}`(UA 스타일시트, 항상 진다) 을 이겨서, hidden 인 패널까지
   display:grid 로 다시 보이게 만들었던 실측 결함(2026-09-07, 실캡처로 6패널이 한 화면에
   쌓여 보임)을 여기서 막는다. */
.chatgpt-reference-v1 .artifact-format-cells-panel-group section[data-format-cells-panel]:not([hidden]) {
  display: grid;
  gap: 12px;
  padding: 12px 0;
}

.chatgpt-reference-v1 .artifact-format-cells-field-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
}

.chatgpt-reference-v1 .artifact-format-cells-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 140px;
}

.chatgpt-reference-v1 .artifact-format-cells-field-label {
  font-size: var(--ds-text-caption-12, 12px);
  color: var(--cg-text-soft);
}

.chatgpt-reference-v1 .artifact-format-cells-checkbox,
.chatgpt-reference-v1 .artifact-format-cells-radio {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--ds-text-body-14);
}

.chatgpt-reference-v1 .artifact-format-cells-fieldset {
  margin: 0;
  padding: 0;
  border: 0;
}

.chatgpt-reference-v1 .artifact-format-cells-hint {
  margin: 0;
  font-size: var(--ds-text-caption-12, 12px);
  color: var(--cg-text-soft);
}

.chatgpt-reference-v1 .artifact-format-cells-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--ds-color-line);
}

.chatgpt-reference-v1 .settings-panel-section {
  min-width: 0;
  background: inherit;
}

.chatgpt-reference-v1 .settings-section-header {
  display: flex;
  min-height: 60px;
  align-items: flex-start;
  padding: 12px 0;
  border-bottom: 1px solid rgba(0, 0, 0, 0.10);
}

.chatgpt-reference-v1 .settings-section-title {
  min-width: 0;
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-title-18);
  font-weight: 400;
  line-height: 28px;
  letter-spacing: 0;
}

.chatgpt-reference-v1 .settings-description {
  margin: 0;
  color: #6f6f6f;
  font-size: var(--ds-text-body-14);
  line-height: 1.5;
}

.chatgpt-reference-v1 .settings-section-note {
  margin: 15px -16px 0;
  padding: 0 12px 0 24px;
  color: rgb(93, 93, 93);
  font-size: var(--ds-text-body-14);
  line-height: 20px;
  letter-spacing: 0.01em;
}

.chatgpt-reference-v1 .settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  min-height: 60px;
  border-bottom: 1px solid #f0f0f0;
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  line-height: 20px;
}

.chatgpt-reference-v1 .settings-row-stacked {
  align-items: flex-start;
  padding: 12px 0;
}

.chatgpt-reference-v1 .settings-row-copy {
  display: grid;
  min-width: 0;
  max-width: 310px;
  gap: 4px;
}

.chatgpt-reference-v1 .settings-row-label {
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  line-height: 20px;
  letter-spacing: 0;
}

.chatgpt-reference-v1 .settings-row-description {
  color: rgb(93, 93, 93);
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  line-height: 20px;
  letter-spacing: 0;
}

.chatgpt-reference-v1 .settings-row-value {
  min-width: 0;
  max-width: 260px;
  overflow: hidden;
  color: rgb(93, 93, 93);
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  line-height: 20px;
  letter-spacing: 0;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .settings-pill,
.chatgpt-reference-v1 .settings-danger-pill,
.chatgpt-reference-v1 .settings-combobox {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 82px;
  height: 36px;
  padding: 0 12px;
  border: 1px solid rgba(0, 0, 0, 0);
  border-radius: 8px;
  background: #ffffff;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-14);
  line-height: 14px;
}

.chatgpt-reference-v1 .settings-choice-control {
  position: relative;
  display: inline-grid;
  justify-items: end;
}

.chatgpt-reference-v1 .settings-combobox:hover,
.chatgpt-reference-v1 .settings-combobox.is-open,
.chatgpt-reference-v1 .settings-pill:hover,
.chatgpt-reference-v1 .settings-danger-pill:hover {
  background: rgba(0, 0, 0, 0.04);
}

.chatgpt-reference-v1 .settings-danger-pill {
  color: #e02e2a;
}

.chatgpt-reference-v1 .settings-pill-disabled,
.chatgpt-reference-v1 .settings-pill-disabled:hover {
  background: transparent;
  color: var(--cg-muted, #6f6f6f);
  cursor: default;
  opacity: 0.72;
}

.chatgpt-reference-v1 .settings-combobox svg {
  width: 16px;
  height: 16px;
  stroke-width: var(--wds-icon-stroke, 1.7);
}

.chatgpt-reference-v1 .settings-choice-popover {
  position: absolute;
  top: calc(100% + 5px);
  right: 0;
  z-index: 85;
  display: grid;
  width: 220px;
  padding: 6px;
  border: 0;
  border-radius: 16px;
  background: var(--cg-menu-bg, #ffffff);
  color: var(--cg-text, #0d0d0d);
  box-shadow: var(--cg-menu-shadow, 0 8px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.62));
}

.chatgpt-reference-v1 .settings-choice-option {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 18px;
  align-items: center;
  width: 208px;
  min-height: 36px;
  padding: 6px 10px;
  border: 0;
  border-radius: var(--ds-radius-sm, 10px);
  background: transparent;
  color: var(--cg-text, #0d0d0d);
  font: inherit;
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  line-height: 20px;
  text-align: left;
  cursor: pointer;
}

.chatgpt-reference-v1 .settings-choice-option:hover,
.chatgpt-reference-v1 .settings-choice-option:focus-visible,
.chatgpt-reference-v1 .settings-choice-option.is-selected {
  background: rgba(0, 0, 0, 0.04);
  outline: none;
}
/* focus-reset-shell-ring-10: 전역 P0-3 폴백보다 특정성을 올린다(위 route-action 주석과 같은 이유). */
.chatgpt-reference-v1.chatgpt-reference-v1 .settings-choice-option:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

.chatgpt-reference-v1 .settings-choice-option span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .settings-choice-option svg {
  width: 16px;
  height: 16px;
  justify-self: end;
  stroke-width: var(--wds-icon-stroke, 1.7);
}

html[data-theme="dark"] .chatgpt-reference-v1 .settings-choice-option:hover,
html[data-theme="dark"] .chatgpt-reference-v1 .settings-choice-option:focus-visible,
html[data-theme="dark"] .chatgpt-reference-v1 .settings-choice-option.is-selected {
  background: rgba(255, 255, 255, 0.10);
}

.chatgpt-reference-v1 .settings-switch {
  position: relative;
  width: 32px;
  height: 20px;
  /* Never let the flex row shrink the track — in the stacked row (wide description text)
     on a narrow phone it was squeezed from 32px to ~25px, so toggles looked uneven. */
  flex: 0 0 auto;
  padding: 2px;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: #3a83f7;
  cursor: pointer;
}

.chatgpt-reference-v1 .settings-switch[aria-checked="false"] {
  background: rgba(0, 0, 0, 0.16);
}

html[data-theme="dark"] .chatgpt-reference-v1 .settings-switch[aria-checked="false"] {
  background: rgba(255, 255, 255, 0.24);
}

.chatgpt-reference-v1 .settings-switch::after {
  content: "";
  position: absolute;
  top: 3px;
  right: 3px;
  width: 14px;
  height: 14px;
  border-radius: var(--ds-radius-pill, 999px);
  background: #ffffff;
}

.chatgpt-reference-v1 .settings-switch[aria-checked="false"]::after {
  right: auto;
  left: 3px;
}

@media (max-width: 520px) {
  /* Full-screen sizing comes from the shared mobile-dialog rule
     ([data-mobile-fullscreen], see "20260708-mobile-dialog-fullscreen-system");
     only the settings-specific inner layout lives here. */
  .chatgpt-reference-v1 .chatgpt-settings-head {
    grid-template-columns: 52px minmax(0, 1fr);
  }

  .chatgpt-reference-v1 .chatgpt-settings-head .chatgpt-icon-button {
    margin-left: 9px;
  }

  .chatgpt-reference-v1 .chatgpt-settings-head h2 {
    padding-left: 0;
  }

  .chatgpt-reference-v1 .chatgpt-settings-body {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }

  .chatgpt-reference-v1 .chatgpt-settings-tabs {
    display: flex;
    gap: 4px;
    padding: 6px;
    overflow-x: auto;
    overflow-y: hidden;
    border-right: 0;
    border-bottom: 1px solid var(--cg-menu-border, #ececec);
    scrollbar-width: none;
  }

  .chatgpt-reference-v1 .chatgpt-settings-tabs::-webkit-scrollbar {
    display: none;
  }

  .chatgpt-reference-v1 .chatgpt-settings-tabs button {
    flex: 0 0 auto;
    width: auto;
    max-width: 180px;
  }

  .chatgpt-reference-v1 .chatgpt-settings-panel {
    min-width: 0;
    padding: 0 16px;
  }

  .chatgpt-reference-v1 .settings-row-copy {
    max-width: 100%;
  }
}

.chatgpt-reference-v1 .settings-shortcut-list {
  display: flex;
  flex-direction: column;
  gap: 32px;
  margin: 0 -4px 0 -4px;
  padding: 16px 0 24px;
}

.chatgpt-reference-v1 .settings-shortcut-group {
  display: grid;
  gap: 0;
}

.chatgpt-reference-v1 .settings-shortcut-group-title {
  margin: 0 0 4px;
  padding: 0 12px 0 20px;
  color: rgb(93, 93, 93);
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  line-height: 20px;
  letter-spacing: 0.01em;
}

.chatgpt-reference-v1 .settings-shortcut-row {
  display: flex;
  min-height: 53px;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px 10px 16px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--cg-text, #0d0d0d);
  font: inherit;
  cursor: pointer;
  text-align: left;
}

.chatgpt-reference-v1 .settings-shortcut-row:hover {
  background: rgba(0, 0, 0, 0.04);
  border-radius: 8px;
}

.chatgpt-reference-v1 .settings-shortcut-label {
  min-width: 0;
  overflow: hidden;
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-body-16);
  font-weight: 400;
  line-height: 24px;
  letter-spacing: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .settings-shortcut-keys {
  display: flex;
  flex: 0 0 150px;
  width: 150px;
  height: 36px;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  color: rgb(93, 93, 93);
}

.chatgpt-reference-v1 .settings-shortcut-keys span {
  display: inline-flex;
  min-width: 24px;
  height: 24px;
  align-items: center;
  justify-content: center;
  padding: 0 6px;
  border-radius: var(--ds-radius-pill, 999px);
  color: rgb(93, 93, 93);
  font-size: var(--ds-text-body-16);
  font-weight: 400;
  line-height: 24px;
}

.chatgpt-reference-v1 .brand-row {
  min-height: 42px;
  padding: 0 4px 12px;
  gap: 0;
}
.chatgpt-reference-v1 .brand-row strong {
  color: var(--cg-text);
  font-size: var(--ds-text-body-16);
  font-weight: 500;
}

.chatgpt-reference-v1 .sidebar .brand-row span,
.chatgpt-reference-v1 .nav-section .section-label {
  display: none;
}

.chatgpt-reference-v1 .sidebar-section {
  position: relative;
  gap: 2px;
  margin: 0;
}

.chatgpt-reference-v1 .sidebar-section.grow {
  flex: 0 1 auto;
  display: flex;
  flex-direction: column;
  gap: 0;
  min-height: 0;
  max-height: calc(100vh - 210px);
}

.chatgpt-reference-v1 .new-chat-button,
.chatgpt-reference-v1 .theme-toggle {
  justify-content: flex-start;
  min-height: 40px;
  padding: 0 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--cg-text);
  box-shadow: none;
  font-size: var(--ds-text-body-14);
  font-weight: 400;
}

.chatgpt-reference-v1 .new-chat-button:hover,
.chatgpt-reference-v1 .theme-toggle:hover {
  background: #ececec;
}

.chatgpt-reference-v1 .nav-item {
  grid-template-columns: minmax(0, 1fr);
  min-height: 40px;
  padding: 0 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .nav-item:hover,
.chatgpt-reference-v1 .nav-item.active {
  background: #ececec;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .nav-item span:not(.nav-icon),
.chatgpt-reference-v1 .nav-item.active span:not(.nav-icon) {
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  font-weight: 400;
}

.chatgpt-reference-v1 .sidebar .nav-icon {
  display: none;
}

.chatgpt-reference-v1 .sidebar .section-label {
  color: #6f6f6f;
  font-size: var(--ds-text-caption-12);
  font-weight: 400;
}

.chatgpt-reference-v1 .chatgpt-recent-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  min-height: 32px;
  /* 20260704-sidebar-recent-header-chatgpt: Chrome-measured ChatGPT recent header uses a 32px row with text starting at x=16. */
  padding: 0 2px 0 10px;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-recent-title {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  gap: 2px;
  min-width: 0;
  height: 32px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  line-height: 20px;
}

.chatgpt-reference-v1 .chatgpt-recent-title svg {
  width: 14px;
  height: 14px;
  color: #8a8a8a;
}

.chatgpt-reference-v1 .chatgpt-recent-header-actions {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  opacity: 0;
  pointer-events: auto;
  transition: opacity 100ms ease;
}

.chatgpt-reference-v1 .chatgpt-recent-header:focus-within .chatgpt-recent-header-actions,
.chatgpt-reference-v1 .chatgpt-recent-header:has([data-recent-header-action="more"][aria-expanded="true"]) .chatgpt-recent-header-actions {
  opacity: 1;
  pointer-events: auto;
}

/* Reveal-on-hover only on real hover devices. On iOS the section :hover fired on the FIRST
   tap of a recent row (the row lives inside .sidebar-section), showing the header ✏️/⋯ and
   swallowing that tap — so the conversation only opened on the second tap. Guarded, the
   first tap now opens the conversation directly. (:focus-within / aria-expanded above stay
   unguarded so keyboard + the open menu still reveal the actions.) */
@media (hover: hover) {
  .chatgpt-reference-v1 .sidebar-section:hover .chatgpt-recent-header-actions {
    opacity: 1;
    pointer-events: auto;
  }
}

.chatgpt-reference-v1 .chatgpt-recent-header-action {
  display: inline-grid;
  place-items: center;
  width: calc(var(--wizon-ai-space-unit) * 7);
  height: calc(var(--wizon-ai-space-unit) * 7);
  border: 0;
  border-radius: var(--wizon-ai-radius-control);
  background: transparent;
  color: var(--cg-muted-2);
}

.chatgpt-reference-v1 .chatgpt-recent-header-action:hover {
  background: var(--cg-hover);
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-recent-header-action:focus-visible {
  outline: 1px solid var(--cg-focus-visible-ring);
  outline-offset: -1px;
  background: var(--cg-focus-bg);
}

.chatgpt-reference-v1 .chatgpt-recent-header-action svg {
  width: 18px;
  height: 18px;
}

.chatgpt-reference-v1 .recent-list {
  /* 20260705-recent-row-token-contract: keep measured ChatGPT row geometry, but resolve every value through AI shell/WDS tokens. */
  --chatgpt-recent-row-height: var(--cg-sidebar-item-height);
  --chatgpt-recent-row-inner-height: calc(var(--chatgpt-recent-row-height) - (var(--wizon-ai-space-unit) / 2));
  --chatgpt-recent-row-radius: var(--wizon-ai-radius-control);
  --chatgpt-recent-row-pad-block: calc(var(--wizon-ai-space-unit) / 4);
  --chatgpt-recent-row-pad-right: var(--wizon-ai-space-unit);
  /* 20260911-recent-label-x-unify (C1): 최근 행엔 아이콘 열이 없어 라벨 x 가 16(=행x6+
     이 값 10)으로, 주메뉴·보관함의 라벨 x 52(=행x6+패딩10+아이콘24+간격12)와 어긋났다.
     11.5 유닛(46px)으로 늘려 같은 46px 오프셋을 재현해 라벨 x 를 1값(52)으로 합친다. */
  --chatgpt-recent-title-pad-left: calc(var(--wizon-ai-space-unit) * 11.5);
  --chatgpt-recent-action-size: calc(var(--wizon-ai-space-unit) * 7);
  /* 20260911-recent-action-overflow (C18): 2px 오프셋이 행의 padding-right(4px) 보다
     안쪽으로 충분히 들어가지 않아 액션 그룹 우측이 행 우측보다 2px 밖으로 넘쳤다
     (행 249 vs 액션 251, 실측). 행 패딩과 같은 4px 로 늘려 액션을 행 안에 넣는다. */
  --chatgpt-recent-action-offset: var(--wizon-ai-space-unit);
  --chatgpt-recent-action-fade: var(--ds-space-6, 24px);
  --chatgpt-recent-title-pad-right: calc((var(--chatgpt-recent-action-size) * 2) + var(--chatgpt-recent-action-fade) + var(--wizon-ai-space-unit));
  --chatgpt-recent-font-size: var(--wds-font-size-body-2);
  --chatgpt-recent-line-height: calc(var(--wizon-ai-space-unit) * 5);
  --chatgpt-recent-rename-pad-x: calc(var(--wizon-ai-space-unit) * 1.5);
  --chatgpt-recent-rename-radius: calc(var(--chatgpt-recent-row-radius) - (var(--wizon-ai-space-unit) / 2));
  --chatgpt-recent-select-indent: calc(var(--wizon-ai-space-unit) * 8.5);
  display: block;
  gap: calc(var(--wizon-ai-space-unit) / 2);
  flex: 0 1 auto;
  overflow-x: hidden;
  overflow-y: auto;
}

body.chatgpt-reference-v1 .citation-impact-queue-entry {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto 16px;
  align-items: center;
  gap: 8px;
  width: calc(100% - 8px);
  margin: 2px 4px 8px;
  padding: 9px 10px;
  border: 1px solid color-mix(in srgb, var(--cg-text) 12%, transparent);
  border-radius: var(--wizon-ai-radius-control);
  background: color-mix(in srgb, var(--ad-amber) 8%, var(--cg-sidebar-bg));
  color: var(--cg-text);
  text-align: left;
  cursor: pointer;
}

body.chatgpt-reference-v1 .citation-impact-queue-entry[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .citation-impact-queue-entry:focus-visible {
  outline: 2px solid var(--cg-text);
  outline-offset: 2px;
}

@media (hover: hover) {
  body.chatgpt-reference-v1 .citation-impact-queue-entry:hover {
    background: color-mix(in srgb, var(--ad-amber) 13%, var(--cg-sidebar-bg));
  }
}

.citation-impact-queue-entry__icon,
.citation-impact-queue-entry__icon svg,
.citation-impact-queue-entry > [data-icon="chevron-right"],
.citation-impact-queue-entry > [data-icon="chevron-right"] svg {
  width: 18px;
  height: 18px;
}

.citation-impact-queue-entry__copy {
  min-width: 0;
  display: grid;
  gap: 1px;
}

.citation-impact-queue-entry__copy strong,
.citation-impact-queue-entry__copy small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.citation-impact-queue-entry__copy strong {
  font-size: var(--ds-text-caption-13);
  line-height: 18px;
}

.citation-impact-queue-entry__copy small {
  color: var(--cg-muted);
  font-size: var(--ds-text-micro-11);
  line-height: 16px;
}

.citation-impact-queue-entry__badge {
  display: inline-grid;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  place-items: center;
  border-radius: var(--ds-radius-pill);
  background: var(--ad-amber);
  color: var(--cg-send-fg);
  font-size: var(--ds-text-micro-11);
  font-weight: var(--wds-font-weight-semibold);
}

.citation-impact-queue-entry[data-status="error"] {
  background: color-mix(in srgb, var(--cg-danger-fg) 8%, var(--cg-sidebar-bg));
}

.chatgpt-reference-v1 .recent-item {
  position: relative;
  display: block;
  width: 100%;
  overflow: hidden;
  min-height: var(--chatgpt-recent-row-height);
  padding: var(--chatgpt-recent-row-pad-block) var(--chatgpt-recent-row-pad-right) var(--chatgpt-recent-row-pad-block) 0;
  border: 0;
  border-radius: var(--chatgpt-recent-row-radius);
  color: var(--cg-text);
  font-size: var(--chatgpt-recent-font-size);
}

/* Guard the hover background behind @media (hover: hover): on iOS Safari a :hover rule on
   a tappable row makes the FIRST tap a hover-only gesture (the click is swallowed, so the
   row highlights but does not open — a second tap is needed). Touch devices must not have
   :hover styling on the recent rows (20260709-mobile-recent-hover-firsttap, doc 143). */
@media (hover: hover) {
  .chatgpt-reference-v1 .recent-item:hover {
    background: var(--cg-hover);
  }
}

.chatgpt-reference-v1 .recent-item:has([data-recent-action="more"][aria-expanded="true"]) {
  background: var(--cg-sidebar-active-bg);
}

.chatgpt-reference-v1 .recent-primary {
  display: grid;
  width: 100%;
  min-height: var(--chatgpt-recent-row-inner-height);
  padding: 0 var(--chatgpt-recent-title-pad-right) 0 var(--chatgpt-recent-title-pad-left);
  border-radius: var(--chatgpt-recent-row-radius);
  color: var(--cg-text);
  font-size: var(--chatgpt-recent-font-size);
  font-weight: 400;
  line-height: var(--chatgpt-recent-line-height);
  /* Removes the 300ms tap delay and the extra hover-tap on touch, so a single tap opens
     the chat (iOS double-tap mitigation; recommended alongside the @media hover guards). */
  touch-action: manipulation;
}

.chatgpt-reference-v1 .recent-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .recent-primary small {
  display: none;
}

.chatgpt-reference-v1 .recent-rename-form {
  display: grid;
  align-items: center;
  width: 100%;
  min-height: var(--chatgpt-recent-row-inner-height);
  padding: 0 calc(var(--wizon-ai-space-unit) * 2);
}

.chatgpt-reference-v1 .recent-rename-input {
  width: 100%;
  height: var(--chatgpt-recent-action-size);
  min-width: 0;
  padding: 0 var(--chatgpt-recent-rename-pad-x);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-recent-rename-radius);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--chatgpt-recent-font-size);
  font-weight: 400;
  line-height: var(--chatgpt-recent-line-height);
  outline: none;
}

.chatgpt-reference-v1 .recent-rename-input:focus,
.chatgpt-reference-v1 .recent-rename-input:focus-visible {
  border-color: var(--cg-muted-2);
  box-shadow: none;
  outline: none;
}

html[data-theme="dark"] .chatgpt-reference-v1 .recent-rename-input {
  border-color: var(--cg-menu-border);
  background: var(--cg-hover);
  color: var(--cg-text);
}

html[data-theme="dark"] .chatgpt-reference-v1 .recent-rename-input:focus,
html[data-theme="dark"] .chatgpt-reference-v1 .recent-rename-input:focus-visible {
  border-color: var(--cg-muted-2);
}

.chatgpt-reference-v1 .recent-actions {
  display: inline-flex;
  position: absolute;
  top: 50%;
  right: var(--chatgpt-recent-action-offset);
  transform: translateY(-50%);
  gap: 0;
  padding-left: var(--chatgpt-recent-action-fade);
  padding-right: 0;
  background: linear-gradient(90deg, color-mix(in srgb, var(--cg-hover) 0%, transparent), var(--cg-hover) var(--chatgpt-recent-action-fade));
}

.chatgpt-reference-v1 .recent-item:has([data-recent-action="more"][aria-expanded="true"]) .recent-actions {
  opacity: 1;
  pointer-events: auto;
}

/* Reveal the row actions on focus (tap) or when the row's ⋯ menu is open. The :hover
   reveal is gated behind @media (hover: hover) on purpose: on iOS Safari, tapping an
   element whose :hover reveals content makes the FIRST tap a hover-only gesture (the
   click is swallowed, so the row highlights but does not open — a second tap is needed).
   Touch devices therefore must not depend on :hover for this reveal (doc 143 item C fix,
   20260709-mobile-recent-hover-firsttap). */
.chatgpt-reference-v1 .recent-item:focus-within .recent-actions {
  opacity: 1;
  pointer-events: auto;
}

@media (hover: hover) {
  .chatgpt-reference-v1 .recent-item:hover .recent-actions {
    opacity: 1;
    pointer-events: auto;
  }
}

.chatgpt-reference-v1 .recent-action {
  width: var(--chatgpt-recent-action-size);
  height: var(--chatgpt-recent-action-size);
  color: var(--cg-muted-2);
}

.chatgpt-reference-v1 .recent-item:focus-within .recent-action,
.chatgpt-reference-v1 .recent-item:has([data-recent-action="more"][aria-expanded="true"]) .recent-action {
  pointer-events: auto;
}

@media (hover: hover) {
  .chatgpt-reference-v1 .recent-item:hover .recent-action {
    pointer-events: auto;
  }
}

.chatgpt-reference-v1 .recent-action:hover {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .recent-pin-action.is-pinned {
  color: var(--cg-text);
}

.chatgpt-reference-v1 .recent-action svg {
  width: 17px;
  height: 17px;
}

.chatgpt-reference-v1 .recent-item.is-selecting .recent-primary {
  padding-left: var(--chatgpt-recent-select-indent);
}

.chatgpt-reference-v1 .recent-item.is-selected {
  background: var(--cg-hover);
}

.chatgpt-reference-v1 .recent-select-control {
  position: absolute;
  top: 50%;
  left: 6px;
  z-index: 2;
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  transform: translateY(-50%);
  cursor: pointer;
}

.chatgpt-reference-v1 .recent-select-control input {
  position: absolute;
  inset: 0;
  opacity: 0;
  z-index: 1;
  pointer-events: none;
  cursor: pointer;
}

.chatgpt-reference-v1 .recent-select-box {
  display: inline-grid;
  place-items: center;
  width: 17px;
  height: 17px;
  border: 1.5px solid var(--cg-muted-2);
  border-radius: 5px;
  background: var(--cg-surface);
  color: var(--cg-text);
  pointer-events: none;
}

.chatgpt-reference-v1 .recent-select-control.is-focused .recent-select-box,
.chatgpt-reference-v1 .recent-select-control input:focus + .recent-select-box,
.chatgpt-reference-v1 .recent-select-control input:focus-visible + .recent-select-box {
  outline: 1px solid var(--cg-focus-visible-ring);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--cg-focus-bg);
}

.chatgpt-reference-v1 .recent-select-control input:checked + .recent-select-box {
  border-color: var(--cg-send-bg);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}

.chatgpt-reference-v1 .recent-select-box svg {
  width: 13px;
  height: 13px;
  stroke-width: 2.4;
}

.chatgpt-reference-v1 .chatgpt-recent-menu {
  --chatgpt-recent-menu-width: calc(var(--wizon-ai-space-unit) * 46.5);
  --chatgpt-recent-menu-gutter: var(--cg-menu-gutter);
  --chatgpt-recent-menu-radius: var(--wizon-ai-radius-popover);
  --chatgpt-recent-menu-row-gap: var(--cg-menu-gutter);
  --chatgpt-recent-menu-row-height: var(--wizon-ai-menu-row-height);
  --chatgpt-recent-menu-row-radius: var(--wizon-ai-radius-menu-row);
  --chatgpt-recent-menu-row-pad-y: var(--cg-menu-gutter);
  --chatgpt-recent-menu-row-pad-left: calc(var(--wizon-ai-space-unit) * 2.5);
  --chatgpt-recent-menu-row-pad-right: var(--ds-space-7, 32px);
  --chatgpt-recent-menu-row-font-size: var(--wds-font-size-body-2);
  --chatgpt-recent-menu-row-line-height: calc(var(--wizon-ai-space-unit) * 5);
  --chatgpt-recent-menu-icon-size: calc(var(--wizon-ai-space-unit) * 4.5);
  --chatgpt-recent-menu-fallback-left: calc(100% - (var(--chatgpt-recent-menu-width) + var(--wizon-ai-space-unit)));
  position: absolute;
  top: var(--recent-menu-top, var(--cg-sidebar-item-height));
  left: var(--recent-menu-left, var(--chatgpt-recent-menu-fallback-left));
  right: auto;
  z-index: var(--wizon-ai-popover-z, 20);
  display: grid;
  width: var(--chatgpt-recent-menu-width);
  max-height: var(--recent-menu-max-height, min(calc(var(--wizon-ai-space-unit) * 90), calc(100vh - var(--ds-space-6, 24px))));
  overflow-y: auto;
  padding: var(--chatgpt-recent-menu-gutter) 0;
  border: 0;
  border-radius: var(--chatgpt-recent-menu-radius);
  background: var(--cg-menu-bg);
  box-shadow: var(--cg-menu-shadow);
}

.chatgpt-reference-v1 .chatgpt-recent-menu[hidden] {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-recent-menu button {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-recent-menu-row-gap);
  width: calc(var(--chatgpt-recent-menu-width) - (var(--chatgpt-recent-menu-gutter) * 2));
  min-height: var(--chatgpt-recent-menu-row-height);
  margin: 0 var(--chatgpt-recent-menu-gutter);
  padding: var(--chatgpt-recent-menu-row-pad-y) var(--chatgpt-recent-menu-row-pad-right) var(--chatgpt-recent-menu-row-pad-y) var(--chatgpt-recent-menu-row-pad-left);
  border: 0;
  border-radius: var(--chatgpt-recent-menu-row-radius);
  background: transparent;
  color: var(--cg-text);
  font-size: var(--chatgpt-recent-menu-row-font-size);
  font-weight: 400;
  line-height: var(--chatgpt-recent-menu-row-line-height);
  text-align: left;
}

.chatgpt-reference-v1 .chatgpt-recent-menu button[hidden] {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-recent-menu button:hover:not(:disabled) {
  background: var(--cg-hover);
}

.chatgpt-reference-v1 .chatgpt-recent-menu button:disabled {
  color: var(--cg-muted-2);
  cursor: default;
}

.chatgpt-reference-v1 .chatgpt-recent-menu [data-recent-menu-action^="delete"] {
  color: var(--cg-danger-fg);
}

.chatgpt-reference-v1 .chatgpt-recent-menu svg {
  width: var(--chatgpt-recent-menu-icon-size);
  height: var(--chatgpt-recent-menu-icon-size);
}

.chatgpt-reference-v1 .main-pane,
.chatgpt-reference-v1 .workbench,
.chatgpt-reference-v1 .chat-pane {
  background: #ffffff;
}

.chatgpt-reference-v1 .workspace-hero,
.chatgpt-reference-v1 .context-pane {
  display: none;
}

.chatgpt-reference-v1 .workbench {
  grid-template-columns: minmax(0, 1fr);
}

.chatgpt-reference-v1 .chat-pane {
  grid-template-rows: minmax(0, 1fr) auto;
  min-height: 100vh;
}

.chatgpt-reference-v1 .message-list {
  align-items: stretch;
  width: min(768px, calc(100% - 48px));
  width: min(calc(var(--cg-chat-max-width) - (var(--cg-thread-gutter) * 2)), calc(100% - (var(--cg-thread-gutter) * 3)));
  margin: 0 auto;
  padding: 32px 0 24px;
}

.chatgpt-reference-v1 .message {
  width: 100%;
}

.chatgpt-reference-v1 .message.assistant .message-body,
.chatgpt-reference-v1 .message.system .message-body {
  padding: 10px 0;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .message.user .message-body {
  max-width: min(620px, 78%);
  padding: 10px 16px;
  border: 0;
  border-radius: var(--ds-radius-xl, 22px);
  background: #f4f4f4;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-empty-home {
  display: grid;
  gap: 0;
  width: 100%;
  text-align: center;
}

.chatgpt-reference-v1 .chatgpt-home-title {
  display: inline-flex;
  align-items: baseline;
  justify-content: normal;
  justify-self: center;
  width: auto;
  min-height: 42px;
  margin: 0;
  color: var(--cg-text);
  font-size: 24px;
  /* ⓐ 홈 제목이 본문과 같은 굵기(400)라 위계가 없었다. 제목·칩3·컴포저가 함께 있어
     요소는 더 많은데 위계는 더 약한 상태였다(2026-08-02 실측). 크기는 24px 유지 —
     굵기만으로 초점을 세운다. */
  font-weight: var(--wds-font-weight-semibold);
  line-height: 28px;
}

.chatgpt-reference-v1 .chatgpt-prompt-shelf {
  justify-content: center;
  gap: 8px;
  margin-top: 0;
}

.chatgpt-reference-v1 .prompt-chip {
  min-height: 40px;
  padding: 0 15px;
  border: 0 solid rgba(0, 0, 0, 0.05);
  border-radius: var(--ds-radius-pill, 999px);
  background: #ffffff;
  color: #3f3f3f;
  font-size: var(--ds-text-body-14);
  font-weight: 400;
}

.chatgpt-reference-v1 .prompt-chip:hover {
  border-color: #d9d9d9;
  background: #f7f7f7;
}

/* login-r33-post-login-stage1 §1-6(Qⓐ, 판정 mockup-post-login/index.html 값):
   채팅 홈 인라인 프로필 완성 카드. 행 해부 1종(28 타일·12·이름 15/600+메타 13·12·>) +
   오른쪽 44 × 버튼. 카드 = --wizon-ai-surface + 1px 선 + r14(3차 문법 재사용,
   .settings-v2-nudge-card 와 같은 표면·선 토큰). */
.chatgpt-reference-v1 .profile-home-nudge {
  display: grid;
  grid-template-columns: 1fr 44px;
  align-items: stretch;
  /* 발주(§1-6): 데스크톱 같은 열 max 720 — 칩 셸(768)보다 좁다, 카드 성격상 의도적. */
  width: min(720px, calc(100% - 48px));
  margin: 20px auto 0;
  background: var(--wizon-ai-surface);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wds-radius-lg);
  overflow: hidden;
  /* §1-6 「등장 = effect 188 디졸브」 — 기존 --wds-spring-effect-default-duration
     토큰 재사용(§1-4 패스키 성공 디졸브와 같은 값). */
  animation: wizon-profile-home-nudge-in var(--wds-spring-effect-default-duration, 188ms) ease-out both;
}

.chatgpt-reference-v1 .profile-home-nudge[hidden] {
  display: none;
}

@keyframes wizon-profile-home-nudge-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .chatgpt-reference-v1 .profile-home-nudge {
    animation-duration: 1ms;
  }
}

.chatgpt-reference-v1 .profile-home-nudge-row {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 0 12px;
  border: 0;
  background: transparent;
  color: var(--cg-text);
  text-align: left;
  cursor: pointer;
}

.chatgpt-reference-v1 .profile-home-nudge-row:hover {
  background: color-mix(in srgb, var(--wds-color-wizon-accent) 6%, var(--wizon-ai-surface));
}

.chatgpt-reference-v1 .profile-home-nudge-tile {
  width: 28px;
  height: 28px;
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-popover-border);
  display: grid;
  place-items: center;
  color: var(--cg-text);
  flex: none;
}

.chatgpt-reference-v1 .profile-home-nudge-tile svg {
  width: 18px;
  height: 18px;
}

.chatgpt-reference-v1 .profile-home-nudge-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.chatgpt-reference-v1 .profile-home-nudge-name {
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  line-height: 20px;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .profile-home-nudge-meta {
  font-size: var(--wizon-ai-font-size-label);
  line-height: 18px;
  color: var(--cg-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .profile-home-nudge-chev {
  width: 20px;
  height: 20px;
  color: var(--cg-text);
  flex: none;
}

.chatgpt-reference-v1 .profile-home-nudge-close {
  width: 44px;
  border: 0;
  border-left: 1px solid var(--wizon-ai-popover-border);
  background: transparent;
  color: var(--cg-muted);
  display: grid;
  place-items: center;
  cursor: pointer;
}

.chatgpt-reference-v1 .profile-home-nudge-close:hover {
  background: color-mix(in srgb, var(--wds-color-wizon-accent) 6%, var(--wizon-ai-surface));
}

.chatgpt-reference-v1 .profile-home-nudge-row:focus-visible,
.chatgpt-reference-v1 .profile-home-nudge-close:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: -2px;
}

.chatgpt-reference-v1 .chatgpt-composer-shell,
.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell {
  display: grid;
  width: min(768px, calc(100% - 48px));
  width: min(calc(var(--cg-chat-max-width) - (var(--cg-thread-gutter) * 2)), calc(100% - (var(--cg-thread-gutter) * 3)));
  min-height: 52px;
  margin: 0 auto 28px;
  padding: 5px 8px;
  gap: 0;
  grid-template-rows: auto auto;
  border: 0 solid rgba(0, 0, 0, 0.05);
  border-radius: var(--ds-radius-2xl, 28px);
  border-radius: calc(var(--cg-composer-radius) + (var(--cg-composer-compact-gutter) / 2));
  background: #ffffff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04), 0 2px 8px rgba(0, 0, 0, 0.04), 0 4px 80px 8px rgba(0, 0, 0, 0.024);
}

.chatgpt-reference-v1 .composer-attachment-lane {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 0 4px 6px 44px;
}

.chatgpt-reference-v1 .composer-attachment-lane[hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-attachment-status {
  min-width: 0;
  padding: 2px 8px 5px 44px;
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-caption-12);
  line-height: 16px;
}

.chatgpt-reference-v1 .composer-attachment-status[hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-attachment-status[data-tone="success"] {
  color: var(--ad-green);
}

.chatgpt-reference-v1 .composer-attachment-status[data-tone="error"] {
  color: var(--cg-danger-fg);
}

.chatgpt-reference-v1 .composer-attachment-status[data-tone="warning"] {
  color: var(--cg-warning-fg);
}

/* R29-P3a 항목2: 붙여넣기 접기/되돌리기 4초 토스트. R29-P3a-2: 판정 §B-3 규칙표 «토스트 = 44 ·
   입력창 위 12 · 가운데 · 999 · 잉크 면 · 13»(Gmail·Slack Undo 관행)으로 옷을 바꿨다 — 옛 연회색
   줄(반경 6·12px)은 레일 칩과 같은 층이라 «알림» 으로 읽히지 않았다. 잉크 면 = --cg-text,
   글자 = --cg-surface 라 다크에서는 밝은 알약에 어두운 글자로 저절로 뒤집힌다. */
.chatgpt-reference-v1 .composer-paste-toast {
  display: flex;
  align-items: center;
  gap: 4px;
  width: fit-content;
  max-width: calc(100% - 32px);
  min-height: 44px;
  margin: 0 auto 12px;
  padding: 0 4px 0 16px;
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--cg-text);
  color: var(--cg-surface);
  font-size: var(--ds-text-caption-13);
}

.chatgpt-reference-v1 .composer-paste-toast[hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-paste-toast-action {
  flex: 0 0 auto;
  min-width: 44px;
  min-height: 44px;
  border: none;
  border-radius: var(--wizon-ai-radius-pill);
  padding: 0 12px;
  background: transparent;
  color: var(--cg-surface);
  font: inherit;
  font-weight: var(--ds-weight-semibold);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.chatgpt-reference-v1 .composer-paste-toast-action[hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-paste-toast-action:hover {
  background: color-mix(in srgb, var(--cg-surface) 14%, transparent);
}

.chatgpt-reference-v1 .composer-paste-toast-action:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

.chatgpt-reference-v1 .composer-chip-removable {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) 26px;
  max-width: min(420px, 100%);
  align-items: center;
  gap: 8px;
  min-height: 48px;
  padding: 5px 5px 5px 7px;
  border: 1px solid #e7e7e7;
  border-radius: 13px;
  background: #f7f7f7;
  color: #2f2f2f;
  font: inherit;
  font-size: var(--ds-text-caption-13);
  line-height: 16px;
  cursor: default;
}

.chatgpt-reference-v1 .composer-attachment-file-icon {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  overflow: hidden;
  background: var(--cg-surface);
  color: var(--ad-blue, var(--wds-brand-blue, #155eef));
  box-shadow: inset 0 0 0 1px var(--cg-menu-border);
}

.chatgpt-reference-v1 .composer-attachment-file-icon svg {
  width: 19px;
  height: 19px;
}

.chatgpt-reference-v1 .composer-attachment-art {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  user-select: none;
}

.chatgpt-reference-v1 .composer-attachment-copy {
  display: grid;
  min-width: 0;
  gap: 1px;
}

.chatgpt-reference-v1 .composer-attachment-type {
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-caption-12);
  line-height: 14px;
}

.chatgpt-reference-v1 .composer-chip-label {
  overflow: hidden;
  min-width: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .composer-chip-remove {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: transparent;
  color: #6f6f6f;
  cursor: pointer;
}

.chatgpt-reference-v1 .composer-chip-remove svg {
  width: 14px;
  height: 14px;
}

.chatgpt-reference-v1 .composer-chip-removable:hover {
  border-color: #d9d9d9;
  background: #efefef;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .composer-chip-remove:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .composer-chip-remove:focus-visible {
  outline: 1px solid var(--cg-focus-visible-ring, var(--cg-focus-ring, #8b5cf6));
  outline-offset: 2px;
}

html[data-theme="dark"] .chatgpt-reference-v1 .composer-chip-removable {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.08);
  color: var(--cg-text, #ececec);
}

html[data-theme="dark"] .chatgpt-reference-v1 .composer-chip-remove {
  color: var(--cg-muted, #b4b4b4);
}

html[data-theme="dark"] .chatgpt-reference-v1 .composer-chip-removable:hover {
  border-color: rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.12);
  color: var(--cg-text, #f4f4f4);
}

html[data-theme="dark"] .chatgpt-reference-v1 .composer-chip-remove:hover {
  background: rgba(255, 255, 255, 0.12);
  color: var(--cg-text, #f4f4f4);
}

.composer-drop-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--wizon-layer-scrim, 190);
  display: grid;
  place-items: center;
  padding: 32px;
  pointer-events: none;
  background: color-mix(in srgb, var(--wizon-ai-surface, #fff) 82%, transparent);
  backdrop-filter: blur(4px);
}

.composer-drop-overlay[hidden] {
  display: none;
}

.composer-drop-overlay-panel {
  display: grid;
  place-items: center;
  width: min(560px, calc(100vw - 48px));
  min-height: 240px;
  padding: 32px;
  border: 2px dashed var(--ad-blue, var(--wds-brand-blue, #155eef));
  border-radius: var(--ds-radius-lg, 24px);
  background: var(--wizon-ai-surface, #fff);
  color: var(--cg-text, #2f2f2f);
  box-shadow: var(--ds-shadow-lg, 0 24px 64px rgba(15, 23, 42, 0.16));
  text-align: center;
}

.composer-drop-overlay-panel strong {
  margin-top: 16px;
  font-size: var(--ds-text-title-20, 20px);
  line-height: 28px;
}

.composer-drop-overlay-panel > span:last-child {
  margin-top: 6px;
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-body-14);
}

.composer-drop-overlay-icon {
  display: inline-grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 16px;
  background: var(--ad-blue-tint);
  color: var(--ad-blue, var(--wds-brand-blue, #155eef));
}

.composer-drop-overlay-icon svg {
  width: 26px;
  height: 26px;
}

@media (max-width: 520px) {
  .composer-drop-overlay {
    padding: 20px;
  }

  .composer-drop-overlay-panel {
    width: 100%;
    min-height: 200px;
    padding: 24px;
  }

  .chatgpt-reference-v1 .composer-chip-removable {
    max-width: 100%;
  }
}

.chatgpt-reference-v1 .composer-prompt-body {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) auto;
  grid-template-rows: minmax(42px, auto);
  grid-template-areas: "lead input actions";
  align-items: center;
  gap: 0 8px;
}

.chatgpt-reference-v1 .composer-leading-action {
  grid-area: lead;
}

.chatgpt-reference-v1 .composer-textarea-scroll {
  grid-area: input;
  display: block;
  min-width: 0;
  max-height: 256px;
  overflow-x: hidden;
  overflow-y: hidden;
  scrollbar-width: thin;
}

.chatgpt-reference-v1 .chatgpt-composer-shell:not(.is-multiline) .composer-prompt-body {
  min-height: 42px;
  margin-block: 0;
}

.chatgpt-reference-v1 .chatgpt-composer-shell:not(.is-multiline) .composer-textarea-scroll,
.chatgpt-reference-v1 .chatgpt-composer-shell:not(.is-multiline) textarea {
  min-height: 26px;
  height: 26px;
}

.chatgpt-reference-v1 .chatgpt-composer-shell:not(.is-multiline) .composer-textarea-scroll {
  align-self: center;
  display: flex;
  align-items: center;
}

.chatgpt-reference-v1 .chatgpt-composer-shell:not(.is-multiline) textarea {
  align-self: center;
}

.chatgpt-reference-v1 .composer-surface textarea {
  display: block;
  flex: 0 0 auto;
  width: 100%;
  min-height: 42px;
  max-height: 256px;
  box-sizing: border-box;
  padding: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-body-16);
  line-height: 26px;
  overflow: hidden;
  resize: none;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline textarea {
  padding: 16px 0 16px;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline,
.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell.is-multiline {
  min-height: min(300px, calc(var(--composer-visible-input-height, 42px) + 44px));
  max-height: 300px;
  padding: 9px 8px;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline .composer-prompt-body {
  grid-template-rows: minmax(0, auto) 36px;
  grid-template-areas:
    "input input input"
    "lead spacer actions";
  align-items: end;
  margin: -10px 0 0;
  padding: 0 10px;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline .composer-textarea-scroll {
  max-height: 256px;
  overflow-y: hidden;
}

/* 20260704-composer-empty-placeholder-guard: keep an empty placeholder in the one-line ChatGPT layout even if a stale multiline state is present. */
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty,
.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell.is-multiline.is-empty,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown),
.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown) {
  min-height: 52px;
  max-height: 52px;
  padding: 5px 8px;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty .composer-prompt-body,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown) .composer-prompt-body {
  grid-template-columns: 36px minmax(0, 1fr) auto;
  grid-template-rows: minmax(42px, auto);
  grid-template-areas: "lead input actions";
  align-items: center;
  min-height: 42px;
  padding: 0;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty .composer-textarea-scroll,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty textarea,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown) .composer-textarea-scroll,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown) textarea {
  min-height: 26px !important;
  height: 26px !important;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty .composer-textarea-scroll,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown) .composer-textarea-scroll {
  align-self: center;
  display: flex;
  align-items: center;
  overflow-y: hidden;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty textarea,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown) textarea {
  align-self: center;
  padding: 0;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty .composer-leading-action,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty .composer-action-dock,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown) .composer-leading-action,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown) .composer-action-dock {
  align-self: center;
}

/* 20260704-composer-empty-inline-height-guard: an empty ChatGPT composer must snap back to the one-line optical center even if a stale inline textarea height remains. */
.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) {
  min-height: 52px;
  max-height: 52px;
  padding: 5px 8px;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body,
.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body {
  grid-template-columns: 36px minmax(0, 1fr) auto;
  grid-template-rows: minmax(42px, auto);
  grid-template-areas: "lead input actions";
  align-items: center;
  min-height: 42px;
  padding: 0;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-textarea-scroll,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) textarea,
.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-textarea-scroll,
.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) textarea {
  min-height: 26px !important;
  height: 26px !important;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-textarea-scroll,
.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-textarea-scroll {
  align-self: center;
  display: flex;
  align-items: center;
  overflow-y: hidden;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) textarea,
.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) textarea,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-leading-action,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-action-dock,
.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-leading-action,
.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-action-dock {
  align-self: center;
}

body.chatgpt-reference-v1 .composer-textarea-scroll:focus-within {
  outline: none;
  box-shadow: none;
}

body.chatgpt-reference-v1 .composer-textarea-scroll.is-scrollable textarea {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline .composer-leading-action,
.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline .composer-action-dock {
  align-self: end;
}

.chatgpt-reference-v1 .composer-surface textarea::placeholder {
  color: #8a8a8a;
}

.chatgpt-reference-v1 .composer-icon-button {
  display: inline-grid;
  place-items: center;
  width: 36px;
  height: 36px;
  min-width: 36px;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: transparent;
  color: var(--cg-text);
  font-size: 24px;
  font-weight: 300;
}

.chatgpt-reference-v1 .composer-icon-button svg,
.chatgpt-reference-v1 .wds-inline-icon {
  /* 20260710-icon-size-tokens (아트디렉션 P0-4): 기본 아이콘 크기를 WDS 토큰으로 (md=1.25rem=20px 동일값). */
  width: var(--wds-icon-size-md, 20px);
  height: var(--wds-icon-size-md, 20px);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--wds-icon-stroke, 1.7);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ===== 아트디렉션 v2 (doc 161, 사용자 승인 목업 · 669 §17.5 D15ⓐ′ 의미색 정본 통합 S1) =====
   기능색 4축 중 **Green(완료)·Amber(주의·전환)만 의미색**이며 값은 WDS 의미 사다리(단일 출처
   `shared/wds/tokens/wds.css`)의 alias다 — 라이트·다크 모두 바이트 동일(0픽셀), 리터럴 hex는
   폴백만. **Blue(정보·모델·문서)·Violet(AI·요약)은 의미색이 아니라 범주 강조**로 재분류했다
   — RESERVED_ARCS 면제 집합 밖, 값은 그대로(이름·역할만 바뀜, 0픽셀). 판정문
   semantic-color-shell-unify-judge D15ⓐ′·결정표 E1·E4 근거.
   원시 데이터 격리: 파일명·ms·모델ID는 1차 표면 노출 금지(확장자 칩·초 단위·표시 라벨만). */
:root {
  /* 범주 강조(의미색 아님) — 면제 집합 밖 */
  --ad-blue: var(--wds-brand-blue, #155eef);
  --ad-violet: var(--wds-violet-600, #6938ef);
  /* 의미색 — 값은 WDS 사다리 alias(success/warning), 폴백 hex는 종전 리터럴과 동일 */
  --ad-green: var(--wds-color-success-text, #15803d);
  --ad-amber: var(--wds-color-warning-text, #b45309);
  /* 틴트는 현재 서피스와 섞어 만들므로 라이트/다크 모두에서 자동으로 맞는 바닥색이 된다 */
  --ad-blue-tint: color-mix(in srgb, var(--wds-blue-500, #3c8ffd) 11%, var(--wizon-ai-surface, #fff));
  --ad-green-tint: color-mix(in srgb, var(--wds-green-600, #16a34a) 10%, var(--wizon-ai-surface, #fff));
  --ad-amber-tint: color-mix(in srgb, var(--wds-amber-500, #f59e0b) 13%, var(--wizon-ai-surface, #fff));
  --ad-violet-tint: color-mix(in srgb, var(--wds-violet-500, #7c5cfc) 11%, var(--wizon-ai-surface, #fff));
}
/* 다크에서는 본색을 밝은 변형으로 (700 계열은 다크 바닥에서 대비 부족) */
html[data-theme="dark"] {
  --ad-blue: var(--wds-blue-400, #66aaff);
  --ad-violet: var(--wds-violet-500, #7c5cfc);
  --ad-green: var(--wds-color-success-text, #4ade80);
  --ad-amber: var(--wds-color-warning-text, #fbbf24);
}

/* 의미 아이콘 틴트 칩 — 내비/액션 아이콘은 무채색 유지, 색은 "의미" 전용 */
.md-chip {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 7px;
}
.md-chip--sm {
  width: 20px;
  height: 20px;
  border-radius: var(--ds-radius-xs, 6px);
}
.md-chip[data-tone] .wds-inline-icon {
  width: 14px;
  height: 14px;
}
.md-chip--sm[data-tone] .wds-inline-icon {
  width: 12px;
  height: 12px;
}
.md-chip[data-tone="blue"] { background: var(--ad-blue-tint); color: var(--ad-blue); }
.md-chip[data-tone="green"] { background: var(--ad-green-tint); color: var(--ad-green); }
.md-chip[data-tone="amber"] { background: var(--ad-amber-tint); color: var(--ad-amber); }
.md-chip[data-tone="violet"] { background: var(--ad-violet-tint); color: var(--ad-violet); }

/* 콜아웃 톤 스킨 (기본 형태는 .md-callout 기존 규칙, 여기서 의미색만 입힘) */
.md-callout[data-callout="summary"] {
  border-color: color-mix(in srgb, var(--ad-violet) 16%, transparent);
  background: var(--ad-violet-tint);
}
.md-callout[data-callout="summary"] .md-callout-head { color: var(--ad-violet); }
.md-callout[data-callout="warning"] {
  border-color: color-mix(in srgb, var(--ad-amber) 22%, transparent);
  background: var(--ad-amber-tint);
}
.md-callout[data-callout="warning"] .md-callout-head { color: var(--ad-amber); }
.md-callout[data-callout="note"] {
  border-color: color-mix(in srgb, var(--ad-blue) 14%, transparent);
  background: var(--ad-blue-tint);
}
.md-callout[data-callout="note"] .md-callout-head { color: var(--ad-blue); }

/* 본문 문서 출처 태그의 아이콘: 블루 틴트 사각 (파비콘과 같은 14px 슬롯) */
.citation-tag .citation-tag-doc {
  display: inline-grid;
  place-items: center;
  width: 14px;
  height: 14px;
  border-radius: 4px;
  background: var(--ad-blue-tint);
  color: var(--ad-blue);
}
.citation-tag .citation-tag-doc .wds-inline-icon {
  width: 10px;
  height: 10px;
}

/* 문서 확장자 칩 (처리 과정 탭 · 원시 파일명 격리) */
.source-ext-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 9px;
  border: 1px solid color-mix(in srgb, var(--ad-blue) 16%, transparent);
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--ad-blue-tint);
  color: var(--ad-blue);
  font: inherit;
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  letter-spacing: 0.03em;
  line-height: 1.5;
  cursor: pointer;
  transition: border-color var(--wds-duration-fast) ease;
}
.source-ext-chip .wds-inline-icon {
  width: 12px;
  height: 12px;
  flex: none;
}
.source-ext-chip:hover {
  border-color: color-mix(in srgb, var(--ad-blue) 38%, transparent);
}
.source-ext-chip:focus-visible {
  outline: 2px solid var(--cg-text, #0d0d0d);
  outline-offset: 1px;
}
.source-ext-more {
  align-self: center;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}

/* 출처 카드 v2 (아트디렉션 §4): 파비콘+사이트명 헤더 / 제목 2줄 / 스니펫 2줄.
   번호 배지 → 우측 뮤트 인덱스, 선택 = 블루 링 + "인용됨". 드로어·신뢰 패널 공용. */
.source-card-v2 {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 13px;
  box-shadow: 0 1px 2px rgba(16, 16, 20, 0.04);
  transition: box-shadow var(--wds-duration-fast) ease, background var(--wds-duration-fast) ease;
}
.source-card-v2:hover {
  box-shadow: 0 1px 2px rgba(16, 16, 20, 0.05), 0 4px 14px rgba(16, 16, 20, 0.05);
}
.source-card-head-v2 {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}
.source-ext-chip--static {
  cursor: default;
}
/* 표준 출처 태그 원자 (doc 161 §8): 파비콘 + 이름. 카드 v2 헤더와 뉴스 피드 kicker가 이 한
   컴포넌트를 공유 — 답변의 모든 웹 출처 표식이 동일하게 읽힌다. .news-fav가 파비콘 베이스. */
.source-tag {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}
.source-tag-fav {
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: 4px; /* matches the .news-fav favicon box these tags share */
}
.source-tag-name {
  overflow: hidden;
  min-width: 0;
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-caption-12);
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.source-card-cited {
  display: none;
  flex: none;
  margin-left: auto;
  align-items: center;
  gap: 4px;
  color: var(--ad-blue);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}
.source-card-cited .wds-inline-icon {
  width: 11px;
  height: 11px;
}
.source-card-v2.is-selected .source-card-cited {
  display: inline-flex;
}
.source-card-v2.is-selected {
  border-color: transparent;
  box-shadow: 0 0 0 2px var(--ad-blue), 0 4px 14px rgba(16, 16, 20, 0.06);
}
.source-card-v2.is-highlighted {
  box-shadow: 0 0 0 2px var(--ad-blue), 0 0 0 6px color-mix(in srgb, var(--ad-blue) 18%, transparent);
  transition: box-shadow 220ms ease;
}
.source-card-title-v2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: -0.01em;
  text-wrap: pretty;
}
.source-card-snippet-v2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-caption-12);
  line-height: 1.55;
}

.chatgpt-reference-v1 .composer-icon-button:hover {
  background: #f4f4f4;
}

.chatgpt-reference-v1 .composer-action-dock {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  grid-area: actions;
  grid-column: auto;
  justify-self: end;
  width: auto;
  /* 20260911-composer-dock-height-unify (C7): 도크 형제(모델필·마이크·전송)를 36 1값으로
     합쳤으므로 바깥 도크도 38→36 으로 맞춰 형제 중심선이 도크 자체의 위/아래 가장자리와도
     맞는다. */
  min-height: 36px;
  gap: 6px;
  flex-wrap: nowrap;
}

.chatgpt-reference-v1 .composer-model-pill {
  display: inline-grid;
  grid-template-columns: auto 16px;
  align-items: center;
  column-gap: 3px;
  /* 20260911-composer-dock-height-unify (C7): 32→36 으로 마이크·전송과 높이를 맞추고,
     반경 12→999(pill)로 마이크·전송과 같은 문법 1종으로 합친다(S6 실측 {32,36,38}/
     반경 2종 → 36/999 1종). */
  min-height: 36px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: transparent;
  color: #6f6f6f;
  font-size: var(--ds-text-caption-13);
  font-weight: 400;
  line-height: 20px;
  cursor: pointer;
}

.chatgpt-reference-v1 .composer-model-pill > span {
  display: inline-flex;
  align-items: center;
  min-height: 20px;
}

.chatgpt-reference-v1 .composer-model-pill:hover,
.chatgpt-reference-v1 .composer-model-pill[aria-expanded="true"] {
  background: #f4f4f4;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .composer-caret {
  justify-content: center;
}

.chatgpt-reference-v1 .composer-caret svg {
  width: 16px;
  height: 16px;
}

.chatgpt-reference-v1 .composer-send-button,
.chatgpt-reference-v1 #sendButton {
  /* 20260911-composer-dock-height-unify (C7): 38→36 으로 마이크(이미 36)·모델필과 높이를
     맞춘다. 반경은 이미 999(pill)라 그대로 둔다. */
  width: 36px;
  height: 36px;
  min-width: 36px;
  padding: 0;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: #0d0d0d;
  color: #ffffff;
  font-size: 22px;
  font-weight: 500;
}

.chatgpt-reference-v1 .composer-send-button:hover,
.chatgpt-reference-v1 #sendButton:hover {
  background: #000000;
}

.chatgpt-reference-v1 #sendButton:disabled {
  background: color-mix(in srgb, var(--ds-color-ink) 12%, var(--ds-color-surface));
  color: var(--ds-color-muted);
}

.chatgpt-reference-v1 .composer-send-button.composer-primary-disabled,
.chatgpt-reference-v1 #sendButton.composer-primary-disabled {
  background: color-mix(in srgb, var(--ds-color-ink) 12%, var(--ds-color-surface));
  color: var(--ds-color-muted);
  cursor: not-allowed;
}

.chatgpt-reference-v1 .composer-send-button.composer-primary-stop,
.chatgpt-reference-v1 #sendButton.composer-primary-stop {
  background: #0d0d0d;
  color: #ffffff;
  cursor: pointer;
}

.chatgpt-reference-v1 .composer-send-button.composer-primary-stop svg,
.chatgpt-reference-v1 #sendButton.composer-primary-stop svg {
  width: 18px;
  height: 18px;
}

.chatgpt-reference-v1 .composer-mic-button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.chatgpt-reference-v1 .composer-mic-button:disabled:hover {
  background: transparent;
}

html[data-chat-state="empty"] .chatgpt-reference-v1 .chat-pane {
  /* login-r33-post-login-stage1 §1-6(Qⓐ): +1 row for #profileHomeNudge, between the
     composer(3) and the chip shelf, which shifted from row4 to row5 below. */
  grid-template-rows: minmax(0, 0.755fr) auto auto auto auto minmax(0, 1fr);
  min-height: 100vh;
  padding: 0;
}

html[data-chat-state="empty"] .chatgpt-reference-v1 .message-list {
  grid-row: 2;
  justify-content: end;
  min-height: auto;
  overflow: visible;
  padding: 0;
  margin: 0 auto 14px;
}

html[data-chat-state="empty"] .chatgpt-reference-v1 .chatgpt-composer-shell {
  grid-row: 3;
  margin: 0 auto;
}

html[data-chat-state="empty"] .chatgpt-reference-v1 .profile-home-nudge {
  grid-row: 4;
}

html[data-chat-state="empty"] .chatgpt-reference-v1 .chatgpt-prompt-shelf {
  grid-row: 5;
  display: flex;
  justify-content: center;
  gap: 8px;
  /* 20260704-empty-action-shelf-chatgpt: ChatGPT keeps the prompt chip shelf on the same 768px layout rail as the composer, then centers the chips inside it. */
  width: min(768px, calc(100% - 48px));
  margin: 24px auto 0;
}

/* 20260823-loading-chat-state: `loading` means "a thread is addressed and its messages have
   not arrived yet". It shares every rule below with `active` on purpose — the frame around a
   thread must not move when the messages land, and it must never be the centred new-chat home
   the shell used to assert before anything knew which screen this was. Rule: any selector that
   keys on [data-chat-state="active"] carries a "loading" sibling, pinned by
   tests/test_conversation_reload_flash.py. */
html[data-chat-state="active"] .chatgpt-reference-v1 .chatgpt-prompt-shelf,
html[data-chat-state="loading"] .chatgpt-reference-v1 .chatgpt-prompt-shelf,
.chatgpt-reference-v1 .chatgpt-prompt-shelf[hidden] {
  display: none;
}

html[data-chat-state="empty"] .chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell {
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
}

.chatgpt-reference-v1.is-auth-gate .app-shell {
  background: #ffffff;
}

@media (max-width: 900px) {
  .chatgpt-reference-v1 .app-shell {
    grid-template-columns: 52px minmax(0, 1fr);
  }

  .chatgpt-reference-v1 .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 70;
    display: flex;
    width: min(260px, calc(100vw - 52px));
    box-shadow: var(--cg-menu-shadow);
  }

  .chatgpt-reference-v1:not(.sidebar-collapsed) .app-shell {
    grid-template-columns: 1fr;
  }

  .chatgpt-reference-v1.sidebar-collapsed .chatgpt-sidebar-rail {
    display: flex;
  }

  .chatgpt-reference-v1 .message-list,
  .chatgpt-reference-v1 .chatgpt-composer-shell,
  .chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell {
    width: min(100% - 24px, 768px);
  }

  .chatgpt-reference-v1 .chatgpt-home-title {
    font-size: 24px;
  }

  .chatgpt-reference-v1 .chatgpt-prompt-shelf {
    display: none;
  }

  .chatgpt-reference-v1 .composer-model-pill {
    display: inline-flex;
    max-width: 68px;
  }
}

/* 20260708-composer-mobile-two-row-v2: the actual two-row reflow lives at the end of this file (search "20260708-composer-mobile-two-row-v2") so it can beat the higher-specificity single-row empty-state rules (styles.css:7071,7120,11772,11823) with matching-specificity + !important + later source order. */

/* WDS AI guideline detail pass: ComposerBar menus, modes, and MessageActions. */
.chatgpt-reference-v1 .chatgpt-composer-shell {
  position: relative;
}

.chatgpt-reference-v1 .composer-mode-slot {
  display: inline-flex;
  min-width: 0;
  margin: 0 0 6px;
}

.chatgpt-reference-v1 .composer-mode-slot[hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-action-dock {
  grid-area: actions;
}

.chatgpt-reference-v1 .composer-mode-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 210px;
  min-height: 32px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: #f4f4f4;
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  white-space: nowrap;
  cursor: pointer;
}

.chatgpt-reference-v1 .composer-mode-chip span:not(.composer-mode-icon):not(.composer-mode-remove) {
  overflow: hidden;
  text-overflow: ellipsis;
}

.chatgpt-reference-v1 .composer-mode-icon,
.chatgpt-reference-v1 .composer-mode-remove {
  display: inline-grid;
  place-items: center;
  color: #5f6368;
}

.chatgpt-reference-v1 .composer-popover,
.chatgpt-reference-v1 .reasoning-popover {
  position: absolute;
  z-index: 60;
  top: calc(100% + 8px);
  bottom: auto;
  display: grid;
  min-width: 260px;
  padding: 6px;
  border: 1px solid #e3e3e3;
  border-radius: 16px;
  background: #ffffff;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.14), 0 4px 14px rgba(0, 0, 0, 0.08);
}

.chatgpt-reference-v1 .composer-popover[hidden],
.chatgpt-reference-v1 .reasoning-popover[hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-popover {
  left: var(--composer-menu-left, 0);
  top: var(--composer-menu-top, calc(100% + 8px));
  bottom: var(--composer-menu-bottom, auto);
  width: 100%;
  max-height: var(--composer-menu-max-height, min(264px, calc(100vh - 190px)));
  padding: 6px 0;
  border: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.62);
}

.chatgpt-reference-v1 .composer-popover::-webkit-scrollbar {
  width: 10px;
}

.chatgpt-reference-v1 .reasoning-popover {
  left: var(--reasoning-menu-left, auto);
  right: var(--reasoning-menu-right, 62px);
  top: var(--reasoning-menu-top, calc(100% + 8px));
  bottom: var(--reasoning-menu-bottom, auto);
  width: 124px;
  min-width: 124px;
  max-height: var(--reasoning-menu-max-height, min(340px, calc(100vh - 16px)));
  padding: 6px 0;
  border: 0;
  border-radius: 16px;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.62);
}

.chatgpt-reference-v1 .reasoning-model-menu {
  right: var(--reasoning-menu-right, -60px);
  width: 126px;
  min-width: 126px;
}

.chatgpt-reference-v1 .composer-menu-item,
.chatgpt-reference-v1 .reasoning-menu-item {
  display: grid;
  align-items: center;
  min-height: 36px;
  border: 0;
  border-radius: var(--ds-radius-sm, 10px);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-14);
  text-align: left;
  cursor: pointer;
}

.chatgpt-reference-v1 [data-gpt-oss-runtime-restore][hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-menu-item {
  grid-template-columns: 28px minmax(0, 1fr) auto;
  gap: 10px;
  padding: 0 10px;
  margin: 0 6px;
}

.chatgpt-reference-v1 .reasoning-menu-item {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  width: 112px;
  min-height: 36px;
  margin: 0 6px;
  padding: 6px 10px;
  line-height: 20px;
}

.chatgpt-reference-v1 #reasoningModelMenu,
.chatgpt-reference-v1 #reasoningLevelMenu {
  width: 190px;
  min-width: 190px;
}

.chatgpt-reference-v1 #reasoningModelMenu .reasoning-menu-item,
.chatgpt-reference-v1 #reasoningLevelMenu .reasoning-menu-item {
  width: auto;
}

.chatgpt-reference-v1 .reasoning-model-group-title {
  margin: 6px 6px 2px;
  padding: 0 10px;
  color: #8a8a8a;
  font-size: var(--ds-text-caption-12);
  font-weight: 400;
  line-height: 16px;
}

.chatgpt-reference-v1 .reasoning-model-option.is-planned {
  cursor: default;
}

.chatgpt-reference-v1 .reasoning-model-option.is-planned > span:first-child {
  color: #8a8a8a;
}

.chatgpt-reference-v1 .reasoning-model-badge {
  flex: 0 0 auto;
  padding: 1px 8px;
  border-radius: var(--ds-radius-pill, 999px);
  background: #ececec;
  color: #5d5d5d;
  font-size: var(--ds-text-micro-11);
  line-height: 16px;
  white-space: nowrap;
}

/* Model labels and status badges vary by runtime state. Size the desktop popover to
   its widest row; JS supplies the measured width and clamps it to the viewport. */
.chatgpt-reference-v1 #reasoningModelMenu {
  width: var(--reasoning-menu-width, max-content);
  min-width: min(220px, calc(100vw - 16px));
  max-width: calc(100vw - 16px);
}

.chatgpt-reference-v1 #reasoningModelMenu .reasoning-menu-item {
  box-sizing: border-box;
  width: auto;
}

.chatgpt-reference-v1 #reasoningMenu {
  width: 210px;
  min-width: 210px;
}

.chatgpt-reference-v1 #reasoningMenu .reasoning-menu-item {
  width: auto;
}

.chatgpt-reference-v1 .reasoning-future-menu {
  width: 200px;
  padding: 12px;
}

.chatgpt-reference-v1 .reasoning-future-empty {
  margin: 0;
  font-size: var(--ds-text-body-14);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .reasoning-future-hint {
  margin: 4px 0 0;
  font-size: var(--ds-text-caption-12);
  line-height: 1.5;
  color: #8a8a8a;
}

.chatgpt-reference-v1 .reasoning-menu-item > span:first-child {
  white-space: nowrap;
}

.chatgpt-reference-v1 .composer-menu-copy {
  display: flex;
  min-width: 0;
  align-items: baseline;
  gap: 12px;
}

.chatgpt-reference-v1 .composer-menu-copy strong {
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  white-space: nowrap;
}

.chatgpt-reference-v1 .composer-menu-copy small {
  overflow: hidden;
  color: #8a8a8a;
  font-size: var(--ds-text-caption-13);
  font-weight: 400;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .composer-menu-item:hover,
.chatgpt-reference-v1 .reasoning-menu-item:hover {
  background: #f4f4f4;
}

/* 2026-08-01 (doc 345 §5): 「이미지 만들기」 아이콘만 핑크 #e83e9d 였다. 같은 메뉴의 다른
   아이콘 6개는 전부 잉크(#3f3f3f)라 하나만 튀었고, doc 221 W-1의 「색은 의미(액션·상태)에만」
   에도 어긋난다 — 메뉴 항목의 종류는 의미색이 아니다. ChatGPT도 이 자리 아이콘이 전부
   무채색이다. 규칙을 지우면 12655행의 --cg-text-soft 가 그대로 적용돼 형제와 같아진다.
   ★함께 지운 `color: #4b5563` 은 이미 죽은 선언이었다 — 12655행에 같은 선택자·같은
   특이도(0,2,0) 규칙이 뒤에 있어 그쪽이 이긴다(형제 실측이 #3f3f3f인 이유). display 와
   place-items 는 12655에 없으므로 여기 남긴다. */
.chatgpt-reference-v1 .composer-menu-icon,
.chatgpt-reference-v1 .composer-menu-chevron,
.chatgpt-reference-v1 .reasoning-check,
.chatgpt-reference-v1 .composer-menu-check {
  display: inline-grid;
  place-items: center;
}

.chatgpt-reference-v1 .composer-menu-dynamic {
  display: grid;
}

.chatgpt-reference-v1 .composer-menu-dynamic[hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-menu-inline-option {
  grid-template-columns: 28px minmax(0, 1fr) 20px;
}

.chatgpt-reference-v1 .composer-menu-app-glyph {
  width: 20px;
  height: 20px;
  color: var(--cg-text);
  font-size: var(--ds-text-body-15);
  line-height: 20px;
}

.chatgpt-reference-v1 .composer-menu-check {
  opacity: 0;
}

.chatgpt-reference-v1 .composer-menu-item.is-selected .composer-menu-check {
  opacity: 1;
}

.chatgpt-reference-v1 .composer-menu-divider {
  height: 1px;
  margin: 6px 0;
  background: #ececec;
}

.chatgpt-reference-v1 .reasoning-popover .composer-menu-divider {
  margin: 4px 0;
}

.chatgpt-reference-v1 .reasoning-menu-title {
  width: 112px;
  min-height: 36px;
  margin: 0 6px;
  padding: 8px 10px;
  color: rgb(143, 143, 143);
  font-size: var(--ds-text-body-14);
  line-height: 20px;
  font-weight: 400;
}

.chatgpt-reference-v1 .reasoning-model-item {
  margin-top: 0;
}

html[data-chat-state="active"] .chatgpt-reference-v1 .composer-popover,
html[data-chat-state="loading"] .chatgpt-reference-v1 .composer-popover {
  top: var(--composer-menu-top, auto);
  bottom: var(--composer-menu-bottom, calc(100% + 10px));
}

/* The upward flip is for composer-anchored popovers only: with an active chat the
   composer sits at the bottom of the viewport, so its menus must open above it.
   The sidebar product selector borrows .reasoning-popover purely for the mobile
   top-layer sheet machinery, but it is anchored to the sidebar head at the very
   top of the page — flipping it put its bottom edge 10px ABOVE the sidebar, i.e.
   off-screen ("메뉴가 위로 펼쳐져 안 보임", intermittent because it only happens
   once a conversation is open). The desktop guard at
   `.chatgpt-product-mode-menu.reasoning-popover` ties this rule on specificity
   (0,3,1) and loses on source order, so exclude the selector explicitly here. */
html[data-chat-state="active"] .chatgpt-reference-v1 .reasoning-popover:not(.chatgpt-product-mode-menu),
html[data-chat-state="loading"] .chatgpt-reference-v1 .reasoning-popover:not(.chatgpt-product-mode-menu) {
  top: var(--reasoning-menu-top, auto);
  bottom: var(--reasoning-menu-bottom, calc(100% + 10px));
}

.chatgpt-reference-v1 .reasoning-menu-item .reasoning-check {
  opacity: 0;
}

.chatgpt-reference-v1 .reasoning-menu-item.is-selected .reasoning-check {
  opacity: 1;
}

.chatgpt-reference-v1 .message-actions {
  display: flex;
  align-items: center;
  /* 20260911-message-actions-gutter-unify (C9): 0→4 로 올려 답변 액션 행의 버튼이 서로
     맞닿던 것(거터 0.0)을 사용자 액션 행(2px)과 함께 4px 1값으로 합친다. */
  gap: 4px;
}

.chatgpt-reference-v1 .message-action-button,
.chatgpt-reference-v1 .feedback-row .message-action-button {
  display: inline-grid;
  place-items: center;
  /* 20260911-message-actions-size-unify (C9): 32→36 으로 명시하고 min-height 도 36 으로
     박아 `styles.css:1498` `.feedback-row button{min-height:38px}`(레거시 일반 셀렉터)가
     이 버튼의 실제 높이를 32 가 아니라 38 로 밀어 올리던 충돌을 없앤다(답변 «복사» 32×38
     vs 질문 «복사» 32×32 실측 — 두 행 모두 36×36 로 합친다). */
  width: 36px;
  height: 36px;
  min-width: 36px;
  min-height: 36px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #4b5563;
  cursor: pointer;
}

.chatgpt-reference-v1 .message-action-button:hover {
  background: #f4f4f4;
  color: var(--cg-text);
}

/* 소리로 듣기 재생 중: 눌린(재생) 상태를 유지 표시 — 같은 버튼이 정지 토글이 된다. */
.chatgpt-reference-v1 .message-action-button.is-speaking {
  background: var(--cg-hover, #f4f4f4);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .feedback-row.message-actions {
  margin-top: 14px;
}

.wds-pattern-fidelity-v2 .context-pane {
  background: var(--ds-color-surface-solid);
  border-left: 1px solid var(--ds-color-line);
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .tabs {
  gap: var(--ds-space-2);
  padding: var(--ds-space-3);
  background: var(--ds-color-surface-solid);
}

.wds-pattern-fidelity-v2 .tab-button {
  min-height: 32px;
  border-radius: var(--ds-radius-xs);
}

.wds-pattern-fidelity-v2 .panel-block,
.wds-pattern-fidelity-v2 .app-detail-panel,
.wds-pattern-fidelity-v2 .app-workflow-panel,
.wds-pattern-fidelity-v2 .app-input-fields,
.wds-pattern-fidelity-v2 .app-result-card,
.wds-pattern-fidelity-v2 .mini-card,
.wds-pattern-fidelity-v2 .app-card,
.wds-pattern-fidelity-v2 .artifact-studio,
.wds-pattern-fidelity-v2 .artifact-editor {
  border-color: var(--ds-card-border);
  border-radius: var(--ds-radius-sm);
  background: var(--ds-color-surface-solid);
  box-shadow: none;
}

.wds-pattern-fidelity-v2 .login-dialog {
  width: min(390px, calc(100vw - var(--wds-space-8, 2rem)));
  max-width: none;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  outline: 0;
}

.wds-pattern-fidelity-v2 .login-dialog::backdrop {
  background: color-mix(in srgb, var(--wds-color-surface, var(--ds-color-bg)) 86%, transparent);
}

.wds-pattern-fidelity-v2 .login-surface ._brand_1p22z_32 {
  font-weight: var(--wds-font-weight-regular);
}

.wds-pattern-fidelity-v2 .login-service-name {
  display: inline;
  width: auto;
  height: auto;
  background: transparent;
  color: inherit;
  font-size: inherit;
  font-weight: var(--wds-font-weight-medium);
  line-height: inherit;
  white-space: nowrap;
}

.wds-pattern-fidelity-v2 .login-password-toggle {
  display: grid;
  place-items: center;
  width: var(--wds-control-height-sm);
  height: var(--wds-control-height-sm);
  border: 0;
  background: transparent;
  color: var(--wds-color-text-muted);
  cursor: pointer;
}

.wds-pattern-fidelity-v2 .login-password-toggle:hover {
  background: var(--wds-color-surface-hover);
  color: var(--wds-color-text);
}

.wds-pattern-fidelity-v2 .login-password-toggle:focus-visible {
  outline: 2px solid var(--wds-color-focus-ring);
  outline-offset: 2px;
}

.wds-pattern-fidelity-v2 .login-password-icon--eye-off,
.wds-pattern-fidelity-v2 .login-password-toggle[aria-pressed="true"] .login-password-icon--eye {
  display: none;
}

.wds-pattern-fidelity-v2 .login-password-toggle[aria-pressed="true"] .login-password-icon--eye-off {
  display: block;
}

.wds-pattern-fidelity-v2 .login-field-error {
  margin: 0;
}

.wds-pattern-fidelity-v2 .login-field-error:empty {
  display: none;
}

.wds-pattern-fidelity-v2 .login-surface ._label_2lf8x_7 {
  /* WDS Login art direction: avoid heavy text inside the auth surface. */
  font-weight: var(--wds-font-weight-regular);
}

.wds-pattern-fidelity-v2 .login-surface ._field_2lf8x_2 {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-2);
}

/* MFA login-time challenge (doc 668 L0) — mirrors #passwordChangeFields'
 * container: same "mode" pattern, but this one also needs to stack its own
 * field + the recovery-code link with a visible gap (a bare [hidden] toggle
 * is enough for #passwordChangeFields because it only ever swaps whole rows,
 * not lay out a link under a field). Author-origin display:flex beats the UA
 * [hidden] rule regardless of source order (see the ._field_2lf8x_2[hidden]
 * comment below), so the explicit override is required, not optional.
 */
.wds-pattern-fidelity-v2 .login-surface .mfa-challenge-fields {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-3);
}

.wds-pattern-fidelity-v2 .login-surface .mfa-challenge-fields[hidden] {
  display: none;
}

.wds-pattern-fidelity-v2 .login-surface ._label_2lf8x_7 {
  display: block;
  color: var(--wds-color-text);
  font-size: var(--wds-font-size-body-2);
  line-height: 1.4;
}

.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 ._input_1h2ox_100,
.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 ._input_1h2ox_100:hover,
.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 ._input_1h2ox_100:focus,
.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 ._input_1h2ox_100:focus-visible {
  width: auto;
  min-width: 0;
  min-height: 0;
  height: auto;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--wds-input-fg);
  box-shadow: none;
  outline: none;
  line-height: normal;
}

/* 부록 2 (doc 668 L1-b): MFA 코드 칸 아래 "다른 방법(복구 코드)"·"취소" 두 링크를
 * (예전 .login-options-row 와 같은) 좌우 배치로 — 세로로 그냥 쌓으면(.mfa-challenge-fields의
 * flex-column만 적용된 상태) 위계 없이 나열된 링크 목록처럼 보인다. */
.wds-pattern-fidelity-v2 .login-surface .mfa-challenge-actions-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wds-space-3);
  min-height: var(--wds-control-height-md);
}

/* Invite-accept mode (portal-invite-accept-screen, app.js enterInviteAcceptMode()):
 * hides the account group and the «로그인 상태 유지» group (R-32: #loginAccountGroup /
 * #loginRememberGroup, formerly the field rows and the remember-me/forgot-password row)
 * when a `?invite_token=` deep link swaps the login form into
 * "set a password to accept the invite" mode, reusing #passwordChangeFields'
 * two fields instead. A bare [hidden] attribute is not enough here -- both
 * ._field_2lf8x_2 above and the R-32 .login-group set display:flex with equal or
 * higher specificity than the UA [hidden] rule (author origin always beats
 * UA origin) -- so this follows the same explicit [hidden]-override
 * convention already used elsewhere in this file (e.g. .sidebar[hidden],
 * #trustPanel.chatgpt-route-page[hidden]).
 */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._field_2lf8x_2[hidden] {
  display: none;
}

.wds-pattern-fidelity-v2 .login-link-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--wds-control-height-md);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--wds-color-primary-text);
  font: inherit;
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-regular);
  line-height: 1.4;
  text-decoration: none;
  cursor: pointer;
}

.wds-pattern-fidelity-v2 .login-surface ._button_lt829_2 {
  font-weight: var(--wds-font-weight-medium);
}

.wds-pattern-fidelity-v2 .login-form-message[data-empty="true"] {
  display: none;
}

/* P0-2: #loginError ships with the WDS FormMessage error variant class
 * (`_error_tqd1n_40`, see shared/wds/ui-web/styles.css) baked into its
 * markup for the common failure case. setLoginFormMessage() in app.js
 * toggles `data-variant` for success/info copy (reset mail sent, invite
 * accepted) — these two rules reuse the same WDS `*-text` ink tokens the
 * shared FormMessage success/info variants use (rather than inventing new
 * colors) and win on specificity (class+attribute vs. the single class
 * above), so a confirmation no longer reads as a red alert. */
.wds-pattern-fidelity-v2 .login-form-message[data-variant="success"] {
  color: var(--wds-color-success-text);
}
.wds-pattern-fidelity-v2 .login-form-message[data-variant="info"] {
  color: var(--wds-color-info-text);
}

.wds-pattern-fidelity-v2 .login-button-spinner[hidden] {
  display: none !important;
}

/* 20260705-login-ai-token-alignment: login keeps WDS structure, with product styling resolved through current AI shell tokens.
   20260924-login-r32-window (R-32 3차, 오너 D6ⓐ 09-24 — judge-login-screen-r32/package-v3.md §A):
   겉 = 회색 캔버스 위 «떠 있는 창» 1개(데스크톱 >520), 속 = 2차 앱 문법(흰 그룹 카드 r14 ·
   행 56 · 28 타일 · 정본 스위치 · 카드 밖 눈썹 · 인접 간격 {8,20} 두 값). 모바일·PWA(≤520)는
   같은 층(회색 시트 + 흰 그룹, package-v3 부수 1)의 바닥 시트다(아래 @media 절).
   ★값은 전부 기존 토큰이다. 공유 토큰 파일(shared/wds/wizon-ai-shell-tokens.css)은 고치지
   않고 로그인 스코프 변수(--login-*)로만 재정의한다 — 새 색 0, 섞음 비율 2개(캔버스 12%,
   muted 9%)만 여기서 정한다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-sheet {
  /* 캔버스 = --wizon-ai-login-backdrop-bg 의 섞음 8% → 12%(#dfdfdf). 창 #f2f2f2 와 1.19:1 —
     8% 그대로면 1.085 로 층 게이트(캔버스:창 ≥ 1.15)에 못 미친다(package-v3 §C-1). */
  --login-canvas: color-mix(in srgb, var(--wizon-ai-canvas) 88%, var(--wizon-ai-text-primary) 12%);
  --login-window-bg: var(--wizon-ai-surface-sheet);
  --login-window-line: transparent;
  --login-group-bg: var(--wizon-ai-surface-group);
  --login-group-line: var(--wizon-ai-sidebar-border);
  --login-tile-bg: var(--wizon-ai-hover-soft-bg);
  --login-row-hover: var(--wizon-ai-hover-soft-bg);
  --login-row-press: var(--wizon-ai-surface-press);
  /* 창·시트 안 muted = #666666(671 §9.10-7). 제품 토큰 #6f6f6f 는 창 면 #f2f2f2 위 4.49:1 로
     AA 미달이라 text-muted 91% + text-primary 9% 로 한 단 짙게 섞는다(= #666666, 5.13:1). */
  --login-muted: color-mix(in srgb, var(--wizon-ai-text-muted) 91%, var(--wizon-ai-text-primary) 9%);
  --login-row-height: 56px;
  --login-icon-button: 44px;
  --login-gap-pair: var(--wds-space-2);
  --login-gap-block: var(--wds-space-5);
  --login-title-size: var(--wizon-ai-font-size-headline-m);
  --login-title-weight: 650;
  --login-input-size: var(--ds-text-body-15);
  --chatgpt-login-backdrop-bg: var(--login-canvas);
  width: min(440px, calc(100vw - (var(--wizon-ai-thread-gutter, 16px) * 2)));
  max-width: none;
  max-height: none;
  /* P9(package.md §2-4): 대화상자 상자가 overflow:auto 면 창 그림자가 네 모서리에서 잘린다. */
  overflow: visible;
  color: var(--wizon-ai-text-primary);
}

/* 다크 = HIG «base → elevated»: 캔버스 #000 → 창 #1c1c1c(+1px 선) → 그룹 #2a2a2a. */
[data-theme="dark"] .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog,
[data-theme="dark"] .chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-sheet {
  --login-canvas: var(--wizon-ai-canvas);
  --login-window-bg: var(--wizon-ai-surface-group);
  --login-window-line: var(--wizon-ai-login-surface-border);
  --login-group-bg: var(--wizon-ai-surface-raised);
  --login-group-line: var(--wizon-ai-surface-press);
  --login-tile-bg: var(--wizon-ai-surface-group);
  --login-row-hover: var(--wizon-ai-surface-group);
  --login-muted: var(--wizon-ai-text-muted);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog::backdrop {
  background: var(--chatgpt-login-backdrop-bg);
}

/* 20260910-login-keyboard-avoidance (doc 668 L1-d) — dialog anchor correction.
   실기기로 잡아 보니 .login-surface의 max-height/overflow만으로는 부족했다: 네이티브
   <dialog>는 UA 기본값(inset-block:0; margin:auto)으로 **전체(키보드로 안 줄어드는)
   레이아웃 뷰포트** 안에서 세로 중앙정렬된다 — 카드를 짧게 캡해도 다이얼로그가 그
   짧아진 카드를 여전히 844px 전체 기준 가운데에 놓으므로 아래쪽이 그대로 키보드 밑에
   남는다(안드로이드 에뮬레이터 실측: 캡 전/후 스크린샷이 픽셀 단위로 동일 — 버튼
   완전히 가려짐, 재현 스크립트 login-l1d-keyboard/measure_keyboard.py). 이 저장소가
   이미 같은 문제([data-mobile-fullscreen], .app-shell — "20260709-mobile-keyboard-fix"
   주석 참고)를 고칠 때 쓴 토큰을 그대로 재사용한다: is-keyboard-open일 때만 다이얼로그
   상단을 --wizon-vv-offset-top(보이는 영역의 실제 상단)에 고정하고 세로 중앙정렬을
   끈다. 가로 중앙정렬(inline 방향)은 건드리지 않는다 — margin-block만 0으로 만들어
   inline 마진은 그대로 UA 기본값이 살아 있게 한다.
   R-32: 폰(≤520)은 대화상자가 화면 전체 호스트이고 시트가 --wizon-kb-inset 만큼 스스로
   올라가므로, 아래 @media 절이 이 고정을 «호스트 inset 0»으로 되돌린다. */
html.is-keyboard-open .wds-pattern-fidelity-v2 .login-dialog {
  position: fixed !important;
  inset-block-start: calc(var(--wizon-vv-offset-top, 0px) + 12px) !important;
  inset-block-end: auto !important;
  margin-block: 0 !important;
}

/* 20260910-login-keyboard-avoidance (doc 668 L1-d, login-l1c-qa/gate.md §2):
   a3's "← 패스키로 돌아가기" link row adds height the a2 view never renders, and on
   Android Chrome that extra height was enough to push #loginButton past the visible
   area once the soft keyboard opens (실기기 실측 — a2 clean, a3 covered, same
   containers otherwise). The dialog had no height cap and no scroll of its own, so
   nothing followed the visualViewport as it shrank. --wizon-vvh already tracks
   visualViewport for other mobile dialogs in this file (see
   "20260709-mobile-keyboard-fix" / --wizon-mobile-dialog-block-size above); this caps
   the login surface at that height (with a small breathing margin) and opens internal
   scroll so the CARD — not the page, which has nothing left to scroll — can reach the
   button.
   R-32(package-v3 §A-1): the window itself is now this box — 440 · padding 20 on all four
   sides · radius 18 (sheet exception, the same object as the phone sheet) · face
   --wizon-ai-surface-sheet · ONE shadow layer (--wizon-ai-overlay-panel-shadow, s0 in
   §D-2). The cap is calc(100dvh - 40px): a 1366×768 password view (739 tall) scrolls
   inside the window while .login-cta sticks to the window's bottom edge. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface {
  --chatgpt-login-surface-padding: var(--login-gap-block);
  --chatgpt-login-surface-gap: var(--login-gap-block);
  --chatgpt-login-body-gap: var(--login-gap-block);
  --chatgpt-login-field-gap: var(--wizon-ai-login-field-gap);
  --chatgpt-login-surface-border: var(--login-window-line);
  --chatgpt-login-surface-shadow: var(--wizon-ai-overlay-panel-shadow);
  --chatgpt-login-link-fg: var(--wizon-ai-text-primary);
  --chatgpt-login-link-hover-fg: var(--wizon-ai-text-primary);
  --chatgpt-login-link-font-size: var(--wizon-ai-login-link-font-size);
  --chatgpt-login-link-font-weight: var(--wizon-ai-login-link-font-weight);
  --chatgpt-login-link-line-height: var(--wizon-ai-login-link-line-height);
  --chatgpt-login-link-target-height: var(--login-icon-button);
  box-sizing: border-box;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-login-surface-gap);
  width: 100%;
  max-width: none;
  margin: 0;
  padding: var(--chatgpt-login-surface-padding);
  border: 0;
  outline: 1px solid var(--chatgpt-login-surface-border);
  outline-offset: -1px;
  border-radius: var(--wizon-ai-radius-sheet);
  background: var(--login-window-bg);
  color: var(--wizon-ai-text-primary);
  box-shadow: var(--chatgpt-login-surface-shadow);
  max-height: min(calc(var(--wizon-vvh, 100dvh) - 40px), calc(100dvh - 40px));
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface::-webkit-scrollbar {
  display: none;
}

/* 20260705-login-layout-surface-token-qa: restore the WDS LoginSurface anatomy for the app-local hashed classes. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._header_1p22z_24,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-body,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-actions {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._header_1p22z_24 {
  align-items: flex-start;
  gap: 0;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-body {
  gap: var(--chatgpt-login-body-gap);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._field_2lf8x_2 {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-login-field-gap);
  min-width: 0;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._label_2lf8x_7 {
  margin: 0;
  color: var(--wizon-ai-text-primary);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 {
  box-sizing: border-box;
  width: 100%;
  border-color: var(--login-group-line);
  border-radius: var(--wds-radius-md);
  background: var(--login-group-bg);
  color: var(--wizon-ai-text-primary);
}

/* 인접 점검(K-4 같은 메커니즘) — 로그인 모드 필드(초대 수락·재설정·MFA)의 포커스 링도
   --cg-text 솔리드(대비 ≥3:1). R-32: 그림자 종류를 늘리지 않도록 box-shadow 링 대신 outline. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2:focus-within {
  border-color: var(--cg-text);
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: -1px;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 ._input_1h2ox_100,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 ._input_1h2ox_100:hover,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 ._input_1h2ox_100:focus,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 ._input_1h2ox_100:focus-visible {
  color: var(--cg-text, var(--wizon-ai-text-primary, #0d0d0d));
  caret-color: var(--cg-text, var(--wizon-ai-text-primary, #0d0d0d));
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._wrap_1h2ox_2 ._input_1h2ox_100::placeholder,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-input::placeholder {
  color: var(--login-muted);
}

/* 20260705-login-admin-contact-token-link: make the admin contact link share the password-help token contract.
   R-32: «접근 권한 요청»은 이제 «다른 방법» 그룹의 행이다(맨 텍스트 발 → 행, package-v2 §C-3).
   .login-link-button 은 MFA 화면의 «다른 방법(복구 코드)»·«취소» 두 링크가 계속 쓴다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-link-button {
  color: var(--chatgpt-login-link-fg);
  font-size: var(--chatgpt-login-link-font-size);
  font-weight: var(--chatgpt-login-link-font-weight);
  line-height: var(--chatgpt-login-link-line-height);
  min-height: var(--chatgpt-login-link-target-height);
  text-decoration: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-link-button:hover {
  color: var(--chatgpt-login-link-hover-fg);
  text-decoration: none;
}

/* D5 (../login-l0-review/review.md §6): 로그인 스코프의 포커스 링은 공유
   --wizon-ai-focus-visible-ring(대비 ≈1.3:1, SC 2.4.11 미달) 대신 기존 고대비 토큰
   --wizon-ai-text-primary 를 쓴다(새 raw 색 0). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-password-toggle:focus-visible,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-link-button:focus-visible,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-back:focus-visible,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._button_lt829_2:focus-visible {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: 2px;
}

/* 주 버튼(패스키 CTA · #loginButton 공통) — 행과 같은 56, 반경 10, 반전 채움.
   높이는 로그인 전용 클래스(.login-primary)에 둔다 — WDS 버튼 클래스 조합에 걸면 같은 조합을 쓰는
   다른 대화상자(defaultLocationDialog 등)의 §A 높이 종류 수에 섞여 들어간다(doc 669 §A 래칫). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-primary {
  height: var(--login-row-height);
  min-height: var(--login-row-height);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._button_lt829_2._primary_lt829_48 {
  width: 100%;
  padding-inline: var(--wds-space-4);
  gap: var(--login-gap-pair);
  border: 0;
  border-radius: var(--wds-radius-md);
  background: var(--wizon-ai-action-primary-bg);
  color: var(--wizon-ai-action-primary-fg);
  font-size: var(--ds-text-body-15);
  font-weight: 600;
}

@media (hover: hover) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._button_lt829_2._primary_lt829_48:hover:not(:disabled) {
    background: var(--wizon-ai-action-primary-bg-hover);
  }
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._button_lt829_2:disabled,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface ._button_lt829_2[aria-disabled="true"] {
  background: var(--wizon-ai-disabled-bg);
  color: var(--wizon-ai-action-primary-fg);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-primary-glyph svg {
  width: 22px;
  height: 22px;
}

/* 20260909-login-l1-passkey-first (doc 668 L1): passkey-first primary view (mockup a1) —
   R-32: 이제 주 동작 슬롯(.login-cta) 안의 «패스키 CTA + 캡션» 한 덩어리다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-passkey-primary {
  display: flex;
  flex-direction: column;
  gap: var(--login-gap-pair);
}

/* login-r33-post-login-stage1(R-32 후속, 오너 결정 Pⓐ·Qⓐ 09-24): 로그인 창 안 마지막 단계 —
   눈썹→8→카드→8→주석 는 .login-group 의 기존 gap(8) 이 맡고, 이 컨테이너는 그 그룹과
   메시지·주 버튼 사이의 20 을 gap 으로 준다(메시지가 비어 있으면 display:none 이라 gap 을
   먹지 않는다 — 위 .login-form-message[data-empty] 규칙 그대로). 새 값 0(기존 --login-gap-*
   토큰만 재사용). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-passkey-offer {
  display: flex;
  flex-direction: column;
  gap: var(--login-gap-block);
}

/* login-r33-post-login-stage1-r3 D1(중앙 iOS 시뮬 09-24): 권유 단계에 «다른 방법»(패스키로 로그인·비밀번호 찾기·
   접근 권한 요청)이 그대로 떠 제목·본문과 «이 계정에 만듭니다» 사이에 끼었다 — showPasskeyOfferView 가 이 그룹을
   건드리지 않았다. 로그인 폼 요소(계정·유지 행·다른 방법)는 보기 속성으로 선언해 숨긴다: JS hidden 은 되돌리는
   경로가 없어(유지 행은 모드 진입/이탈만 복원) 로그아웃 뒤 다음 로그인 창에서 유지 행이 사라지는 잠복 결함이 된다.
   목업 mockup-post-login(p=a) 순서 = 타일 → 제목 → 본문 → 눈썹 → 계정 카드 → 주석 → 패스키 만들기 → 나중에. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginForm[data-auth-view="passkey-offer"] :is(#loginAccountGroup, #loginRememberGroup, #loginOtherMethods) {
  display: none;
}

/* 버튼→나중에 는 20 이 아니라 8 이어야 한다 — 컨테이너 gap(20) 을 그 한 자리만 8 로 줄인다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-passkey-offer-later {
  margin-top: calc(var(--login-gap-pair) - var(--login-gap-block));
  width: 100%;
  text-align: center;
}

/* 제목 위 56 타일(inv, 열쇠 글리프) — .login-app-tile 과 같은 반전 면·크기, 제목과는 20 간격. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-passkey-offer-tile {
  margin: 0 0 var(--login-gap-block);
}

/* §1-4 「성공 → 버튼 체크 그리기(effect 188) + 라벨 …」 — 새 SVG·새 애니메이션 자산은 0 이다.
   기존 --wds-spring-effect-default-duration(188ms) 디졸브가 라벨 교체(markPasskeyOfferSuccess())를
   자연히 드러낸다. 색은 기존 성공 토큰(없으면 텍스트 기본색 fallback)만 쓴다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-passkey-offer .login-primary.is-success {
  transition: background-color var(--wds-spring-effect-default-duration, 188ms) ease-out;
}

/* ══ R-34 단계 2b — 잠금 · 메일 코드 · 보호 모드(목업 mockup-security ②③④) ═══════════════════════
   새 부품 2개(잠금 상태 행 · 코드 6칸)는 기존 행 해부(28 타일 · 56 행 · 반전 타일)와 기존 토큰만 쓴다 — 새 색 0 ·
   새 시간 0(흔들림은 --login-shake-duration 그대로) · 새 간격 0({8,20} = --login-gap-pair/-block).
   잠금·코드 보기에서는 로그인 폼 요소(유지 행 · 다른 방법)를 보기 속성으로 숨긴다(권유 단계 D1 과 같은 이유 —
   JS hidden 은 되돌리는 경로가 없다). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginForm:is([data-auth-view="locked"], [data-auth-view="code"]) :is(#loginRememberGroup, #loginOtherMethods) {
  display: none;
}

/* 코드 보기 부제의 주소(목업 .sub b) — 본문 잉크 600. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-subtitle b {
  color: var(--wizon-ai-text-primary);
  font-weight: 600;
}

/* 남은 시간(분:초)이 초마다 바뀌어도 폭이 흔들리지 않게 숫자 폭을 고정한다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-tabular {
  font-variant-numeric: tabular-nums;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-locked-actions {
  display: flex;
  flex-direction: column;
  gap: var(--login-gap-pair);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-locked-note {
  text-align: center;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code-resend {
  width: 100%;
  font-variant-numeric: tabular-nums;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code-resend:disabled {
  color: var(--login-muted);
  cursor: default;
}

/* ══ R-34 ⑥⑧⑨(login-r34-front2) — 보안 알림 «내가 아니에요» · 관리자 강한 인증(목업 mockup-security ⑥⑨) ══════════════
   새 부품 0: 알림 내용·결과 카드는 기존 행 해부(28 타일 · 56 행) · 강한 인증은 R-33 권유 단계(.login-passkey-offer)와 같은
   자리·같은 모양. 새 색 0(확인 필요 타일 = 입력 행 [data-invalid] 과 같은 위험 잉크) · 새 간격 0({8,20}) · 새 시간 0.
   로그인 폼 요소(계정 · 유지 행 · 다른 방법)는 보기 속성으로 숨긴다(잠금·권유 단계와 같은 이유 — JS hidden 은 되돌릴 경로가 없다). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginForm:is([data-auth-view="security-notice"], [data-auth-view="strong-auth"]) :is(#loginAccountGroup, #loginRememberGroup, #loginOtherMethods) {
  display: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-notice-actions {
  display: flex;
  flex-direction: column;
  gap: var(--login-gap-pair);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-notice-actions[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-strong-totp-qr[hidden] {
  display: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-notice-secondary {
  width: 100%;
  text-align: center;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-notice-note {
  text-align: center;
}

/* 제목(tabindex=-1)은 스크립트로만 초점을 받는다(«조용한 초점» — 탭 순서에 없다). ⑥ 알림 보기는 데스크톱에서도 제목에 초점을
   두므로(파괴적 주 버튼 초점 금지) 전역 :focus-visible 링이 제목에 상자를 그렸다 — 제목에서만 끈다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginTitle:focus-visible {
  outline: none;
}

/* failed_steps 의 단계 = «확인 필요» — 반전(완료) 타일 대신 위험 잉크 테두리 타일. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row[data-notice-row-state="check"] .login-tile {
  border-color: var(--wizon-ai-danger-text);
  color: var(--wizon-ai-danger-text);
}

/* 인증 앱 QR — qrcode.js SVG 는 흰 바탕·검은 모듈을 스스로 그린다(다크에서도 스캔 가능). 크기 = 행 높이 × 3. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-strong-totp-qr {
  display: flex;
  justify-content: center;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-strong-totp-qr svg {
  width: min(100%, calc(var(--login-row-height) * 3));
  height: auto;
  border-radius: var(--wds-radius-lg);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-strong-totp-key,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-strong-recovery-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.04em;
  overflow-wrap: anywhere;
  user-select: all;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-strong-recovery-codes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--login-gap-pair);
  margin: 0;
  padding: var(--wds-space-3) 10px;
  list-style: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-strong-recovery-code {
  color: var(--wizon-ai-text-primary);
  font-size: var(--ds-text-body-15);
  line-height: 20px;
  text-align: center;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code-fields {
  display: flex;
  flex-direction: column;
}

/* 코드 6칸 = 표시층. 실제 입력(한 칸)은 6칸 위를 덮는 투명 입력이다 — 탭 어디든 초점, OS 코드 자동 채우기·
   붙여넣기가 한 번에 들어온다. 칸 = 행 높이 56 · 반경 컨트롤 · 그룹 면/선(행 카드와 같은 층). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code {
  position: relative;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--login-gap-pair);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code-input {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: transparent;
  caret-color: transparent;
  /* iOS 는 16px 미만 입력에 초점이 가면 화면을 확대한다(토큰 16 — 모바일 입력 등재 예외와 같은 값). */
  font-size: var(--ds-text-body-16);
  opacity: 0;
  box-shadow: none;
  cursor: text;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code-cell {
  box-sizing: border-box;
  display: grid;
  place-items: center;
  height: var(--login-row-height);
  border: 1px solid var(--login-group-line);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--login-group-bg);
  color: var(--wizon-ai-text-primary);
  /* 목업 22 는 토큰이 없다 — 가장 가까운 기존 토큰 title-l(20)을 쓴다(새 값 0). */
  font-size: var(--wizon-ai-font-size-title-l);
  font-weight: var(--login-title-weight);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code.is-focused .login-code-cell.is-current {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: -2px;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code.is-invalid .login-code-cell {
  border-color: var(--wizon-ai-danger-text);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code.is-disabled .login-code-cell {
  color: var(--login-muted);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code.is-shaking {
  animation: login-shake var(--login-shake-duration) var(--wds-spring-effect-default, ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code.is-shaking {
    animation: none;
  }
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-code-fields[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-locked-actions[hidden] {
  display: none;
}

/* ══ R-34 단계 3 프런트 ⑩ — 민감 작업 재확인 시트(settings-v2.js openReauthSheet) ══════════════════
   로그인 창과 같은 층·행 문법을 쓴다: 데스크톱 = 떠 있는 창(440 · r18 · 그림자 1겹 · 안쪽 20), 모바일(≤520) = 바닥
   시트(위 반경 18 · 손잡이 · 안쪽 16/안전 영역). 토큰은 .login-dialog 와 같은 규칙이 싣는다(위 --login-* 선택자 목록).
   설정 dialog(top layer) 안의 position:fixed 덮개 — 초대 모달과 같은 방식(중첩 <dialog> 없음). 새 색·값 0. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--login-gap-block, 20px);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-scrim {
  position: absolute;
  inset: 0;
  background: var(--wizon-scrim);
  animation: wizon-scrim-in var(--wds-spring-effect-default-duration, 188ms) linear both;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-sheet {
  position: relative;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--login-gap-block);
  width: min(440px, 100%);
  max-height: min(calc(var(--wizon-vvh, 100dvh) - 40px), calc(100dvh - 40px));
  margin: 0;
  padding: var(--login-gap-block);
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: var(--wizon-ai-radius-sheet);
  outline: 1px solid var(--login-window-line);
  outline-offset: -1px;
  background: var(--login-window-bg);
  box-shadow: var(--wizon-ai-overlay-panel-shadow);
  color: var(--wizon-ai-text-primary);
  font-family: var(--wizon-ai-font-family);
  animation: login-window-in var(--wds-spring-spatial-default-duration, 372ms) var(--wds-spring-spatial-default, ease-out) both;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-head {
  display: flex;
  flex-direction: column;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-grab {
  display: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-actions {
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* 주 버튼 = 로그인 주 버튼과 같은 56(행 높이) · 반전 채움(WDS primary). 높이는 이 시트 클래스에만 건다(doc 669 §A 래칫). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-primary {
  width: 100%;
  height: var(--login-row-height);
  min-height: var(--login-row-height);
  font-size: var(--ds-text-body-15);
  font-weight: 600;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-ghost {
  width: 100%;
  min-height: var(--login-icon-button);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-primary:focus-visible,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-ghost:focus-visible {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: 2px;
}

@media (max-width: 520px) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth {
    align-items: flex-end;
    padding: 0;
    /* 키보드가 뜨면 시트가 보이는 영역 바닥에 선다(전역 syncMobileViewportMetrics 신호 — 로그인 외 시트와 같은 경로). */
    padding-bottom: var(--wizon-kb-inset, 0px);
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-sheet {
    --login-title-size: 19px;
    --login-title-weight: 630;
    --login-input-size: var(--ds-text-body-16);
    width: 100%;
    max-height: calc(var(--wizon-vvh, 100dvh) - env(safe-area-inset-top, 0px) - var(--login-gap-pair));
    padding: var(--login-gap-block) 16px calc(12px + env(safe-area-inset-bottom, 0px));
    border-radius: var(--wizon-ai-radius-sheet);
    border-bottom-right-radius: 0;
    border-bottom-left-radius: 0;
    outline: 0;
    border-top: 1px solid var(--login-group-line);
    box-shadow: none;
    /* 바닥 고정 시트 입장은 넘침 없는 effect 스프링(doc 146 §1 — spatial 은 ~6px 들렸다 내려앉아 틈이 보인다). */
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms) var(--wds-spring-effect-default, ease-out) backwards;
  }

  /* R-40(M-3 ③, 오너 결정 3ⓐ) — 결정형(본인 재확인 = 비밀번호 결정)은 손잡이 제거·끌어 닫힘
     없음. 예전엔 여기서 display:grid 로 되살렸다 — 위 chatgpt-reference-v1.wds-pattern-
     fidelity-v2 .settings-reauth-grab { display: none; } 베이스 선언(18594행)이 그대로
     이기게 이 오버라이드를 지운다(마크업·JS 는 그대로, 손잡이만 다시 숨는다). */
}

/* R-41(build sheet-drag-shared, S13) — 위 animation: 만으로는 전역 하한(20260710-
   reduced-motion-floor, animation-duration:0.01ms !important)을 못 이긴다: 이 셀렉터
   (클래스 2개, 특이도 0,2,0)는 하한 셀렉터보다 파일 앞이라 특이도가 같으면(하한도 클래스
   기반 목록 중 하나) 소스 순서로 진다. 로그인 창(19547행 부근)·S1b(43168행 부근)와 같은
   문법으로 animation-duration 을 다시 !important 로 박는다 — 그래야 !important 끼리
   겨루어 이 규칙이 이긴다(레퍼런스: 로그인 창의 124ms 흐려짐). */
@media (prefers-reduced-motion: reduce) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .settings-reauth-sheet {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }
}

/* 20260909-login-l1c-hidden-override (doc 668 L1-c, ../login-l1b-qa/gate.md §1):
 * showPasskeyPrimaryView()/showPasswordView() toggle `.login-body`, `.login-actions`
 * and `.login-passkey-primary` with the bare `hidden` attribute, but all three set
 * their own author-origin `display:flex` above with equal-or-higher specificity than
 * the UA `[hidden]{display:none}` rule -- author origin always beats UA regardless of
 * source order (see the ._field_2lf8x_2[hidden]/mfa-challenge-fields[hidden] comments
 * elsewhere in this file), so a1 and a3 rendered both views stacked on top of each
 * other on every device QA tested. The same gap exists on three more elements this
 * L1 change also started toggling: `#loginPasskeyUnsupportedCallout` (`.login-form-
 * callout`), the secondary passkey control, and `#loginBackToPasskeyButton`.
 * R-32 moved the secondary passkey control from `.login-actions` into the «다른 방법»
 * card (`.login-row`) and turned every row/group/badge into an author `display:grid|flex`
 * box that JS toggles with `hidden` too — they join the same override (still no
 * !important: the class+attribute selectors win on specificity).
 * Confirmed with getComputedStyle()/getBoundingClientRect() (not `.hidden` alone --
 * that is exactly how the QA gate first missed this).
 */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-body[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-actions[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-passkey-primary[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-passkey-offer[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginPasskeyOfferHeroTile[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-form-callout[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-actions [data-auth-method="passkey"][hidden],
.wds-pattern-fidelity-v2 .login-link-button[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-group[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-status[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-back[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-service-badge[hidden],
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-field-error[hidden] {
  display: none;
}

/* ── R-32 머리: 브랜드 줄(앱 타일 56 + 이름) 또는 «‹ 패스키» 뒤로(44) → 20 → 제목 → 8 → 부제 ── */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-service-badge {
  display: flex;
  align-items: center;
  gap: var(--wds-space-3);
  min-width: 0;
  margin: 0 0 var(--login-gap-block);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-app-tile {
  display: grid;
  flex: none;
  place-items: center;
  width: var(--login-row-height);
  height: var(--login-row-height);
  border-radius: var(--wds-radius-lg);
  background: var(--wizon-ai-text-primary);
  color: var(--wizon-ai-canvas);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-service-name {
  color: var(--wizon-ai-text-primary);
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  line-height: 20px;
  white-space: nowrap;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-back {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  height: var(--login-icon-button);
  margin: 0 0 var(--login-gap-block) -4px;
  padding: 0 10px 0 4px;
  border: 0;
  border-radius: var(--wds-radius-md);
  background: transparent;
  color: var(--wizon-ai-text-primary);
  font: inherit;
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  cursor: pointer;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-title {
  margin: 0;
  color: var(--wizon-ai-text-primary);
  font-size: var(--login-title-size);
  font-weight: var(--login-title-weight);
  line-height: 1.25;
  letter-spacing: -0.02em;
  outline: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-subtitle {
  margin: var(--login-gap-pair) 0 0;
  color: var(--login-muted);
  font-size: var(--wizon-ai-font-size-label);
  line-height: 18px;
}

/* ── 그룹: 카드 밖 눈썹 12/600 → 8 → 흰 카드 r14 → 8 → 주석 12 ── */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-group,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-status {
  display: flex;
  flex-direction: column;
  gap: var(--login-gap-pair);
  min-width: 0;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-eyebrow {
  margin: 0;
  color: var(--login-muted);
  font-size: var(--wizon-ai-font-size-caption);
  font-weight: 600;
  line-height: 16px;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-group-card {
  overflow: hidden;
  border: 1px solid var(--login-group-line);
  border-radius: var(--wds-radius-lg);
  background: var(--login-group-bg);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-foot-note,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-method-note {
  margin: 0;
  color: var(--login-muted);
  font-size: var(--wizon-ai-font-size-caption);
  line-height: 16px;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-field-error {
  margin: 0;
  color: var(--wizon-ai-danger-text);
  font-size: var(--wizon-ai-font-size-caption);
  line-height: 16px;
}

/* ── 행 해부 1종: [28 타일] 12 [이름/메타] 12 [액세서리] — 행 56, 좌우 10 (settings v2 행 문법) ── */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row {
  position: relative;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--wds-space-3);
  width: 100%;
  min-height: var(--login-row-height);
  margin: 0;
  padding: 0 10px;
  border: 0;
  background: transparent;
  color: var(--wizon-ai-text-primary);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast);
}

/* 보이는 행 사이에만 헤어라인 — hidden 행(보기 전환·SSO)을 건너뛴다(settings nav 카드와 같은 규칙). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-group-card > .login-row:not([hidden]) ~ .login-row:not([hidden]) {
  border-top: 1px solid var(--login-group-line);
}

@media (hover: hover) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row:hover {
    background: var(--login-row-hover);
  }
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row:active {
  background: var(--login-row-press);
}

/* iOS 시뮬 검수 I5: 비밀번호 보기 → 가장자리 스와이프 뒤로 → 복귀 뒤 «비밀번호로 로그인» 행이 눌림/흐림으로
   남았다. 경쟁 원인 셋 — ① 시스템 제스처가 touchend 없이 가져간 :active(눌림 면 --login-row-press) ② Safari 기본
   탭 하이라이트(반투명 회색, grid 행이라 셀별로 쪼개 칠한다 — 위 md-table 카드 행 선례와 같은 모양)
   ③ 끈적한 :hover(이미 (hover: hover) 로 막혀 있어 iOS 에선 제외). 헤드리스 WebKit 은 ①②를 재현하지 못해
   둘 다 막는다: ② 기본 하이라이트 끔(눌림 피드백은 :active 면·주 버튼 .98 이 이미 맡는다) ·
   ① 보기 전환 직후 남은 :active 는 그리지 않고, 다음 새 누름(pointerdown)에서 .is-press-reset 을 뗀다
   (app.js suppressStaleLoginPressState). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-back,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-primary,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-password-toggle {
  -webkit-tap-highlight-color: transparent;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface.is-press-reset .login-row:active {
  background: transparent;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row:focus-visible {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: -2px;
  border-radius: var(--wds-radius-lg);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row--static,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row--static:hover,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row--static:active {
  background: transparent;
  cursor: default;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-tile {
  box-sizing: border-box;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--login-group-line);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--login-tile-bg);
  color: var(--wizon-ai-text-primary);
  transition:
    background-color var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    color var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-tile svg {
  width: 20px;
  height: 20px;
}

/* 켬 = 반전 타일(settings v2 활성 nav 타일과 같은 잉크/캔버스 쌍) · 틀림 2회째 «비밀번호 찾기» 강조도 같다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-tile.is-on,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row.is-suggested .login-tile {
  border-color: var(--wizon-ai-text-primary);
  background: var(--wizon-ai-text-primary);
  color: var(--wizon-ai-canvas);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row-name {
  color: var(--wizon-ai-text-primary);
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  line-height: 20px;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-row-meta {
  color: var(--login-muted);
  font-size: var(--wizon-ai-font-size-label);
  line-height: 18px;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-chevron {
  width: 22px;
  height: 22px;
  color: var(--wizon-ai-text-primary);
}

/* 입력 행(같은 해부): [타일] [입력] [눈 44]. 초점은 행 면(L2) + 안쪽 2px 링으로 보인다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-input-row {
  cursor: text;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-input-row:hover {
  background: transparent;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-input-row:focus-within {
  background: var(--login-row-hover);
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: -2px;
  border-radius: var(--wds-radius-lg);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-input-row[data-invalid="true"] .login-tile {
  border-color: var(--wizon-ai-danger-text);
  color: var(--wizon-ai-danger-text);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-input {
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  height: 20px;
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--wizon-ai-text-primary);
  caret-color: var(--wizon-ai-text-primary);
  font: inherit;
  font-size: var(--login-input-size);
  /* 전역 input:focus 링(box-shadow)을 끈다 — 초점은 행의 면·안쪽 링이 맡는다(창 안 그림자 1종). */
  box-shadow: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-password-toggle {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--login-icon-button);
  height: var(--login-icon-button);
  margin-right: -10px;
  padding: 0;
  border: 0;
  border-radius: var(--wds-radius-md);
  background: transparent;
  color: var(--login-muted);
  cursor: pointer;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-password-toggle:hover {
  color: var(--wizon-ai-text-primary);
}

/* 스위치 = settings-v2.css 의 .settings-v2-switch 정본(44×26 알약 · thumb 20 · 정본 thumb 그림자)을
   그대로 쓴다. 켬 상태는 행(button[role=switch][aria-checked]) 이 정한다 — settings-v2.css 의
   «[role=switch][aria-checked=true] > .settings-v2-switch» 공유 규칙(R-32 에서 추출). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-switch {
  flex: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ── 주 동작: 메시지(주 버튼 «위», 6px 점 + 문장 / 중립 문장) → 8 → 주 버튼 56 → 8 → 캡션 ── */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-cta {
  display: flex;
  flex-direction: column;
  gap: var(--login-gap-pair);
  /* 창 높이가 모자라면(1366×768 비밀번호 보기) 창 안이 스크롤되고 주 버튼 영역은 창 아래
     가장자리에 붙는다 — 모바일 시트 하단 고정 영역과 같은 규칙. */
  position: sticky;
  bottom: 0;
  z-index: 1;
  background: var(--login-window-bg);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-form-message {
  display: flex;
  align-items: center;
  gap: var(--login-gap-pair);
  margin: 0;
  min-height: 18px;
  color: var(--wizon-ai-text-primary);
  font-size: var(--wizon-ai-font-size-label);
  line-height: 18px;
}

/* 빈 메시지는 자리를 차지하지 않는다(setLoginFormMessage 가 data-empty 를 관리). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-form-message[data-empty="true"] {
  display: none;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-form-message-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-danger-text);
}

/* 취소·안내·성공은 무채 문장(점 없음) — 실패만 6px 점(669 A11). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-form-message:not([data-variant="error"]) {
  color: var(--login-muted);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-form-message:not([data-variant="error"]) .login-form-message-dot {
  display: none;
}

/* 비 plain 모드(초대 수락·재설정·임시 비밀번호 변경·MFA)에서는 유지 행·다른 방법·패스키 CTA 를
   숨긴다 — MFA 는 서버가 첫 요청의 remember 를 이어받는다(L2 446383adc). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginForm:is([data-password-mode="invite"], [data-password-mode="reset"], [data-password-mode="change"], [data-password-mode="mfa"]) :is(#loginRememberGroup, #loginOtherMethods, #loginPasskeyPrimary) {
  display: none;
}

/* 모바일 전용 요소는 데스크톱에서 숨긴다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-hero,
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-grab {
  display: none;
}

/* login-r32-short-height(오너 09-24 18:1x Windows 11 캡처 — 1536×730 에서 «접근 권한 요청» 이 창 밑단에서 잘려 보임).
   데스크톱 창(>520)은 높이 = min(내용, 뷰포트 − 40) 이고 넘치면 창 안이 스크롤되지만 스크롤바를 숨겨 단서가 0 이었다.
   ⓐ 주 버튼은 이미 .login-cta sticky bottom 0 이라 늘 창 안에 보인다(시험으로 고정).
   ⓑ 아래로 더 볼 내용이 있는 동안(app.js syncLoginScrollHint → .has-login-scroll-more) 창 하단 32 에 창 면 색 페이드.
      창 안 마지막 흐름 항목(::before + order 99 — 폰 시트의 빈 칸은 ::after 가 쓴다, sticky bottom)이라 스크롤해도 창 바닥에 머물고, 끝까지 내리면 사라진다. 자리는 음수 여백으로
      지워 내용 높이 변화 0. 창 아래 안쪽 여백(20)만큼 bottom 을 내려 창 가장자리에 붙인다. 모션 줄이기면 전환 없음.
   ⓒ 창 높이 ≤ 700 CSS px 면 «다른 방법» 그룹을 압축한다: 행 56 → 44(--login-icon-button, 대화형 높이 사다리 {56,44}
      안) · 주 동작 → 그룹 간격 20 → 12(--wds-space-3, 행 열 간격과 같은 값). 새 값 0. */
@media (min-width: 521px) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface::before {
    content: "";
    order: 99;
    position: sticky;
    bottom: calc(-1 * var(--chatgpt-login-surface-padding));
    z-index: 2;
    flex: none;
    height: 32px;
    /* 좌우는 내용 폭(그룹 카드 폭)만 덮는다 — 음수 좌우 여백으로 창 안쪽 여백까지 넓히면 z-index 층이 다크 창의
       1px 윤곽선을 덮었다(1440 다크 권유 단계 x920~940 실측). */
    margin: calc(-32px - var(--chatgpt-login-surface-gap)) 0 0;
    background: linear-gradient(to bottom, transparent, var(--login-window-bg));
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--wds-spring-effect-fast-duration, 124ms) linear;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface.has-login-scroll-more::before {
    opacity: 1;
  }
}

@media (min-width: 521px) and (prefers-reduced-motion: reduce) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface::before {
    transition: none;
  }
}

@media (min-width: 521px) and (max-height: 700px) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginOtherMethods {
    margin-top: calc(var(--wds-space-3) - var(--chatgpt-login-surface-gap));
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginOtherMethods .login-row {
    min-height: var(--login-icon-button);
  }
}

/* ══ 모바일 웹·PWA(≤520): 닫을 수 없는 바닥 시트(D3ⓐ·D5ⓐ, package-v2 §A) ══════════════════
   대화상자 = 화면 전체 호스트(캔버스 면 + 브랜드 히어로), .login-surface = 시트.
   [data-mobile-bottom-sheet] 정본(§11)은 대화상자 «자신»을 시트로 만들어 히어로를 둘 캔버스가
   없고 max-height 85vh 캡이 large detent(상태막대 아래 8)와 충돌해 옵트인하지 않았다 — 대신
   그 정본의 모양·모션 토큰(위 반경 --wizon-ai-radius-sheet · 1px 윗선 · 그림자 0 ·
   wizon-sheet-up 372ms 스프링 · 모션 줄이기 끔)을 그대로 재사용한다(새 키프레임 0). */
@media (max-width: 520px) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog {
    /* 모바일 제목 19/630(package-v2 §A-1: 모바일 폰트 사다리 {19,15,13,12} + 입력 16 등재 예외).
       19 는 토큰이 없는 값이라 이 스코프 변수 한 곳에만 둔다. */
    --login-title-size: 19px;
    --login-title-weight: 630;
    --login-input-size: var(--ds-text-body-16);
    --login-sheet-top-gap: calc(env(safe-area-inset-top, 0px) + var(--login-gap-pair));
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: var(--login-canvas);
    overflow: hidden;
  }

  html.is-keyboard-open body.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog {
    inset-block-start: 0 !important;
    inset-block-end: 0 !important;
  }

  /* R-32 모바일 히어로 ⓑ(login-r32-mobile-hero, 오너 결정 Hⓑ): 캔버스 좌상단 고정(42/137 비대칭,
     판정 레인 실측)을 버리고 «상태막대 ~ 시트 윗선» 가용 공간의 세로·가로 가운데로 옮긴다.
     --login-sheet-height(app.js setLoginSheetHeight) 로 시트 윗선을 알아 bottom 을 잡는다 — 초기
     페인트(첫 sync 전) 폴백은 60%. 시트가 커져 가용이 좁아지면(large detent) app.js 가 붙이는
     is-hero-tight 로 124ms 디졸브 거둔다(아래). */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-hero {
    position: absolute;
    top: env(safe-area-inset-top, 0px);
    right: 16px;
    left: 16px;
    bottom: calc(var(--login-visible-bottom, 0px) + var(--login-sheet-height, 60%));
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--wds-space-3);
    text-align: center;
    transition:
      bottom var(--wds-spring-spatial-default-duration, 372ms) var(--wds-spring-spatial-default, cubic-bezier(0.16, 1, 0.3, 1)),
      opacity var(--wds-spring-effect-fast-duration, 124ms) linear;
  }

  /* 히어로 타일 72(대화형 아님 — 판정 레인 8축 «비대화형 타일 하나» 예외), 반경은 시트와 같은
     값(--wizon-ai-radius-sheet, 18)을 재사용해 종류를 늘리지 않는다. 글리프는 타일에 맞춰 36. */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-hero .login-app-tile {
    width: 72px;
    height: 72px;
    border-radius: var(--wizon-ai-radius-sheet);
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-hero .login-app-tile svg {
    width: 36px;
    height: 36px;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-hero-name {
    /* 폰트 크기 종류 {19,15,13,12} 불변 — 제목과 같은 값을 재사용한다(§A-1 모바일 19/630). */
    font-size: var(--login-title-size);
    font-weight: var(--login-title-weight);
    line-height: 24px;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-hero-tag {
    margin-top: -8px;
    /* 히어로는 창 면이 아니라 캔버스(#dfdfdf) 위다 — muted #666 은 여기서 4.30:1(axe color-contrast)이라
       한 단 짙은 text-secondary(#3f3f3f, 다크 #e3e3e3)를 쓴다. */
    color: var(--wizon-ai-text-secondary);
    font-size: var(--wizon-ai-font-size-label);
    line-height: 18px;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface {
    position: absolute;
    right: 0;
    /* iOS 시뮬 검수 I1: 시트 상자 = «보이는 영역»(대화상자 사각형 vs visualViewport, app.js
       syncLoginSheetViewportBox). 첫 동기화 전에는 옛 전역 변수로 떨어진다. */
    bottom: var(--login-visible-bottom, var(--wizon-kb-inset, 0px));
    left: 0;
    max-height: calc(var(--login-visible-height, var(--wizon-vvh, 100dvh)) - var(--login-sheet-top-gap));
    padding: var(--login-gap-block) 16px 0;
    outline: 0;
    border-top: 1px solid var(--login-group-line);
    /* 위 두 모서리만 시트 반경(18) — 창과 같은 값 하나(§A 반경 종류 수를 늘리지 않게 풀어 쓴다). */
    border-radius: var(--wizon-ai-radius-sheet);
    border-bottom-right-radius: 0;
    border-bottom-left-radius: 0;
    box-shadow: none;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms)
      var(--wds-spring-spatial-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-grab {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    display: grid;
    place-items: center;
    height: var(--login-gap-block);
    touch-action: none;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-grab::before {
    content: "";
    width: 36px;
    height: 5px;
    border-radius: var(--wizon-ai-radius-pill);
    background: var(--wizon-ai-text-muted);
    opacity: 0.5;
  }

  /* 브랜드는 패스키 보기(a1)만 시트 위 히어로가 맡는다 — data-auth-view="passkey-primary" 로
     좁힌다. 비밀번호 보기(a2/a3, data-auth-view="password")·비 plain 모드(초대·재설정·변경·mfa,
     data-auth-view 미설정)는 이 오버라이드가 안 걸려 시트 머리 배지가 JS setLoginBrandVisible 이
     정하는 hidden 속성(데스크톱과 같은 규칙)을 그대로 따른다 — a2 는 보임(브랜드 1회),
     a3 는 hidden=true(‹ 패스키 뒤로가 대신, 브랜드 0회). 현행은 이 셀렉터가 없어 첫 방문
     비밀번호 보기(a2)에 화면 어디에도 브랜드가 없었다(판정 레인 실측). */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginForm[data-auth-view="passkey-primary"] .login-surface .login-service-badge {
    display: none;
  }

  /* R-34 단계 2b: 잠금 보기(medium detent)도 시트 위 히어로가 브랜드를 맡는다(목업 ② 모바일 — 브랜드 1회). */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginForm[data-auth-view="locked"] .login-surface .login-service-badge {
    display: none;
  }

  /* R-34 ⑥⑨: 알림 보기 · 강한 인증도 시트 위 히어로가 브랜드를 맡는다(브랜드 1회). */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 #loginForm:is([data-auth-view="security-notice"], [data-auth-view="strong-auth"]) .login-surface .login-service-badge {
    display: none;
  }

  /* 주 버튼 = 시트 하단 고정 영역(C-v3-6): 본문 아래 8 + 선 1 + 11 = 20 → 주 버튼 → 8 → 캡션. */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-cta {
    order: 1;
    margin: -12px -16px 0;
    /* iOS 시뮬 검수 I1: iOS Safari 탭에서 입력 중이면 떠 있는 주소 알약 여유(56, 근거는 app.js
       LOGIN_IOS_BROWSER_CHROME_CLEARANCE_PX)를 더한다 — 설치형 PWA·Android·데스크톱은 0. */
    padding: 11px 16px calc(12px + env(safe-area-inset-bottom, 0px) + var(--login-browser-chrome-clearance, 0px));
    border-top: 1px solid transparent;
  }

  /* iOS 시뮬 검수 I4: 시트가 내용보다 크면(large · 키 큰 폰의 비밀번호 보기) 남는 높이는 주 버튼 영역
     «위»에 모인다 — 주 버튼·캡션은 늘 시트 바닥. 빈 flex 항목(order 0, 주 버튼 영역 order 1 바로 앞)이
     늘어나고, 이 항목이 만드는 flex gap 하나는 음수 여백으로 지운다(남는 높이 0 이면 기하 변화 0). */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface::after {
    content: "";
    order: 0;
    flex: 1 0 0px;
    margin-top: calc(-1 * var(--chatgpt-login-surface-gap));
  }

  /* iOS 시뮬 검수 I2: 입력 중(키보드·보조 막대)엔 시트 위 히어로를 숨긴다 — 보이는 영역이 줄어 앱 타일이
     시트 윗선에 반쯤 잘려 보였다. visibility 라 자리·기하는 그대로다. */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-login-typing .login-hero {
    visibility: hidden;
  }

  /* R-32 히어로 뒤 입력 중 회귀(login-r32-hero-typing, 중앙 iOS 시뮬 09-24): 히어로 ⓑ 가 a2(첫 방문 비밀번호
     보기)에 되살린 시트 머리 배지(56 + 간격 20)는 입력 칸을 76 아래로 밀어, 키보드가 뜬 large 시트에서
     비밀번호 칸이 하단 고정 주 버튼 영역(알약 여유 56 포함) 뒤로 들어갔다(iPhone 17 Pro 기하: 칸 아래 367 >
     주 버튼 영역 위 358). 입력 중엔 히어로처럼(I2) 브랜드를 거둬 입력 기하를 kb-robust(49e032256, 시뮬
     정상)와 같게 한다. 쉴 때(키보드 없음)는 배지 그대로 — 히어로 ⓑ 의 «a2 브랜드 1회» 계약 유지. */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-login-typing #loginForm .login-surface .login-service-badge {
    display: none;
  }

  /* R-34 ⑨ 인증 앱 등록 F1(login-r34-front2-fix, 중앙 iOS 26.5 시뮬 09-24): 코드 칸을 눌러 키패드가 오르면 QR(행 높이 × 3
     = 168)이 코드 칸을 하단 고정 «확인» 영역 뒤로 밀어 6칸이 안 보였다(WebKit iPhone 실측: 칸 440~497 > 확인 위 392).
     입력 중엔 QR 을 접는다 — 스캔은 입력 «전»에 끝난 일이고, 직접 입력 키 행은 남긴다. 초점이 빠지면 다시 편다. */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-login-typing #loginForm[data-strong-step="totp"] .login-strong-totp-qr {
    display: none;
  }

  /* R-32 모바일 히어로 ⓑ(login-r32-mobile-hero): 시트가 가용 공간보다 크면(large detent · 위로
     끄는 중) 히어로 상자가 시트에 반쯤 가리기 전에 124ms 디졸브로 거둔다(목업 실측 tileTop −7,
     타일 조각이 비치는 것 방지) — app.js setLoginSheetHeight/syncLoginHeroTight 가 붙이는
     is-hero-tight. visibility(위 is-login-typing, I2)와는 별개 계약이라 충돌 0 — 입력 중엔 이
     규칙을 새로 넣지 않는다(발주서 §1-6). */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-hero-tight .login-hero {
    opacity: 0;
  }

  /* R-32 Android 키보드 가림 근본 수리(login-r32-android-kb-robust): Android 급(터치 · 비 iOS) 입력 중엔 주 버튼을
     하단 sticky 에서 풀어 입력 칸 바로 아래 «흐름 안»에 둔다 — 자동완성 막대·떠 있는 키보드는 높이 신호가 없어
     바닥에 붙은 버튼은 늘 가릴 수 있었다(근거는 app.js LOGIN_INFLOW_BOTTOM_RESERVE_PX). 순서만 바꾼다(DOM·Tab 순서
     불변): 칸(login-body) → 주 버튼 → 유지 행 → 다른 방법 → 빈 칸(남는 높이 + 바닥 막대 예약 56). 위 간격은
     음수 여백 −12 + 윗 안쪽 11 + 선 1 = 표면 간격 20 그대로, 아래 안쪽 0(다음 항목과 간격 20). */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-login-cta-inflow .login-cta {
    order: 0;
    position: relative;
    bottom: auto;
    padding-bottom: 0;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-login-cta-inflow :is(#loginRememberGroup, #loginOtherMethods, #loginSecurityAlternatives, #loginCodeFootNote) {
    order: 1;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-login-cta-inflow .login-surface::after {
    order: 2;
    min-height: 56px;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-method-note {
    text-align: center;
  }
}

/* login-mobile-short-height(2026-09-25, iOS 시뮬 3면 실검수 — «접근 권한 요청» 행이 로그인 버튼
   뒤로 잘림, 경미): 주 버튼은 order:1 + sticky bottom:0 라 «다른 방법»·«로그인 상태 유지» 그룹보다
   시각 순서가 뒤다(§C-v3-6) — 내용이 뷰포트보다 길면 스크롤 맨 위(0)에서부터 그 사다리꼴 겹침이
   생긴다. 데스크톱의 같은 짧은창 대응(위 @media (min-width:521px) and (max-height:700px))과
   같은 값 사다리(행 56→44 = --login-icon-button, 새 토큰 0)로 전체 내용 길이를 줄여 겹치는 폭을
   낮춘다 — 완전한 제거는 아니지만(320×568 같은 극단적으로 낮은 창은 남는다), 실측 390×700·
   320×690 은 겹침 0, 375×667 은 64.75px → 16.75px 로 줄었다(레인 실측, WebKit 회귀 하네스). */
@media (max-width: 520px) and (max-height: 700px) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 :is(#loginRememberGroup, #loginOtherMethods) .login-row {
    min-height: var(--login-icon-button);
  }
}

/* login-mobile-scroll-more-cue(2026-09-25): 위 압축으로도 못 지운 겹침은 «스크롤 표지 없음»이
   진짜 결함이다(QA 관측 원문) — 데스크톱의 has-login-scroll-more 는 겹침이 아니라 창 아래 클리핑
   대응(페이드)이라 모바일의 불투명 주 버튼 위에 그대로 겹쳐 그리면 어색하다. 이미 있는
   `.login-cta { border-top: 1px solid transparent }`(공간 계산용 자리 차지 선, C-v3-6)를 이
   상태에서만 실제 선으로 바꿔 «이 버튼 아래 더 있다»는 요철(설정 화면 그룹 헤어라인과 같은 값
   --login-group-line)을 준다 — 새 색·그림자 종류 0. */
@media (max-width: 520px) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface.has-login-scroll-more .login-cta {
    border-top-color: var(--login-group-line);
  }
}

/* login-sheet-scroll-structure(2026-09-25, 중앙 캡처 판정 18:4x — 375×667 «접근 권한 요청» 부제가 주 버튼 뒤로 잘림):
   시트 = 스크롤 본문 + 바닥 고정 주 동작 영역(.login-cta sticky bottom 0) 두 칸. DOM 은 Tab 순서 계약(§B-3: 유지 행 →
   주 버튼 → 다른 방법) 때문에 그대로 두고, 두 칸의 경계를 «스크롤 영역의 바닥 인셋»으로 선언한다 — 네이티브 시트가 바닥
   막대 높이만큼 스크롤 뷰 인셋을 갖는 것(HIG 시트 + 하단 막대)과 같은 구조, 첨부 시트의 scroll-padding-block 과 같은 수단.
   ⓐ scroll-padding-bottom = 주 동작 영역 높이(app.js syncLoginCtaBlock → --login-cta-block) + 32(아래 ⓑ 페이드 높이 —
      초점 링이 페이드에 흐려지지 않게, 1차 값 8 은 WebKit 캡처에서 링 아래쪽이 흐려졌다): 초점 이동·scrollIntoView 가
      행을 경계 «위»에 세운다(Chromium 320×568 Tab → «접근 권한 요청» 가림 44/44px → 0, WCAG 2.2 SC 2.4.11). 흐름 안
      주 버튼(Android 입력 중)은 고정 영역이 아니므로 0.
   ⓑ 모바일에서 아래로 더 있는 동안(has-login-scroll-more) 경계 선(위 규칙) 위 32 에 창 면 색 페이드 — 데스크톱 창 하단
      페이드와 같은 값(32 · 124ms · 모션 줄이기면 전환 없음). 첫 화면에서 걸친 행은 버튼에 «썰리지» 않고 경계에서
      흐려진다(스크롤 단서). 끝까지 내리면 사라진다 — 그때 마지막 행은 경계 위에 온전히 선다(시험으로 고정). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface {
  scroll-padding-bottom: calc(var(--login-cta-block, 0px) + 32px);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-login-cta-inflow .login-surface {
  scroll-padding-bottom: 0;
}

@media (max-width: 520px) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-cta::before {
    content: "";
    position: absolute;
    right: 0;
    bottom: calc(100% + 1px);
    left: 0;
    height: 32px;
    background: linear-gradient(to bottom, transparent, var(--login-window-bg));
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--wds-spring-effect-fast-duration, 124ms) linear;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface.has-login-scroll-more .login-cta::before {
    opacity: 1;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-login-cta-inflow .login-cta::before {
    display: none;
  }
}

@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-cta::before {
    transition: none;
  }
}

/* 20260924-login-r32-motion (login-screen-r32-build-b, package-v3 §B-1·B-2) — 모션 5순간 + 마이크로.
   시간·곡선은 wds 스프링 토큰(372 spatial · 256 spatial-fast · 188 effect · 124 effect-fast)과
   --wizon-sheet-exit-ms/-ease(300)만 쓴다. 예외 1: 흔들림 320ms(--login-shake-duration — 2차 목업 값을
   유지, 671 §9.10 등재 제안). 불투명도 전환은 기존 wizon-scrim-in/-out, 시트 퇴장은 wizon-sheet-down,
   대기 맥동은 기존 pulse 키프레임을 재사용한다(새 키프레임 = 창 확대 입·퇴장 · 보기 밀기 2 · 흔들림).
   모션 줄이기(HIG Reduce Motion): 미끄러짐·확대 → 124ms 디졸브, 맥동·흔들림·확대 누름 끔(CSS + JS FLIP 건너뜀). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog {
  --login-shake-duration: 320ms;
}

@keyframes login-window-in {
  from { opacity: 0; transform: scale(0.96); }
  to { opacity: 1; transform: none; }
}

@keyframes login-window-out {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: scale(0.98); }
}

@keyframes login-view-forward {
  from { opacity: 0; transform: translateX(24px); }
  to { opacity: 1; transform: none; }
}

@keyframes login-view-back {
  from { opacity: 0; transform: translateX(-24px); }
  to { opacity: 1; transform: none; }
}

@keyframes login-shake {
  0%, 100% { transform: none; }
  20% { transform: translateX(-8px); }
  40% { transform: translateX(7px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(3px); }
}

/* 등장: 배경이 뒤 위로 188(effect) 에 깔리고, 창은 .96→1 + 불투명도 372(spatial 스프링). */
@media (min-width: 521px) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog[open] .login-surface {
    animation: login-window-in var(--wds-spring-spatial-default-duration, 372ms)
      var(--wds-spring-spatial-default, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog[open]::backdrop {
    animation: wizon-scrim-in var(--wds-spring-effect-default-duration, 188ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }

  /* 성공 퇴장: 창 .98 + 불투명도 0, 배경 동시 — exit 300. */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-leaving .login-surface {
    animation: login-window-out var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-leaving::backdrop {
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
  }
}

/* 보기 전환: 창(시트) 안 블록 x ±24 + 불투명도, spatial-fast 256. 창 높이는 JS FLIP(데스크톱 1곳). */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface.is-view-forward > :not(.login-grab) {
  animation: login-view-forward var(--wds-spring-spatial-fast-duration, 256ms)
    var(--wds-spring-spatial-fast, cubic-bezier(0.16, 1, 0.3, 1)) both;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface.is-view-back > :not(.login-grab) {
  animation: login-view-back var(--wds-spring-spatial-fast-duration, 256ms)
    var(--wds-spring-spatial-fast, cubic-bezier(0.16, 1, 0.3, 1)) both;
}

/* 대기: 패스키 글리프 맥동 1s(effect) + 라벨 «패스키 확인 중…» */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-primary[data-busy="true"] {
  cursor: progress;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-primary[data-busy="true"] .login-primary-glyph {
  animation: pulse 1s var(--wds-spring-effect-default, ease-in-out) infinite;
}

/* 비활성(잠금 카운트다운·오프라인·로딩)은 반전 면을 흐리게(.56 — 목업 3차) — 회색 면으로 바꾸면 흰 라벨이
   거의 안 보인다. 대기(data-busy)는 «눌린 채 진행 중»이라 흐리지 않는다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-primary:disabled {
  background: var(--wizon-ai-action-primary-bg);
  color: var(--wizon-ai-action-primary-fg);
  opacity: 0.56;
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-primary[data-busy="true"]:disabled {
  opacity: 1;
}

/* 실패: 주 버튼 흔들림 ±8→3px(320ms, effect 곡선) + 버튼 위 6px 점 문장 */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-primary.is-shaking {
  animation: login-shake var(--login-shake-duration) var(--wds-spring-effect-default, ease-out);
}

/* 마이크로: 누름 .98(effect-fast 124) — 행 면·스위치·타일 전환은 a 절에 이미 124 로 있다. */
.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-primary {
  transition:
    transform var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    background-color var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast);
}

.chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-primary:active:not(:disabled) {
  transform: scale(0.98);
}

/* 폰 시트(≤520): detent 높이 전환은 spatial 372, 키보드 보정(bottom)은 spatial-fast 256. 드래그 중엔
   손가락을 1:1 따라가도록 전환을 끈다. 성공 퇴장은 정본 wizon-sheet-down(300). */
@media (max-width: 520px) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface {
    transition:
      height var(--wds-spring-spatial-default-duration, 372ms) var(--wds-spring-spatial-default, ease-out),
      bottom var(--wds-spring-spatial-fast-duration, 256ms) var(--wds-spring-spatial-fast, ease-out);
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface.is-dragging {
    transition: none;
  }

  /* 잡는 곳 = 손잡이 + 시트 머리(버튼·입력 위에서는 JS 가 드래그를 시작하지 않는다). */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-head {
    touch-action: none;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-leaving .login-surface {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
  }
}

/* R-32 모바일 히어로 ⓑ 모션 줄이기(발주서 §1-5): bottom 의 372ms 스프링 미끄러짐은 위치 이동이라
   HIG Reduce Motion 대상 — 즉시(전환 없음), opacity 만 124ms 디졸브로 남긴다. */
@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-hero {
    transition: opacity var(--wds-spring-effect-fast-duration, 124ms) linear;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* 전역 하한(20260710-reduced-motion-floor: 모든 애니 0.01ms !important)의 «세부 조정» — 로그인 창의
     등장·보기 전환만은 HIG Reduce Motion 대로 «즉시»가 아니라 짧은 디졸브(124)로 둔다(package-v3 §B-1).
     미끄러짐·확대는 없고 불투명도만 바뀐다 — 전정 자극이 없는 전환이라 하한의 취지와 충돌하지 않는다. */
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog[open] .login-surface,
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog[open]::backdrop,
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface.is-view-forward > :not(.login-grab),
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface.is-view-back > :not(.login-grab) {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-leaving .login-surface,
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-dialog.is-leaving::backdrop,
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-primary.is-shaking,
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-primary[data-busy="true"] .login-primary-glyph {
    animation: none;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface,
  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-primary {
    transition: none;
  }

  .chatgpt-reference-v1.wds-pattern-fidelity-v2 .login-surface .login-primary:active:not(:disabled) {
    transform: none;
  }
}

.wds-pattern-fidelity-v2.is-auth-gate .app-shell {
  background: var(--ds-color-bg);
  visibility: hidden;
  pointer-events: none;
  user-select: none;
}

.wds-pattern-fidelity-v2.is-auth-gate .ai-chat-header,
.wds-pattern-fidelity-v2.is-auth-gate .composer-surface,
.wds-pattern-fidelity-v2.is-auth-gate .calm-chat-home,
.wds-pattern-fidelity-v2.is-auth-gate .context-pane {
  filter: none;
}

@media (max-width: 900px) {
  .wds-pattern-fidelity-v2 .app-shell {
    grid-template-columns: 1fr;
  }

  .wds-pattern-fidelity-v2 .sidebar {
    min-height: auto;
  }

  .wds-pattern-fidelity-v2 .ai-chat-header,
  .wds-pattern-fidelity-v2 .composer-surface {
    width: calc(100% - var(--ds-space-5));
  }

  .wds-pattern-fidelity-v2 .ai-chat-header {
    justify-content: flex-start;
    margin-top: var(--ds-space-4);
  }

  .wds-pattern-fidelity-v2 .message-list {
    padding: var(--ds-space-5) var(--ds-space-3);
  }

  .wds-pattern-fidelity-v2 .calm-chat-home {
    padding: var(--ds-space-5);
  }

  .wds-pattern-fidelity-v2 .quick-start-grid {
    grid-template-columns: 1fr;
  }

  .wds-pattern-fidelity-v2 .nav-section {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .wds-pattern-fidelity-v2 .nav-item {
    grid-template-columns: 24px minmax(0, 1fr);
    justify-content: start;
    min-width: 0;
    text-align: left;
  }

  .wds-pattern-fidelity-v2 .nav-item span:not(.nav-icon) {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

}

/* ChatGPT theme parity layer: keep the WIZON shell light/dark colors on one palette. */
.chatgpt-reference-v1 {
  /* 2026-08-01 셸 층 중복 제거. body 에 셸 클래스 6개가 index.html 에서 항상 함께 붙어
     있어(토글 없음), 같은 특이도로 같은 속성을 거는 선언은 마지막 것만 이긴다. 여기서
     지던 선언은 화면에 한 번도 닿은 적이 없어 지웠다 — 계산 스타일 무변화로 검증. */
  --cg-chat-max-width: var(--wizon-ai-chat-max-width, 800px);
  --cg-chat-gutter: var(--wizon-ai-chat-gutter, 20px);
  --cg-thread-gutter: var(--wizon-ai-thread-gutter, 16px);
  --cg-composer-gutter: var(--wizon-ai-composer-gutter, 12px);
  --cg-composer-compact-gutter: var(--wizon-ai-composer-compact-gutter, 8px);
  --cg-composer-radius: var(--wizon-ai-composer-radius, 24px);
  --cg-menu-gutter: var(--wizon-ai-menu-gutter, 6px);
  --cg-menu-radius: var(--wizon-ai-menu-radius, 12px);
  --cg-bg: var(--wizon-ai-canvas, #fcfcfc);
  --cg-surface: var(--wizon-ai-surface, #ffffff);
  --cg-sidebar-bg: var(--wizon-ai-sidebar-bg, #f9f9f9);
  --cg-sidebar-border: var(--wizon-ai-sidebar-border, #ececec);
  --cg-sidebar-item-height: var(--wizon-ai-sidebar-row-height, 36px);
  --cg-sidebar-item-width: var(--wizon-ai-sidebar-row-width, 248px);
  --cg-sidebar-account-width: var(--wizon-ai-account-button-width, 246px);
  --cg-sidebar-account-height: var(--wizon-ai-account-button-height, 52px);
  --cg-text: var(--wizon-ai-text-primary, #0d0d0d);
  --cg-text-soft: var(--wizon-ai-text-secondary, #3f3f3f);
  --cg-muted: var(--wizon-ai-text-muted, #6f6f6f);
  --cg-muted-2: var(--wizon-ai-text-muted-2, #8a8a8a);
  /* 2026-08-01 복원. .chatgpt-library-preview-dialog 의 grid-template-rows 가 참조하는데
     정의가 없어 선언이 무효였다(값은 그 자리에 남아 있던 폴백 56px). ★--chatgpt-* 이름이지만
     14357행의 --chatgpt-route-gap-* 블록에 두지 않는다 — 그 블록은 .chatgpt-route-page
     스코프라 다이얼로그에 닿지 않을 수 있다. 여기(.chatgpt-reference-v1)는 body 레벨이다. */
  --chatgpt-header-height: 56px;
  --cg-hover: var(--wizon-ai-hover-bg, #ececec);
  --cg-hover-soft: var(--wizon-ai-hover-soft-bg, #f4f4f4);
  --cg-focus-bg: var(--wizon-ai-focus-bg, #f3f3f3);
  /* 20260915-focus-ring-contrast-global (judge continuation-surfaces-design-judge D-1/D-2):
     반투명 halo rgba(13,13,13,.16)/rgba(255,255,255,.18)(--wizon-ai-focus-ring, shared
     토큰 파일)는 인접 면 실효 대비 1.42:1(L)/1.78:1(D) — WCAG 2.2 SC 1.4.11(AA 3:1)
     FAIL, 13개 컨트롤·양 테마 전부(실 브라우저 Tab 순회 + 400ms 정착 + 배경 합성 픽셀
     실측). 표면별로 12곳 이상을 따로 고치는 대신, 이미 주버튼이 쓰는 불투명 ink 토큰
     --cg-send-bg(라이트 #0d0d0d=19.4:1 / 다크 #ffffff, 테마마다 자동 반전)로 링 색
     자체를 재정의한다 — 블랭킷(41534행 근방 20260710-focus-visible-base)과 컴포넌트별
     :focus-visible 규칙이 전부 var() 간접참조라 이 2줄만으로 전부 따라온다. */
  --cg-focus-ring: var(--cg-send-bg);
  --cg-focus-visible-ring: var(--cg-focus-ring);
  --cg-scrollbar-thumb: var(--wizon-ai-scrollbar-thumb, #c7c7c7);
  --cg-scrollbar-track: var(--wizon-ai-scrollbar-track, transparent);
  --cg-sidebar-active-bg: var(--wizon-ai-sidebar-active-bg, rgba(0, 0, 0, 0.05));
  --cg-menu-bg: var(--wizon-ai-popover-bg, #ffffff);
  --cg-menu-border: var(--wizon-ai-popover-border, #e3e3e3);
  --cg-user-bubble-bg: var(--wizon-ai-message-user-bg, #f4f4f4);
  --cg-composer-bg: var(--wizon-ai-composer-bg, #ffffff);
  --cg-composer-border: var(--wizon-ai-composer-border, rgba(0, 0, 0, 0.05));
  --cg-input-placeholder: var(--wizon-ai-input-placeholder, #8a8a8a);
  --cg-send-bg: var(--wizon-ai-action-primary-bg, #0d0d0d);
  --cg-send-fg: var(--wizon-ai-action-primary-fg, #ffffff);
  --cg-disabled-bg: var(--wizon-ai-disabled-bg, #d9d9d9);
  /* 의미색 통합 S2 — 셸 danger(#ba2623)가 아니라 WDS error-text 사다리를 직접
     alias 한다(판정문 D15ⓐ′). canvas 실측(getComputedStyle→canvas→getImageData):
     라이트 6.196→6.47:1, 다크 7.52→7.592:1(둘 다 AA 텍스트 4.5:1 이상, 대표 표본
     — 이 토큰의 소비처 24곳 전부를 개별 재측정하지는 않았다). */
  --cg-danger-fg: var(--wds-color-error-text, rgb(185, 28, 28));
  /* 경고(warning). danger와 같은 층에 둔다 — 이 자리가 비어 있던 탓에 경고 표면이
     개인 설정인 --accent(ink/gray/blue)를 빌려 썼고, 파란 accent를 고른 사용자에게는
     의료 경고가 파란 막대로 나왔다. 위험도가 취향으로 결정되면 안 된다.
     셸 토큰이 테마별 값을 갖는다(라이트 amber-700 / 다크 amber-400, 의미색 S3) — 한 값으로는
     두 테마를 못 덮기 때문이다: amber-700은 라이트 4.57:1인데 다크 3.21:1, amber-500은
     다크 7.50:1인데 라이트 1.95:1. amber-500(구 다크값)은 amber-700·amber-500 두 칸만
     비교하고 amber-400을 보지 않은 채 골랐던 것 — amber-400은 실렌더 다크 대비
     12.58:1(canvas getImageData 실측, 판정 #60)로 amber-500의 9.78:1보다 높다. 폴백도
     토큰이라 색 리터럴이 늘지 않는다. */
  --cg-warning-fg: var(--wizon-ai-warning-text);
  --cg-danger-bg: var(--wizon-ai-danger-bg, #ff002a);
  --cg-danger-bg-hover: var(--wizon-ai-danger-bg-hover, #e50026);
  /* doc 167 §4.1 (phantom-token cleanup): these two names were consumed by the question-card
     family as if they were tokens but never defined — every use silently fell to its literal
     fallback (and --cg-surface-secondary had DIFFERENT fallbacks per call site, 0.03 vs 0.05).
     Defined here for real: success rides the art-direction green axis (doc 161, dark-aware),
     the secondary surface rides the themed hover-soft neutral. */
  --cg-success: var(--ad-green, #15803d);
  --cg-surface-secondary: var(--cg-hover-soft);
  --cg-shadow: var(--wizon-ai-composer-shadow, 0 0 0 1px rgba(0, 0, 0, 0.04), 0 2px 8px rgba(0, 0, 0, 0.04), 0 4px 80px 8px rgba(0, 0, 0, 0.024));
  --cg-menu-shadow: var(--wizon-ai-popover-shadow, 0 8px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.62));
  --cg-dialog-shadow: var(--wizon-ai-dialog-shadow, var(--cg-menu-shadow));
  /* anti-drift reconcile: --cg-floating-shadow was never defined anywhere, so
     every var(--cg-floating-shadow, <literal>) call site always fell through
     to its own hardcoded fallback (same phantom-token shape as the
     --cg-success cleanup above). 7 call sites, only 4 distinct values —
     defined here for real, one token per value, fallback dropped at each
     site. */
  --cg-shadow-float-lg: 0 18px 48px rgba(15, 15, 15, 0.16);
  --cg-shadow-float-xl: 0 24px 72px rgba(0, 0, 0, 0.24);
  --cg-shadow-float-md: 0 18px 54px rgba(0, 0, 0, 0.2);
  --cg-shadow-float-xs: 0 1px 2px rgba(15, 15, 15, 0.12);
  --cg-transition-basic-duration: var(--wizon-ai-transition-basic-duration, 150ms);
  --cg-ease-enter: var(--wizon-ai-ease-enter, cubic-bezier(0.19, 1, 0.22, 1));
  --cg-ease-exit: var(--wizon-ai-ease-exit, cubic-bezier(0.8, 0, 0.4, 1));
  --cg-ease-move: var(--wizon-ai-ease-move, cubic-bezier(0.65, 0, 0.35, 1));
  color-scheme: light;
}

body.chatgpt-reference-v1 {
  background: var(--cg-bg);
  color: var(--cg-text);
  font-family: var(--wizon-ai-font-family, -apple-system-body, ui-sans-serif, -apple-system, "system-ui", "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol");
  overflow: hidden;
}

body.chatgpt-reference-v1 button,
body.chatgpt-reference-v1 input,
body.chatgpt-reference-v1 textarea,
body.chatgpt-reference-v1 select {
  font-family: inherit;
}

@media (max-width: 900px) {
  .chatgpt-reference-v1 {
    /* 20260705-mobile-account-bottom-safe: keep the full account row inside short mobile viewports. */
    --cg-sidebar-account-height: 44px;
  }

  .chatgpt-reference-v1 .chatgpt-account-area {
    padding-top: 6px;
  }

  .chatgpt-reference-v1 .chatgpt-account-button {
    padding: 5px 6px 5px 8px;
  }
}

html[data-theme="dark"] .chatgpt-reference-v1 {
  color-scheme: dark;
}

html[data-theme="dark"] body.chatgpt-reference-v1,
html[data-theme="dark"] body.chatgpt-reference-v1.premium-product-shell-v1,
html[data-theme="dark"] body.chatgpt-reference-v1.service-product-v28 {
  background: var(--cg-bg);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .app-shell,
.chatgpt-reference-v1 .main-pane,
.chatgpt-reference-v1 .workbench,
.chatgpt-reference-v1 .chat-pane,
.chatgpt-reference-v1.is-auth-gate .app-shell {
  background: var(--cg-bg);
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .app-shell,
body.chatgpt-reference-v1 .main-pane,
body.chatgpt-reference-v1 .workbench {
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}

/* 20260912-chat-keyboard-stick (chat-soft-keyboard-last-message-visible, 오너 승인 G):
 * .chat-pane 만 --wizon-vvh(handleViewportReflow → syncMobileViewportMetrics 가 매
 * visualViewport resize/scroll·virtualKeyboard geometrychange 마다 갱신)를 쓴다. 폴백은
 * 그대로 100dvh라 --wizon-vvh 가 아직 안 채워진 첫 페인트나 데스크톱(키보드 없음 →
 * visualViewport.height == innerHeight == 100dvh 수치)에서는 완전히 no-op이다. 형제
 * app-shell/main-pane/workbench는 그대로 100dvh 유지 — 이 레인의 경쟁 원인 실측(Playwright
 * WebKit, 뷰포트 852→532)이 가리키는 것은 .chat-pane/.message-list 뿐이라 다른 셸 레이어를
 * 건드릴 근거가 없다.
 */
body.chatgpt-reference-v1 .chat-pane {
  height: var(--wizon-vvh, 100dvh);
  min-height: 0;
  overflow: hidden;
}

html[data-theme="dark"] body.chatgpt-reference-v1 .app-shell,
html[data-theme="dark"] body.chatgpt-reference-v1 .main-pane,
html[data-theme="dark"] body.chatgpt-reference-v1 .workbench,
html[data-theme="dark"] body.chatgpt-reference-v1 .chat-pane,
html[data-theme="dark"] body.chatgpt-reference-v1.service-product-v28 .chat-pane,
html[data-theme="dark"] body.chatgpt-reference-v1.premium-product-shell-v1 .app-shell {
  background: var(--cg-bg);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .sidebar,
.chatgpt-reference-v1.sidebar-collapsed .chatgpt-sidebar-rail {
  background: var(--cg-sidebar-bg);
  border-right-color: var(--cg-sidebar-border);
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .sidebar {
  height: 100dvh;
  min-height: 0;
  overflow: visible;
}

body.chatgpt-reference-v1.sidebar-collapsed .chatgpt-sidebar-rail {
  height: 100dvh;
  min-height: 0;
  overflow: visible;
}

html[data-theme="dark"] body.chatgpt-reference-v1 .sidebar,
html[data-theme="dark"] body.chatgpt-reference-v1.service-product-v28 .sidebar,
html[data-theme="dark"] body.chatgpt-reference-v1.premium-product-shell-v1 .sidebar,
html[data-theme="dark"] body.chatgpt-reference-v1.sidebar-collapsed .chatgpt-sidebar-rail {
  background: var(--cg-sidebar-bg);
  border-right-color: var(--cg-sidebar-border);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-sidebar-head strong,
.chatgpt-reference-v1 .brand-row strong,
.chatgpt-reference-v1 .chatgpt-sidebar-link,
.chatgpt-reference-v1 .new-chat-button.chatgpt-sidebar-link,
.chatgpt-reference-v1 .new-chat-button,
.chatgpt-reference-v1 .theme-toggle,
.chatgpt-reference-v1 .nav-item,
.chatgpt-reference-v1 .nav-item span:not(.nav-icon),
.chatgpt-reference-v1 .nav-item.active span:not(.nav-icon),
.chatgpt-reference-v1 .chatgpt-recent-title,
.chatgpt-reference-v1 .recent-item,
.chatgpt-reference-v1 .recent-primary,
.chatgpt-reference-v1 .chatgpt-account-button,
.chatgpt-reference-v1 .chatgpt-account-copy strong,
.chatgpt-reference-v1 .chatgpt-account-menu-head strong,
.chatgpt-reference-v1 .chatgpt-home-title,
.chatgpt-reference-v1 .message-body,
.chatgpt-reference-v1 .message-body p,
.chatgpt-reference-v1 .message-body li,
.chatgpt-reference-v1 .message-body h3,
.chatgpt-reference-v1 .message-body h4,
.chatgpt-reference-v1 .message-body h5,
.chatgpt-reference-v1 .message-body strong,
.chatgpt-reference-v1 .message.assistant .message-body,
.chatgpt-reference-v1 .message.system .message-body,
.chatgpt-reference-v1 .composer-surface textarea,
.chatgpt-reference-v1 .composer-menu-copy strong,
.chatgpt-reference-v1 .chatgpt-settings-head h2,
.chatgpt-reference-v1 .chatgpt-settings-panel h3 {
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-icon-button,
.chatgpt-reference-v1 .chatgpt-rail-button,
.chatgpt-reference-v1 .composer-icon-button,
.chatgpt-reference-v1 .message-action-button,
.chatgpt-reference-v1 .feedback-row .message-action-button,
.chatgpt-reference-v1 .composer-menu-icon,
.chatgpt-reference-v1 .composer-menu-chevron,
.chatgpt-reference-v1 .reasoning-check,
.chatgpt-reference-v1 .composer-mode-icon,
.chatgpt-reference-v1 .composer-mode-remove {
  color: var(--cg-text-soft);
}

.chatgpt-reference-v1 .chatgpt-sidebar-shortcut,
.chatgpt-reference-v1 .sidebar .section-label,
.chatgpt-reference-v1 .chatgpt-recent-title svg,
.chatgpt-reference-v1 .chatgpt-recent-header-action,
.chatgpt-reference-v1 .recent-action,
.chatgpt-reference-v1 .recent-primary small,
.chatgpt-reference-v1 .chatgpt-account-copy small,
.chatgpt-reference-v1 .chatgpt-account-menu-head small,
.chatgpt-reference-v1 .settings-description,
.chatgpt-reference-v1 .composer-model-pill,
.chatgpt-reference-v1 .composer-menu-copy small,
.chatgpt-reference-v1 .reasoning-menu-title,
.chatgpt-reference-v1 .source-snippet,
.chatgpt-reference-v1 .evidence-card summary small,
.chatgpt-reference-v1 .evidence-note {
  color: var(--cg-muted);
}

/* 20260710-hover-pointer-guard (아트디렉션 P0-1): 통합 인터랙션 상태 레이어를 hover/state로 분리.
   :hover는 진짜 hover 포인터에서만 — iOS Safari는 탭 후 :hover가 남아(첫탭 hover 문제, doc 147 규칙1)
   아이콘버튼·사이드바·recent 행 등이 눌린 색으로 고착됐다. 상태(.active/.is-selected/[aria-expanded])는
   포인터와 무관하므로 가드 밖 유지. 새 :hover 규칙은 반드시 @media (hover: hover) 안에 쓸 것. */
.chatgpt-reference-v1 .chatgpt-sidebar-link.active,
.chatgpt-reference-v1 .nav-item.active,
.chatgpt-reference-v1 .chatgpt-account-button[aria-expanded="true"],
.chatgpt-reference-v1 .chatgpt-settings-tabs button.is-active,
.chatgpt-reference-v1 .recent-item.is-selected,
.chatgpt-reference-v1 .composer-model-pill[aria-expanded="true"] {
  background: var(--cg-hover);
  color: var(--cg-text);
}
@media (hover: hover) {
  .chatgpt-reference-v1 .chatgpt-sidebar-link:hover,
  .chatgpt-reference-v1 .new-chat-button:hover,
  .chatgpt-reference-v1 .theme-toggle:hover,
  .chatgpt-reference-v1 .nav-item:hover,
  .chatgpt-reference-v1 .chatgpt-icon-button:hover,
  .chatgpt-reference-v1 .chatgpt-rail-button:hover,
  .chatgpt-reference-v1 .chatgpt-account-button:hover,
  .chatgpt-reference-v1 .chatgpt-settings-tabs button:hover,
  .chatgpt-reference-v1 .chatgpt-recent-header-action:hover,
  .chatgpt-reference-v1 .recent-item:hover,
  .chatgpt-reference-v1 .recent-action:hover,
  .chatgpt-reference-v1 .chatgpt-recent-menu button:hover:not(:disabled),
  .chatgpt-reference-v1 .composer-icon-button:hover,
  .chatgpt-reference-v1 .composer-model-pill:hover,
  .chatgpt-reference-v1 .composer-menu-item:hover,
  .chatgpt-reference-v1 .reasoning-menu-item:hover,
  .chatgpt-reference-v1 .message-action-button:hover,
  .chatgpt-reference-v1 .feedback-row .message-action-button:hover {
    background: var(--cg-hover);
    color: var(--cg-text);
  }
}

.chatgpt-reference-v1 .chatgpt-sidebar-link.active,
.chatgpt-reference-v1 .chatgpt-sidebar-link.active:hover {
  background: var(--cg-sidebar-active-bg);
}

.chatgpt-reference-v1 .chatgpt-more-menu,
.chatgpt-reference-v1 .chatgpt-account-menu,
.chatgpt-reference-v1 .chatgpt-recent-menu,
.chatgpt-reference-v1 .composer-popover,
.chatgpt-reference-v1 .reasoning-popover,
.chatgpt-reference-v1 .chatgpt-settings-dialog {
  background: var(--cg-menu-bg);
  border-color: var(--cg-menu-border);
  color: var(--cg-text);
  box-shadow: var(--cg-menu-shadow);
}

.chatgpt-reference-v1 .chatgpt-more-menu button,
.chatgpt-reference-v1 .chatgpt-account-menu button,
.chatgpt-reference-v1 .chatgpt-recent-menu button,
.chatgpt-reference-v1 .composer-menu-item,
.chatgpt-reference-v1 .reasoning-menu-item,
.chatgpt-reference-v1 .chatgpt-settings-tabs button,
.chatgpt-reference-v1 .settings-row,
.chatgpt-reference-v1 .settings-pill,
.chatgpt-reference-v1 .settings-combobox,
.chatgpt-reference-v1 .settings-switch {
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-more-menu button:hover,
.chatgpt-reference-v1 .chatgpt-account-menu button:hover {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .chatgpt-account-menu {
  border: 0;
  border-radius: var(--chatgpt-account-menu-radius, var(--wizon-ai-radius-popover));
  box-shadow: var(--cg-menu-shadow);
}

.chatgpt-reference-v1 .chatgpt-settings-dialog {
  border: 0;
  border-radius: 16px;
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.62);
}

.chatgpt-reference-v1 .reasoning-popover {
  width: 124px;
  min-width: 124px;
  padding: 6px 0;
  border: 0;
  border-radius: 16px;
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.62);
}

.chatgpt-reference-v1 .reasoning-model-menu {
  width: 126px;
  min-width: 126px;
}

.chatgpt-reference-v1 .chatgpt-recent-menu {
  border: 0;
  border-radius: 16px;
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.62);
}

.chatgpt-reference-v1 .chatgpt-recent-menu [data-recent-menu-action^="delete"]:not(:disabled) {
  color: var(--cg-danger-fg);
}

.chatgpt-reference-v1 .chatgpt-settings-tabs button:hover,
.chatgpt-reference-v1 .chatgpt-settings-tabs button.is-active {
  background: rgba(0, 0, 0, 0.04);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-menu-separator,
.chatgpt-reference-v1 .composer-menu-divider,
.chatgpt-reference-v1 .chatgpt-settings-head,
.chatgpt-reference-v1 .chatgpt-settings-tabs,
.chatgpt-reference-v1 .settings-row {
  border-color: var(--cg-menu-border);
}

.chatgpt-reference-v1 .chatgpt-menu-separator,
.chatgpt-reference-v1 .composer-menu-divider {
  background: var(--cg-menu-border);
}

.chatgpt-reference-v1 .settings-pill,
.chatgpt-reference-v1 .settings-combobox,
.chatgpt-reference-v1 .prompt-chip {
  background: var(--cg-surface);
  border-color: rgba(0, 0, 0, 0);
  color: var(--cg-text-soft);
}

.chatgpt-reference-v1 .settings-combobox:hover,
.chatgpt-reference-v1 .settings-pill:hover {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .settings-switch {
  background: #3a83f7;
}

html[data-theme="dark"] .chatgpt-reference-v1 .settings-switch::after {
  background: var(--cg-bg);
}

.chatgpt-reference-v1 .prompt-chip:hover {
  background: var(--cg-hover);
  border-color: var(--cg-menu-border);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  min-height: 40px;
  padding: 10px 16px 10px 14px;
  /* ⓑ 하드 테두리가 바로 아래 컴포저와 시선을 다퉜다. iOS 레퍼런스의 첨부 칩처럼
     «테두리 없는 연회색 면»으로 바꿔 무게를 낮춘다. 칩 자체는 제품 진입점이라 없애지
     않는다. 대비 실측: 다크 13.63:1(AA 4.5 여유). 테두리는 0 이 아니라 transparent —
     굵기를 지우면 레이아웃이 1px 씩 움직인다. */
  border: 1px solid transparent;
  border-radius: 100px;
  background: var(--cg-hover-soft);
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  line-height: 21px;
}

.chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip .prompt-chip-icon {
  display: inline-flex;
  width: 20px;
  height: 20px;
  color: #8f8f8f;
}

.chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip .prompt-chip-icon svg {
  width: 20px;
  height: 20px;
  stroke: currentColor;
}

.chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip:hover {
  background: rgba(0, 0, 0, 0.04);
  border-color: transparent;
  color: var(--cg-text);
}

html[data-theme="dark"] .chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip {
    border-color: transparent;
    background: var(--cg-hover-soft);
  color: #ececec;
}

html[data-theme="dark"] .chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip .prompt-chip-icon {
  color: #b4b4b4;
}

html[data-theme="dark"] .chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: transparent;
  color: #ececec;
}

.chatgpt-reference-v1 .message.user .message-body {
  background: var(--cg-user-bubble-bg);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .message-body code:not(pre code) {
  background: var(--cg-hover);
  color: var(--cg-text);
}

/* ★갭 #65(doc 669 ※4, 원 번호 #64, 오너 09-12 결정 3C): 이 규칙은 `.code-card` 안의 `<pre class="code-block">`
   와 특이도가 동률(0,2,1)이라 소스 순서로 항상 이겨 코드 카드를 테마와 무관하게 늘 어둡게
   고정시켰다(원인 실측). `.code-card` 를 배제해 코드 카드는 자기 토큰(`--wizon-ai-code-*`)을
   따르게 하고, 이 규칙은 code-card 가 아닌 다른 원시 `<pre>`(예: 오류 상세·붙여넣기 미리보기)
   전용으로 남는다 — 그 표면의 시각은 이 후보의 범위 밖이라 리터럴을 그대로 둔다. */
.chatgpt-reference-v1 .message-body pre:not(.code-card *),
.chatgpt-reference-v1 .code-block:not(.code-card *) {
  background: #0d0d0d;
  color: #ececec;
}

html[data-theme="dark"] .chatgpt-reference-v1 .message-body pre:not(.code-card *),
html[data-theme="dark"] .chatgpt-reference-v1 .code-block:not(.code-card *) {
  background: #171717;
  color: #ececec;
}

.chatgpt-reference-v1 .chatgpt-composer-shell,
.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell {
  background: var(--cg-composer-bg);
  border-color: var(--cg-composer-border);
  color: var(--cg-text);
  box-shadow: var(--cg-shadow);
}

html[data-theme="dark"] body.chatgpt-reference-v1.service-product-v28 .composer-surface.chatgpt-composer-shell,
html[data-theme="dark"] body.chatgpt-reference-v1.premium-product-shell-v1 .composer-surface.chatgpt-composer-shell {
  background: var(--cg-composer-bg);
  border-color: var(--cg-composer-border);
  color: var(--cg-text);
  box-shadow: var(--cg-shadow);
}

html[data-chat-state="empty"] .chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell {
  box-shadow: var(--cg-shadow);
}

.chatgpt-reference-v1 .composer-surface textarea::placeholder {
  color: var(--cg-input-placeholder);
}

.chatgpt-reference-v1 .composer-mode-chip {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .composer-send-button,
.chatgpt-reference-v1 #sendButton {
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}

.chatgpt-reference-v1 .composer-send-button:hover,
.chatgpt-reference-v1 #sendButton:hover {
  background: var(--cg-send-bg);
}

.chatgpt-reference-v1 #sendButton:disabled {
  background: var(--cg-disabled-bg);
  color: var(--cg-send-fg);
}

.chatgpt-reference-v1 .recent-actions {
  background: linear-gradient(90deg, color-mix(in srgb, var(--cg-hover) 0%, transparent), var(--cg-hover) var(--chatgpt-recent-action-fade));
}

html[data-theme="dark"] .chatgpt-reference-v1 .recent-actions {
  background: linear-gradient(90deg, color-mix(in srgb, var(--cg-hover) 0%, transparent), var(--cg-hover) var(--chatgpt-recent-action-fade));
}

.chatgpt-reference-v1 .recent-select-box {
  background: var(--cg-surface);
  border-color: var(--cg-muted-2);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-recent-menu button:disabled {
  color: var(--cg-muted-2);
}

.chatgpt-reference-v1 .evidence-card,
.chatgpt-reference-v1 .source-card,
.chatgpt-reference-v1 .tool-status-card,
.chatgpt-reference-v1 .answer-activity {
  background: var(--cg-menu-bg);
  border-color: var(--cg-menu-border);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .source-card-head strong,
.chatgpt-reference-v1 .source-card-head span,
.chatgpt-reference-v1 .evidence-card summary span {
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chat-pane {
  position: relative;
}

body.chatgpt-reference-v1 .chatgpt-conversation-actions {
  position: absolute;
  top: 8px;
  right: 22px;
  z-index: 40;
  display: inline-flex;
  align-items: flex-start;
  gap: 0;
}

body.chatgpt-reference-v1 .chatgpt-conversation-actions[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .chatgpt-conversation-health {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  padding: 0;
  border: 0;
  border-radius: var(--ds-radius-sm);
  background: transparent;
  color: var(--cg-muted);
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-conversation-health[hidden] {
  display: none;
}
body.chatgpt-reference-v1 .chatgpt-conversation-health:hover,
body.chatgpt-reference-v1 .chatgpt-conversation-health:focus-visible {
  background: var(--cg-hover);
  color: var(--cg-text);
}
body.chatgpt-reference-v1 .chatgpt-conversation-health:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring);
  outline-offset: 1px;
}
body.chatgpt-reference-v1 .chatgpt-conversation-health[data-status="attention"] {
  color: var(--cg-warning-fg);
}
body.chatgpt-reference-v1 .chatgpt-conversation-health[data-status="approval"] {
  color: var(--ad-blue);
}
body.chatgpt-reference-v1 .chatgpt-conversation-health[data-status="unavailable"] {
  color: var(--cg-danger-fg);
}
.chatgpt-conversation-health-icon,
.chatgpt-conversation-health-icon svg {
  width: 20px;
  height: 20px;
}
.chatgpt-conversation-health-badge {
  position: absolute;
  top: 1px;
  right: 1px;
  display: inline-grid;
  place-items: center;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: var(--ds-radius-pill);
  background: var(--cg-text);
  color: var(--cg-surface);
  font-size: var(--ds-text-micro-11);
  font-weight: var(--wds-font-weight-semibold);
  line-height: 1;
}
body.chatgpt-reference-v1 .chatgpt-conversation-health[data-status="attention"] .chatgpt-conversation-health-badge {
  background: var(--cg-warning-fg);
}
body.chatgpt-reference-v1 .chatgpt-conversation-health[data-status="approval"] .chatgpt-conversation-health-badge {
  background: var(--ad-blue);
}
body.chatgpt-reference-v1 .chatgpt-conversation-health[data-status="unavailable"] .chatgpt-conversation-health-badge {
  background: var(--cg-danger-fg);
}

/* 20260710-conversation-actions-in-titlebar: on mobile the consolidated "..."
   lives in the top title bar, immediately left of the 새 채팅(compose) icon.
   z-index sits above the topbar row but below the sidebar drawer scrim (65),
   so opening the drawer covers it. */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-conversation-actions {
    position: fixed;
    top: 8px;
    right: 52px;
    z-index: 64;
  }
}

body.chatgpt-reference-v1 .chatgpt-conversation-share,
body.chatgpt-reference-v1 .chatgpt-conversation-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 36px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  line-height: 20px;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-conversation-share {
  gap: 6px;
  width: 98px;
  padding: 0 12px;
  font-weight: 500;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-conversation-more {
  width: 36px;
  padding: 0;
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-conversation-share:hover,
body.chatgpt-reference-v1 .chatgpt-conversation-more:hover,
body.chatgpt-reference-v1 .chatgpt-conversation-more[aria-expanded="true"] {
  background: var(--cg-hover);
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-conversation-share svg {
  width: 20px;
  height: 20px;
}

body.chatgpt-reference-v1 .chatgpt-conversation-more svg {
  width: 20px;
  height: 20px;
}

body.chatgpt-reference-v1 .chatgpt-conversation-menu {
  position: absolute;
  top: 36px;
  right: 0;
  z-index: 45;
  display: grid;
  /* 20260710-menu-fit: size to the widest row (그룹 채팅 시작 + 준비 중 badge) so nothing
     wraps to a second line on desktop. Mobile bottom sheet overrides width below. */
  width: max-content;
  min-width: 186px;
  max-width: 260px;
  padding: 6px 0;
  border: 0;
  border-radius: 16px;
  background: var(--cg-menu-bg);
  color: var(--cg-text);
  box-shadow: var(--cg-menu-shadow);
}

body.chatgpt-reference-v1 .chatgpt-conversation-menu[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .chatgpt-conversation-menu button {
  display: flex;
  align-items: center;
  gap: 6px;
  box-sizing: border-box;
  width: auto;
  height: 36px;
  min-height: 36px;
  margin: 0 6px;
  padding: 6px 14px 6px 10px;
  border: 0;
  border-radius: var(--ds-radius-sm, 10px);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-14);
  font-weight: 400;
  line-height: 20px;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-conversation-menu button:hover {
  background: var(--cg-hover-soft);
}

body.chatgpt-reference-v1 .chatgpt-conversation-menu button.danger {
  color: var(--cg-danger-fg);
}

/* 20260710-conversation-menu-consolidation: the group-chat item is an honest
   "준비 중" placeholder — muted, with a badge — until the workspace substrate lands. */
body.chatgpt-reference-v1 .chatgpt-conversation-menu button[aria-disabled="true"] {
  color: var(--cg-muted-2);
}

body.chatgpt-reference-v1 .chatgpt-conversation-menu-badge {
  margin-left: auto;
  padding: 1px 6px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-hover-soft);
  color: var(--cg-muted-2);
  font-size: var(--ds-text-micro-11);
  font-weight: 500;
  line-height: 16px;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-conversation-menu svg {
  width: 20px;
  height: 20px;
}

body.chatgpt-reference-v1 .chatgpt-share-dialog {
  width: min(520px, calc(100vw - 32px));
  max-width: none;
  padding: 0;
  border: 0;
  /* S5-c(measure.md §0 사실 확인): 셸 반경은 --wizon-ai-radius-popover(16px) — 안쪽 표면은
     --wizon-ai-radius-card(10px) 1값으로 아래 .share-v2-panel/.share-v2-btn/.share-v2-url
     이 따로 정의한다("카드 반경 ≤2" work-order 표). */
  border-radius: var(--wizon-ai-radius-popover, 16px);
  /* S5-c 수정(measure.md §S5-c 수정 4 와 같은 판단, 실렌더에서 재발 확인): 셸은 «베이스»
     표면(--cg-surface — 다크 #000/라이트 #fff)이어야 그 위 .share-v2-panel(그룹,
     --ds-color-surface-muted)이 다크에서 «밝게 올라온다». 이 규칙은 원래 --cg-menu-bg
     (다크 #2f2f2f)를 썼는데, 라이트에서는 --cg-surface 와 값이 같아(#fff) 시각 회귀가
     없지만 다크에서는 패널(33,33,33)이 셸(47,47,47)보다 더 어두워 층 역전이 났다 — 실
     Chromium 캡처로 발견(2026-09-10). */
  background: var(--cg-surface);
  color: var(--cg-text);
  opacity: 0;
  box-shadow: var(--cg-dialog-shadow);
  transform: translate3d(0, 12px, 0) scale(0.985);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
  will-change: opacity, transform;
}

body.chatgpt-reference-v1 .chatgpt-share-dialog::backdrop {
  background: rgba(0, 0, 0, 0.28);
  opacity: 0;
  transition: opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease);
}

body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="open"] {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}

body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(0.985);
}

body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="open"]::backdrop {
  opacity: 1;
}

body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="closing"]::backdrop {
  opacity: 0;
}

body.chatgpt-reference-v1 .chatgpt-share-shell {
  display: grid;
  gap: 0;
}

body.chatgpt-reference-v1 .chatgpt-share-head {
  display: flex;
  min-height: 56px;
  align-items: center;
  justify-content: space-between;
  padding: 10px 10px 10px 16px;
  border-bottom: 1px solid var(--cg-menu-border);
}

body.chatgpt-reference-v1 .chatgpt-share-head h2 {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-title-18);
  font-weight: 400;
  line-height: 28px;
}

/* S5-c fix5(정적 디자인 게이트 판정, 2026-09-11): width/height/border-radius 3줄을 지운다.
   이 클래스를 쓰는 요소는 index.html 에 딱 하나(#shareDialog 헤더 닫기 버튼)뿐이고,
   S5-b 가 넣은 `#shareDialog .chatgpt-share-close`(특이도 1,1,0)가 같은 3속성을
   32px/32px/var(--wizon-ai-radius-card, 10px) 로 이미 덮는다(0,2,1 < 1,1,0) — 여기 값은
   한 번도 그려진 적이 없다. 실렌더 getComputedStyle 로 제거 전/후 32px·10px 동일 확인
   (1440·390 두 뷰포트). 죽은 채 남아 §A 반경·컨트롤 높이 «종류»를 각각 1씩 부풀렸다. */
body.chatgpt-reference-v1 .chatgpt-share-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-share-close:hover {
  background: var(--cg-hover);
}

body.chatgpt-reference-v1 .chatgpt-share-body {
  display: grid;
  /* S5-c(measure.md §8.3·§8.5 세로 리듬 확정): gap/패딩을 같은 스케일(--ds-space-3, 12px)
     로 통일 — 이전 14/16 은 어느 리듬 종류에도 안 맞는 임의값이었다(work-order "세로
     리듬" 절, 값 종류 ≤3). 좌우 20px 는 정렬 x값 유지를 위해 그대로 둔다. */
  gap: var(--ds-space-3, 12px);
  padding: var(--ds-space-3, 12px) var(--ds-space-5, 20px);
}

body.chatgpt-reference-v1 .chatgpt-share-status {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-body-14);
  line-height: 20px;
}
/* S5-c fix2(중앙 게이트 2차 지적 #4, 세로 리듬 표): 이 문단은 대개 비어 있다(app.js 가
   `textContent = ""` 로 초기화/정리하고, 전송 중에만 문구를 채운다). 빈 채로도 `.share-
   v2-panel { display:grid; gap:8px }` 의 그리드 아이템 한 칸을 차지해 메타 줄과 아래
   구분선(.share-v2-disclosure) 사이 간격이 이중으로 계상됐다(실측 20px, 계약 12px
   대칭). `:empty` 로 접어 자리 자체를 없앤다 — `textContent` 가 비문자열로 채워지는
   순간(위 app.js 대입) 더 이상 :empty 가 아니게 되어 같은 DOM 변경 한 번으로 다시
   보인다(별도 hidden 토글 불필요, aria-live 갱신과 동시에 표시). */
#shareDialog .chatgpt-share-status:empty {
  display: none;
}

body.chatgpt-reference-v1 .chatgpt-share-link-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
}

body.chatgpt-reference-v1 .chatgpt-share-url {
  min-width: 0;
  height: 40px;
  padding: 0 12px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-sm, 10px);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-14);
}

body.chatgpt-reference-v1 .chatgpt-share-copy {
  height: 40px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
  font: inherit;
  font-size: var(--ds-text-body-14);
  font-weight: 500;
  cursor: pointer;
}

/* ── 대화 공유 S2 신규 클래스 3종 (docs/product/662-conversation-share.md §S2) ──────────
   오너 승인 목업①②를 그대로 옮긴다. 클래스는 정확히 이 3개뿐 — 나머지는
   .chatgpt-drive-permissions-section/.chatgpt-route-action/.ds-chip 등 기존 클래스를
   재사용한다(work-order §1 범위 제약). */
body.chatgpt-reference-v1 .chatgpt-share-callout {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--cg-menu-border);
  border-left: 3px solid var(--cg-send-bg);
  border-radius: var(--cg-menu-radius);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
  font-size: var(--cg-text-meta-size);
  line-height: 19px;
}
body.chatgpt-reference-v1 .chatgpt-share-callout .wds-inline-icon { width: 18px; height: 18px; margin-top: 1px; }
body.chatgpt-reference-v1 .chatgpt-share-callout-title { display: block; margin-bottom: 3px; font-weight: var(--cg-text-strong-weight); }
/* ★렌더 함정 (1): <strong> 은 굵기만 바꾼다 — display 는 절대 건드리지 않는다. 본문
   어딘가의 전역 규칙이 strong{display:block} 을 매기면 Callout 안 인라인 강조가 줄바꿈되어
   문장이 깨진다. 이 셀렉터로 명시적으로 inline 성질을 지킨다. */
body.chatgpt-reference-v1 .chatgpt-share-callout p strong { font-weight: var(--cg-text-strong-weight); }
body.chatgpt-reference-v1 .chatgpt-share-callout p { margin: 0; color: var(--cg-muted); }
body.chatgpt-reference-v1 .chatgpt-share-callout p + p { margin-top: 6px; }

/* 공유받은 사람이 보는 읽기 전용 뷰의 상단 배너 / 하단 도크. ★렌더 함정 (2): 이 배너·도크가
   감싸는 읽기 전용 뷰 루트는 .chatgpt-route-page 를 쓰지 않는다 — 그 클래스는
   #portalPage/#trustPanel/#artifactPanel 전용 도킹·애니메이션 상태(library-scope-docked 등)를
   끌고 와서 컴포저 없는 단순 페이지에 어울리지 않는 전환·레이아웃을 강제한다. 대신
   [data-shared-view-root] 속성 선택자로 스코프한다(app.js 가 매긴다, 새 클래스 아님). */
/* fix3(QA gate.md §5 "/s/ 배너 배지 토큰 충돌" — fix2 가 남긴 부수 발견 재현):
   `--cg-hover-soft`(= `--wizon-ai-hover-soft-bg`)는 `.ds-badge--neutral`(전역
   `--ds-badge-neutral-bg`, 같은 별칭)과 같은 값이라 라이트 244=244·다크 33=33 대비 0 —
   배지가 배너 위에서 사라진다. 공유 다이얼로그 fix2(`.share-v2-panel` →
   `--cg-menu-bg`)와 같은 처방: 배지(`.ds-badge`, 전역 1곳)는 그대로 두고 면(배너)만
   다른 토큰으로 옮긴다(라이트 #fff/다크 #2f2f2f — 배지 라이트 #f4f4f4/다크 #212121과
   구분됨). 구조는 손대지 않는다. */
body.chatgpt-reference-v1 .chatgpt-shared-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 14px min(6vw, 64px);
  border-bottom: 1px solid var(--cg-menu-border);
  background: var(--cg-menu-bg);
  color: var(--cg-text);
  font-size: var(--cg-text-meta-size);
}
body.chatgpt-reference-v1 .chatgpt-shared-banner-icon { display: inline-flex; color: var(--cg-muted); }
body.chatgpt-reference-v1 .chatgpt-shared-banner-icon .wds-inline-icon { width: 18px; height: 18px; }
body.chatgpt-reference-v1 .chatgpt-shared-banner-copy { display: grid; gap: 2px; min-width: 0; }
body.chatgpt-reference-v1 .chatgpt-shared-banner-title { font-size: var(--cg-text-label-size); font-weight: var(--cg-text-strong-weight); line-height: 21px; }
body.chatgpt-reference-v1 .chatgpt-shared-banner-meta { color: var(--cg-muted); }
body.chatgpt-reference-v1 .chatgpt-shared-banner-spacer { flex: 1 1 auto; }
body.chatgpt-reference-v1 .chatgpt-shared-dock {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px 14px;
  padding: 18px min(6vw, 64px) 26px;
  border-top: 1px solid var(--cg-menu-border);
  background: var(--cg-surface);
  color: var(--cg-muted);
  font-size: var(--cg-text-meta-size);
}
/* ★수리(2026-09-22, 오너 신고 "공유된 대화가 스크롤되지 않는다"): body.chatgpt-reference-v1
   자체가 `overflow: hidden`(styles.css 18427, 실시간 채팅 셸이 자기 안의 .chat-pane 을
   스크롤시키는 전제)이고 이 공유 뷰는 그 body 를 그대로 입은 채 내부 스크롤 컨테이너를 하나도
   만들지 않았다 — 뷰포트보다 긴 대화가 모든 브라우저(WebKit·Chromium)에서 휠·터치·키보드
   스크롤 불가로 «그냥 잘려» 보였다(양성 대조: window.scrollTo 는 먹지만 사용자 입력은 0,
   overflow:hidden 은 clip 과 달리 프로그램 스크롤만 허용하는 스펙 차이). 축 ㉮(thread 트랙에
   자체 스크롤러 부여)를 택했다 — 축 ㉯(body 의 overflow:hidden 을 이 화면에서만 풂)는
   `body.chatgpt-reference-v1` 전역 규칙을 조건부로 되돌려야 해서 실시간 채팅 셸의 스크롤
   전제(그 안의 .chat-pane 자체 스크롤)를 건드릴 위험이 더 크다. `min-height:100vh` →
   `height:100%`: body 가 이미 `height:100%`(1097행) 로 고정돼 있어 min-height:100vh 는
   부모보다 커질 수 있는 높이일 뿐 스크롤 없이는 의미가 없었다 — 고정 height:100% 로 그리드
   세 행(배너/스레드/도크)을 뷰포트 안에 못박고, 가운데 스레드 행만 아래에서 자체 스크롤시킨다.
   grid-template-rows 의 minmax(0,1fr) 가 이미 있어 스레드 행 자체는 넘치지 않는다(부모가
   커지지 않음) — `overflow-y:auto` 를 그 실제 DOM 요소([data-shared-thread])에 매겨야
   스크롤바가 생긴다(grid 부모에 달면 자식이 아니라 그리드 트랙 자체가 넘칠 때만 반응). */
[data-shared-view-root] {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: 100%;
  background: var(--cg-surface);
}
[data-shared-view-root] [data-shared-thread] {
  padding: 28px min(6vw, 64px);
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
/* ★렌더 함정 (3, 2026-09-10 오너 실측): init() 은 bootstrap() 이 이 라우트를 알아보기
   *전에* render() 를 한 번 불러 `document.documentElement.dataset.chatState = "empty"`
   를 찍고(state.messages 가 아직 비어 있으므로), 공유 뷰 경로(bootstrapSharedConversationView
   → renderSharedConversationView)는 그 뒤로 이 속성을 다시 쓰는 그 render() 를 절대
   호출하지 않는다 — 실측: 공유 뷰가 살아있는 내내 data-chat-state 는 "empty" 로 고정.
   body 는 이 화면에서도 늘 .chatgpt-reference-v1 을 지니므로
   `html[data-chat-state="empty"] .chatgpt-reference-v1 .message-list`(실시간 "새 대화"
   웰컴 화면 전용 — justify-content:end 로 그리드 트랙을 콘텐츠 폭까지 줄이고 오른쪽에
   붙인다) 가 이 스코프까지 새어 들어와 본문 전체가 화면 우측 ~586px 기둥으로 무너지고
   위젯 카드 제목이 세로로 찢어졌다(오너 1920 실측, 계산 스타일로 재현: listWidth 1792→
   grid-template-columns 586.25px, justify-content "end"). `body.chatgpt-reference-v1
   .message-list`(실시간 채팅 기본값, max-width:none/margin:0 도 그리드 트랙 폭엔 무력)도
   특이도 (0,2,1) 로 이 규칙 (0,2,0) 보다 세다. `[data-shared-view-root]` 는 이 화면 밖에
   서는 절대 나타나지 않는 마커(app.js 주석 실측)라, 그 안에서 이 화면 전용 값을
   !important 로 확정해도 실시간 채팅으로 새지 않는다(#shareDialog[hidden] 재선언과 같은
   근거). display:grid 대신 flex 컬럼으로 바꾼 이유: 그리드의 auto 트랙은
   justify-content 가 stretch 가 아니면 콘텐츠 폭으로 줄어드는데, flex-column 의
   justify-content 는 세로(메인) 축만 건드리고 가로(교차) 축은 align-items:stretch 가
   폭을 지킨다 — 같은 부류의 다른 data-chat-state 규칙이 justify-content 를 다시 흘려도
   가로 붕괴가 구조적으로 불가능해진다. 폭 공식은 실시간 채팅
   (`.chatgpt-reference-v1 .message-list`)과 같은 토큰·같은 계산을 그대로 재사용한다
   (--cg-chat-max-width 자체는 .chatgpt-reference-v1 스코프에서만 정의되므로 그 원본
   토큰 --wizon-ai-chat-max-width/--wizon-ai-thread-gutter 를 직접 쓴다 — 정의는
   어디에도 없고 항상 폴백(800px/16px)에 의존하는 값이라 스코프 무관). */
[data-shared-view-root] .message-list {
  display: flex !important;
  flex-direction: column !important;
  align-items: stretch !important;
  justify-content: flex-start !important;
  gap: 26px;
  overflow: visible;
  width: 100% !important;
  max-width: min(calc(var(--wizon-ai-chat-max-width, 800px) - (var(--wizon-ai-thread-gutter, 16px) * 2)), calc(100% - (var(--wizon-ai-thread-gutter, 16px) * 3))) !important;
  margin: 0 auto !important;
  padding: 0 !important;
}
/* 사용자 말풍선은 실시간 채팅과 같은 메커니즘(.chatgpt-reference-v1 .message.user 자체가
   flex-column + align-items:flex-end)을 그대로 물려받는다 — 이 스코프에서 새로 만들지
   않는다(형제 렌더 diff 를 이 스코프 밖으로 새지 않게 하는 목적과 같다). */
[data-shared-view-root] .message-list > * {
  width: 100% !important;
  max-width: 100%;
}
[data-shared-view-root] .message-actions,
[data-shared-view-root] .feedback-row { display: none; }
/* ★수리(2026-09-22): 사용자 메시지 액션 행은 `.message-actions` 가 아니라 `.user-actions`
   다(app.js renderUserActions, 부모 체인 div.user-actions > article.message.user >
   div.message-list) — 위 규칙만으로는 안 숨었다. 읽기 전용 뷰에서 «복사» 는 호버하면
   나타나고 클릭하면 클립보드에 빈 문자열을, «편집»(연필)은 호버하면 나타나고 클릭해도
   아무 일도 안 하는 죽은 쓰기 어포던스로 남아 있었다(표 «크게 보기»·«···» 메뉴·코드 «복사»는
   다른 클래스(.visual-answer-media/.code-card-copy 등)라 이 규칙과 무관 — 회귀 아님).
   ★특이도: `.message.user .user-actions{display:flex}`(styles.css 33757, 파일 뒤쪽이라
   순서로도 이김)가 (0,3,0) 으로 `[data-shared-view-root] .user-actions`(0,2,0) 보다 세다 —
   `.message.user` 체인까지 셀렉터에 넣어 (0,4,0) 으로 이겨야 실제로 숨는다(실측: 체인 없이
   두면 getComputedStyle 이 display:flex 를 돌려준다). */
[data-shared-view-root] .message.user .user-actions { display: none !important; }
/* 첨부 참조 칩(스냅샷에 바이트를 굽지 않고 받는 사람 권한으로 다시 여는 자리) */
[data-shared-view-root] [data-shared-attachment-row] { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
[data-shared-view-root] [data-shared-attachment-row] .ds-chip { gap: 6px; cursor: default; position: relative; }
[data-shared-view-root] [data-shared-attachment-row] .ds-chip .wds-inline-icon { width: 14px; height: 14px; }
/* ds-chip-touch-target-44: 이 스코프의 .ds-chip 은 variant 클래스(--segmented/--count)를
   달지 않아 min-height 규칙이 전혀 적용되지 않고, border 1px + 14px 텍스트 줄높이만 남아
   실측 26.1px 로 렌더된다(WCAG 2.5.8 44px 미만, qa 09-22 실측: base 8c93da3c7 에도 동일 —
   기존 결함). 폭(223.8px)은 이미 충분하므로 breadcrumb-item/route-action 정본 패턴
   (top:50%/left:0/width:100%/translateY(-50%), 09-11 채택 C)을 그대로 재사용해 세로만
   넓힌다 — 좌우는 자기 경계를 넘지 않아 같은 줄 이웃 칩(가로 gap 8px)과 겹치지 않는다.
   전역 .ds-chip 이 아니라 이 스코프에만 건다: 다른 화면의 맨 .ds-chip(드라이브 인계·권한
   배지 등)은 <span>/비대화형 상태 표시로 href·버튼이 없어 터치 타깃 규정 대상이 아니고,
   .ds-chip--segmented/--count 버튼은 이 결함과 무관한 별도 후보다.
   ★44px 정본 값을 그대로 쓰지 않는 이유(실측 09-22, 발주서가 미리 경고한 «촘촘한 배치»
   케이스): 이 행은 2개뿐인 QA 픽스처에선 1440 에서 한 줄이라 안 보이지만, 390/375/430
   같은 좁은 폭에서는 [data-shared-attachment-row] 가 flex-wrap 으로 두 줄로 접힌다.
   그 줄 사이 세로 간격은 바로 위 규칙의 `gap: 8px`(같은 값을 새 토큰 없이 재사용 —
   이 규칙 밖에서 이미 리터럴로 쓰인 값). height:44px(±9px 확장)를 그대로 쓰면 확장분이
   줄 간격 8px 를 넘어 다음 줄 칩의 영역까지 먹어(실측: 코너 히트 2/4 가 옆 칩으로 감 —
   새 오탭 경로), 이 칩 자신의 자기-적중률이 떨어진다. `calc(100% + 8px)`(=약 34px, 자기
   경계에서 줄 간격의 절반씩만 확장)은 다음 줄 칩의 경계에 닿을 뿐 넘지 않아 겹침 0 을
   실측으로 확인했다 — 44 에는 못 미치지만 이 밀집 맥락에서 겹침 없이 낼 수 있는 상한. */
[data-shared-view-root] [data-shared-attachment-row] .ds-chip::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: calc(100% + 8px);
  transform: translateY(-50%);
}
[data-shared-view-root] [data-shared-empty-state] {
  display: grid;
  justify-items: center;
  gap: 10px;
  padding: 20vh 20px;
  text-align: center;
  color: var(--cg-muted);
}
[data-shared-view-root] [data-shared-empty-state] h1 { margin: 0; color: var(--cg-text); font-size: var(--ds-text-title-18); font-weight: 500; }
[data-shared-view-root] [data-shared-empty-state] p { margin: 0; max-width: 46ch; }

/* 다이얼로그 안 hidden 토글 대상은 각자 display 를 매기고 있어 native [hidden] 기본값이
   지지 않는다 — #shareDialog 스코프로만 명시적으로 되돌린다(전역 부작용 방지). S5-c:
   .chatgpt-share-link-row/.chatgpt-drive-share-create/.chatgpt-route-action 은 이 다이얼로그
   에서 사라졌다(share-v2-* 로 전면 교체) — 대상을 옮긴다. */
#shareDialog .share-v2-linkrow[hidden],
#shareDialog .share-v2-menu[hidden],
#shareDialog .share-v2-confirm[hidden],
#shareDialog .share-v2-state[hidden],
#shareDialog .share-v2-meta[hidden] { display: none; }

/* S5-c fix4(work-order §1): 초기 포커스를 시트 컨테이너(다이얼로그 자신)로 돌린 뒤
   (openShareDialog, app.js) 남는 UA 기본 :focus 아웃라인을 지운다 — 형제
   .image-preview-dialog:focus 와 같은 처방(같은 "헤더 아이콘 두 개" 문제, styles.css
   :29475대). tabindex="-1" 요소는 :focus-visible 이 뜨지 않는 브라우저가 있어 :focus 로
   스코프한다(더 넓어 보이지만 이 요소는 프로그램 포커스만 받는다 — 사용자가 Tab 으로
   이 다이얼로그 자체에 도달할 경로가 없다). */
#shareDialog:focus { outline: none; }

/* S5-a(spec §2-1·2-4·2-5): 신설 컴포넌트는 share-v2-* 접두사로 격리한다 — 원본
   .chatgpt-route-action(app.js 59회+index.html 4회)·.chatgpt-drive-permissions-section*
   (드라이브 권한 다이얼로그 3곳 공유) 규칙은 건드리지 않는다(work-order §5 형제 스코프
   위험). ★렌더 함정 (5)(disabled 세그먼트 칩 점선 방지)는 세그먼트 칩 자체가 이제
   #shareDialog 안에 없어 대상이 사라졌다 — 죽은 규칙을 지운다. 대체 규칙은
   .share-v2-option:disabled 아래에 있다. */
#shareDialog .chatgpt-share-head { position: relative; }
#shareDialog .share-v2-head-actions { display: flex; align-items: center; gap: 4px; }

/* S5-b(work-order 시험: "44px 미만 버튼 0"): .chatgpt-route-action 계열(스냅샷
   업데이트/완료)은 이미 전역 20260903-touch-target-44-drive-and-actions 블록의
   ::after 슬롭을 물려받는다(실측 확인, width:100%/height:44px). 이 둘(메뉴 토글·닫기)은
   새 조합이라 슬롭이 없었다 — .chatgpt-drive-history-button 4px 간격 쌍과 같은 중간선
   기법을 그대로 적용한다(코드 발명 없음). */
@media (pointer: coarse) {
  #shareDialog .chatgpt-share-close,
  #shareDialog .share-v2-menu-toggle {
    position: relative;
  }
  #shareDialog .chatgpt-share-close::after,
  #shareDialog .share-v2-menu-toggle::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(100% + 4px);
    height: 44px;
    transform: translate(-50%, -50%);
  }
}

#shareDialog .share-v2-menu-toggle {
  display: inline-flex;
  width: 32px;
  height: 32px;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--wizon-ai-radius-card, 10px);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}
#shareDialog .share-v2-menu-toggle:hover,
#shareDialog .share-v2-menu-toggle[aria-expanded="true"] { background: var(--cg-hover); }
/* S5-c fix2(중앙 게이트 2차 지적 #6): 이 버튼엔 `:focus-visible` 규칙이 아예 없어
   포커스가 남으면 브라우저 UA 기본 아웃라인(둥근 "테두리 알약"처럼 보인다)이 그려졌다
   — 캡처 3장 전부에서 관측(추정 원인: 캡처 스크립트가 blur() 없이 찍음). 실제 border
   규칙은 없다(위 `border: 0` 그대로, "맨 아이콘" 계약 유지) — 형제 `.chatgpt-share-close
   :focus-visible`(18800행대)과 같은 처방으로 키보드 포커스에만 뜨는 의도된 링을 준다. */
#shareDialog .share-v2-menu-toggle:focus-visible {
  outline: 1px solid var(--cg-focus-visible-ring);
  outline-offset: -1px;
  background: var(--cg-focus-bg);
}

#shareDialog .share-v2-menu {
  position: absolute;
  top: 100%;
  right: 40px;
  z-index: 1;
  display: grid;
  min-width: 180px;
  padding: var(--ds-space-1, 4px);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--wizon-ai-radius-card, 10px);
  background: var(--cg-menu-bg);
  box-shadow: var(--cg-shadow-float-lg, var(--cg-dialog-shadow));
}
#shareDialog .share-v2-menu button {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2, 8px);
  height: 32px;
  padding: 0 var(--ds-space-2, 8px);
  border: 0;
  border-radius: var(--wizon-ai-radius-card, 10px);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  text-align: left;
  cursor: pointer;
}
#shareDialog .share-v2-menu button:hover,
#shareDialog .share-v2-menu button:focus-visible { background: var(--cg-hover); outline: none; }
#shareDialog .share-v2-menu button.is-danger { color: var(--ds-badge-bad-fg, #b42318); }

/* S5-a(spec §2-5): 즉시 DELETE 앞에 두는 인라인 확인 1단계 — 중첩 <dialog> 는
   뒤로가기=오버레이 1개 규칙(doc 667)을 새로 어긴다. */
#shareDialog .share-v2-confirm {
  display: grid;
  gap: var(--ds-space-2, 8px);
  padding: var(--ds-space-3, 12px);
  border: 1px solid var(--ds-badge-bad-fg, #b42318);
  border-radius: var(--wizon-ai-radius-card, 10px);
  background: var(--ds-badge-bad-bg, rgba(180, 35, 24, 0.08));
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  line-height: 20px;
}
#shareDialog .share-v2-confirm p { margin: 0; }
#shareDialog .share-v2-confirm-actions { display: flex; justify-content: flex-end; gap: var(--ds-space-2, 8px); }
#shareDialog .share-v2-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  padding: 0 var(--ds-space-3, 12px);
  border: 1px solid var(--ds-color-line);
  border-radius: var(--wizon-ai-radius-card, 10px);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  cursor: pointer;
}
#shareDialog .share-v2-btn:hover,
#shareDialog .share-v2-btn:focus-visible { background: var(--cg-hover); outline: none; }
/* work-order share-s5-c-unify fix1 #1(중앙 게이트 지적, 목업 measure.md §5 계약): 「완료」
   = 주 버튼(채움, 라이트 검정/다크 흰), 「링크 복사」= 보조(위 기본 규칙, 외곽선) — 버튼
   종류는 정확히 2(주1·보조1). `.is-primary` 클래스는 마크업에 이미 있었지만 이 채움 규칙이
   없어 완료 버튼도 보조와 똑같은 외곽선으로 렌더됐다(실 Chromium 캡처로 확인). `--cg-send-bg`/
   `--cg-send-fg` 는 이 코드베이스에서 유일한 "배경+글자가 짝인 잉크 필" 쌍(라이트 #0d0d0d/
   흰, 다크에서 흰/검정으로 뒤집힌다) — 목업(share-s5-c-mockup)이 쓰는 것과 같은 토큰이다. */
#shareDialog .share-v2-btn.is-primary {
  border-color: var(--cg-send-bg);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}
@media (hover: hover) {
  #shareDialog .share-v2-btn.is-primary:hover { opacity: 0.88; }
}
#shareDialog .share-v2-btn.is-danger {
  border-color: transparent;
  background: var(--ds-button-danger-bg);
  color: var(--ds-button-danger-fg);
}
@media (hover: hover) {
  #shareDialog .share-v2-btn.is-danger:hover { filter: brightness(1.08); }
  /* 다크: brightness(1.08) 이 red-600 면을 밝혀 흰 글자 대비가 AA 밑으로 떨어진다 —
     같은 위험 버튼 한 벌의 hover 토큰(red-700)으로 어둡게. 라이트는 위 규칙 그대로. */
  html[data-theme="dark"] #shareDialog .share-v2-btn.is-danger:hover {
    background: var(--ds-button-danger-bg-hover);
    filter: none;
  }
}

/* S5-a(spec §2-4): 세그먼트 칩 → 설명 있는 선택 «행»(role="radio"). 비활성 사유는 title
   툴팁이 아니라 행 안 문장으로 둔다(터치 기기에서도 읽힌다). S5-c: 왼쪽 자리 20→24px —
   work-order "행 해부 1종" 표가 요구한 폭(목업 share-s5-c-mockup 과 동일 계약).
   work-order share-s5-c-unify fix1 #3·#6(중앙 게이트 지적): 옵션 목록은 패널(.share-v2-panel)
   과 같은 표면 위에 놓인다 — 자체 테두리·배경을 갖지 않는다(목업 share-v2-unified.css 실측:
   `.share-v2-options { display:grid; }`, `.share-v2-option { background:transparent; }`).
   전에는 이 블록에 자체 테두리(`border`)와 각 행에 흰/검정 배경(`--cg-surface`)이 있어
   패널 테두리 안에 또 테두리 있는 카드가 생겼다 — 라이트에서는 은은해 안 보였지만 다크에서는
   패널(밝게 올라온 --cg-menu-bg)보다 옵션 행(더 검은 --cg-surface, 다크 #000)이 더 어두워
   층이 뒤집히는 "샌드위치"(패널보다 검은 안쪽 카드)로 드러났다(실 Chromium 캡처 확인). */
#shareDialog .share-v2-options {
  display: grid;
}
#shareDialog .share-v2-option {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--ds-space-3, 12px);
  min-height: 56px;
  padding: var(--ds-space-3, 12px);
  border: 0;
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
#shareDialog .share-v2-option + .share-v2-option { border-top: 1px solid var(--cg-menu-border); }
#shareDialog .share-v2-option:hover:not(:disabled) { background: var(--cg-hover-soft); }
#shareDialog .share-v2-option:disabled { cursor: not-allowed; opacity: 0.6; }
#shareDialog .share-v2-option-mark {
  display: inline-flex;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--ds-color-line);
  border-radius: 50%;
  color: var(--cg-menu-bg);
}
#shareDialog .share-v2-option[aria-checked="true"] .share-v2-option-mark {
  border-color: var(--cg-text);
  background: var(--cg-text);
}
#shareDialog .share-v2-option-mark .wds-inline-icon { width: 12px; height: 12px; }
#shareDialog .share-v2-option-copy { display: grid; gap: 2px; min-width: 0; }
/* S5-c fix2(중앙 게이트 2차 지적 #5): `<b>` 에 font-size 선언이 없어 앙비언트(body,
   실측 15px)를 그대로 상속했다 — #shareDialog 안 폰트 크기 종류 수(work-order §1 "폰트
   크기 ≤4": title-18/body-14/caption-13/caption-12)에 안 잡히는 5번째 크기였다. 상태
   줄(.share-v2-state)과 같은 14px 로 명시해 4종 계약 안으로 되돌린다. */
#shareDialog .share-v2-option-copy b { font-size: var(--ds-text-body-14); font-weight: var(--cg-text-strong-weight, 600); }
#shareDialog .share-v2-option-copy span { color: var(--cg-muted); font-size: var(--ds-text-caption-13); }
#shareDialog .share-v2-option .ds-badge { margin-top: 2px; }

/* S5-b(spec §3): 죽은 토큰 수리 — --chatgpt-route-gap-{2xs,xs,sm,md} 는
   styles.css:18867 대의 라우트 페이지 크롬 셀렉터(위 렌더 함정 (2) 주석이 가리키는 바로
   그 래퍼) 한 곳에서만 정의된다(judge 실측). 공유 다이얼로그는 그 래퍼를 의도적으로
   쓰지 않아 그 4개가 빈 값 → gap:normal, padding-bottom:0px 로 조용히 떨어졌다.
   전역 폴백을 추가하는 대신 #shareDialog 스코프로만 같은 값을 다시 정의한다 —
   .chatgpt-drive-permissions-section 규칙 자체(드라이브 권한 다이얼로그 3곳 공유)는
   손대지 않고, 그 규칙이 참조하는 변수만 이 스코프 안에서 채운다. S5-c 는 이 다이얼로그
   에서 그 래퍼 클래스를 더는 쓰지 않지만(§패널 신설), 변수 자체는 형제 스코프 위험
   회피의 선례라 그대로 남긴다(죽은 코드 아님 — 아래 test_shareDialog_defines… 가 계속
   지킨다). */
#shareDialog {
  --chatgpt-route-gap-2xs: 6px;
  --chatgpt-route-gap-xs: var(--ds-space-2, 8px);
  --chatgpt-route-gap-sm: var(--ds-space-3, 12px);
  --chatgpt-route-gap-md: var(--ds-space-6, 24px);
}

/* S5-b(spec §2-2): 액션 컨트롤 높이 32px 1종·radius 카드 1값. .chatgpt-share-close 는 이
   다이얼로그에만 있는 클래스라 안전하다(헤더 아이콘 버튼 — .share-v2-btn 과는 별도 축). */
#shareDialog .chatgpt-share-close {
  width: 32px;
  height: 32px;
  border-radius: var(--wizon-ai-radius-card, 10px);
}

/* ══════════════════════════════════════════════════════════════════════════
   S5-c(work-order «통일 + 밀도» — share-s5-c-mockup 승인 3차판이 정본, measure.md
   §1·§3·§7·§8 이 픽셀 계약이다) — 화면 전체를 share-v2-* 문법으로 통일한다.
   ══════════════════════════════════════════════════════════════════════════ */

/* ① 핵심 상태 1개 — 공유가 활성일 때만 보인다(반쯤 채워진 두 표면 대신 한 줄로). */
#shareDialog .share-v2-state {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--ds-space-2, 8px);
  min-height: 24px;
  margin: 0;
  font-size: var(--ds-text-body-14);
  color: var(--cg-text);
}
#shareDialog .share-v2-state strong { font-weight: var(--cg-text-strong-weight, 600); }
#shareDialog .share-v2-state-sep { color: var(--cg-menu-border); }
#shareDialog .share-v2-state-rest { color: var(--cg-muted); }
/* 유채 ≤1(work-order 종류 수 표) — 공유 중임을 알리는 초록 점 하나뿐. */
#shareDialog .share-v2-live-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ds-badge-ok-fg);
  flex: 0 0 auto;
}

/* ② 인셋 패널 — 「누가 볼 수 있나」·「링크」 두 섹션이 같은 표면 문법을 쓴다(반경 1값,
   work-order "카드 반경 ≤2" 의 «안쪽» 값). 「액세스 권한이 있는 사용자」섹션은 흡수돼
   사라졌다 — 위 상태 줄이 그 정보(회사 구성원·읽기 전용)를 이미 말한다.
   S5-c fix2(중앙 게이트 2차 지적 #1, 실 Chromium 캡처로 발견, 2026-09-10): 배경을
   `--ds-color-surface-muted` 로 두면 그 값이 `.ds-badge`(`--ds-badge-neutral-bg`, component.css:53)
   와 **같은 토큰**이라 배지 알약이 카드 위에서 완전히 사라진다(라이트 #f4f4f4=#f4f4f4,
   다크 #212121=#212121 — 육안 대비 0). 목업(share-s5-c-mockup measure.md §0) 실측 픽셀:
   카드=라이트 #fff+테두리/다크 #2f2f2f, 배지=라이트 #f4f4f4/다크 #212121 — 서로 다른
   토큰이어야 한다. 카드는 `--cg-menu-bg`(라이트 #fff, 다크 #2f2f2f — 셸 `--cg-surface`
   다크 #000보다 밝아 "시트보다 밝은 group 면" 계약도 함께 만족)로 되돌린다. */
#shareDialog .share-v2-panel {
  display: grid;
  gap: var(--ds-space-2, 8px);
  padding: var(--ds-space-3, 12px);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--wizon-ai-radius-card, 10px);
  background: var(--cg-menu-bg);
}
#shareDialog .share-v2-panel-head {
  display: flex;
  align-items: baseline;
  gap: var(--ds-space-2, 8px);
}
#shareDialog .share-v2-panel-head h3 {
  margin: 0;
  flex: 1 1 auto;
  font-size: var(--ds-text-caption-13);
  font-weight: var(--cg-text-strong-weight, 600);
  color: var(--cg-muted);
}
/* work-order share-s5-c-unify fix1 #3·#6 이 위 §S5-b "표면 3층"(패널 톤다운 → 옵션 흰)
   판정을 뒤집는다 — 목업(share-s5-c-mockup)은 옵션 그룹에 별도 흰 인셋을 주지 않고 패널과
   같은 표면 1장으로 그린다(위 `.share-v2-option { background: transparent; }` 참고). 옛
   3층 규칙(`.share-v2-panel .share-v2-options { background: var(--cg-surface); }` + 첫
   행 라운드 처리)은 그 3층을 만들던 자리였다 — 이제 대상이 없어 지운다(죽은 규칙을 남기지
   않는다). */

/* 링크 행 + URL 톤 면(테두리 0 — 패널 테두리 안에 또 테두리 알약을 두지 않는다). */
#shareDialog .share-v2-linkrow {
  display: grid;
  align-items: center;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--ds-space-2, 8px);
}
#shareDialog .share-v2-url {
  display: flex;
  align-items: center;
  min-width: 0;
  height: 32px;
  padding: 0 var(--ds-space-3, 12px);
  border: 0;
  border-radius: var(--wizon-ai-radius-card, 10px);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
#shareDialog .share-v2-touch { position: relative; display: inline-flex; padding: 6px 0; }
/* ★렌더 함정(실 Chromium 캡처로 발견, 2026-09-10): 44px 히트슬롭 래퍼가 explicit display 를
   매겨(위 규칙) 안의 <button data-share-create> 를 hidden 해도 래퍼 자신은 그대로 남아
   빈 44px 자리를 차지했다 — 링크 카드 아래 간격이 8px 대신 60px 로 부풀어 보였다. 같은
   병(★렌더 함정 6, .share-v2-menu/.share-v2-confirm)과 같은 처방. app.js 의
   setCreateButtonHidden() 이 이제 래퍼도 함께 hidden 한다. */
#shareDialog .share-v2-touch[hidden] { display: none; }

/* ⑥ 출처·기준시각 줄 — 카드 밖(모바일 시트 관례), 링크 섹션 안(데스크톱 패널 관례). */
#shareDialog .share-v2-meta {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-1, 4px) var(--ds-space-2, 8px);
  font-size: var(--ds-text-caption-12);
  color: var(--cg-muted);
  font-variant-numeric: tabular-nums;
}
#shareDialog .share-v2-meta .wds-inline-icon { width: 13px; height: 13px; }
#shareDialog .share-v2-meta-dot { color: var(--cg-menu-border); }
/* S5-c 수정 2(목업 measure.md §7 #2): 좁은 폭에서 flex-wrap 이 문장 중간에 줄바꿈하면
   구분점(·)이 줄 끝에 고아로 남는다 — 「설계된」 2줄(스냅샷·만료 / 기간 정책)로 굳힌다. */
#shareDialog .share-v2-meta--stacked { display: grid; gap: var(--ds-space-1, 4px); }
#shareDialog .share-v2-meta--stacked .share-v2-meta-row {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--ds-space-1, 4px) var(--ds-space-2, 8px);
}
#shareDialog .share-v2-reopen-note {
  margin: 0;
  font-size: var(--ds-text-caption-12);
  line-height: 18px;
  color: var(--cg-muted);
}

/* ④ 밀도: 콜아웃 → 접힘 상세(1줄 + 셰브런). 왼쪽 띠(border-left) 제거 — 같은 그룹
   문법 + 아이콘으로 대체(work-order "카드 반경" 행 "콜아웃은 왼쪽 띠 제거"). */
#shareDialog .share-v2-disclosure { border-top: 1px solid var(--cg-menu-border); padding-top: var(--ds-space-3, 12px); }
/* fix3(QA gate.md §3 #22, playwright-mobile 실측): 이 행(summary 자체가 클릭 대상,
   아이콘 버튼처럼 ::after 힛슬롭이 필요한 게 아니라 행 자체 높이를 키운다)이 h=24px
   로 44px 터치 타깃 미달이었다 — `.share-v2-btn`(KindCountContractTests 가 이미
   고정한 32 데스크톱/44 터치 두 값 체계)과 같은 문법을 그대로 적용한다. 바깥
   `.share-v2-disclosure` 의 `padding-top: 12px`(세로 리듬 표 "구분선→푸터" 대칭 값)는
   건드리지 않는다 — 행 안(min-height)만 바뀐다. */
#shareDialog .share-v2-disclosure > summary {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2, 8px);
  min-height: 32px;
  list-style: none;
  cursor: pointer;
  font-size: var(--ds-text-caption-13);
  font-weight: var(--ds-weight-medium, 500);
  color: var(--cg-text);
}
#shareDialog .share-v2-disclosure > summary::-webkit-details-marker { display: none; }
#shareDialog .share-v2-disclosure > summary:focus-visible { outline: 2px solid var(--cg-text); outline-offset: 2px; }
#shareDialog .share-v2-disclosure > summary .wds-inline-icon { width: 15px; height: 15px; color: var(--cg-muted); }
#shareDialog .share-v2-chevron { margin-left: auto; transition: transform 120ms ease; }
#shareDialog .share-v2-disclosure[open] .share-v2-chevron { transform: rotate(180deg); }
#shareDialog .share-v2-disclosure-body { margin: var(--ds-space-2, 8px) 0 0; padding-left: calc(15px + var(--ds-space-2, 8px)); }
#shareDialog .share-v2-disclosure-body p { margin: 0 0 var(--ds-space-2, 8px); font-size: var(--ds-text-caption-12); line-height: 18px; color: var(--cg-muted); }
#shareDialog .share-v2-disclosure-body p:last-child { margin-bottom: 0; }
#shareDialog .share-v2-disclosure-body strong { color: var(--cg-text); font-weight: var(--cg-text-strong-weight, 600); }

/* 「완료」위치 — 390 첫 화면에 보이도록 고정 푸터(work-order 표). 데스크톱에서는 sticky 가
   자리를 옮기지 않는다(본문이 이미 스크롤 없이 다 보인다, measure.md §3 "밀도" 행). */
#shareDialog .share-v2-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ds-space-2, 8px);
  padding: var(--ds-space-3, 12px) var(--ds-space-5, 20px);
  border-top: 1px solid var(--cg-menu-border);
  background: var(--cg-menu-bg);
}
/* R-40(M-3 ③) — 손잡이 히트 ≥44 로 통일(이전엔 8px 위 패딩 + 4px 막대 = 12px 미달).
   시각(36×4)은 그대로, min-height 로만 히트를 키운다 — 다른 손잡이(§B2 위 두 곳)와 같은 값. */
#shareDialog .share-v2-drag { display: none; justify-content: center; align-items: center; min-height: 44px; padding: 0; cursor: grab; touch-action: none; }
#shareDialog.is-dragging .share-v2-drag { cursor: grabbing; }
#shareDialog .share-v2-drag span { width: 36px; height: 4px; border-radius: var(--wizon-ai-radius-pill, 999px); background: var(--cg-menu-border); }

/* S5-c(work-order "모바일은 바텀시트" — 위즈온 정본 시트 게이트 usesMobileSheetPresentation,
   app.js, 와 같은 두 조건). 이 다이얼로그는 <dialog>.showModal() 엔진(포커스 트랩·ESC·
   doc 667 오버레이 히스토리가 이미 검증됨)을 그대로 두고, 시각 처방만
   20260709-mobile-bottom-sheet 블록(다른 시트들이 이미 쓰는 고정 위치·위쪽만 둥근 모서리·
   리프트 그림자·드래그 핸들 레시피)을 재사용한다 — <dialog> 를 팝오버 엔진으로 갈아타는
   것은 이미 작동하는 포커스 트랩/오버레이 히스토리 등록을 다시 배선해야 하는 별도 엔진
   교체라 이 M 후보 범위 밖으로 판단했다(가정, 확인 못 함 — 승인 필요하면 후속 후보). */
@media (max-width: 900px), (pointer: coarse) {
  /* R-41 B1(inventory S7 — «시간·곡선 정렬»): 이 폭은 top-layer 시트(<dialog> 도 진짜
     top layer)라 위 20460 데스크톱 transition(188, opacity+transform)이 Safari 에서
     페인트되지 않는다(M-13 ③, 20260709-motion-m1 과 같은 이유) — transition 을 끄고
     다른 시트와 같은 키프레임(372 올라옴/300 내려감)으로 완전히 대체한다. transform 대신
     translate 를 쓰는 이유도 같다(다른 시트 §11 과 동일 관례 — transform:none 과 공존). */
  body.chatgpt-reference-v1 .chatgpt-share-dialog {
    position: fixed;
    inset: auto 0 0 0;
    margin: 0;
    width: 100%;
    max-width: none;
    max-height: calc(var(--wizon-vvh, 100dvh) - var(--wizon-safe-top, 0px) - 40px);
    border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0;
    box-shadow: var(--wizon-ai-sheet-shadow);
    opacity: 1;
    transform: none;
    translate: 0 100%;
    transition: none;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }
  body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="open"] { translate: 0 0; }
  body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="closing"] {
    translate: 0 0;
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }
  body.chatgpt-reference-v1 .chatgpt-share-dialog::backdrop {
    /* 669 §M-11 — 모달 시트 스크림 1토큰(데스크톱 중앙 창의 옛 .28 은 이 폭 밖에서만 남는다). */
    background: var(--wizon-scrim);
    transition: none;
    opacity: 0;
    animation: wizon-scrim-in var(--wds-spring-effect-default-duration, 188ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }
  body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="open"]::backdrop { opacity: 1; }
  body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="closing"]::backdrop {
    opacity: 0;
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }
  /* R-41 B2(M-14, S7) — 손잡이 끌기. 위 [data-motion-state="open"] 규칙과 특이도가 같아
     소스 순서로 이긴다(뒤가 이김) — 새 셀렉터를 더 강하게 만들지 않는다. 오프셋은 공유 변수
     --wizon-sheet-drag-y(기본 0)를 읽는 translate 로, 스크림은 ::backdrop 이 dialog 원소의
     같은 이름 변수를 상속해 따라간다(엔진 실측: Chromium·WebKit, M-14 ⑥). */
  body.chatgpt-reference-v1 .chatgpt-share-dialog.is-dragging {
    transition: none;
    animation: none;
    translate: 0 var(--wizon-sheet-drag-y, 0);
  }
  body.chatgpt-reference-v1 .chatgpt-share-dialog.is-dragging::backdrop {
    animation: none;
    opacity: calc(1 - var(--wizon-sheet-drag-progress, 0));
  }
  /* 임계 미달 복귀 — wizon-sheet-snap(188ms), 이 후보가 허용된 새 키프레임 1개. */
  body.chatgpt-reference-v1 .chatgpt-share-dialog.is-sheet-snapping {
    transition: none;
    animation: wizon-sheet-snap var(--wds-spring-effect-default-duration, 188ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) forwards;
  }
  #shareDialog .chatgpt-share-shell {
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    max-height: inherit;
    min-height: 0;
  }
  #shareDialog .share-v2-drag { display: flex; }
  #shareDialog .chatgpt-share-head { padding-left: var(--ds-space-4, 16px); min-height: 40px; }
  #shareDialog .chatgpt-share-body {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: var(--ds-space-3, 12px) var(--ds-space-4, 16px);
    gap: var(--ds-space-3, 12px);
  }
  #shareDialog .share-v2-btn { height: 44px; }
  /* fix3 수리①: 접힘 상세 행도 같은 44px 터치 타깃 값을 받는다(위 데스크톱 32px 규칙의
     모바일/터치 오버라이드 — `.share-v2-btn` 과 같은 블록·같은 기법). */
  #shareDialog .share-v2-disclosure > summary { min-height: 44px; }
  #shareDialog .share-v2-touch { padding: 0; }
  #shareDialog .share-v2-foot {
    position: sticky;
    bottom: 0;
    padding: var(--ds-space-3, 12px) var(--ds-space-4, 16px) calc(var(--ds-space-3, 12px) + var(--wizon-safe-bottom, 0px));
  }
  #shareDialog .share-v2-foot .share-v2-btn.is-primary { flex: 1 1 auto; }

  /* work-order share-s5-c-unify fix1 #3(중앙 게이트 지적, 목업 share-s5-c-mockup mobile-390
     계약 — 위즈온 시트 더보기 레퍼런스와 같은 문법): 모바일은 캡션(«누가 볼 수 있나»·
     «링크»)이 카드 밖(시트 면 위)에 있고 카드는 1겹이어야 한다. 데스크톱은 같은 마크업을
     그대로 두고 캡션이 카드 안에 남는다(work-order "DOM 재사용 허용" 지시) — 이 미디어쿼리
     안에서만 패널의 표면(배경·테두리·패딩)을 지우고, 실제 "카드"인 자식(옵션 목록·링크
     행)에게 그 표면을 옮긴다. 결과: 시트 → 카드 2층뿐(옛 시트 → 패널 → 카드 3층 제거,
     위 §S5-b "표면 3층" 판정이 이미 뒤집혔으므로 다크 "샌드위치"(fix1 #6)도 여기서 함께
     해소된다). */
  #shareDialog .share-v2-panel {
    background: transparent;
    border: 0;
    padding: 0;
  }
  #shareDialog .share-v2-panel-head { margin-left: 2px; }
  /* S5-c fix2(중앙 게이트 2차 지적 #3, 목업 계약 "링크 섹션도 카드 1겹" — 안내 문장은
     카드 안, 메타 2줄은 카드 밖): freshToken 없는 재열람 상태에서는 `.share-v2-linkrow`
     가 `[hidden]` 이라 카드가 통째로 사라지고 `.share-v2-reopen-note` 문장이 시트 면에
     맨몸으로 남았다 — 안내 문장도 URL 행과 같은 "카드" 취급을 받아야 섹션 구조가 fresh/
     재열람 두 상태에서 1종이 된다. */
  #shareDialog .share-v2-panel .share-v2-options,
  #shareDialog .share-v2-panel .share-v2-linkrow,
  #shareDialog .share-v2-panel .share-v2-reopen-note {
    border: 1px solid var(--cg-menu-border);
    border-radius: var(--wizon-ai-radius-card, 10px);
    /* fix2(지적 #1·#2, 실 Chromium 캡처): `--ds-color-surface-muted` 는 `.ds-badge`
       (`--ds-badge-neutral-bg`)와 같은 토큰이라 옵션 행 배지 알약이 카드 위에서 사라졌다
       (라이트 #f4f4f4=#f4f4f4, 다크 #212121=#212121). 목업 실측 픽셀은 카드=라이트 흰/
       다크 #2f2f2f, 배지=라이트 #f4f4f4/다크 #212121 — `--cg-menu-bg`(라이트 #fff+위
       테두리로 "흰 카드", 다크 #2f2f2f — 시트 셸 `--cg-surface` 다크 #000보다 밝은
       "group 면")로 바꿔 배지·카드 표면을 분리한다. */
    background: var(--cg-menu-bg);
    overflow: hidden;
  }
  #shareDialog .share-v2-panel .share-v2-linkrow,
  #shareDialog .share-v2-panel .share-v2-reopen-note { padding: var(--ds-space-3, 12px); }
  #shareDialog .share-v2-panel .share-v2-meta { margin-left: 2px; }
}

@media (max-width: 520px) {
  [data-shared-view-root] .chatgpt-shared-banner,
  [data-shared-view-root] .chatgpt-shared-dock { padding-left: 16px; padding-right: 16px; }
  [data-shared-view-root] [data-shared-thread] { padding-left: 16px; padding-right: 16px; }
}

body.chatgpt-reference-v1 .chatgpt-search-dialog[open] {
  position: fixed;
  inset: 0;
  width: min(680px, calc(100vw - 20px));
  height: min(442px, calc(100vh - 40px));
  max-height: calc(100vh - 40px);
  margin: auto;
  padding: 0;
  overflow: visible;
  border: 1px solid rgba(0, 0, 0, 0.10);
  border-radius: 16px;
  background: var(--cg-menu-bg, #ffffff);
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-body-16);
  line-height: 24px;
  box-shadow: 0 14px 62px rgba(0, 0, 0, 0.25);
  opacity: 0;
  transform: translate3d(0, 10px, 0) scale(0.985);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
  will-change: opacity, transform;
}

body.chatgpt-reference-v1 .chatgpt-search-dialog[data-motion-state="open"] {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}

body.chatgpt-reference-v1 .chatgpt-search-dialog[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(0, 10px, 0) scale(0.985);
}

body.chatgpt-reference-v1 .chatgpt-search-dialog::backdrop {
  background: rgba(0, 0, 0, 0.50);
  opacity: 0;
  transition: opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease);
}

body.chatgpt-reference-v1 .chatgpt-search-dialog[data-motion-state="open"]::backdrop {
  opacity: 1;
}

body.chatgpt-reference-v1 .chatgpt-search-dialog[data-motion-state="closing"]::backdrop {
  opacity: 0;
}

html[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-search-dialog::backdrop {
  background: rgba(0, 0, 0, 0.54);
}

body.chatgpt-reference-v1 .chatgpt-search-shell {
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100%;
  max-height: inherit;
}

body.chatgpt-reference-v1 .chatgpt-search-input-row {
  display: flex;
  align-items: center;
  min-height: 64px;
  max-height: 64px;
  margin: 0 16px 0 12px;
  padding: 0;
  border-bottom: 0;
}

body.chatgpt-reference-v1 .chatgpt-search-divider {
  flex: 0 0 1px;
  width: 100%;
  height: 1px;
  margin: 0;
  overflow: hidden;
  border: 0;
  background: var(--cg-menu-border, rgba(0, 0, 0, 0.10));
}

body.chatgpt-reference-v1 .chatgpt-search-input-row input {
  width: 100%;
  height: 40px;
  min-height: 40px;
  min-width: 0;
  border: 0;
  border-radius: 0;
  outline: 0;
  background: transparent;
  color: var(--cg-text, #0d0d0d);
  font: inherit;
  font-size: var(--ds-text-body-16);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 24px;
  padding: 8px 12px;
  box-shadow: none;
}

body.chatgpt-reference-v1 .chatgpt-search-input-row input::placeholder {
  color: var(--cg-input-placeholder, #8f8f8f);
}

body.chatgpt-reference-v1 .chatgpt-search-input-row input::-webkit-search-cancel-button,
body.chatgpt-reference-v1 .chatgpt-search-input-row input::-webkit-search-decoration {
  appearance: none;
  display: none;
}

/* 인접 점검(K-4 같은 메커니즘) — 채팅 검색 대화상자 input 도 outline: none 뿐이었다.
   같은 --cg-text 솔리드 링으로 통일. */
body.chatgpt-reference-v1 .chatgpt-search-input-row input:focus,
body.chatgpt-reference-v1 .chatgpt-search-input-row input:focus-visible {
  border-color: transparent;
  box-shadow: none;
  outline: 2px solid var(--cg-text);
  outline-offset: 2px;
}

body.chatgpt-reference-v1 .chatgpt-search-input-row .chatgpt-icon-button {
  display: flex;
  flex: 0 0 28px;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-left: 16px;
  padding: 4px;
  border-radius: var(--ds-radius-pill, 999px);
  color: var(--cg-text, #0d0d0d);
}

body.chatgpt-reference-v1 .chatgpt-search-input-row .chatgpt-icon-button svg {
  width: 20px;
  height: 20px;
  color: #8f8f8f;
}

body.chatgpt-reference-v1 .chatgpt-search-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 8px 0;
  scrollbar-gutter: stable;
}

body.chatgpt-reference-v1 .chatgpt-search-results {
  min-height: 0;
  overflow: visible;
  margin: 0;
  padding: 0;
}

body.chatgpt-reference-v1 .chatgpt-search-section {
  display: grid;
  gap: 0;
  min-width: 0;
}

body.chatgpt-reference-v1 .chatgpt-search-section h3 {
  width: calc(100% - 20px);
  margin: 8px 8px 8px;
  padding: 8px 16px 0;
  color: var(--cg-input-placeholder, #8f8f8f);
  font-size: var(--ds-text-caption-12);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 16px;
}

body.chatgpt-reference-v1 .chatgpt-search-row {
  display: flex;
  align-items: center;
  gap: 8px;
  /* 20260704-search-dialog-row-chatgpt-measured: Chrome ChatGPT row is 647px wide inside a 678px shell at 1920px viewport. */
  box-sizing: border-box;
  width: calc(100% - 20px);
  height: 44px;
  min-height: 44px;
  margin: 0 8px;
  padding: 12px 16px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: var(--cg-text, #0d0d0d);
  font: inherit;
  font-size: var(--ds-text-body-16);
  line-height: 24px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-search-row:hover,
body.chatgpt-reference-v1 .chatgpt-search-row:focus-visible,
body.chatgpt-reference-v1 .chatgpt-search-row[data-chat-search-active="true"] {
  background: var(--cg-hover-soft, #f3f3f3);
}

body.chatgpt-reference-v1 .chatgpt-search-result-icon {
  display: flex;
  flex: 0 0 20px;
  align-items: center;
  justify-content: center;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--cg-text, #0d0d0d);
}

body.chatgpt-reference-v1 .chatgpt-search-result-icon svg {
  width: 20px;
  height: 20px;
  stroke-width: var(--wds-icon-stroke, 1.7);
}

body.chatgpt-reference-v1 .chatgpt-search-result-copy {
  display: grid;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
}

body.chatgpt-reference-v1 .chatgpt-search-result-copy strong {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--ds-text-body-16);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 24px;
}

body.chatgpt-reference-v1 .chatgpt-search-result.is-query-result {
  height: 64px;
  min-height: 64px;
  padding: 12px 16px;
}

body.chatgpt-reference-v1 .chatgpt-search-result.is-query-result .chatgpt-search-result-copy {
  gap: 2px;
}

body.chatgpt-reference-v1 .chatgpt-search-result.is-query-result .chatgpt-search-result-copy strong {
  font-size: var(--ds-text-body-16);
  line-height: 24px;
}

body.chatgpt-reference-v1 .chatgpt-search-result-preview {
  min-width: 0;
  overflow: hidden;
  color: var(--cg-muted, #8a8a8a);
  font-size: var(--ds-text-caption-12);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 16px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-search-mark {
  padding: 0;
  border-radius: 2px;
  background: rgba(255, 214, 102, 0.42);
  color: inherit;
  font: inherit;
}

html[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-search-mark {
  background: rgba(255, 214, 102, 0.30);
}

body.chatgpt-reference-v1 .chatgpt-search-empty {
  color: var(--cg-muted, #6b7280);
  font-size: var(--ds-text-body-16);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 22px;
}

body.chatgpt-reference-v1 .chatgpt-search-empty {
  margin: 0;
  padding: 14px 34px 24px;
}

@media (max-width: 520px) {
  /* 20260708-search-dialog-mobile-fullscreen: the search dialog is a content panel, so
     on mobile it goes full-screen via the shared [data-mobile-fullscreen] rule (ChatGPT
     parity) instead of the old centered 420px card. Only inner layout lives here. */
  body.chatgpt-reference-v1 .chatgpt-search-input-row {
    min-height: 56px;
    max-height: 56px;
    margin: 0 10px 0 8px;
  }

  body.chatgpt-reference-v1 .chatgpt-search-body {
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    scroll-padding-block: 8px;
  }

  body.chatgpt-reference-v1 .chatgpt-search-row {
    width: calc(100% - 12px);
    margin: 0 6px;
    padding: 10px 12px;
  }

  body.chatgpt-reference-v1 .chatgpt-search-result.is-query-result {
    height: auto;
    min-height: 64px;
  }

  body.chatgpt-reference-v1 .chatgpt-search-result-preview {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-dialog[open] {
  width: min(448px, calc(100vw - 32px));
  padding: 0;
  overflow: hidden;
  border: 0;
  border-radius: 16px;
  background: var(--cg-menu-bg, #ffffff);
  color: var(--cg-text, #0d0d0d);
  box-shadow: var(--cg-dialog-shadow);
  font-size: var(--ds-text-body-16);
  line-height: 24px;
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(0.985);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
  will-change: opacity, transform;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-dialog[data-motion-state="open"] {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-dialog[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(0.985);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-dialog::backdrop {
  background: rgba(0, 0, 0, 0.16);
  opacity: 0;
  transition: opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-dialog[data-motion-state="open"]::backdrop {
  opacity: 1;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-dialog[data-motion-state="closing"]::backdrop {
  opacity: 0;
}

html[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-delete-confirm-dialog::backdrop {
  background: rgba(0, 0, 0, 0.52);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-shell {
  display: flex;
  flex-direction: column;
  width: 100%;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-head {
  display: flex;
  min-height: 52px;
  align-items: center;
  justify-content: space-between;
  padding: 10px 10px 10px 16px;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-head h2 {
  margin: 0;
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-title-18);
  font-weight: 400;
  line-height: 28px;
  letter-spacing: 0;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-body {
  padding: 4px 16px 16px;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-primary {
  margin: 0;
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-body-16);
  line-height: 24px;
  letter-spacing: 0;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-primary strong {
  font-weight: 600;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-memory-note {
  margin: 8px 0 0;
  color: var(--cg-muted, #8f8f8f);
  font-size: var(--ds-text-body-14);
  line-height: 20px;
  letter-spacing: 0;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-memory-note button {
  border: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: inherit;
  text-decoration: underline;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 16px;
  padding: 0;
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-cancel,
body.chatgpt-reference-v1 .chatgpt-delete-confirm-danger,
body.chatgpt-reference-v1 .chatgpt-delete-confirm-primary-action {
  min-width: 50px;
  height: 36px;
  padding: 0 12px;
  border-radius: var(--ds-radius-pill, 999px);
  font: inherit;
  font-size: var(--ds-text-body-14);
  font-weight: 500;
  line-height: 20px;
  cursor: pointer;
  transition:
    background-color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    border-color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    transform var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-cancel {
  border: 1px solid rgba(0, 0, 0, 0.15);
  background: #ffffff;
  color: var(--cg-text);
}

html[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-delete-confirm-cancel {
  border-color: var(--cg-menu-border);
  background: var(--cg-menu-bg);
  color: var(--cg-text);
}

html[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-settings-tabs button:hover,
html[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-settings-tabs button.is-active {
  background: var(--cg-hover);
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-cancel:hover {
  background: var(--cg-hover-soft, #f4f4f4);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-danger {
  border: 1px solid transparent;
  background: var(--ds-button-danger-bg);
  color: var(--ds-button-danger-fg);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-danger:hover {
  border-color: transparent;
  background: var(--ds-button-danger-bg-hover);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-primary-action {
  border: 1px solid transparent;
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-primary-action:hover {
  border-color: transparent;
  background: var(--cg-send-bg);
}

body.chatgpt-reference-v1 .chatgpt-delete-confirm-cancel:active,
body.chatgpt-reference-v1 .chatgpt-delete-confirm-danger:active,
body.chatgpt-reference-v1 .chatgpt-delete-confirm-primary-action:active {
  transform: scale(0.985);
}

/* 이동(⌘G) 대화상자 (doc 597 P1-12 · doc 544 §38). 위 chatgpt-delete-confirm-* 셸을 그대로
   재사용한다(삭제 확인·업로드 충돌·버전 기록과 같은 셸 — 새 셸 클래스를 만들지 않는다).
   입력 칸 하나만 이 문서에 아직 없어서 새로 추가한다 — 값은 sheet-function-controls 의
   input[type="search"](테두리·라운드·배경·포커스 링)에서 그대로 옮겼다, 새 원시값 없음. */
body.chatgpt-reference-v1 .artifact-sheet-goto-label {
  display: block;
  margin: 0 0 4px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
}

body.chatgpt-reference-v1 .artifact-sheet-goto-input {
  width: 100%;
  height: 36px;
  padding: 0 12px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-sm);
  background: var(--cg-composer-bg, var(--bg));
  color: inherit;
  font: inherit;
  font-size: var(--ds-text-body-14);
}

body.chatgpt-reference-v1 .artifact-sheet-goto-input:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}

/* 오류 문구 — chatgpt-delete-confirm-memory-note 를 재사용하되 색만 토큰으로 바꾼다(원시 색이
   아니라 var() 참조라 anti-drift raw-color 래칫에 잡히지 않는다). */
body.chatgpt-reference-v1 .chatgpt-delete-confirm-memory-note[data-tone="error"] {
  color: var(--wds-color-error-text);
}

body.chatgpt-reference-v1 .message-list {
  width: 100%;
  max-width: none;
  margin: 0;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: max(24px, calc((100% - 768px) / 2));
  padding-left: max(24px, calc((100% - 768px) / 2));
  scrollbar-gutter: stable;
}

html[data-chat-state="active"] body.chatgpt-reference-v1 .message-list,
html[data-chat-state="loading"] body.chatgpt-reference-v1 .message-list {
  grid-row: 1;
  padding-top: 72px;
  padding-bottom: 24px;
}

body.chatgpt-reference-v1 .recent-item.is-active,
body.chatgpt-reference-v1 .recent-item.is-active:hover {
  background: var(--cg-sidebar-active-bg);
}

body.chatgpt-reference-v1 .recent-item.is-active .recent-primary {
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .recent-item.is-active .recent-actions {
  background: linear-gradient(90deg, color-mix(in srgb, var(--cg-sidebar-active-bg) 0%, transparent), var(--cg-sidebar-active-bg) var(--chatgpt-recent-action-fade));
}

html[data-theme="dark"] body.chatgpt-reference-v1 .recent-item.is-active .recent-actions {
  background: linear-gradient(90deg, color-mix(in srgb, var(--cg-sidebar-active-bg) 0%, transparent), var(--cg-sidebar-active-bg) var(--chatgpt-recent-action-fade));
}

body.chatgpt-reference-v1 .composer-surface textarea:focus {
  border-color: transparent;
  box-shadow: none;
  outline: none;
}

/* K-4(2.4.7 AA) 수리 회귀 정정(후보 41 cf0ad9117 → composer-focus-ring-pill) —
   textarea 자체에 outline 을 얹었더니 알약(셸) 링과 겹쳐 알약 안쪽에 검은 직사각형이
   보였다(오너 운영 캡처 09-12). 2.4.7 의 "보이는 표시"는 컨트롤 자체 링일 필요가 없으므로
   textarea 는 다시 outline:none(위 :focus 규칙과 동일)으로 두고, 셸 레벨
   :has(textarea:focus-visible) 링 1종만 표시 — 알약 반경을 그대로 따른다(Chrome 94+ outline
   은 border-radius 를 따름). textarea 의 focus-visible 상태 자체는 셸 셀렉터가 그대로 읽으므로
   마우스 클릭·Tab 모두 셸 링이 켜진다. */
body.chatgpt-reference-v1 .composer-surface textarea:focus-visible {
  border-color: transparent;
  box-shadow: none;
  outline: none;
}

body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:focus-within,
body.chatgpt-reference-v1 .chatgpt-composer-shell:focus-within {
  border-color: var(--cg-composer-border);
  box-shadow: var(--cg-shadow);
  outline: none;
}

body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(textarea:focus-visible),
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:focus-visible) {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: 2px;
}

body.chatgpt-reference-v1 .chatgpt-more-menu button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-account-menu button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-recent-menu button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-conversation-menu button:focus-visible,
body.chatgpt-reference-v1 .recent-action:focus-visible,
body.chatgpt-reference-v1 .composer-menu-item:focus-visible,
body.chatgpt-reference-v1 .reasoning-menu-item:focus-visible,
body.chatgpt-reference-v1 .chatgpt-settings-tabs button:focus-visible,
body.chatgpt-reference-v1 .settings-combobox:focus-visible,
body.chatgpt-reference-v1 .settings-switch:focus-visible,
body.chatgpt-reference-v1 .chatgpt-search-row:focus-visible,
body.chatgpt-reference-v1 .chatgpt-share-close:focus-visible {
  outline: 1px solid var(--cg-focus-visible-ring);
  outline-offset: -1px;
  background: var(--cg-focus-bg);
}

body.chatgpt-reference-v1 .chatgpt-share-copy:focus-visible {
  outline: 1px solid var(--cg-focus-visible-ring);
  outline-offset: 2px;
}

body.chatgpt-reference-v1 .chatgpt-settings-tabs button:focus:not(:focus-visible) {
  outline: none;
  outline-offset: 0;
}

body.chatgpt-reference-v1 .composer-popover,
body.chatgpt-reference-v1 .chatgpt-search-body,
body.chatgpt-reference-v1 .chatgpt-settings-tabs,
body.chatgpt-reference-v1 .chatgpt-settings-panel,
body.chatgpt-reference-v1 .message-list,
body.chatgpt-reference-v1 .recent-list {
  scrollbar-color: var(--cg-scrollbar-thumb) var(--cg-scrollbar-track);
  scrollbar-width: thin;
}

body.chatgpt-reference-v1 .composer-popover::-webkit-scrollbar,
body.chatgpt-reference-v1 .chatgpt-search-body::-webkit-scrollbar,
body.chatgpt-reference-v1 .chatgpt-settings-tabs::-webkit-scrollbar,
body.chatgpt-reference-v1 .chatgpt-settings-panel::-webkit-scrollbar,
body.chatgpt-reference-v1 .message-list::-webkit-scrollbar,
body.chatgpt-reference-v1 .recent-list::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

body.chatgpt-reference-v1 .composer-popover::-webkit-scrollbar-thumb,
body.chatgpt-reference-v1 .chatgpt-search-body::-webkit-scrollbar-thumb,
body.chatgpt-reference-v1 .chatgpt-settings-tabs::-webkit-scrollbar-thumb,
body.chatgpt-reference-v1 .chatgpt-settings-panel::-webkit-scrollbar-thumb,
body.chatgpt-reference-v1 .message-list::-webkit-scrollbar-thumb,
body.chatgpt-reference-v1 .recent-list::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-scrollbar-thumb);
  background-clip: content-box;
}

html[data-theme="dark"] .chatgpt-reference-v1 ::selection {
  background: #ffffff;
  color: #000000;
}

/* WIZON motion grammar layer: Apple-like continuity on top of WDS spring tokens. */
.chatgpt-reference-v1 {
  --wizon-motion-effect-fast: var(--wizon-ai-ease-enter, var(--wizon-ai-motion-effect-fast, var(--wds-spring-effect-fast, var(--wds-ease-out, cubic-bezier(0.16, 1, 0.3, 1)))));
  --wizon-motion-effect-ease: var(--wizon-ai-ease-move, var(--wizon-ai-motion-effect-ease, var(--wds-spring-effect-default, var(--wds-ease-out, cubic-bezier(0.16, 1, 0.3, 1)))));
  --wizon-motion-spatial-ease: var(--wizon-ai-ease-move, var(--wizon-ai-motion-spatial-ease, var(--wds-spring-spatial-default, var(--wds-ease-emphasized-decelerate, cubic-bezier(0.05, 0.7, 0.1, 1)))));
  --wizon-motion-micro-duration: var(--wizon-ai-transition-basic-duration, var(--wizon-ai-motion-micro-duration, var(--wds-spring-effect-fast-duration, 124ms)));
  --wizon-motion-surface-duration: var(--wizon-ai-motion-surface-duration, var(--wds-spring-effect-default-duration, 188ms));
  --wizon-motion-panel-duration: var(--wizon-ai-motion-panel-duration, var(--wds-spring-spatial-default-duration, 372ms));
  --wizon-message-enter-duration: var(--wizon-ai-message-enter-duration, 420ms);
  --wizon-message-action-delay: var(--wizon-ai-message-action-delay, 92ms);
}

@keyframes chatgpt-message-entrance {
  from {
    opacity: 0;
    transform: translate3d(0, 10px, 0) scale(0.992);
    filter: blur(1.5px);
  }

  to {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
    filter: blur(0);
  }
}

@keyframes chatgpt-pending-breathe {
  0%,
  100% {
    opacity: 0.46;
    transform: scale(0.92);
  }

  50% {
    opacity: 1;
    transform: scale(1.08);
  }
}

@keyframes chatgpt-action-rise {
  from {
    opacity: 0;
    transform: translate3d(0, 6px, 0);
  }

  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

body.chatgpt-reference-v1 .message {
  transform-origin: center bottom;
  will-change: opacity, transform;
}

/* 20260924-feedback-picker-escape (W6b 수리, doc 199 회귀): article.message 는 항상
   will-change: opacity, transform 을 건다(메시지 등장 애니메이션, 위 규칙) — 스펙상
   will-change 목록에 transform 이 있으면 실제 transform 값이 없어도 position:fixed
   후손의 containing block 이 된다. 390 바텀시트 사유 피커(.feedback-picker-row,
   position:fixed)가 뷰포트가 아니라 부모 article 기준으로 배치돼 top -112~-144px·
   bottom 256~288px 로 떴다 — 가르는 값은 picker.bottom === article.bottom(소수점 일치).
   경쟁 수리안: (a) 피커를 body 직속으로 포털 — app.js 의 열기/닫기·포커스 트랩·outside
   click 로직이 모두 article 상대 DOM 순회(closest("[data-message-id]"))를 쓰고 있어
   재작성 범위가 크다. (b) 선례(20260709-mobile-sheet-escape, 사이드바 드로어)와 같은
   패턴으로 열린 동안만 will-change 를 해제 — 채택. .feedback-picker-row 는 JS가
   열렸을 때만 DOM에 넣으므로(renderDislikeReasonPicker, 조건부 렌더) 존재 자체가
   열림 상태이고, :has() 로 좁히면 전역 will-change 는 그대로 유지된다(성능 영향 없음,
   메시지 애니메이션은 열림 순간 이미 끝나 있다). */
body.chatgpt-reference-v1 .message:has(.feedback-picker-row) {
  will-change: auto !important;
}

body.chatgpt-reference-v1 .message[data-message-motion="enter"] {
  animation: chatgpt-message-entrance var(--wizon-message-enter-duration) var(--wizon-motion-spatial-ease) both;
}

body.chatgpt-reference-v1 .message.pending .thinking-inline {
  animation: chatgpt-pending-breathe 1180ms var(--wizon-motion-effect-ease) infinite;
  transform-origin: center;
}

body.chatgpt-reference-v1 .message[data-message-state="pending"] .answer-activity {
  transition:
    background-color var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    border-color var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
}

body.chatgpt-reference-v1 .message[data-message-state="complete"] .message-actions {
  animation: chatgpt-action-rise var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease) both;
  animation-delay: var(--wizon-message-action-delay);
}

body.chatgpt-reference-v1 .app-shell {
  transition: grid-template-columns var(--wizon-motion-panel-duration) var(--wizon-motion-spatial-ease);
}

body.chatgpt-reference-v1 .sidebar,
body.chatgpt-reference-v1 .chatgpt-sidebar-rail {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-panel-duration) var(--wizon-motion-spatial-ease);
  will-change: opacity, transform;
}

body.chatgpt-reference-v1 .sidebar[data-motion-state="opening"],
body.chatgpt-reference-v1 .sidebar[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(-8px, 0, 0);
}

body.chatgpt-reference-v1 .sidebar[data-motion-state="open"],
body.chatgpt-reference-v1 .chatgpt-sidebar-rail[data-motion-state="open"] {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

body.chatgpt-reference-v1 .chatgpt-sidebar-rail[data-motion-state="opening"],
body.chatgpt-reference-v1 .chatgpt-sidebar-rail[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(-4px, 0, 0) scale(0.985);
}

body.chatgpt-reference-v1[data-sidebar-transition="collapsing"] .sidebar,
body.chatgpt-reference-v1[data-sidebar-transition="expanding"] .sidebar {
  position: absolute;
  inset: 0 auto 0 0;
  z-index: 120;
  width: min(260px, calc(100vw - 52px));
  height: 100dvh;
  pointer-events: none;
}

body.chatgpt-reference-v1[data-sidebar-transition="collapsing"] .chatgpt-sidebar-rail,
body.chatgpt-reference-v1[data-sidebar-transition="expanding"] .chatgpt-sidebar-rail {
  display: flex;
}

body.chatgpt-reference-v1[data-sidebar-transition] .chatgpt-tooltip {
  display: none !important;
  opacity: 0 !important;
  visibility: hidden !important;
}

.chatgpt-reference-v1 .chatgpt-sidebar-link,
.chatgpt-reference-v1 .new-chat-button,
.chatgpt-reference-v1 .theme-toggle,
.chatgpt-reference-v1 .nav-item,
.chatgpt-reference-v1 .chatgpt-icon-button,
.chatgpt-reference-v1 .chatgpt-rail-button,
.chatgpt-reference-v1 .chatgpt-account-button,
.chatgpt-reference-v1 .chatgpt-recent-header-action,
.chatgpt-reference-v1 .recent-action,
.chatgpt-reference-v1 .prompt-chip,
.chatgpt-reference-v1 .composer-icon-button,
.chatgpt-reference-v1 .composer-model-pill,
.chatgpt-reference-v1 .composer-send-button,
.chatgpt-reference-v1 #sendButton,
.chatgpt-reference-v1 .message-action-button,
body.chatgpt-reference-v1 .chatgpt-conversation-share,
body.chatgpt-reference-v1 .chatgpt-conversation-more {
  transition:
    background-color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    border-color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    box-shadow var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    transform var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast);
  will-change: transform;
}

.chatgpt-reference-v1 .chatgpt-sidebar-link:active,
.chatgpt-reference-v1 .new-chat-button:active,
.chatgpt-reference-v1 .theme-toggle:active,
.chatgpt-reference-v1 .nav-item:active,
.chatgpt-reference-v1 .chatgpt-icon-button:active,
.chatgpt-reference-v1 .chatgpt-rail-button:active,
.chatgpt-reference-v1 .chatgpt-account-button:active,
.chatgpt-reference-v1 .chatgpt-recent-header-action:active,
.chatgpt-reference-v1 .recent-action:active,
.chatgpt-reference-v1 .prompt-chip:active,
.chatgpt-reference-v1 .composer-icon-button:active,
.chatgpt-reference-v1 .composer-model-pill:active,
.chatgpt-reference-v1 .composer-send-button:active,
.chatgpt-reference-v1 #sendButton:active,
.chatgpt-reference-v1 .message-action-button:active,
body.chatgpt-reference-v1 .chatgpt-conversation-share:active,
body.chatgpt-reference-v1 .chatgpt-conversation-more:active {
  transform: scale(0.985);
}

.chatgpt-reference-v1 .chatgpt-more-menu,
.chatgpt-reference-v1 .chatgpt-account-menu,
.chatgpt-reference-v1 .chatgpt-recent-menu,
.chatgpt-reference-v1 .composer-popover,
.chatgpt-reference-v1 .reasoning-popover,
body.chatgpt-reference-v1 .chatgpt-conversation-menu {
  --wizon-popover-y: 6px;
  opacity: 0;
  pointer-events: none;
  transform: translate3d(0, var(--wizon-popover-y, 6px), 0) scale(0.985);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
  will-change: opacity, transform;
}

.chatgpt-reference-v1 .chatgpt-more-menu,
.chatgpt-reference-v1 .chatgpt-recent-menu,
body.chatgpt-reference-v1 .chatgpt-conversation-menu {
  transform-origin: right top;
}

.chatgpt-reference-v1 .chatgpt-account-menu {
  transform-origin: left bottom;
}

.chatgpt-reference-v1 .composer-popover {
  transform-origin: left bottom;
}

.chatgpt-reference-v1 .reasoning-popover {
  transform-origin: right bottom;
}

.chatgpt-reference-v1 .reasoning-model-menu {
  transform-origin: left bottom;
}

.chatgpt-reference-v1 .chatgpt-more-menu[data-motion-state="open"],
.chatgpt-reference-v1 .chatgpt-account-menu[data-motion-state="open"],
.chatgpt-reference-v1 .chatgpt-recent-menu[data-motion-state="open"],
.chatgpt-reference-v1 .composer-popover[data-motion-state="open"],
.chatgpt-reference-v1 .reasoning-popover[data-motion-state="open"],
body.chatgpt-reference-v1 .chatgpt-conversation-menu[data-motion-state="open"] {
  opacity: 1;
  pointer-events: auto;
  transform: translate3d(0, 0, 0) scale(1);
}

.chatgpt-reference-v1 .chatgpt-more-menu[data-motion-state="closing"],
.chatgpt-reference-v1 .chatgpt-account-menu[data-motion-state="closing"],
.chatgpt-reference-v1 .chatgpt-recent-menu[data-motion-state="closing"],
.chatgpt-reference-v1 .composer-popover[data-motion-state="closing"],
.chatgpt-reference-v1 .reasoning-popover[data-motion-state="closing"],
body.chatgpt-reference-v1 .chatgpt-conversation-menu[data-motion-state="closing"] {
  opacity: 0;
  pointer-events: none;
  transform: translate3d(0, var(--wizon-popover-y, 6px), 0) scale(0.985);
}

.chatgpt-reference-v1 .chatgpt-more-menu button,
.chatgpt-reference-v1 .chatgpt-account-menu button,
.chatgpt-reference-v1 .chatgpt-recent-menu button,
.chatgpt-reference-v1 .composer-menu-item,
.chatgpt-reference-v1 .reasoning-menu-item,
body.chatgpt-reference-v1 .chatgpt-conversation-menu button {
  transition:
    background-color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    transform var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast);
}

.chatgpt-reference-v1 .chatgpt-more-menu button:active,
.chatgpt-reference-v1 .chatgpt-account-menu button:active,
.chatgpt-reference-v1 .chatgpt-recent-menu button:active,
.chatgpt-reference-v1 .composer-menu-item:active,
.chatgpt-reference-v1 .reasoning-menu-item:active,
body.chatgpt-reference-v1 .chatgpt-conversation-menu button:active {
  transform: scale(0.99);
}

.chatgpt-reference-v1 .chatgpt-settings-dialog[open] {
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(0.985);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
  will-change: opacity, transform;
}

.chatgpt-reference-v1 .chatgpt-settings-dialog[data-motion-state="open"] {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}

.chatgpt-reference-v1 .chatgpt-settings-dialog[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(0.985);
}

.chatgpt-reference-v1 .chatgpt-settings-dialog::backdrop {
  opacity: 0;
  transition: opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease);
}

.chatgpt-reference-v1 .chatgpt-settings-dialog[data-motion-state="open"]::backdrop {
  opacity: 1;
}

.chatgpt-reference-v1 .chatgpt-settings-dialog[data-motion-state="closing"]::backdrop {
  opacity: 0;
}

body.chatgpt-reference-v1 .context-pane {
  position: fixed;
  top: 16px;
  right: 16px;
  bottom: 16px;
  z-index: 55;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  width: min(420px, calc(100vw - 32px));
  min-width: 0;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-lg, 18px);
  background: var(--cg-menu-bg);
  color: var(--cg-text);
  box-shadow: 0 28px 80px rgba(0, 0, 0, 0.18), 0 8px 28px rgba(0, 0, 0, 0.10);
  opacity: 0;
  pointer-events: none;
  transform: translate3d(24px, 0, 0) scale(0.985);
  transform-origin: right center;
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-panel-duration) var(--wizon-motion-spatial-ease);
  will-change: opacity, transform;
}

body.chatgpt-reference-v1 .context-pane[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .context-pane[data-motion-state="open"] {
  opacity: 1;
  pointer-events: auto;
  transform: translate3d(0, 0, 0) scale(1);
}

body.chatgpt-reference-v1 .context-pane[data-motion-state="closing"] {
  opacity: 0;
  pointer-events: none;
  transform: translate3d(24px, 0, 0) scale(0.985);
}

body.side-panel-open.chatgpt-reference-v1 .chat-pane {
  transition: filter var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease);
}

body.chatgpt-reference-v1 .chatgpt-side-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 64px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--cg-menu-border);
}

body.chatgpt-reference-v1 .chatgpt-side-panel-head h2 {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-title-18);
  font-weight: 600;
  line-height: 24px;
  letter-spacing: 0;
}

body.chatgpt-reference-v1 .chatgpt-side-panel-kicker {
  margin: 0 0 2px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 500;
  line-height: 16px;
  letter-spacing: 0;
}

body.chatgpt-reference-v1 .context-pane .tabs {
  display: flex;
  gap: 6px;
  padding: 10px 12px;
  overflow-x: auto;
  border-bottom: 1px solid var(--cg-menu-border);
  scrollbar-width: none;
}

body.chatgpt-reference-v1 .context-pane .tabs::-webkit-scrollbar {
  display: none;
}

body.chatgpt-reference-v1 .context-pane .tab-button {
  flex: 0 0 auto;
  min-height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: transparent;
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-13);
  font-weight: 500;
  line-height: 18px;
}

body.chatgpt-reference-v1 .context-pane .tab-button:hover,
body.chatgpt-reference-v1 .context-pane .tab-button.active {
  background: var(--cg-hover);
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .context-pane .tab-content {
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  padding: 16px;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

body.chatgpt-reference-v1 .context-pane .panel-block {
  margin: 0 0 14px;
  border-color: var(--cg-menu-border);
  background: var(--cg-surface);
  color: var(--cg-text);
}

html[data-theme="dark"] body.chatgpt-reference-v1 .context-pane {
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.52), 0 8px 30px rgba(0, 0, 0, 0.36);
}

.chatgpt-reference-v1 .message-list[hidden],
.chatgpt-reference-v1 .chatgpt-composer-shell[hidden],
.chatgpt-reference-v1 .chatgpt-prompt-shelf[hidden] {
  display: none !important;
}

.chatgpt-reference-v1.portal-route-open .chat-pane {
  display: block;
  min-height: 100vh;
  overflow-y: auto;
}

/* 20260706-projects-route-no-home-composer-qa: ChatGPT route pages do not show the home chat prompt. */
.chatgpt-reference-v1.portal-route-open .message-list,
.chatgpt-reference-v1.portal-route-open .chatgpt-composer-shell,
.chatgpt-reference-v1.portal-route-open .composer-surface.chatgpt-composer-shell,
.chatgpt-reference-v1.portal-route-open .chatgpt-prompt-shelf,
/* #trustPanel is a .chatgpt-route-page too, but it is rendered as the LAST child of .chat-pane
   (after the composer) — NOT a preceding sibling of .message-list — so it never matches this
   thread-hide rule. Excluding it here with an ID would raise this rule's specificity and beat the
   docked-file-panel's message-list override, so we keep the selector ID-free. */
.chatgpt-reference-v1 .chatgpt-route-page:not([hidden]) ~ .message-list,
.chatgpt-reference-v1 .chatgpt-route-page:not([hidden]) ~ .chatgpt-composer-shell,
.chatgpt-reference-v1 .chatgpt-route-page:not([hidden]) ~ .composer-surface.chatgpt-composer-shell,
.chatgpt-reference-v1 .chatgpt-route-page:not([hidden]) ~ .chatgpt-prompt-shelf,
html[data-chat-state="route"] body.chatgpt-reference-v1 .message-list,
html[data-chat-state="route"] body.chatgpt-reference-v1 .chatgpt-composer-shell,
html[data-chat-state="route"] body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell,
html[data-chat-state="route"] body.chatgpt-reference-v1 .chatgpt-prompt-shelf,
html[data-portal-route-open="true"] body.chatgpt-reference-v1 .message-list,
html[data-portal-route-open="true"] body.chatgpt-reference-v1 .chatgpt-composer-shell,
html[data-portal-route-open="true"] body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell,
html[data-portal-route-open="true"] body.chatgpt-reference-v1 .chatgpt-prompt-shelf {
  display: none !important;
}

.chatgpt-reference-v1 .chatgpt-route-page[hidden] {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-route-page {
  --chatgpt-route-page-width: min(var(--wizon-ai-chat-max-width), calc(100% - (var(--wizon-ai-thread-gutter) * 6)));
  --chatgpt-route-page-top: calc(var(--wizon-ai-space-unit) * 28);
  --chatgpt-route-page-bottom: var(--ds-space-10, 64px);
  --chatgpt-route-gap-lg: var(--ds-space-8, 40px);
  --chatgpt-route-gap-md: var(--ds-space-6, 24px);
  --chatgpt-route-gap-sm: var(--ds-space-3, 12px);
  --chatgpt-route-gap-xs: var(--ds-space-2, 8px);
  --chatgpt-route-gap-2xs: calc(var(--wizon-ai-space-unit) * 1.5);
  --chatgpt-route-control-height: var(--wizon-ai-menu-row-height);
  --chatgpt-route-control-pad-x: calc(var(--wizon-ai-space-unit) * 3.5);
  --chatgpt-route-control-pad-y: var(--ds-space-2, 8px);
  --chatgpt-route-search-width: calc(var(--wizon-ai-space-unit) * 60);
  --chatgpt-route-search-icon-column: calc(var(--wizon-ai-space-unit) * 4.5);
  --chatgpt-route-search-font-size: var(--wds-font-size-body-2, 14px);
  --chatgpt-route-search-line-height: var(--wds-line-height-label, 20px);
  --chatgpt-route-row-height: calc(var(--wizon-ai-space-unit) * 16.5);
  --chatgpt-route-row-head-height: var(--wizon-ai-menu-row-height);
  --chatgpt-route-row-icon-size: var(--wizon-ai-action-target-sm);
  --chatgpt-route-radius-control: var(--wizon-ai-radius-control);
  --chatgpt-route-radius-lg: var(--wizon-ai-radius-popover);
  --chatgpt-route-radius-pill: var(--wizon-ai-radius-pill);
  --chatgpt-route-body-font-size: var(--wds-font-size-body-2);
  --chatgpt-route-body-line-height: var(--wds-line-height-label);
  --chatgpt-route-title-size: var(--wds-font-size-headline-m, 1.75rem);
  width: var(--chatgpt-route-page-width);
  margin: 0 auto;
  padding: var(--chatgpt-route-page-top) 0 var(--chatgpt-route-page-bottom);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-route-page-inner {
  display: grid;
  gap: var(--chatgpt-route-gap-lg);
}

/* The "새로 만들기" plus glyph only appears when the button collapses to an icon in the
   docked panel header; the full-width library page keeps the labelled "새로 만들기 v" pill. */
.chatgpt-reference-v1 .chatgpt-route-primary-plus {
  display: none;
}
.chatgpt-reference-v1 .chatgpt-route-primary-plus svg {
  width: 20px;
  height: 20px;
}

/* 20260710-conversation-files-desktop-dock: on desktop the chat-scoped file view (docs/153)
   docks as a right rail so the conversation stays visible on the left, instead of taking over
   the whole screen like mobile does. Body class is set only for fine pointers (see
   syncPortalRouteSurfaceVisibility), so mobile/tablet keep the full-screen route. The chat-pane
   gets a matching right padding; the route page is pinned absolutely into that reserved strip,
   so in-flow chat content and the rail never overlap. */
body.library-scope-docked.chatgpt-reference-v1 .chat-pane {
  position: relative;
  padding-right: clamp(360px, 38%, 520px);
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage.chatgpt-route-page {
  position: absolute;
  inset: 0 0 0 auto;
  width: clamp(360px, 38%, 520px);
  margin: 0;
  max-width: none;
  padding: 20px 20px calc(20px + var(--wizon-safe-bottom, 0px));
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--cg-menu-bg, var(--bg));
  border-left: 1px solid var(--line);
  box-shadow: -14px 0 34px rgba(0, 0, 0, 0.06);
  z-index: 40;
  /* the route head (title + fixed-width search + 새로 만들기) is built for a wide page;
     in the narrow rail let it wrap and the search go full-width. */
  --chatgpt-route-search-width: 100%;
}
/* Docked header matches the mobile pattern: title + "새로 만들기" collapse to a "+" icon
   (with hover tooltip) on row 1, and the search goes full-width on row 2. display:contents
   dissolves the actions wrapper so h1 / 새로 만들기 / search can be placed on the grid. */
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-route-page-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "title new"
    "search search";
  align-items: center;
  column-gap: 8px;
  row-gap: 12px;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-route-page-head > h1 {
  grid-area: title;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-route-page-actions {
  display: contents;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-route-search {
  grid-area: search;
  width: 100%;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-library-menu-anchor {
  grid-area: new;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-route-primary {
  width: 36px;
  height: 36px;
  min-width: 36px;
  padding: 0;
  gap: 0;
  justify-content: center;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-route-primary-plus {
  display: inline-flex;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-route-primary-label,
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-route-primary-caret {
  display: none;
}
/* keep the conversation "..." actions clear of the docked rail */
body.library-scope-docked.chatgpt-reference-v1 .chatgpt-conversation-actions {
  right: calc(clamp(360px, 38%, 520px) + 22px);
}

/* 20260710-dock-motion: the desktop chat-scoped file rail is NON-modal — the conversation stays
   fully visible, scrollable and interactive on the left (no dimming scrim). The rail slides in/out
   from the right with the same offcanvas motion as the left sidebar drawer. Clicking the empty
   chat background closes it (handleDockBackgroundClick); the closing class runs the exit slide. */
body.library-scope-docked.chatgpt-reference-v1 #portalPage.chatgpt-route-page {
  animation: wizon-dock-in var(--wds-spring-spatial-default-duration, 372ms)
    var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1));
}
body.library-dock-closing.chatgpt-reference-v1 #portalPage.chatgpt-route-page {
  animation: wizon-dock-out var(--wizon-sheet-exit-ms, 300ms)
    var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
}
@keyframes wizon-dock-in {
  from { translate: 100% 0; }
  to { translate: 0 0; }
}
@keyframes wizon-dock-out {
  from { translate: 0 0; }
  to { translate: 100% 0; }
}
@media (prefers-reduced-motion: reduce) {
  body.library-scope-docked.chatgpt-reference-v1 #portalPage.chatgpt-route-page,
  body.library-dock-closing.chatgpt-reference-v1 #portalPage.chatgpt-route-page {
    animation: none !important;
  }
}

/* 20260710-dock-chat-visible: the global "a route page is open → hide the home composer +
   message-list" rules (display:none !important) also blank the conversation in the docked file
   rail. In the docked view we WANT the chat visible + scrollable on the left, so a more-specific
   override (extra .library-scope-docked class → wins on specificity, not just source order)
   restores the message list and composer. The chat-pane already reserves the right strip
   (padding-right) so the two never overlap. */
html[data-portal-route-open="true"] body.library-scope-docked.chatgpt-reference-v1 .message-list {
  display: block !important;
}

/* 20260710-dock-card-redesign: the desktop chat-scoped file view is a FLOATING ROUNDED CARD on the
   right (over codex's flush full-height rail), matching the Codex/ChatGPT reference — margin on all
   sides, rounded corners, soft shadow. It reuses the mobile-collapsed route header and adds a
   leading "‹" back button (the desktop equivalent of the mobile topbar back). The chat stays
   visible on the left. These override codex's docked #portalPage rules (later source order). */
/* The card OVERLAYS the empty right gutter — it does NOT reserve space, so the centered
   conversation column keeps its exact position/width (no reflow). Override codex's reservation. */
body.library-scope-docked.chatgpt-reference-v1 .chat-pane {
  padding-right: 0;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage.chatgpt-route-page {
  inset: 12px 12px 12px auto;
  width: clamp(360px, 34%, 480px);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.16);
}
/* add a leading "back" column to codex's docked header grid (title | new / search) */
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-route-page-head {
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "back title new"
    "search search search";
}
.chatgpt-reference-v1 .chatgpt-library-dock-back {
  display: none;
}
/* 휴지통 라우트(judge L6, 2026-09-11) 는 독(docked) 패널이 아니라 전체화면 라우트라
   body.library-scope-docked 셀렉터를 타지 않는다 — 이 라우트에서만 뒤로 버튼을 복원한다.
   클릭 핸들러(app.js:36724 data-library-deleted-back)는 이미 존재·동작한다. */
.chatgpt-reference-v1 .chatgpt-library-page--deleted .chatgpt-library-dock-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-right: 2px;
  padding: 0;
  border: 0;
  border-radius: var(--wizon-ai-library-dock-back-radius, 8px);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}
.chatgpt-reference-v1 .chatgpt-library-page--deleted .chatgpt-library-dock-back:hover {
  background: var(--cg-hover-soft);
}
.chatgpt-reference-v1 .chatgpt-library-page--deleted .chatgpt-library-dock-back svg {
  width: 22px;
  height: 22px;
}
.chatgpt-reference-v1 .chatgpt-library-page--deleted .chatgpt-route-page-head h1 {
  margin-right: auto;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-library-dock-back {
  grid-area: back;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-right: 2px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-library-dock-back:hover {
  background: var(--cg-hover-soft);
}
body.library-scope-docked.chatgpt-reference-v1 #portalPage .chatgpt-library-dock-back svg {
  width: 22px;
  height: 22px;
}

.chatgpt-reference-v1 .chatgpt-route-page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-md);
}

.chatgpt-reference-v1 .chatgpt-route-page-head h1 {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--chatgpt-route-title-size);
  font-weight: var(--wds-font-weight-regular);
  letter-spacing: 0;
  line-height: calc(var(--chatgpt-route-title-size) + (var(--wizon-ai-space-unit) * 2));
}

.chatgpt-reference-v1 .chatgpt-route-page-head p {
  margin: var(--chatgpt-route-gap-sm) 0 0;
  color: var(--cg-muted);
  font-size: var(--wds-font-size-body-1);
  line-height: calc(var(--wds-font-size-body-1) * 1.5);
}

.chatgpt-reference-v1 .chatgpt-route-page-actions {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-sm);
}

.chatgpt-reference-v1 .chatgpt-route-search {
  display: inline-grid;
  grid-template-columns: var(--chatgpt-route-search-icon-column) minmax(0, 1fr);
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  width: var(--chatgpt-route-search-width);
  min-height: var(--chatgpt-route-control-height);
  padding: 0 var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-surface);
  color: var(--cg-muted-2);
}

.chatgpt-reference-v1 .chatgpt-route-search input {
  min-width: 0;
  min-height: 0;
  height: calc(var(--chatgpt-route-control-height) - 2px);
  padding: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--cg-text);
  box-shadow: none;
  font: inherit;
  font-size: var(--chatgpt-route-search-font-size);
  line-height: var(--chatgpt-route-search-line-height);
}

/* K-4(2.4.7 AA) 수리 — outline: none 이 라이브러리/드라이브/프로젝트/앱/공유 링크 검색 input
   (`[data-portal-route-search]`, 전부 이 한 클래스 공유) 포커스 링을 지웠다. citation-tag 링
   문법(--cg-text 솔리드, 새 원시색 없음)을 그대로 쓴다. test_static_contract.py 의
   test_chatgpt_library_route_inputs_do_not_inherit_legacy_focus_rings 가 이 두 셀렉터 줄을
   콤마로 이어진 리터럴로 검사해 :focus/:focus-visible 을 분리하지 않는다(둘 다 링 표시). */
.chatgpt-reference-v1 .chatgpt-route-search input:focus,
.chatgpt-reference-v1 .chatgpt-route-search input:focus-visible {
  border-color: transparent;
  outline: 2px solid var(--cg-text);
  outline-offset: 2px;
  box-shadow: none;
}

.chatgpt-reference-v1 .chatgpt-route-primary,
.chatgpt-reference-v1 .chatgpt-route-filter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--chatgpt-route-gap-2xs);
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-control-pad-y) var(--chatgpt-route-control-pad-x);
  border: 0;
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
  font-size: var(--chatgpt-route-body-font-size);
  font-weight: var(--wds-font-weight-medium);
  letter-spacing: 0;
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-route-filter {
  border: 1px solid var(--cg-menu-border);
  background: var(--cg-surface);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-route-tabs {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-2xs);
}

/* library-toolbar-siblings 채택 C(§4): 높이를 고정하려면 세로 패딩을 0으로 비워야
   하는데(고정 높이 + 남은 세로 패딩은 상자를 다시 키운다), 그러면 텍스트가 줄
   높이만큼만 차지해 상자 위쪽에 붙는다. inline-flex + center 로 상자 자체가
   내용을 수직 중앙에 놓게 해 높이가 바뀌어도(36 ↔ 44) 다시 계산할 값이 없다. */
.chatgpt-reference-v1 .chatgpt-route-tabs button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--chatgpt-route-control-height);
  min-height: var(--chatgpt-route-control-height);
  padding: 0 var(--chatgpt-route-control-pad-x);
  border: 0;
  border-radius: var(--chatgpt-route-radius-pill);
  background: transparent;
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

/* library-toolbar-siblings 채택 C(§4): 칩·아이콘 버튼·목록 선택이 같은 hover 배경
   (--cg-hover-soft)을 공유해 hover 와 선택이 화면에서 구별되지 않았다. 선택에만
   테두리를 더해 둘을 가른다 — box-sizing: border-box 라 높이·폭은 그대로. */
.chatgpt-reference-v1 .chatgpt-route-tabs button.is-active {
  background: var(--cg-hover-soft);
  border: 1px solid var(--cg-menu-border);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-table {
  display: grid;
  gap: 0;
  margin-top: 0;
}

.chatgpt-reference-v1 .chatgpt-library-menu-anchor {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.chatgpt-reference-v1 .chatgpt-library-popover {
  position: absolute;
  z-index: 35;
  top: calc(100% + var(--chatgpt-route-gap-xs));
  right: 0;
  display: grid;
  min-width: calc(var(--wizon-ai-space-unit) * 50);
  padding: var(--chatgpt-route-gap-2xs);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  box-shadow: var(--cg-shadow-float-lg);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-popover strong {
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm) var(--chatgpt-route-gap-2xs);
  color: var(--cg-muted);
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-library-menu-item {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  width: 100%;
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-text);
  font-size: var(--chatgpt-route-body-font-size);
  font-weight: var(--wds-font-weight-regular);
  line-height: var(--chatgpt-route-body-line-height);
  text-align: left;
}

.chatgpt-reference-v1 .chatgpt-library-menu-item.is-selected {
  color: var(--cg-text);
  font-weight: var(--wds-font-weight-medium);
}

.chatgpt-reference-v1 .chatgpt-library-menu-check {
  display: inline-grid;
  place-items: center;
  width: var(--wizon-ai-icon-size-md);
  height: var(--wizon-ai-icon-size-md);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-menu-item:hover,
.chatgpt-reference-v1 .chatgpt-library-menu-item:focus-visible {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .chatgpt-library-menu-item-danger {
  color: var(--cg-danger-fg);
}

.chatgpt-reference-v1 .chatgpt-library-menu-item-muted {
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-library-filter-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-md);
}

/* 20260710-conversation-files: scope bar for "채팅 내 파일 보기" (library route scoped to one chat). */
.chatgpt-reference-v1 .chatgpt-library-scope-bar {
  display: flex;
  margin: 4px 0 2px;
}

.chatgpt-reference-v1 .chatgpt-library-scope-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 6px 0 10px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  font-weight: 500;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-library-scope-chip svg {
  width: 16px;
  height: 16px;
}

.chatgpt-reference-v1 .chatgpt-library-scope-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: transparent;
  color: var(--cg-muted-2);
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-library-scope-clear:hover {
  background: var(--cg-hover);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-scope-clear svg {
  width: 15px;
  height: 15px;
}

.chatgpt-reference-v1 .chatgpt-library-scope-note {
  margin: 8px 2px 0;
  font-size: var(--ds-text-caption-12);
  color: var(--cg-muted-2);
}

.chatgpt-reference-v1 .chatgpt-library-view-controls {
  display: inline-flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-2xs);
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-library-view-control {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: var(--chatgpt-route-control-height);
  height: var(--chatgpt-route-control-height);
  border: 0;
  border-radius: var(--chatgpt-route-radius-pill);
  background: transparent;
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-library-view-control:hover {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

/* library-toolbar-siblings 채택 C(§4): 칩과 같은 문법 — 선택에만 테두리, hover 는 배경만. */
.chatgpt-reference-v1 .chatgpt-library-view-control.is-active,
.chatgpt-reference-v1 .chatgpt-library-view-control[aria-pressed="true"] {
  background: var(--cg-hover-soft);
  border: 1px solid var(--cg-menu-border);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-filter-count {
  position: absolute;
  right: calc(var(--chatgpt-route-gap-2xs) * -1);
  top: calc(var(--chatgpt-route-gap-2xs) * -1);
  display: inline-grid;
  place-items: center;
  min-width: calc(var(--wizon-ai-space-unit) * 4);
  height: calc(var(--wizon-ai-space-unit) * 4);
  padding: 0 var(--chatgpt-route-gap-2xs);
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
  line-height: 1;
}

/* library-toolbar-siblings 채택 C(§4): 구분선 자체 마진을 없애고 컨테이너 거터
   (--chatgpt-route-gap-2xs, 6px) 하나로 형제 사이 간격을 통일한다. 높이는 버튼
   높이의 고정 비율(0.5)로 재서 형제 높이가 바뀌어도 같이 따라간다. */
.chatgpt-reference-v1 .chatgpt-library-view-divider {
  width: 1px;
  height: calc(var(--chatgpt-route-control-height) * 0.5);
  align-self: center;
  margin: 0;
  background: var(--cg-menu-border);
}

.chatgpt-reference-v1 .chatgpt-library-action-feedback {
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 [data-library-bulk-bar] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
}

.chatgpt-reference-v1 [data-library-bulk-bar] .chatgpt-library-create-submit {
  margin-left: auto;
}

.chatgpt-reference-v1 .chatgpt-library-row {
  display: grid;
  grid-template-columns: var(--ds-space-9, 48px) minmax(0, 1fr) calc(var(--wizon-ai-space-unit) * 40) calc(var(--wizon-ai-space-unit) * 28);
  align-items: center;
  width: 100%;
  min-height: var(--chatgpt-route-row-height);
  padding: 0;
  border: 0;
  border-bottom: 1px solid var(--cg-menu-border);
  background: transparent;
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  font-family: inherit;
  line-height: var(--chatgpt-route-body-line-height);
  text-align: left;
}

.chatgpt-reference-v1 .chatgpt-library-row:not(.chatgpt-library-row--head),
.chatgpt-reference-v1 .chatgpt-library-row-menu-trigger {
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-library-row:hover,
.chatgpt-reference-v1 .chatgpt-library-row:focus-visible {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .chatgpt-library-row:not(.chatgpt-library-row--head):active {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-row--head {
  min-height: var(--chatgpt-route-row-head-height);
  padding: 0;
  background: transparent;
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-library-row-shell {
  position: relative;
}

.chatgpt-reference-v1 .chatgpt-library-file-copy {
  display: grid;
  min-width: 0;
  gap: 3px;
  padding: 8px 12px 8px 0;
}

/* 부제(수정일 · 크기 병합)는 폰 전용 슬롯이다. 데스크톱은 열이 충분해 표가 맞으므로 숨긴다 —
   숨김이라 스크린리더도 읽지 않아 date/size 열과 중복 낭독되지 않는다. 폰에서는 반대로
   date/size 를 숨기고 이 줄만 남긴다(20260802-library-mobile-native-list). */
.chatgpt-reference-v1 .chatgpt-library-row-meta {
  display: none;
}

/* 휴지통 행은 폴더명·복구 상태가 유일한 정보 통로다(judge L1, 2026-09-11) — 색만으로
   구분되던 상태(K-12 위반)를 문구로 되돌리기 위해 데스크톱에서도 복원한다. 일반
   라이브러리 행(위 규칙)은 date/size 열이 이미 있어 그대로 숨겨 둔다. */
.chatgpt-reference-v1 .chatgpt-library-row-shell[data-library-deleted-document-id] .chatgpt-library-row-meta {
  display: block;
}

.chatgpt-reference-v1 .chatgpt-library-file-name {
  overflow: hidden;
  color: var(--cg-text);
  font-weight: var(--wds-font-weight-medium);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-library-search-snippet {
  display: flex;
  align-items: center;
  gap: 8px;
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.35;
}

.chatgpt-reference-v1 .chatgpt-library-search-snippet > span {
  display: -webkit-box;
  min-width: 0;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.chatgpt-reference-v1 .chatgpt-library-search-snippet > small {
  flex: 0 0 auto;
  color: var(--cg-text);
  font: inherit;
  font-weight: var(--wds-font-weight-medium);
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-library-search-snippet mark {
  border-radius: var(--chatgpt-route-gap-2xs);
  background: color-mix(in srgb, var(--wds-brand-blue) 12%, transparent);
  color: inherit;
}

.chatgpt-reference-v1 .chatgpt-library-search-status {
  min-height: 18px;
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

.chatgpt-reference-v1 .chatgpt-library-search-status.is-error {
  color: var(--cg-warning-fg);
}

.chatgpt-reference-v1 .chatgpt-library-processing-state {
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  line-height: 1.25;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-library-processing-state[data-state="failed"] {
  color: var(--cg-danger-fg);
}

.chatgpt-reference-v1 .chatgpt-library-processing-state[data-state="background"] {
  color: var(--ad-blue);
}

.chatgpt-reference-v1 .chatgpt-library-processing-state[data-state="degraded"] {
  color: var(--cg-warning-fg);
}

.chatgpt-reference-v1 .chatgpt-library-processing-progress {
  display: block;
  appearance: none;
  width: min(240px, 100%);
  height: 3px;
  overflow: hidden;
  border: 0;
  border-radius: var(--ds-radius-pill);
  background: var(--cg-menu-border);
}

.chatgpt-reference-v1 .chatgpt-library-processing-progress::-webkit-progress-bar {
  background: var(--cg-menu-border);
}

.chatgpt-reference-v1 .chatgpt-library-processing-progress::-webkit-progress-value,
.chatgpt-reference-v1 .chatgpt-library-processing-progress::-moz-progress-bar {
  border-radius: var(--ds-radius-pill);
  background: var(--ad-blue);
}

.chatgpt-reference-v1 .chatgpt-library-row.is-selected,
.chatgpt-reference-v1 .chatgpt-library-row-shell.is-selected .chatgpt-library-row {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-create-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: var(--ds-space-6, 24px);
  background: rgba(0, 0, 0, 0.38);
}

.chatgpt-reference-v1 .chatgpt-library-create-form {
  display: grid;
  gap: var(--chatgpt-route-gap-md);
  width: min(calc(var(--wizon-ai-space-unit) * 112), calc(100vw - var(--ds-space-8, 40px)));
  padding: var(--ds-space-6, 24px);
  border: 1px solid var(--cg-menu-border);
  border-radius: calc(var(--chatgpt-route-radius-control) + var(--chatgpt-route-gap-xs));
  background: var(--cg-surface);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.22);
  color: var(--cg-text);
  font-family: inherit;
}

.chatgpt-reference-v1 .chatgpt-library-create-form h2 {
  margin: 0;
  font-size: calc(var(--chatgpt-route-title-size) * 0.5);
  font-weight: 600;
  line-height: calc(var(--chatgpt-route-title-size) * 0.68);
  letter-spacing: 0;
}

.chatgpt-reference-v1 .chatgpt-library-create-field {
  display: grid;
  gap: var(--chatgpt-route-gap-xs);
  color: var(--cg-text);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-library-create-input {
  min-width: 0;
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
}

.chatgpt-reference-v1 .chatgpt-library-create-input:focus-visible {
  outline: 2px solid var(--cg-focus-ring, rgba(16, 163, 127, 0.45));
  outline-offset: 2px;
}

.chatgpt-reference-v1 .chatgpt-library-create-actions {
  display: inline-flex;
  justify-content: flex-end;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
}

.chatgpt-reference-v1 .chatgpt-library-create-submit,
.chatgpt-reference-v1 .chatgpt-library-create-cancel {
  display: inline-grid;
  place-items: center;
  min-width: calc(var(--wizon-ai-space-unit) * 16);
  min-height: var(--wizon-ai-action-target-sm);
  padding: 0 var(--chatgpt-route-gap-sm);
  border-radius: var(--chatgpt-route-radius-pill);
  font: inherit;
  font-weight: 600;
}

.chatgpt-reference-v1 .chatgpt-library-create-cancel {
  border: 1px solid var(--cg-menu-border);
  background: var(--cg-surface);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-create-submit {
  border: 1px solid var(--cg-text);
  background: var(--cg-text);
  color: var(--cg-surface);
}

.chatgpt-reference-v1 .chatgpt-library-create-submit:hover,
.chatgpt-reference-v1 .chatgpt-library-create-submit:focus-visible,
.chatgpt-reference-v1 .chatgpt-library-create-cancel:hover,
.chatgpt-reference-v1 .chatgpt-library-create-cancel:focus-visible {
  filter: brightness(0.96);
}

.chatgpt-reference-v1 .chatgpt-library-rename-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: var(--ds-space-6, 24px);
  background: rgba(0, 0, 0, 0.38);
}

.chatgpt-reference-v1 .chatgpt-library-rename-form {
  display: grid;
  gap: var(--chatgpt-route-gap-md);
  width: min(calc(var(--wizon-ai-space-unit) * 112), calc(100vw - var(--ds-space-8, 40px)));
  padding: var(--ds-space-6, 24px);
  border: 1px solid var(--cg-menu-border);
  border-radius: calc(var(--chatgpt-route-radius-control) + var(--chatgpt-route-gap-xs));
  background: var(--cg-surface);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.22);
  color: var(--cg-text);
  font-family: inherit;
}

.chatgpt-reference-v1 .chatgpt-library-rename-form h2 {
  margin: 0;
  font-size: calc(var(--chatgpt-route-title-size) * 0.5);
  font-weight: 600;
  line-height: calc(var(--chatgpt-route-title-size) * 0.68);
  letter-spacing: 0;
}

.chatgpt-reference-v1 .chatgpt-library-rename-input {
  min-width: 0;
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
}

.chatgpt-reference-v1 .chatgpt-library-rename-input:focus-visible {
  outline: 2px solid var(--cg-focus-ring, rgba(16, 163, 127, 0.45));
  outline-offset: 2px;
}

.chatgpt-reference-v1 .chatgpt-library-rename-actions {
  display: inline-flex;
  justify-content: flex-end;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
}

.chatgpt-reference-v1 .chatgpt-library-rename-submit,
.chatgpt-reference-v1 .chatgpt-library-rename-cancel {
  display: inline-grid;
  place-items: center;
  min-width: calc(var(--wizon-ai-space-unit) * 16);
  min-height: var(--wizon-ai-action-target-sm);
  padding: 0 var(--chatgpt-route-gap-sm);
  border-radius: var(--chatgpt-route-radius-pill);
  font: inherit;
  font-weight: 600;
}

.chatgpt-reference-v1 .chatgpt-library-rename-cancel {
  border: 1px solid var(--cg-menu-border);
  background: var(--cg-surface);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-rename-submit {
  border: 1px solid var(--cg-text);
  background: var(--cg-text);
  color: var(--cg-surface);
}

.chatgpt-reference-v1 .chatgpt-library-rename-submit:hover,
.chatgpt-reference-v1 .chatgpt-library-rename-submit:focus-visible,
.chatgpt-reference-v1 .chatgpt-library-rename-cancel:hover,
.chatgpt-reference-v1 .chatgpt-library-rename-cancel:focus-visible {
  filter: brightness(0.96);
}

.chatgpt-reference-v1 .chatgpt-library-move-backdrop {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: var(--ds-space-6, 24px);
  background: rgba(0, 0, 0, 0.38);
}

.chatgpt-reference-v1 .chatgpt-library-move-dialog {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  width: min(calc(var(--wizon-ai-space-unit) * 218), calc(100vw - var(--ds-space-8, 40px)));
  height: min(calc(100svh - calc(var(--wizon-ai-space-unit) * 38)), calc(var(--wizon-ai-space-unit) * 157));
  min-height: calc(var(--wizon-ai-space-unit) * 96);
  overflow: hidden;
  border: 1px solid var(--cg-menu-border);
  border-radius: calc(var(--chatgpt-route-radius-control) + var(--chatgpt-route-gap-xs));
  background: var(--cg-surface);
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.24);
  color: var(--cg-text);
  font-family: inherit;
}

.chatgpt-reference-v1 .chatgpt-library-move-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-width: 0;
  padding: var(--chatgpt-route-gap-lg) var(--chatgpt-route-gap-lg) var(--chatgpt-route-gap-sm);
}

.chatgpt-reference-v1 .chatgpt-library-move-dialog h2 {
  margin: 0;
  overflow: hidden;
  font-size: calc(var(--chatgpt-route-title-size) * 0.5);
  font-weight: 600;
  line-height: calc(var(--chatgpt-route-title-size) * 0.68);
  letter-spacing: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-library-move-close {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-pill);
  background: transparent;
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-library-move-close:hover,
.chatgpt-reference-v1 .chatgpt-library-move-close:focus-visible {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-move-breadcrumb {
  min-width: 0;
  padding: 0 var(--chatgpt-route-gap-lg) var(--chatgpt-route-gap-sm);
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-library-move-list {
  min-height: 0;
  margin: 0 var(--chatgpt-route-gap-lg);
  overflow: auto;
  border-top: 1px solid var(--cg-menu-border);
  border-bottom: 1px solid var(--cg-menu-border);
}

.chatgpt-reference-v1 .chatgpt-library-move-entry {
  display: grid;
  grid-template-columns: var(--chatgpt-route-row-icon-size) minmax(0, 1fr);
  align-items: center;
  gap: var(--chatgpt-route-gap-sm);
  width: 100%;
  min-height: calc(var(--wizon-ai-space-unit) * 16);
  padding: 0 var(--chatgpt-route-gap-sm);
  border: 0;
  border-bottom: 1px solid var(--cg-menu-border);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  text-align: left;
}

.chatgpt-reference-v1 .chatgpt-library-move-entry:last-child {
  border-bottom: 0;
}

.chatgpt-reference-v1 .chatgpt-library-move-entry:hover,
.chatgpt-reference-v1 .chatgpt-library-move-entry:focus-visible,
.chatgpt-reference-v1 .chatgpt-library-move-entry.is-selected {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .chatgpt-library-move-entry.is-disabled {
  color: var(--cg-muted);
  cursor: default;
}

.chatgpt-reference-v1 .chatgpt-library-move-entry.is-empty {
  display: grid;
  place-items: center;
  min-height: calc(var(--wizon-ai-space-unit) * 24);
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-library-move-entry-icon {
  display: inline-grid;
  place-items: center;
  width: var(--chatgpt-route-row-icon-size);
  height: var(--chatgpt-route-row-icon-size);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-move-footer {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  padding: var(--chatgpt-route-gap-md) var(--chatgpt-route-gap-lg) var(--chatgpt-route-gap-lg);
}

.chatgpt-reference-v1 .chatgpt-library-move-footer-spacer {
  flex: 1 1 auto;
}

.chatgpt-reference-v1 .chatgpt-library-move-new-folder,
.chatgpt-reference-v1 .chatgpt-library-move-cancel,
.chatgpt-reference-v1 .chatgpt-library-move-submit {
  display: inline-grid;
  place-items: center;
  min-width: calc(var(--wizon-ai-space-unit) * 16);
  min-height: var(--wizon-ai-action-target-sm);
  padding: 0 var(--chatgpt-route-gap-sm);
  border-radius: var(--chatgpt-route-radius-pill);
  font: inherit;
  font-weight: 600;
}

.chatgpt-reference-v1 .chatgpt-library-move-new-folder,
.chatgpt-reference-v1 .chatgpt-library-move-cancel {
  border: 1px solid var(--cg-menu-border);
  background: var(--cg-surface);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-move-submit {
  border: 1px solid var(--cg-text);
  background: var(--cg-text);
  color: var(--cg-surface);
}

.chatgpt-reference-v1 .chatgpt-library-move-new-folder:hover,
.chatgpt-reference-v1 .chatgpt-library-move-new-folder:focus-visible,
.chatgpt-reference-v1 .chatgpt-library-move-cancel:hover,
.chatgpt-reference-v1 .chatgpt-library-move-cancel:focus-visible,
.chatgpt-reference-v1 .chatgpt-library-move-submit:hover,
.chatgpt-reference-v1 .chatgpt-library-move-submit:focus-visible {
  filter: brightness(0.96);
}

.chatgpt-reference-v1 .chatgpt-library-row-menu-trigger {
  position: absolute;
  top: 50%;
  right: var(--chatgpt-route-gap-2xs);
  display: inline-grid;
  place-items: center;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-pill);
  background: transparent;
  color: var(--cg-muted);
  transform: translateY(-50%);
}

.chatgpt-reference-v1 .chatgpt-library-row-menu-trigger:hover,
.chatgpt-reference-v1 .chatgpt-library-row-menu-trigger:focus-visible,
.chatgpt-reference-v1 .chatgpt-library-row-menu-trigger[aria-expanded="true"] {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-file-menu {
  top: calc(100% - var(--chatgpt-route-gap-2xs));
  right: var(--chatgpt-route-gap-2xs);
}

.chatgpt-reference-v1 .chatgpt-library-file-icon {
  display: inline-grid;
  place-items: center;
  width: var(--chatgpt-route-row-icon-size);
  height: var(--chatgpt-route-row-icon-size);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .chatgpt-library-table.is-grid-view {
  grid-template-columns: repeat(auto-fill, minmax(calc(var(--wizon-ai-space-unit) * 52), 1fr));
  gap: var(--chatgpt-route-gap-sm);
  margin-top: 0;
}

.chatgpt-reference-v1 .chatgpt-library-table.is-grid-view .chatgpt-library-row--head {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-library-table.is-grid-view .chatgpt-library-row-shell {
  min-height: calc(var(--wizon-ai-space-unit) * 28);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
}

.chatgpt-reference-v1 .chatgpt-library-table.is-grid-view .chatgpt-library-row {
  grid-template-columns: var(--chatgpt-route-row-icon-size) minmax(0, 1fr);
  grid-template-rows: auto auto auto;
  gap: var(--chatgpt-route-gap-2xs) var(--chatgpt-route-gap-sm);
  align-content: start;
  min-height: calc(var(--wizon-ai-space-unit) * 28);
  padding: var(--chatgpt-route-gap-md) calc(var(--wizon-ai-action-target-sm) + var(--chatgpt-route-gap-md)) var(--chatgpt-route-gap-md) var(--chatgpt-route-gap-md);
  border-bottom: 0;
}

.chatgpt-reference-v1 .chatgpt-library-table.is-grid-view .chatgpt-library-file-copy {
  color: var(--cg-text);
  font-weight: var(--wds-font-weight-medium);
}

.chatgpt-reference-v1 .chatgpt-library-table.is-grid-view .chatgpt-library-row-date,
.chatgpt-reference-v1 .chatgpt-library-table.is-grid-view .chatgpt-library-row-size {
  grid-column: 2;
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-library-empty {
  display: grid;
  justify-items: center;
  gap: var(--chatgpt-route-gap-sm);
  padding: calc(var(--wizon-ai-space-unit) * 12) var(--chatgpt-route-gap-md);
  color: var(--cg-text);
  text-align: center;
}

.chatgpt-reference-v1 .chatgpt-library-empty p {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-library-preview-backdrop {
  position: fixed;
  inset: 0;
  z-index: 320;
  display: flex;
  background: var(--cg-surface);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-preview-dialog {
  display: grid;
  grid-template-rows: var(--chatgpt-header-height) minmax(0, 1fr) auto;
  width: 100%;
  min-width: 0;
  min-height: 100svh;
  background: var(--cg-surface);
  outline: none;
}

.chatgpt-reference-v1 .chatgpt-library-preview-topbar {
  display: grid;
  grid-template-columns: var(--wizon-ai-action-target-sm) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--chatgpt-route-gap-md);
  min-width: 0;
  padding: 0 var(--chatgpt-route-gap-lg);
  border-bottom: 1px solid var(--cg-menu-border);
}

.chatgpt-reference-v1 .chatgpt-library-preview-breadcrumb {
  display: inline-flex;
  align-items: center;
  min-width: 0;
  gap: var(--chatgpt-route-gap-xs);
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-library-preview-breadcrumb strong {
  min-width: 0;
  overflow: hidden;
  color: var(--cg-text);
  font-weight: var(--wds-font-weight-medium);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-library-preview-actions {
  display: inline-flex;
  align-items: center;
  justify-self: end;
  gap: var(--chatgpt-route-gap-2xs);
}

.chatgpt-reference-v1 .chatgpt-library-preview-icon-button,
.chatgpt-reference-v1 .chatgpt-library-preview-composer-button,
.chatgpt-reference-v1 .chatgpt-library-preview-submit {
  display: inline-grid;
  place-items: center;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-pill);
  background: transparent;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-preview-icon-button:hover,
.chatgpt-reference-v1 .chatgpt-library-preview-icon-button:focus-visible,
.chatgpt-reference-v1 .chatgpt-library-preview-composer-button:hover,
.chatgpt-reference-v1 .chatgpt-library-preview-composer-button:focus-visible {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .chatgpt-library-preview-stage {
  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 0;
  padding: var(--chatgpt-route-gap-lg);
  background: var(--cg-surface);
  overflow: auto;
}

.chatgpt-reference-v1 .chatgpt-library-preview-image {
  display: block;
  max-width: min(calc(var(--wizon-ai-space-unit) * 168), calc(100% - var(--chatgpt-route-gap-lg) * 2));
  max-height: min(68svh, calc(var(--wizon-ai-space-unit) * 150));
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  object-fit: contain;
}

.chatgpt-reference-v1 .chatgpt-library-preview-document-pane {
  display: grid;
  justify-items: stretch;
  align-content: start;
  gap: var(--chatgpt-route-gap-sm);
  width: min(calc(var(--wizon-ai-space-unit) * 168), calc(100% - var(--chatgpt-route-gap-lg) * 2));
  max-width: min(calc(var(--wizon-ai-space-unit) * 168), calc(100% - var(--chatgpt-route-gap-lg) * 2));
  max-height: min(68svh, calc(var(--wizon-ai-space-unit) * 150));
  padding: 0;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  box-shadow: none;
  text-align: left;
  overflow: hidden;
}

.chatgpt-reference-v1 .chatgpt-library-preview-file-icon {
  display: inline-grid;
  place-items: center;
  width: var(--chatgpt-route-row-icon-size);
  height: var(--chatgpt-route-row-icon-size);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-library-preview-document-title strong {
  display: block;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
  word-break: break-word;
  color: var(--cg-text);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-library-preview-document-title {
  display: flex;
  align-items: center;
  min-width: 0;
  gap: var(--chatgpt-route-gap-sm);
  padding: var(--chatgpt-route-gap-md) var(--chatgpt-route-gap-lg) 0;
}

.chatgpt-reference-v1 .chatgpt-library-preview-document-title span:last-child {
  color: var(--cg-muted);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-label);
}

.chatgpt-reference-v1 .chatgpt-library-preview-document-body {
  min-height: calc(var(--wizon-ai-space-unit) * 44);
  max-height: min(54svh, calc(var(--wizon-ai-space-unit) * 120));
  margin: var(--chatgpt-route-gap-md) 0 0;
  padding: var(--chatgpt-route-gap-md) var(--chatgpt-route-gap-lg);
  border: 0;
  border-top: 1px solid var(--cg-menu-border);
  border-radius: 0;
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  line-height: var(--chatgpt-route-body-line-height);
  overflow: auto;
  white-space: pre-wrap;
}

.chatgpt-reference-v1 .chatgpt-library-preview-document-body.is-empty {
  display: grid;
  place-items: center;
  color: var(--cg-muted);
  text-align: center;
}

.chatgpt-reference-v1 .chatgpt-library-preview-document-pane span:last-child {
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
}

.chatgpt-reference-v1 .chatgpt-library-preview-composer.chatgpt-composer-shell {
  position: relative;
  z-index: 1;
  width: min(calc(var(--cg-chat-max-width) - (var(--cg-thread-gutter) * 2)), calc(100% - (var(--cg-thread-gutter) * 3)));
  margin: 0 auto var(--chatgpt-route-gap-lg);
}

.chatgpt-reference-v1 .chatgpt-library-preview-composer .composer-prompt-body {
  grid-template-columns: 36px minmax(0, 1fr) auto;
  grid-template-rows: 42px;
  grid-template-areas: "lead input actions";
  align-items: center;
  min-height: 42px;
  padding: 0;
}

.chatgpt-reference-v1 .chatgpt-library-preview-composer .composer-attachment-lane {
  display: none;
}

.chatgpt-reference-v1 .chatgpt-library-preview-composer .composer-textarea-scroll {
  align-self: center;
  display: flex;
  align-items: center;
  min-height: 26px;
  height: 26px;
  overflow-y: hidden;
}

.chatgpt-reference-v1 .chatgpt-library-preview-composer .composer-action-dock {
  align-self: center;
  display: inline-flex;
  flex-flow: row nowrap;
  align-items: center;
  justify-content: flex-end;
  width: auto;
  min-width: 0;
  min-height: 38px;
  gap: 6px;
}

.chatgpt-reference-v1 .chatgpt-library-preview-prompt-input {
  display: block;
  width: 100%;
  min-width: 0;
  min-height: 26px;
  height: 26px;
  max-height: 26px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--cg-text);
  box-shadow: none;
  font: inherit;
  font-size: var(--ds-text-body-16);
  line-height: 26px;
  outline: none;
  overflow: hidden;
  resize: none;
}

/* 인접 점검(K-4 같은 메커니즘, composer-focus-ring-pill 09-12) — input 자체와 셸
   (.chatgpt-library-preview-composer, .chatgpt-composer-shell 이기도 하다, 21463행)이
   둘 다 outline 을 얹어 메인 컴포저와 같은 겹침 결함을 안고 있었다. input 자체 링은 제거하고
   셸 :focus-within 링 1개만 — 알약 반경 그대로. */
.chatgpt-reference-v1 .chatgpt-library-preview-prompt-input:focus,
.chatgpt-reference-v1 .chatgpt-library-preview-prompt-input:focus-visible,
body.chatgpt-reference-v1 .chatgpt-library-preview-composer .chatgpt-library-preview-prompt-input:focus,
body.chatgpt-reference-v1 .chatgpt-library-preview-composer .chatgpt-library-preview-prompt-input:focus-visible {
  border-color: transparent;
  outline: none;
  box-shadow: none;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-composer:focus-within {
  border-color: transparent;
  outline: 2px solid var(--cg-text);
  outline-offset: 2px;
  box-shadow: none;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-prompt-input::placeholder {
  color: var(--cg-input-placeholder);
}

.chatgpt-reference-v1 .chatgpt-library-preview-submit {
  width: 38px;
  height: 38px;
  min-width: 38px;
  background: var(--cg-text);
  color: var(--cg-surface);
}

@media (max-width: 520px) {
  .chatgpt-reference-v1 .chatgpt-library-preview-composer .composer-action-dock {
    min-width: 0;
    gap: 4px;
  }
}

.chatgpt-reference-v1 .chatgpt-project-empty {
  display: grid;
  justify-items: center;
  gap: calc(var(--wizon-ai-space-unit) * 4.5);
  margin-top: calc(var(--wizon-ai-space-unit) * 13.5);
  color: var(--cg-text);
  font-size: var(--wds-font-size-body-1);
  font-weight: var(--wds-font-weight-regular);
  line-height: calc(var(--wds-font-size-body-1) * 1.5);
}

.chatgpt-reference-v1 .chatgpt-project-empty p {
  margin: 0;
  max-width: calc(var(--wizon-ai-space-unit) * 96);
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-project-action-feedback {
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(calc(var(--wizon-ai-space-unit) * 58), 1fr));
  gap: var(--chatgpt-route-gap-sm);
}

.chatgpt-reference-v1 .chatgpt-project-card {
  display: grid;
  grid-template-columns: var(--wizon-ai-action-target-lg) minmax(0, 1fr);
  align-items: center;
  gap: var(--chatgpt-route-gap-sm);
  min-height: calc(var(--wizon-ai-space-unit) * 19);
  padding: var(--ds-space-4, 16px);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  text-align: left;
}

.chatgpt-reference-v1 .chatgpt-project-card:hover,
.chatgpt-reference-v1 .chatgpt-project-card:focus-visible,
.chatgpt-reference-v1 .chatgpt-project-card.is-active {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .chatgpt-project-card-copy {
  display: grid;
  gap: var(--chatgpt-route-gap-2xs);
  min-width: 0;
}

.chatgpt-reference-v1 .chatgpt-project-card-copy strong,
.chatgpt-reference-v1 .chatgpt-project-card-copy small {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-project-card-copy small {
  color: var(--cg-muted);
  font-size: var(--wds-font-size-caption);
}

/* 20260708-project-card-manage: per-project options menu (rename / delete) + inline
   rename input. The card is wrapped in a relatively-positioned shell so the ⋯ trigger
   can overlay the top-right without nesting an interactive control inside the card. */
.chatgpt-reference-v1 .chatgpt-project-card-shell {
  position: relative;
  display: flex;
}

.chatgpt-reference-v1 .chatgpt-project-card-shell .chatgpt-project-card {
  flex: 1;
  min-width: 0;
  padding-right: calc(var(--ds-space-4, 16px) + 28px);
}

.chatgpt-reference-v1 .chatgpt-project-card-shell.is-renaming {
  align-items: center;
  gap: var(--chatgpt-route-gap-sm);
  min-height: calc(var(--wizon-ai-space-unit) * 19);
  padding: var(--ds-space-4, 16px);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
}

.chatgpt-reference-v1 .chatgpt-project-rename-input {
  flex: 1;
  min-width: 0;
  min-height: 36px;
  padding: 0 10px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-sm, 10px);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
}

.chatgpt-reference-v1 .chatgpt-project-card-menu-anchor {
  position: absolute;
  top: 8px;
  right: 8px;
}

.chatgpt-reference-v1 .chatgpt-project-card-menu-trigger {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  color: var(--cg-muted);
  background: transparent;
}

.chatgpt-reference-v1 .chatgpt-project-card-menu-trigger:hover,
.chatgpt-reference-v1 .chatgpt-project-card-menu-trigger:focus-visible,
.chatgpt-reference-v1 .chatgpt-project-card-menu-trigger[aria-expanded="true"] {
  background: var(--cg-hover-soft, #f4f4f5);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-project-card-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  min-width: 148px;
  padding: 6px;
  border: 1px solid var(--cg-menu-border);
  border-radius: 12px;
  background: var(--cg-surface);
  box-shadow: var(--cg-shadow);
}

.chatgpt-reference-v1 .chatgpt-project-card-menu-danger {
  color: #b91c1c;
}

.chatgpt-reference-v1 .chatgpt-project-create-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: var(--ds-space-6, 24px);
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(1px);
}

.chatgpt-reference-v1 .chatgpt-project-create-dialog {
  display: grid;
  gap: var(--chatgpt-route-gap-md);
  width: min(calc(var(--wizon-ai-space-unit) * 118), calc(100vw - var(--ds-space-8, 40px)));
  padding: var(--ds-space-6, 24px);
  border: 1px solid var(--cg-menu-border);
  border-radius: calc(var(--chatgpt-route-radius-control) + var(--chatgpt-route-gap-xs));
  background: var(--cg-surface);
  box-shadow: var(--cg-shadow-float-xl);
  color: var(--cg-text);
  font-family: inherit;
}

.chatgpt-reference-v1 .chatgpt-project-create-head {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--chatgpt-route-gap-xs);
  min-height: var(--wizon-ai-action-target-sm);
}

.chatgpt-reference-v1 .chatgpt-project-create-settings-anchor {
  position: relative;
  display: inline-grid;
  place-items: center;
}

.chatgpt-reference-v1 .chatgpt-project-create-settings,
.chatgpt-reference-v1 .chatgpt-project-create-close {
  display: inline-grid;
  place-items: center;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-project-create-settings:hover,
.chatgpt-reference-v1 .chatgpt-project-create-close:hover,
.chatgpt-reference-v1 .chatgpt-project-create-settings:focus-visible,
.chatgpt-reference-v1 .chatgpt-project-create-close:focus-visible,
.chatgpt-reference-v1 .chatgpt-project-create-settings[aria-expanded="true"] {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-project-create-settings-menu {
  position: absolute;
  top: calc(100% + var(--chatgpt-route-gap-xs));
  right: 0;
  z-index: 2;
  display: grid;
  gap: var(--chatgpt-route-gap-2xs);
  width: min(calc(var(--wizon-ai-space-unit) * 80), calc(100vw - var(--ds-space-8, 40px)));
  padding: var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  box-shadow: var(--cg-shadow-float-md);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-project-create-settings-menu > strong {
  font-size: var(--chatgpt-route-body-font-size);
  font-weight: var(--wds-font-weight-semibold, 600);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-project-create-settings-menu > small {
  color: var(--cg-muted);
  font-size: var(--wds-font-size-caption, 0.8125rem);
  line-height: calc(var(--wds-font-size-caption, 0.8125rem) * 1.4);
}

.chatgpt-reference-v1 .chatgpt-project-memory-option {
  display: grid;
  grid-template-columns: var(--wizon-ai-action-target-xs) minmax(0, 1fr);
  gap: var(--chatgpt-route-gap-xs);
  align-items: start;
  min-height: calc(var(--wizon-ai-space-unit) * 15);
  padding: var(--chatgpt-route-gap-xs);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-text);
  text-align: left;
  font: inherit;
}

.chatgpt-reference-v1 .chatgpt-project-memory-option:hover,
.chatgpt-reference-v1 .chatgpt-project-memory-option:focus-visible,
.chatgpt-reference-v1 .chatgpt-project-memory-option[aria-checked="true"] {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .chatgpt-project-memory-option > span:first-child {
  display: inline-grid;
  place-items: center;
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-project-memory-option strong,
.chatgpt-reference-v1 .chatgpt-project-memory-option small {
  display: block;
}

.chatgpt-reference-v1 .chatgpt-project-memory-option strong {
  font-size: var(--chatgpt-route-body-font-size);
  font-weight: var(--wds-font-weight-semibold, 600);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-project-memory-option small {
  margin-top: var(--chatgpt-route-gap-2xs);
  color: var(--cg-muted);
  font-size: var(--wds-font-size-caption, 0.8125rem);
  line-height: calc(var(--wds-font-size-caption, 0.8125rem) * 1.4);
}

.chatgpt-reference-v1 .chatgpt-project-create-dialog h2 {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--wds-font-size-title-l);
  font-weight: var(--wds-font-weight-semibold, 600);
  line-height: calc(var(--wds-font-size-title-l) * 1.35);
}

.chatgpt-reference-v1 .chatgpt-project-create-dialog p {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: calc(var(--chatgpt-route-body-line-height) * 1.15);
}

.chatgpt-reference-v1 .chatgpt-project-create-field {
  display: grid;
  gap: var(--chatgpt-route-gap-xs);
  color: var(--cg-text);
  font-size: var(--chatgpt-route-body-font-size);
  font-weight: var(--wds-font-weight-medium);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-project-create-input {
  min-width: 0;
  min-height: calc(var(--wizon-ai-space-unit) * 11);
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
}

.chatgpt-reference-v1 .chatgpt-project-create-input:focus-visible {
  outline: 2px solid var(--cg-focus-ring, rgba(16, 163, 127, 0.45));
  outline-offset: 2px;
}

.chatgpt-reference-v1 .chatgpt-project-create-submit {
  display: inline-grid;
  place-items: center;
  justify-self: end;
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-control-pad-y) var(--chatgpt-route-control-pad-x);
  border: 1px solid var(--cg-text);
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-text);
  color: var(--cg-surface);
  font: inherit;
  font-weight: var(--wds-font-weight-semibold, 600);
}

.chatgpt-reference-v1 .chatgpt-project-create-submit:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.chatgpt-reference-v1 .chatgpt-route-empty-icon {
  display: inline-grid;
  place-items: center;
  width: calc(var(--wizon-ai-space-unit) * 14);
  height: calc(var(--wizon-ai-space-unit) * 14);
  border-radius: var(--chatgpt-route-radius-lg);
  background: var(--cg-hover);
}

.chatgpt-reference-v1 .chatgpt-route-empty-icon svg {
  width: calc(var(--wizon-ai-space-unit) * 7);
  height: calc(var(--wizon-ai-space-unit) * 7);
}

.chatgpt-reference-v1 .chatgpt-scheduled-page {
  gap: var(--ds-space-7, 32px);
}

/* 20260708-scheduled-task-list: render actual scheduled tasks (previously the page
   only ever showed an empty state). */
.chatgpt-reference-v1 .chatgpt-scheduled-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.chatgpt-reference-v1 .chatgpt-scheduled-item {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid var(--cg-menu-border, #ececec);
  border-radius: 16px;
  background: var(--cg-surface);
}

.chatgpt-reference-v1 .chatgpt-scheduled-item[data-scheduled-status="completed"] {
  opacity: 0.62;
}

.chatgpt-reference-v1 .chatgpt-scheduled-item-icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: var(--cg-hover-soft, #f4f4f5);
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-scheduled-item-body {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.chatgpt-reference-v1 .chatgpt-scheduled-item-title {
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  color: var(--cg-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-scheduled-item[data-scheduled-status="completed"] .chatgpt-scheduled-item-title {
  text-decoration: line-through;
}

.chatgpt-reference-v1 .chatgpt-scheduled-item-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-scheduled-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--ds-radius-pill, 999px);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}

.chatgpt-reference-v1 .chatgpt-scheduled-badge.is-active {
  background: rgba(22, 163, 74, 0.12);
  color: #15803d;
}

.chatgpt-reference-v1 .chatgpt-scheduled-badge.is-paused {
  background: rgba(217, 119, 6, 0.12);
  color: #b45309;
}

.chatgpt-reference-v1 .chatgpt-scheduled-badge.is-completed {
  background: rgba(107, 114, 128, 0.14);
  color: #4b5563;
}

.chatgpt-reference-v1 .chatgpt-scheduled-item-actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.chatgpt-reference-v1 .chatgpt-scheduled-item-action {
  padding: 6px 12px;
  border: 1px solid var(--cg-menu-border, #ececec);
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-surface);
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  font-weight: 500;
  transition: background 150ms ease, border-color 150ms ease;
}

.chatgpt-reference-v1 .chatgpt-scheduled-item-action:hover,
.chatgpt-reference-v1 .chatgpt-scheduled-item-action:focus-visible {
  background: var(--cg-hover-soft, #f4f4f5);
}

.chatgpt-reference-v1 .chatgpt-scheduled-item-action.is-danger {
  color: #b91c1c;
}

.chatgpt-reference-v1 .chatgpt-scheduled-item-action.is-danger:hover,
.chatgpt-reference-v1 .chatgpt-scheduled-item-action.is-danger:focus-visible {
  background: rgba(185, 28, 28, 0.08);
  border-color: rgba(185, 28, 28, 0.28);
}

@media (max-width: 520px) {
  .chatgpt-reference-v1 .chatgpt-scheduled-item {
    grid-template-columns: 36px minmax(0, 1fr);
    grid-template-areas:
      "icon body"
      "actions actions";
    row-gap: 10px;
  }
  .chatgpt-reference-v1 .chatgpt-scheduled-item-icon { grid-area: icon; }
  .chatgpt-reference-v1 .chatgpt-scheduled-item-body { grid-area: body; }
  .chatgpt-reference-v1 .chatgpt-scheduled-item-actions {
    grid-area: actions;
    justify-content: flex-end;
    flex-wrap: wrap;
  }
}

/* 축③ S4 — 워크플로우 합집합 뷰("진행 중"). 새 색상·반경·그림자 토큰을 만들지 않는다 —
   .chatgpt-scheduled-item 이 이미 쓰는 것과 같은 var() 집합만 재사용한다. */
.chatgpt-reference-v1 .workflow-inbox-page {
  gap: var(--ds-space-7, 32px);
}

.chatgpt-reference-v1 .workflow-inbox-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.chatgpt-reference-v1 .workflow-inbox-item {
  margin: 0;
  padding: 0;
}

.chatgpt-reference-v1 .workflow-inbox-item-open,
.chatgpt-reference-v1 .workflow-inbox-item-static {
  display: grid;
  width: 100%;
  grid-template-columns: 40px minmax(0, 1fr);
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 150ms ease, border-color 150ms ease;
}

/* W4(doc 648 §7) — 실행 줄은 누를 곳이 없다. 손 모양 커서를 주면 «눌러도 아무 일이
   없는» 줄이 된다. */
.chatgpt-reference-v1 .workflow-inbox-item-static {
  cursor: default;
}

/* 결재 줄: 본문 버튼 + (안 읽었으면) 읽음 버튼. 읽음 버튼은 .btn--lg 를 그대로 쓰므로
   min-height 44px 가 이미 걸려 있다 — 여기서 다시 적지 않는다. */
.chatgpt-reference-v1 .workflow-inbox-ledger-row {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
}

.chatgpt-reference-v1 .workflow-inbox-ledger-row .workflow-inbox-item-open {
  grid-template-columns: 40px minmax(0, 1fr) auto;
}

.chatgpt-reference-v1 .workflow-inbox-read-action {
  flex: 0 0 auto;
}

.chatgpt-reference-v1 .workflow-inbox-unread-badge {
  padding: var(--ds-space-1) var(--ds-space-2);
  border-radius: var(--ds-radius-xs);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  font-weight: var(--ds-weight-semibold);
  white-space: nowrap;
}

.chatgpt-reference-v1 .workflow-inbox-item-open:hover,
.chatgpt-reference-v1 .workflow-inbox-item-open:focus-visible {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .workflow-inbox-item-icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .workflow-inbox-item-body {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.chatgpt-reference-v1 .workflow-inbox-item-title {
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  color: var(--cg-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .workflow-inbox-item-detail {
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .workflow-inbox-status {
  margin: 0;
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .workflow-inbox-status.is-error {
  color: var(--cg-text);
}

.chatgpt-reference-v1 .workflow-inbox-truncated-note {
  margin: 0;
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted);
}

/* ── W4(doc 648 §7) 결재함 — «진행 중» 안의 탭 ────────────────────────────────────
   새 라우트도 새 사이드바 항목도 만들지 않는다. 결재는 이 화면이 이미 모으고 있는
   «내가 처리해야 할 일»과 같은 축이라, 별도 화면으로 빼면 모드별 메뉴 3곳과 라우트
   접근 목록을 늘리고도 사용자는 두 곳을 봐야 한다(doc 648 §7.2 판정). */
.chatgpt-reference-v1 .workflow-inbox-tabs {
  display: flex;
  gap: var(--ds-space-2);
}

.chatgpt-reference-v1 .workflow-inbox-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  min-height: 44px;
  padding: 0 var(--ds-space-4);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  color: var(--cg-muted);
  font: inherit;
  font-size: var(--ds-text-body-15);
  font-weight: var(--ds-weight-semibold);
  cursor: pointer;
}

.chatgpt-reference-v1 .workflow-inbox-tab.is-active {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .workflow-inbox-tab-count {
  padding: 0 var(--ds-space-2);
  border-radius: var(--ds-radius-xs);
  background: var(--cg-surface);
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
}

.chatgpt-reference-v1 .work-approval-list {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.chatgpt-reference-v1 .work-approval-card {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-2);
  margin: 0;
  padding: var(--ds-space-4);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
}

.chatgpt-reference-v1 .work-approval-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-2);
}

.chatgpt-reference-v1 .work-approval-subject {
  color: var(--cg-text);
  font-size: var(--ds-text-body-15);
}

/* 만료는 저장된 상태가 아니라 파생값이다(039). 그래서 «기한 지남»은 목록에서 지우는
   대신 다른 색으로 남는다 — 사라지면 왜 결정할 수 없는지 화면이 말하지 못한다. */
.chatgpt-reference-v1 .work-approval-expiry {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  white-space: nowrap;
}

.chatgpt-reference-v1 .work-approval-expiry.is-expired {
  color: var(--cg-danger-fg);
}

.chatgpt-reference-v1 .work-approval-requester,
.chatgpt-reference-v1 .work-approval-reason {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
}

.chatgpt-reference-v1 .work-approval-reason {
  color: var(--cg-text);
}

.chatgpt-reference-v1 .work-approval-actions {
  display: flex;
  gap: var(--ds-space-2);
}

/* 20260925-work-approval-decision-dialog (결정형 규칙, doc 669 §M 초안 — 오너 R-40) —
   <div class="work-approval-sheet-scrim"><section role="dialog">.. 를 네이티브
   <dialog>.showModal() 로 바꾸면서 스크림을 감싸는 div 대신 ::backdrop 하나로 준다
   (포인터 차단은 UA 가 실제로 해준다 — 새 JS 0, styles.css 25954행 부근의 같은 판단).
   토큰은 옛 스크림이 쓰던 --wds-modal-scrim 을 그대로 재사용한다(값 변경 없음). */
.chatgpt-reference-v1 .work-approval-sheet::backdrop {
  background: var(--wds-modal-scrim);
}

.chatgpt-reference-v1 .work-approval-sheet {
  margin: auto;
  border: 0;
  max-width: none;
  width: min(480px, calc(100vw - 32px));
  padding: var(--ds-space-5);
  border-radius: var(--wizon-ai-radius-sheet);
  background: var(--cg-surface);
  color: var(--cg-text);
  box-shadow: var(--wizon-ai-dialog-shadow);
}

/* UA 는 <dialog> 를 열렸을 때만(그 전엔 [[isModal]]=false 라 display:none) block 으로
   그린다 — 세로 쌓기는 [open] 에만 준다(닫힌 상태에 display:flex 를 주면 showModal() 을
   거치지 않고도 보이는 상자가 된다, MDN dialog 기본 UA 스타일시트 참고). */
.chatgpt-reference-v1 .work-approval-sheet[open] {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
}

.chatgpt-reference-v1 .work-approval-sheet-title {
  margin: 0;
  font-size: var(--ds-text-body-15);
}

.chatgpt-reference-v1 .work-approval-sheet-label {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
}

.chatgpt-reference-v1 .work-approval-sheet-reason {
  width: 100%;
  padding: var(--ds-space-3);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  resize: vertical;
}

.chatgpt-reference-v1 .work-approval-sheet-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--ds-space-2);
}

/* 좁은 화면(390 포함)에서는 바텀시트로 — 가운데 뜬 상자는 한 손으로 닿지 않는다.
   분기점은 정본 520 이다(breakpoints.md §2). 480 을 썼다가 design-system 계약이
   «비정본 폭 분기점»으로 잡았다 — 버튼은 이미 .btn--lg(44px)라 크기를 다시 정하지
   않고 «폭»만 채운다. */
@media (max-width: 520px) {
  /* 위치·반경(M-4)·모션·바닥 안전영역(M-6) 은 §11 공통 [data-mobile-bottom-sheet] 어댑터
     (아래, [data-mobile-bottom-sheet="work-approval"] 옵트인)가 준다 — 여기서 다시 적지
     않는다. 이 블록은 그 위에 얹는 «결정형» 전용 오버라이드만 남긴다: 그래버 0(M-3 —
     결정형은 끌어내림이 없다), 키보드 가림 0(M-8 — 이미 살아 있는 --wizon-kb-inset,
     새 JS 0. workspacePicker·첨부 목록·재확인·로그인과 같은 선례). */
  .chatgpt-reference-v1 .work-approval-sheet[data-mobile-bottom-sheet="work-approval"][open] {
    bottom: var(--wizon-kb-inset, 0px) !important;
  }

  .chatgpt-reference-v1 .work-approval-sheet[data-mobile-bottom-sheet="work-approval"][open]::before {
    display: none !important;
  }

  /* R-41 B1(inventory S9 — 이전엔 모션 0; chain-r2 에서 native <dialog> 위로 재배치). 시트
     본체의 wizon-sheet-up(열림)/wizon-sheet-down(닫힘 300ms)은 §11 공통 어댑터
     ([data-mobile-bottom-sheet][open] / [data-motion-state="closing"])가 이미 준다 — 옛
     .work-approval-sheet-scrim div 의 페이드는 이제 ::backdrop 이 맡는다. 폰에서만 바텀시트가
     되므로 모션도 이 블록 안에서만 얹는다(데스크톱 중앙 모달은 모션 0 그대로). */
  .chatgpt-reference-v1 .work-approval-sheet[data-mobile-bottom-sheet="work-approval"][open]::backdrop {
    animation: wizon-scrim-in var(--wds-spring-effect-default-duration, 188ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }

  .chatgpt-reference-v1 .work-approval-sheet[data-mobile-bottom-sheet="work-approval"][data-motion-state="closing"]::backdrop {
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }

  .chatgpt-reference-v1 .work-approval-sheet-actions > .btn,
  .chatgpt-reference-v1 .work-approval-actions > .btn {
    flex: 1 1 0;
  }

  /* ★390 실측(2026-09-05): 배지와 「읽음」 버튼이 같이 있으면 제목이 «제안서 v3 을 고객…»
     으로 잘리고 설명이 두 줄로 접힌다. 「읽음」 버튼이 있다는 것 자체가 이미 «안 읽음»을
     말하므로, 좁은 화면에서는 배지를 접고 제목에 폭을 준다. */
  .chatgpt-reference-v1 .workflow-inbox-unread-badge {
    display: none;
  }

  .chatgpt-reference-v1 .workflow-inbox-ledger-row .workflow-inbox-item-open {
    grid-template-columns: 40px minmax(0, 1fr);
  }
}

/* R-41 B1b(S9·M-15) — 위 §S9 블록엔 reduce 대응이 아예 없었다. 로그인 창과 같은 문법.
   chain-r2(native <dialog> 위 재배치): 시트 본체의 124ms 흐려짐은 §11 어댑터 reduce 블록
   ([data-mobile-bottom-sheet][open]/[data-motion-state="closing"])이 이미 준다 — 여기서는 옛
   스크림 div 자리를 대신하는 ::backdrop 만 같은 124ms 로 맞춘다. */
@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  .chatgpt-reference-v1 .work-approval-sheet[data-mobile-bottom-sheet="work-approval"][open]::backdrop {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }
  .chatgpt-reference-v1 .work-approval-sheet[data-mobile-bottom-sheet="work-approval"][data-motion-state="closing"]::backdrop {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
}

.chatgpt-reference-v1 .chatgpt-scheduled-filter-anchor {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.chatgpt-reference-v1 .chatgpt-scheduled-filter-menu {
  position: absolute;
  z-index: 35;
  top: calc(100% + var(--chatgpt-route-gap-xs));
  right: 0;
  display: grid;
  min-width: calc(var(--wizon-ai-space-unit) * 42);
  padding: var(--chatgpt-route-gap-2xs);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  box-shadow: var(--cg-shadow-float-lg);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-scheduled-filter-option {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-text);
  font-size: var(--chatgpt-route-body-font-size);
  font-weight: var(--wds-font-weight-medium);
  line-height: var(--chatgpt-route-body-line-height);
  text-align: left;
}

.chatgpt-reference-v1 .chatgpt-scheduled-filter-option:hover {
  background: var(--cg-hover-soft);
}
/* focus-reset-shell-ring-10: 전역 P0-3 폴백보다 특정성을 올린다. */
.chatgpt-reference-v1.chatgpt-reference-v1 .chatgpt-scheduled-filter-option:focus-visible {
  background: var(--cg-hover-soft);
  outline: none;
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

.chatgpt-reference-v1 .chatgpt-scheduled-filter-option[aria-checked="true"] {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-scheduled-composer {
  display: grid;
  grid-template-columns: var(--ds-space-8, 40px) minmax(0, 1fr) var(--ds-space-8, 40px);
  align-items: center;
  gap: calc(var(--wizon-ai-space-unit) / 2);
  min-height: calc(var(--wizon-ai-space-unit) * 14.5);
  padding: var(--chatgpt-route-gap-xs) calc(var(--wizon-ai-space-unit) * 2.5);
  border: 1px solid var(--cg-menu-border);
  border-radius: calc(var(--wizon-ai-space-unit) * 7);
  background: var(--cg-surface);
  box-shadow: var(--cg-shadow);
}

.chatgpt-reference-v1 .chatgpt-scheduled-composer input {
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--cg-text);
  font-size: var(--wds-font-size-body-1);
  line-height: calc(var(--wds-font-size-body-1) * 1.5);
}

/* K-4(2.4.7 AA) — 예약 컴포저(.chatgpt-scheduled-composer, app.js:41374)도 메인과
   별도 마크업이며 input 자체는 outline:none(위) 이라 포커스 표시가 전무했다
   (composer-focus-ring-siblings QA 09-12). 메인 셸 링과 같은 규약(같은 토큰·두께·offset)을
   셸에 적용 — 필드는 textarea 가 아니라 input 이므로 셀렉터만 그에 맞춘다. 셸 반경은
   calc(var(--wizon-ai-space-unit) * 7)(알약)이라 outline 이 그대로 따른다.
   candidate 62(9690e65d8)가 착지하면 셀렉터를 그 규칙 목록에 콤마로 합칠 것. */
.chatgpt-reference-v1 .chatgpt-scheduled-composer:has(input:focus-visible) {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: 2px;
}

.chatgpt-reference-v1 .chatgpt-scheduled-composer .chatgpt-route-composer-button {
  display: inline-grid;
  place-items: center;
  width: calc(var(--wizon-ai-space-unit) * 9);
  height: calc(var(--wizon-ai-space-unit) * 9);
  min-width: calc(var(--wizon-ai-space-unit) * 9);
  border: 0;
  border-radius: var(--chatgpt-route-radius-pill);
  background: transparent;
  color: var(--cg-text-soft);
  transition:
    background-color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    transform var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast);
}

.chatgpt-reference-v1 .chatgpt-scheduled-composer .chatgpt-route-composer-button svg {
  width: calc(var(--wizon-ai-space-unit) * 5);
  height: calc(var(--wizon-ai-space-unit) * 5);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--wds-icon-stroke, 1.7);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chatgpt-reference-v1 .chatgpt-scheduled-composer .chatgpt-route-composer-button:hover {
  background: var(--cg-hover);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-scheduled-composer .chatgpt-route-composer-button:active {
  transform: scale(0.985);
}

.chatgpt-reference-v1 .chatgpt-scheduled-composer .chatgpt-route-composer-send {
  width: calc(var(--wizon-ai-space-unit) * 9.5);
  height: calc(var(--wizon-ai-space-unit) * 9.5);
  min-width: calc(var(--wizon-ai-space-unit) * 9.5);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}

.chatgpt-reference-v1 .chatgpt-scheduled-composer .chatgpt-route-composer-send:hover {
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}

.chatgpt-reference-v1 .chatgpt-scheduled-add-anchor {
  position: relative;
  display: inline-grid;
  place-items: center;
  min-width: 0;
}

.chatgpt-reference-v1 .chatgpt-scheduled-add-menu {
  position: absolute;
  left: 0;
  bottom: calc(100% + var(--chatgpt-route-gap-xs));
  z-index: 40;
  display: grid;
  gap: var(--chatgpt-route-gap-2xs);
  width: min(calc(var(--wizon-ai-space-unit) * 76), calc(100vw - var(--chatgpt-route-gap-lg) * 2));
  padding: var(--chatgpt-route-gap-xs);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  box-shadow: var(--cg-shadow-float-lg);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-scheduled-add-item {
  display: grid;
  grid-template-columns: var(--wizon-ai-action-target-xs) minmax(0, 1fr);
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-gap-xs);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  text-align: left;
}

.chatgpt-reference-v1 .chatgpt-scheduled-add-item:hover {
  background: var(--cg-hover-soft);
}
/* focus-reset-shell-ring-10: 전역 P0-3 폴백보다 특정성을 올린다. */
.chatgpt-reference-v1.chatgpt-reference-v1 .chatgpt-scheduled-add-item:focus-visible {
  background: var(--cg-hover-soft);
  outline: none;
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

.chatgpt-reference-v1 .chatgpt-scheduled-add-item svg {
  width: calc(var(--wizon-ai-space-unit) * 5);
  height: calc(var(--wizon-ai-space-unit) * 5);
}

.chatgpt-reference-v1 .chatgpt-scheduled-add-item span {
  display: grid;
  gap: calc(var(--wizon-ai-space-unit) / 2);
  min-width: 0;
}

.chatgpt-reference-v1 .chatgpt-scheduled-add-item strong {
  overflow: hidden;
  color: var(--cg-text);
  font-weight: var(--wds-font-weight-medium);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-scheduled-add-item small {
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--wds-font-size-caption);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-scheduled-empty {
  display: grid;
  justify-items: center;
  gap: var(--chatgpt-route-gap-sm);
  padding: calc(var(--wizon-ai-space-unit) * 8) var(--chatgpt-route-gap-md);
  color: var(--cg-text);
  text-align: center;
}

.chatgpt-reference-v1 .chatgpt-scheduled-empty p {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-app-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(calc(var(--wizon-ai-space-unit) * 55), 1fr));
  gap: var(--chatgpt-route-gap-sm);
}

.chatgpt-reference-v1 .chatgpt-app-card {
  display: grid;
  grid-template-columns: var(--wizon-ai-action-target-sm) minmax(0, 1fr) var(--wizon-ai-action-target-xs);
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  min-height: calc(var(--wizon-ai-space-unit) * 18);
  padding: var(--ds-space-4, 16px);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-gap-sm);
  background: var(--cg-surface);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    background-color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast),
    border-color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast);
}

.chatgpt-reference-v1 .chatgpt-app-card:hover {
  background: var(--cg-hover-soft);
  border-color: var(--cg-menu-border);
}

.chatgpt-reference-v1 .chatgpt-app-card-icon,
.chatgpt-reference-v1 .chatgpt-app-card-cta {
  display: inline-grid;
  place-items: center;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  border-radius: var(--chatgpt-route-radius-control);
  color: var(--cg-text-soft);
}

.chatgpt-reference-v1 .chatgpt-app-card-icon {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

.chatgpt-reference-v1 .chatgpt-app-card-icon svg,
.chatgpt-reference-v1 .chatgpt-app-card-cta svg {
  width: calc(var(--wizon-ai-space-unit) * 5);
  height: calc(var(--wizon-ai-space-unit) * 5);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--wds-icon-stroke, 1.7);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chatgpt-reference-v1 .chatgpt-app-card-copy {
  display: grid;
  gap: var(--chatgpt-route-gap-2xs);
  min-width: 0;
}

.chatgpt-reference-v1 .chatgpt-app-card-copy strong {
  min-width: 0;
  overflow: hidden;
  color: var(--cg-text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-app-card-copy p,
.chatgpt-reference-v1 .chatgpt-route-empty-copy {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

.chatgpt-reference-v1 .chatgpt-app-empty {
  display: grid;
  grid-column: 1 / -1;
  justify-items: center;
  gap: var(--chatgpt-route-gap-sm);
  padding: calc(var(--wizon-ai-space-unit) * 10) var(--chatgpt-route-gap-md);
  color: var(--cg-text);
  text-align: center;
}

.chatgpt-reference-v1 .chatgpt-app-empty p {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--chatgpt-route-body-font-size);
  line-height: var(--chatgpt-route-body-line-height);
}

/* 20260704-composer-empty-optical-center-guard: keep the ChatGPT one-line prompt optically centered after new-chat, focus, and stale multiline transitions. */
body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment),
body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment),
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment),
body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) {
  grid-template-rows: 42px !important;
  min-height: 52px !important;
  max-height: 52px !important;
  padding: 5px 8px !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-prompt-body,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-prompt-body {
  grid-template-columns: 36px minmax(0, 1fr) auto !important;
  grid-template-rows: 42px !important;
  grid-template-areas: "lead input actions" !important;
  align-items: center !important;
  min-height: 42px !important;
  height: 42px !important;
  padding: 0 !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-textarea-scroll,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-textarea-scroll {
  align-self: center !important;
  display: flex !important;
  align-items: center !important;
  min-height: 26px !important;
  height: 26px !important;
  max-height: 26px !important;
  overflow-y: hidden !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) textarea {
  align-self: center !important;
  min-height: 26px !important;
  height: 26px !important;
  max-height: 26px !important;
  padding: 0 !important;
  line-height: 26px !important;
  overflow-y: hidden !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-leading-action,
body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-action-dock,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-leading-action,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-action-dock {
  align-self: center !important;
}

/* 20260704-composer-hidden-attachment-center-guard: stale attachment classes must not keep an empty prompt in the tall multiline layout. */
body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) {
  grid-template-rows: 42px !important;
  min-height: 52px !important;
  max-height: 52px !important;
  padding: 5px 8px !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body {
  grid-template-columns: 36px minmax(0, 1fr) auto !important;
  grid-template-rows: 42px !important;
  grid-template-areas: "lead input actions" !important;
  align-items: center !important;
  min-height: 42px !important;
  height: 42px !important;
  padding: 0 !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-textarea-scroll,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-textarea-scroll {
  align-self: center !important;
  display: flex !important;
  align-items: center !important;
  min-height: 26px !important;
  height: 26px !important;
  max-height: 26px !important;
  overflow-y: hidden !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) textarea {
  align-self: center !important;
  min-height: 26px !important;
  height: 26px !important;
  max-height: 26px !important;
  padding: 0 !important;
  line-height: 26px !important;
  overflow-y: hidden !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-leading-action,
body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-action-dock,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-leading-action,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-action-dock {
  align-self: center !important;
}

/* 20260704-composer-single-line-baseline-guard: keep the one-line prompt on the same centerline as ChatGPT's placeholder. */
body.chatgpt-reference-v1 .chatgpt-composer-shell:not(.is-multiline) textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) textarea {
  transform: none;
}

/* 20260704-composer-direct-textarea-center-guard: support stale/deployed composer DOM without the textarea scroll wrapper. */
body.chatgpt-reference-v1 .chatgpt-composer-shell:not(.is-multiline) .composer-prompt-body > textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-prompt-body > textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-prompt-body > textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body > textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body > textarea {
  grid-area: input !important;
  align-self: center !important;
  display: block !important;
  min-height: 26px !important;
  height: 26px !important;
  max-height: 26px !important;
  padding: 0 !important;
  line-height: 26px !important;
  overflow-y: hidden !important;
}

/* 20260704-composer-placeholder-state-reset-guard: placeholder state must always collapse stale multiline geometry back to ChatGPT's single-line rail. */
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) {
  grid-template-rows: 42px !important;
  min-height: 52px !important;
  height: 52px !important;
  max-height: 52px !important;
  padding: 5px 8px !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body {
  grid-template-columns: 36px minmax(0, 1fr) auto !important;
  grid-template-rows: 42px !important;
  grid-template-areas: "lead input actions" !important;
  align-items: center !important;
  min-height: 42px !important;
  height: 42px !important;
  max-height: 42px !important;
  margin: 0 !important;
  padding: 0 !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-textarea-scroll {
  grid-area: input !important;
  align-self: stretch !important;
  display: grid !important;
  align-items: center !important;
  min-height: 42px !important;
  height: 42px !important;
  max-height: 42px !important;
  overflow-y: hidden !important;
}

body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) textarea {
  align-self: center !important;
  min-height: 26px !important;
  height: 26px !important;
  max-height: 26px !important;
  padding: 0 !important;
  line-height: 26px !important;
  overflow-y: hidden !important;
  transform: none !important;
}

/* 20260704-composer-visible-attachment-expansion-guard: a visible attachment lane wins over stale empty-state height clamps. */
/* 재수리(composer-structure-card-viewport-clamp v2, 09-22): 100dvh 는 소프트 키보드가
   떠도 줄지 않는 브라우저가 있다(iOS Safari) — --wizon-vvh(syncMobileViewportMetrics,
   app.js 3349행)는 이미 이 코드베이스 전역에서 그 대신 쓰는 키보드 인지 뷰포트 높이
   변수다(styles.css 17981·19843·34113행 등 선례). 폴백 100dvh 는 그대로 둬 이 변수가
   아직 안 채워진 첫 페인트에서도 동작하게 한다. */
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-attachment-lane:not([hidden])),
body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(.composer-attachment-lane:not([hidden])) {
  grid-template-rows: auto auto !important;
  min-height: 84px !important;
  height: auto !important;
  max-height: min(360px, calc(var(--wizon-vvh, 100dvh) - 24px)) !important;
  padding: 5px 8px !important;
}

/* 재수리(composer-structure-card-viewport-clamp v2, 09-22): 1차 수리(220px 고정 예산)는
   «빈 멀티라인 52px 예약» 만 가정했다 — 사용자가 실제로 몇 줄 입력하면 입력칸이
   resizeComposer(app.js)로 자라 셸 예산을 잠식, 카드가 여전히 220px를 요구해 원래
   결함(입력칸·전송 버튼이 뷰포트 밖)이 재발했다(실측: 390×844 «5줄 입력+카드» bottom
   +58px, 844×390/736×360/812×375 +92px, RED 재현 로그 참조). 고정 예산을 버리고 CSS
   Grid 의 fr 트랙 재계산(스펙 11.5 "Find the Size of an 'fr'" — 정의된 max-height 아래
   fr 트랙은 고정 트랙이 먼저 가져간 만큼을 제외한 «남는 공간»만 받는다)에 맡긴다.
   ★실측 정정(v2 초안이 틀렸던 지점): 셸의 grid row1 은 첨부 «레인» 이 아니라
   #composerAttachmentStatus(빈 상태줄, 거의 0px) 이고, 레인은 그 다음
   #composerAttachmentRail(레인+좌우 nav 버튼을 감싸는 래퍼, index.html 664행) 가
   row2 다 — row 개수를 getComputedStyle(shell).gridTemplateRows 로 실측해 확인했다
   (DevTools 감이 아니라 3트랙 "0px 502px 86px" 실측, 상태줄·레일·입력행 순). 그래서
   3트랙을 전부 명시한다 — row1(상태줄, auto)·row2(레일, 남는 공간)·row3(입력행, 실제
   textarea 높이만큼 자란 자기 콘텐츠 크기 그대로). */
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-attachment-lane:not([hidden])):has(.composer-structure-card),
body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(.composer-attachment-lane:not([hidden])):has(.composer-structure-card) {
  grid-template-rows: auto minmax(0, 1fr) auto !important;
}

/* 구조 카드가 있을 때 첨부 레일(그리드 아이템 자신 — 기본 align-self:stretch 로 위
   grid fr 트랙이 준 높이를 그대로 물려받는 «definite height» 보장 지점) 이 스크롤
   경계가 된다. min-height:0 은 flex/grid 아이템 기본 min-height:auto(콘텐츠 기준)가
   트랙 축소를 막는 것을 푼다. 레인 자신(.composer-attachment-lane)에는 손대지
   않는다 — 레인은 폭 ≤900px(styles.css 34346행, 모바일 첨부 레일)에서
   overflow-y:hidden 인 가로 캐러셀 문법을 다른 첨부 칩들과 공유해, 거기서 스크롤
   경계를 잡으면 그 회귀. 레일(.composer-attachment-rail)은 카드 전용 구조라
   scoped :has() 로 건드려도 다른 첨부(파일·링크) 시나리오에 영향이 없다. */
body.chatgpt-reference-v1 .composer-attachment-rail:has(.composer-structure-card) {
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* ★실측 정정(재수리 v2 QA 발견, 09-22): .composer-attachment-rail:not([hidden])
     (styles.css 34375행 부근)이 align-items:center 를 준다(모바일 전용이 아니라
     항상 적용 — 실측 확인) — 첨부 칩 캐러셀은 짧아서 문제없지만, 레일보다 키가 큰
     구조 카드가 세로로 "가운데 정렬" 되면 카드 «위쪽»(닫기·되돌리기 버튼이 있는
     헤더)이 레일의 스크롤 가능 범위 밖(음수 scrollTop 이 필요한 위치)으로 밀려나
     scrollTop:0 에서도 안 보이고 클릭도 안 된다(실측: 1440×900 elementFromPoint 가
     헤더 위치에서 뒤에 있는 .chatgpt-home-title 을 히트). 카드가 있을 때는 위쪽을
     기준으로 고정해야 한다 — 34375행과 같은 특이도라 !important 로 이긴다. */
  align-items: flex-start !important;
}

/* 결함수리(mixed-rail-card-layout, 09-22, 오너 신고 R-29): 첨부 레일에 «파일 칩 +
   구조 카드»가 같이 있으면 카드가 오른쪽으로 밀려 좁게 열리고 파일 칩이 세로 가운데
   떠 보였다. 가르는 값(1440×900, 실측 getBoundingClientRect, 파일 1 + 텍스트 1) —
   레인(.composer-attachment-lane, 34419행 부근의 `:not([hidden])` 규칙이 소스순서상
   이겨 flex-wrap:nowrap; overflow-x:auto; align-items:center 로 굳는다)이 가로
   캐러셀이고, 카드(styles.css 45852행 `.composer-structure-card { flex:0 0 auto;
   width:min(420px,100%) }`)는 그 캐러셀의 낱개 형제 아이템이라 파일 칩 옆에
   420px(레인 폭 737px 의 57%)로만 앉았다 — card.x=557(왼쪽에서 187px 밀림),
   fileChip.y=489(레인 높이 477px 의 세로 가운데, 카드 헤더보다 한참 아래). 카드
   «전용»(A3′, 텍스트 칩 단독) 케이스는 레인의 유일한 자식이라 애초에 밀릴 형제가
   없어 이 결함이 없었다 — 그래서 스코프를 «레인에 카드와 첨부 칩이 함께 있을 때»로
   좁힌다(:has() 이중 조건, DOM 순서 무관 — 카드가 먼저 오든 나중에 오든 똑같이
   고친다). 목표(발주서 §2): 카드가 열리면 레일의 한 줄 전체를 쓰고 파일 칩은 별도
   줄에 남는다. flex-wrap:wrap 으로 줄바꿈을 허용하고, 카드 자신의 flex-basis 를
   100%로 강제해 항상 자기 줄을 혼자 차지하게 한다(폭 상한 min(420px,100%)는 그대로
   둬 좁은 뷰포트 동작을 안 바꾼다 — flex-basis 가 auto 가 아니면 width 는 기본
   폭 계산에 쓰이지 않지만, 100% 로 이미 그 상한과 같아지므로 시각적 차이가 없다).
   레인 자신의 특이도 3클래스(.composer-attachment-lane + :has() 인자 2개)가
   34419행의 2클래스(.composer-attachment-lane + :not([hidden]))를 이겨
   !important 없이 적용된다. */
body.chatgpt-reference-v1 .composer-attachment-lane:has(.composer-structure-card):has(.composer-attachment-card) {
  flex-wrap: wrap;
  align-items: flex-start;
  overflow-x: hidden;
}

body.chatgpt-reference-v1 .composer-attachment-lane:has(.composer-structure-card):has(.composer-attachment-card) .composer-structure-card {
  flex-basis: 100%;
}

body.chatgpt-reference-v1 .composer-attachment-lane:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
  overflow: visible;
}

/* 20260706-project-route-surface-v2: route pages must win after late empty composer clamps and cached CSS. */
html[data-portal-route-open="true"] body.chatgpt-reference-v1 .message-list,
html[data-portal-route-open="true"] body.chatgpt-reference-v1 .chatgpt-composer-shell,
html[data-portal-route-open="true"] body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell,
html[data-portal-route-open="true"] body.chatgpt-reference-v1 .chatgpt-prompt-shelf,
body.chatgpt-reference-v1.portal-route-open .message-list,
body.chatgpt-reference-v1.portal-route-open .chatgpt-composer-shell,
body.chatgpt-reference-v1.portal-route-open .composer-surface.chatgpt-composer-shell,
body.chatgpt-reference-v1.portal-route-open .chatgpt-prompt-shelf {
  display: none !important;
}

/* 20260707-library-preview-composer-route-restore: route pages hide the home composer, but the file preview prompt is part of the detail dialog. */
html[data-chat-state="route"] body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell,
html[data-portal-route-open="true"] body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell,
body.chatgpt-reference-v1.portal-route-open .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell {
  display: grid !important;
}

/* 20260707-library-preview-mobile-composer-reference: match Chrome-observed ChatGPT mobile composer geometry and beat global empty-composer clamps. */
@media (max-width: 520px) {
  html[data-chat-state="route"] body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]),
  html[data-portal-route-open="true"] body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]),
  body.chatgpt-reference-v1.portal-route-open .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]),
  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) {
    width: calc(100% - 32px) !important;
    grid-template-columns: minmax(0, 1fr) !important;
    grid-template-rows: 74px !important;
    min-height: 84px !important;
    height: 84px !important;
    max-height: 84px !important;
    margin: 0 auto var(--chatgpt-route-gap-lg) !important;
    padding: 5px 8px !important;
    border-radius: 28px !important;
    overflow: clip !important;
  }

  html[data-chat-state="route"] body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface.is-empty:has(textarea[data-library-preview-prompt-input]:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
  html[data-portal-route-open="true"] body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface.is-empty:has(textarea[data-library-preview-prompt-input]:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
  body.chatgpt-reference-v1.portal-route-open .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface.is-empty:has(textarea[data-library-preview-prompt-input]:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface.is-empty:has(textarea[data-library-preview-prompt-input]:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) {
    grid-template-columns: minmax(0, 1fr) !important;
    grid-template-rows: 74px !important;
    min-height: 84px !important;
    height: 84px !important;
    max-height: 84px !important;
  }

  html[data-chat-state="route"] body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-prompt-body,
  html[data-portal-route-open="true"] body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-prompt-body,
  body.chatgpt-reference-v1.portal-route-open .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-prompt-body,
  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-prompt-body {
    grid-template-columns: 36px minmax(0, 1fr) auto !important;
    grid-template-rows: 38px 36px !important;
    grid-template-areas:
      "input input input"
      "lead spacer actions" !important;
    align-items: center !important;
    min-height: 74px !important;
    height: 74px !important;
    max-height: 74px !important;
    gap: 0 8px !important;
    padding: 0 !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-textarea-scroll {
    grid-area: input !important;
    align-self: stretch !important;
    display: grid !important;
    align-items: center !important;
    min-height: 38px !important;
    height: 38px !important;
    max-height: 38px !important;
    padding: 0 7px !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .chatgpt-library-preview-prompt-input {
    align-self: center !important;
    min-height: 26px !important;
    height: 26px !important;
    max-height: 26px !important;
    padding: 0 !important;
    font-size: 16px !important;
    line-height: 26px !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-leading-action {
    grid-area: lead !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-action-dock {
    grid-area: actions !important;
    align-self: center !important;
    display: inline-flex !important;
    flex-flow: row nowrap !important;
    align-items: center !important;
    justify-content: flex-end !important;
    min-width: 0 !important;
    min-height: 36px !important;
    height: 36px !important;
    gap: 4px !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-model-pill,
  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-mic-button,
  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-leading-action,
  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .chatgpt-library-preview-submit {
    display: inline-grid !important;
    place-items: center !important;
    min-height: 36px !important;
    height: 36px !important;
    border-radius: 999px !important;
    background: rgba(0, 0, 0, 0.05);
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-leading-action,
  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-mic-button,
  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .chatgpt-library-preview-submit {
    width: 36px !important;
    min-width: 36px !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-model-pill {
    display: inline-flex !important;
    flex: 0 0 auto !important;
    width: auto !important;
    min-width: 74px !important;
    padding: 0 14px !important;
    gap: 6px !important;
    color: rgb(143, 143, 143);
    font-size: 16px !important;
    line-height: 26px !important;
    white-space: nowrap !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-model-pill > span {
    flex: 0 0 auto !important;
    white-space: nowrap !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .composer-caret {
    width: 16px !important;
    min-width: 16px !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog .chatgpt-library-preview-composer.chatgpt-composer-shell.composer-surface:has([data-library-preview-prompt-input]) .chatgpt-library-preview-submit {
    background: #000000;
    color: #ffffff;
  }
}

@media (max-width: 900px) {
  .chatgpt-reference-v1 .chatgpt-route-page {
    --chatgpt-route-page-mobile-width: min(100%, calc(100% - (var(--wizon-ai-thread-gutter) * 2)));
    /* the route title now lives in the top app bar (20260709-mobile-route-navbar),
       so the page no longer needs a large top gap for a big H1 — reclaim that space */
    --chatgpt-route-page-mobile-top: calc(var(--wizon-ai-space-unit) * 4);
    --chatgpt-route-page-mobile-bottom: var(--ds-space-8, 40px);
    /* --chatgpt-route-table-mobile-min 은 여기 있었다. 폰에서 라이브러리 표에 최소 폭을 줘 가로
       스크롤을 만들던 유일한 토큰이었고, 20260802-library-mobile-native-list 로 그 표 자체가
       리스트가 되면서 소비처가 0이 됐다. 정의만 남기면 죽은 토큰이라 함께 걷어낸다. */
    width: var(--chatgpt-route-page-mobile-width);
    padding: var(--chatgpt-route-page-mobile-top) 0 var(--chatgpt-route-page-mobile-bottom);
  }

  .chatgpt-reference-v1 .chatgpt-route-page-inner {
    gap: var(--chatgpt-route-gap-md);
  }

  .chatgpt-reference-v1 .chatgpt-route-page-head {
    flex-direction: column;
    align-items: stretch;
    gap: var(--ds-space-4, 16px);
  }

  /* the top app bar already shows the route title inline — drop the duplicate big
     H1 on mobile so the page isn't titled twice (NN/g: no redundant chrome). */
  body.portal-route-open.chatgpt-reference-v1 .chatgpt-route-page-head h1 {
    display: none;
  }

  /* 20260706-route-mobile-rail-v1 / 20260706-library-route-mobile-rail-guard: keep library controls inside the route rail while table rows scroll internally. */
  .chatgpt-reference-v1 .chatgpt-route-page-head,
  .chatgpt-reference-v1 .chatgpt-route-page-actions,
  .chatgpt-reference-v1 .chatgpt-library-filter-row,
  .chatgpt-reference-v1 .chatgpt-library-table {
    box-sizing: border-box;
    width: 100%;
    max-width: 100%;
    min-width: 0;
  }

  .chatgpt-reference-v1 .chatgpt-route-page-actions {
    flex-wrap: wrap;
    align-items: stretch;
  }

  .chatgpt-reference-v1 .chatgpt-route-search {
    width: 100%;
  }

  .chatgpt-reference-v1 .chatgpt-library-table {
    overflow-x: auto;
    scrollbar-width: none;
  }

  /* library-toolbar-siblings 채택 C(§15.4 peek): 390 에서 3번째 칩(«파일»)이 글자
     한가운데서 잘린다(레일 폭 358px = 칩 3개 139px + 거터, 여유 0px). 라벨 축약·2행은
     아트디렉션이라 채택하지 않는다 — 대신 오른쪽 끝을 페이드로 지워 «잘린 글자»가
     아니라 «다음이 이어진다»는 스크롤 가능 신호로 읽히게 한다. 폭은 신규 토큰을
     만들지 않고 기존 --wizon-ai-action-target-xs(24px)를 그대로 쓴다. */
  .chatgpt-reference-v1 .chatgpt-route-tabs {
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-mask-image: linear-gradient(to right, black calc(100% - var(--wizon-ai-action-target-xs)), transparent);
    mask-image: linear-gradient(to right, black calc(100% - var(--wizon-ai-action-target-xs)), transparent);
    /* 20261003-library-tabs-focus-ring-clip: overflow-x:auto 는 overflow-y 도 패딩 박스에서
       자른다. 레일 위·아래 여유 0 이라 Tab 포커스 링(2px + 오프셋 = 4px)이 위아래로 잘렸다.
       패딩으로 클립 영역을 열고 같은 만큼 음수 마진으로 되갚는다(trust-chip 선례) —
       레이아웃 높이·탭 위치 변화 0. 오른쪽 페이드 마스크는 그대로. */
    padding-block: 4px;
    margin-block: -4px;
  }

  /* library-toolbar-siblings 채택 C(§4): flex-start 가 칩 행과 아이콘 행의 위쪽
     가장자리만 맞추고 두 행의 높이가 다르면(칩 36 vs 아이콘 32였다) 아래선이
     갈라졌다 — 높이를 1값으로 맞춘 지금은 center 로 되돌려 형제 중심선을 공유한다. */
  .chatgpt-reference-v1 .chatgpt-library-filter-row {
    align-items: center;
    gap: var(--chatgpt-route-gap-sm);
  }

  .chatgpt-reference-v1 .chatgpt-library-view-controls {
    flex: 0 0 auto;
  }

  .chatgpt-reference-v1 .chatgpt-route-tabs::-webkit-scrollbar,
  .chatgpt-reference-v1 .chatgpt-library-table::-webkit-scrollbar {
    display: none;
  }

  .chatgpt-reference-v1 .chatgpt-route-tabs button {
    flex: 0 0 auto;
    white-space: nowrap;
  }

  /* 20260802-library-mobile-native-list (북극성 ③, doc 143 §3): 폰에서 라이브러리는 데이터 표가
     아니라 리스트다. 배포 0583703ab 를 iPhone 17 시뮬(실 WebKit)로 재니 4열이 그대로 남아 표가
     가로로 스크롤했고(폭 358px 에 scrollWidth 376), 정작 파일명은 5행 전부 말줄임이었다. 「크기」는
     5행 모두 값이 없었고 「수정됨」은 5행 모두 같은 값이라 둘이 폭만 먹었다. 아이콘 한 칸 + 제목
     (2줄까지) + 병합된 부제 한 줄로 접는다. 데스크톱(>=901px)은 무변경 — 거긴 표가 맞다. */
  .chatgpt-reference-v1 .chatgpt-library-table {
    min-width: 0;
    overflow-x: visible;
  }

  .chatgpt-reference-v1 .chatgpt-library-row--head {
    display: none;
  }

  .chatgpt-reference-v1 .chatgpt-library-row {
    grid-template-columns: 32px minmax(0, 1fr);
    grid-template-areas:
      "icon name"
      "icon meta";
    align-items: center;
    column-gap: 12px;
    row-gap: 3px;
    min-width: 0;
    /* 오른쪽 44px 은 .chatgpt-library-row-menu-trigger(⋯) 자리 — 제목이 그 밑으로 흐르지 않게. */
    padding: 12px 44px 12px 0;
  }

  .chatgpt-reference-v1 .chatgpt-library-file-icon {
    grid-area: icon;
  }

  .chatgpt-reference-v1 .chatgpt-library-file-copy {
    grid-area: name;
    padding: 0;
  }

  .chatgpt-reference-v1 .chatgpt-library-file-name {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    line-height: 1.35;
    white-space: normal;
  }

  .chatgpt-reference-v1 .chatgpt-library-row-date,
  .chatgpt-reference-v1 .chatgpt-library-row-size {
    display: none;
  }

  .chatgpt-reference-v1 .chatgpt-library-row-meta {
    grid-area: meta;
    display: block;
    justify-self: start;
    overflow: hidden;
    min-width: 0;
    color: var(--cg-muted);
    font-size: var(--ds-text-caption-13);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* 20260707-library-preview-mobile-fullscreen-reference: ChatGPT mobile file detail covers the rail; no sidebar tab remains visible. */
  body.chatgpt-reference-v1.sidebar-collapsed .chatgpt-library-preview-backdrop,
  body.chatgpt-reference-v1 .chatgpt-library-preview-backdrop {
    left: 0 !important;
    right: 0 !important;
    width: 100vw !important;
  }

  body.chatgpt-reference-v1.sidebar-collapsed .chatgpt-library-preview-dialog,
  body.chatgpt-reference-v1 .chatgpt-library-preview-dialog {
    width: 100vw !important;
  }

  .chatgpt-reference-v1 .chatgpt-library-preview-topbar {
    grid-template-columns: var(--wizon-ai-action-target-sm) minmax(0, 1fr) auto;
    gap: var(--chatgpt-route-gap-xs);
    padding: 0 var(--chatgpt-route-gap-sm);
  }

  .chatgpt-reference-v1 .chatgpt-library-preview-stage {
    padding: var(--chatgpt-route-gap-md) var(--chatgpt-route-gap-sm);
  }

  .chatgpt-reference-v1 .chatgpt-library-preview-document-pane {
    width: 100%;
    max-width: 100%;
  }

  body.chatgpt-reference-v1 .context-pane {
    inset: auto 8px 8px 8px;
    width: auto;
    height: min(78vh, 720px);
    transform: translate3d(0, 24px, 0) scale(0.985);
    transform-origin: center bottom;
  }

  body.chatgpt-reference-v1 .context-pane[data-motion-state="closing"] {
    transform: translate3d(0, 24px, 0) scale(0.985);
  }
}


@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .app-shell,
  body.chatgpt-reference-v1 .sidebar,
  body.chatgpt-reference-v1 .chatgpt-sidebar-rail,
  body.chatgpt-reference-v1 .context-pane,
  body.chatgpt-reference-v1 .message[data-message-motion="enter"],
  body.chatgpt-reference-v1 .message.pending .thinking-inline,
  body.chatgpt-reference-v1 .message[data-message-state="complete"] .message-actions,
  .chatgpt-reference-v1 .chatgpt-more-menu,
  .chatgpt-reference-v1 .chatgpt-account-menu,
  .chatgpt-reference-v1 .chatgpt-recent-menu,
  .chatgpt-reference-v1 .composer-popover,
  .chatgpt-reference-v1 .reasoning-popover,
  body.chatgpt-reference-v1 .chatgpt-conversation-menu,
  body.chatgpt-reference-v1 .chatgpt-share-dialog[open],
  body.chatgpt-reference-v1 .chatgpt-share-dialog::backdrop,
  body.chatgpt-reference-v1 .chatgpt-search-dialog[open],
  body.chatgpt-reference-v1 .chatgpt-search-dialog::backdrop,
  body.chatgpt-reference-v1 .chatgpt-delete-confirm-dialog[open],
  body.chatgpt-reference-v1 .chatgpt-delete-confirm-dialog::backdrop,
  .chatgpt-reference-v1 .chatgpt-settings-dialog[open],
  .chatgpt-reference-v1 .chatgpt-settings-dialog::backdrop {
    transition-duration: 1ms !important;
    transform: none !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* R-41(build sheet-drag-shared, S7) — 공유 시트는 위 목록에서 빼지 않는다(다른 15개 셀렉터의
   즉시-스냅 동작은 이 시험 범위 밖 화면이라 그대로 둔다, 회귀는 정적 계약 시험으로 고정).
   대신 «가장 좁은 셀렉터»(위와 글자 그대로 같은 셀렉터, `.chatgpt-share-dialog[open]`/
   `::backdrop`)를 파일 뒤에 다시 둔다 — 특이도가 같으므로 !important 끼리는 소스 순서로
   갈리고, 뒤에 있는 이 규칙이 이겨 S7 만 124ms 흐려짐(로그인 창과 같은 문법)을 받는다. */
@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .chatgpt-share-dialog[open] {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
    transform: none !important;
    translate: 0 0 !important;
  }
  body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
    translate: 0 0 !important;
  }
  body.chatgpt-reference-v1 .chatgpt-share-dialog::backdrop {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }
  body.chatgpt-reference-v1 .chatgpt-share-dialog[data-motion-state="closing"]::backdrop {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
}

/* 20260707-mobile-shell-chatgpt-parity: match ChatGPT mobile — hide the rail, full-width content, hamburger topbar opens the sidebar as an overlay drawer. */
.chatgpt-reference-v1 .chatgpt-mobile-topbar {
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  height: 54px;
  padding: 4px 8px;
}

.chatgpt-reference-v1 .chatgpt-mobile-topbar-button {
  width: 40px;
  height: 40px;
}

/* 20260729-touch-target-a2: 상단바 아이콘(40x40) 탭 타깃을 44px로 — 시각 박스는 그대로 두고
   투명 ::after로만 히트영역을 넓힌다(2026-07-10에 추가된 아트디렉션 P1-5 히트슬롭과 같은 기법이지만, 그 규칙은
   @media (pointer: coarse) 단독 조건이라 실기기/에뮬레이션에서 pointer가 정확히 coarse로
   보고되지 않으면 적용되지 않을 수 있다 — 여기서는 폭 기준을 별도로 추가해 390px 실측/자동화
   검증 모두에서 결정적으로 적용되게 한다. 기존 규칙은 .chatgpt-icon-button도 함께 묶어 전체
   다이얼로그 닫기 버튼 등 이 작업 범위 밖의 요소까지 건드리므로, 이번 작업은 범위를 좁혀
   .chatgpt-mobile-topbar-button만 별도 규칙으로 추가한다). 좌/중/우 세 슬롯은 gap 8px로
   서로 배타적으로만 노출되므로(syncPortalRouteSurfaceVisibility: 트레일 슬롯은 드라이브 메뉴/
   휴지통 비우기/새채팅 중 항상 하나만 visible) 슬롭이 다른 아이콘과 겹칠 자리가 없다(실측). */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar-button {
    position: relative;
  }
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar-button::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }
}

/* 20260725-mobile-topbar-workspace: three-slot nav bar (Material 3 / Apple HIG). The old
   layout was flat `space-between` children with an absolutely-positioned WIZON wordmark and a
   `flex: 1` title, so the title slid sideways whenever a trailing action appeared.
   20260725-mobile-topbar-grid (M1 title-centering fix): the leading/trailing slots used to share
   a flex-grow factor so free space split evenly BETWEEN them — which only keeps the middle
   optically centred while neither slot's min-content exceeds its equal share. A long title (less
   free space to split) or an asymmetric slot (more buttons on one side) can push a slot past that
   floor, and the middle drifts off-centre. `.chatgpt-mobile-topbar` is now `display: grid` with
   fixed-floor side columns (see the <=900px block below) — each side's width is a function of ITS
   OWN content only, independent of the title's length or the viewport width, so the centre column
   never wobbles. The slots keep `display: flex` for their OWN children's internal layout. */
.chatgpt-reference-v1 .chatgpt-mobile-topbar-slot {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

.chatgpt-reference-v1 .chatgpt-mobile-topbar-trail {
  justify-content: flex-end;
}

.chatgpt-reference-v1 .chatgpt-mobile-topbar-center {
  justify-content: center;
}

/* Workspace (product mode) switcher — the middle slot's chat-surface state. It opens the very
   same #productModeMenu the sidebar head opens, which is already registered as a mobile
   top-layer sheet, so it escapes the drawer's stacking context with no new menu markup. */
.chatgpt-reference-v1 .chatgpt-mobile-workspace-trigger {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-width: 0;
  max-width: 100%;
  min-height: 40px;
  padding: 0 6px 0 10px;
  border: 0;
  /* --ds-radius-sm resolves to --wds-radius-md = 10px; token rather than the literal so the
     anti-drift raw-border-radius ratchet stays at its current floor (net 0 for this change). */
  border-radius: var(--ds-radius-sm);
  background: transparent;
  color: var(--cg-text);
  font-size: var(--cg-text-topbar-brand-size);
  font-weight: 600;
  line-height: 22px;
  letter-spacing: -0.2px;
  cursor: pointer;
}

.chatgpt-reference-v1 .chatgpt-mobile-workspace-trigger > span:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .chatgpt-mobile-workspace-trigger[aria-expanded="true"] {
  background: var(--cg-hover);
}

.chatgpt-reference-v1 .chatgpt-mobile-workspace-caret {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 18px;
  height: 18px;
  color: var(--cg-muted);
}

.chatgpt-reference-v1 .chatgpt-mobile-workspace-caret svg {
  width: 16px;
  height: 16px;
}

@media (max-width: 900px) {
  body.chatgpt-reference-v1 .app-shell {
    grid-template-columns: minmax(0, 1fr);
  }

  body.chatgpt-reference-v1 .main-pane {
    grid-column: 1 / -1;
    min-width: 0;
  }

  body.chatgpt-reference-v1.sidebar-collapsed .chatgpt-sidebar-rail {
    display: none;
  }

  /* 20260725-mobile-topbar-grid (M1): three-column bar with the title in the middle.
     20260725-topbar-centre-equal-columns (audit fix): the first cut sized the side columns
     `minmax(36px, max-content)` — "each side is a function of ITS OWN content only". That is
     exactly what breaks centring when the two sides are asymmetric: on /library the trailing slot
     carries TWO buttons (새로 만들기 + 새 채팅 = 84px) against a 40px leading slot, so the middle
     column started 44px late and the title sat 22px left of the viewport centre (measured at
     390px: title mid − viewport mid = −22.0). Equal `1fr` side tracks put the centre column
     dead-centre no matter how many actions a surface exposes, which is what the approved mockup's
     `grid-template-columns: 36px 1fr 36px` does — and each `1fr` still floors at its own
     `grid-template-columns: 36px 1fr 36px` does. The centre track is `minmax(0, auto)` so a long
     title ellipsises inside its own share instead of pushing the sides apart, and the side tracks
     floor at `max-content` so that squeeze can never collapse the actions themselves: with a plain
     `1fr` floor a 50-character title drove both side slots to 0px wide at 375/390 (the ⋯ and 뒤로
     became untappable). With the max-content floor the tracks stay equal whenever there is room —
     the real case for every route title — and degrade to content width only when there is not.
     Regression pin: MobileTopbarTitleCentringTests. */
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar {
    display: grid;
    grid-template-columns: minmax(max-content, 1fr) minmax(0, auto) minmax(max-content, 1fr);
    position: relative;
  }

  /* 20260725-sidebar-wordmark-only: keyed to the SAME breakpoint that reveals the app bar above,
     so the workspace switcher is never absent — it simply moves from the drawer to the bar. */
  body.chatgpt-reference-v1 .chatgpt-product-mode-button {
    display: none;
  }

  body.chatgpt-reference-v1 .chatgpt-sidebar-wordmark {
    display: inline-flex;
  }

  /* 20260710-library-new-in-topbar: the library "새로 만들기" create action moves into the
     fixed topbar ("+" next to 새 채팅) on mobile so it stays visible while the file list
     scrolls. Hide the scrolling route-head trigger here (its popover anchor stays for the
     bottom sheet). Desktop keeps the labeled route-head button (no mobile topbar there). */
  body.chatgpt-reference-v1 .chatgpt-library-page .chatgpt-route-page-head .chatgpt-route-primary {
    display: none;
  }

  body.chatgpt-reference-v1 .sidebar {
    width: min(300px, calc(100vw - 48px));
  }

  body.chatgpt-reference-v1:not(.sidebar-collapsed) .chatgpt-sidebar-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 65;
    background: var(--wizon-scrim, rgba(0, 0, 0, 0.24));
  }
}

.chatgpt-reference-v1 .chatgpt-sidebar-scrim {
  display: none;
}

/* (Removed a redundant `@media (max-width: 520px) .chatgpt-settings-dialog { border-radius: 0 }`
   block — the settings dialog now opts into [data-mobile-fullscreen], and the shared
   mobile-dialog rule "20260708-mobile-dialog-fullscreen-system" already sets
   border-radius via --wizon-mobile-dialog-radius !important. Single source of truth.) */

/* 20260708-composer-mobile-two-row-v2: match ChatGPT mobile — reflow the home composer to two rows (textarea on top; [+] ... [pill][mic][voice] below) so every control fits at narrow widths instead of wrapping/overflowing. The single-line "placeholder-state-reset-guard" (styles.css:11858,11867) clamps the shell to 52px and the prompt-body to a single 42px row with (0,8,2) !important selectors; this block MIRRORS those exact selectors (plus the simpler empty-state ones) so equal specificity + later source order + !important wins. Geometry follows the library-preview mobile composer template (styles.css:11968). */
@media (max-width: 520px) {
  /* Release the fixed single-line height clamp on the shell so two rows can show,
     and give the shell symmetric inner padding so the bottom controls are not flush
     against the rounded edge. */
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
  body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
  body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))),
  body.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty {
    /* 106 = 10 top pad + 40 input row + 6 row gap + 40 control row + 10 bottom pad */
    min-height: 106px !important;
    height: auto !important;
    max-height: none !important;
    padding: 10px 12px !important;
  }

  /* Two-row prompt-body: textarea spans the top row; +, pill, mic, voice sit on the
     bottom row. The input row is flexible (minmax(40px, auto)) so it grows with
     multi-line text while the controls row stays a fixed 40px pinned at the bottom
     (like the desktop composer). The extra `.chatgpt-composer-shell .composer-prompt-body`
     selector + !important also override the desktop multiline rule (styles.css:7046),
     which would otherwise win. Bottom row is 40px so the 38px voice button centers
     with room; the shell's 10px bottom padding keeps the controls off the edge. */
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body,
  body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body,
  body.chatgpt-reference-v1 .chatgpt-composer-shell .composer-prompt-body,
  body.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline .composer-prompt-body,
  body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-prompt-body,
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(.has-attachment) .composer-prompt-body,
  body.chatgpt-reference-v1 .chatgpt-composer-shell.is-empty:not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body,
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(textarea:placeholder-shown):not(:has(.composer-mode-slot:not([hidden]))):not(:has(.composer-attachment-lane:not([hidden]))) .composer-prompt-body,
  body.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline.is-empty .composer-prompt-body,
  body.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline:has(textarea:placeholder-shown) .composer-prompt-body {
    grid-template-columns: 36px minmax(0, 1fr) auto !important;
    grid-template-rows: minmax(40px, auto) 40px !important;
    grid-template-areas:
      "input input input"
      "lead spacer actions" !important;
    align-items: center !important;
    min-height: 86px !important;
    height: auto !important;
    max-height: none !important;
    gap: 6px 8px !important;
    padding: 0 !important;
  }

  /* Multi-line: the textarea grows then scrolls internally at max(30svh, 5rem) —
     matching ChatGPT mobile (`max-h-[max(30svh,5rem)]`, ~8 lines) — while the shell
     grows to fit. The broad shell selector + !important beat the desktop multiline
     shell clamp (styles.css:7042 max-height:300px, no !important). */
  body.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline,
  body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell.is-multiline {
    min-height: 0 !important;
    height: auto !important;
    max-height: none !important;
    padding: 10px 12px !important;
  }

  body.chatgpt-reference-v1 .chatgpt-composer-shell .composer-textarea-scroll,
  body.chatgpt-reference-v1 .chatgpt-composer-shell .composer-textarea-scroll textarea {
    max-height: max(30svh, 5rem) !important;
  }

  body.chatgpt-reference-v1 .chatgpt-composer-shell.is-multiline .composer-textarea-scroll {
    align-self: stretch !important;
    overflow-y: auto !important;
  }

  /* keep the action dock on a single horizontal row (wds-ai-portal-v1 forces column) and let the pill show its full label */
  body.chatgpt-reference-v1 .chatgpt-composer-shell .composer-action-dock {
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    align-items: center;
    gap: 6px;
    justify-self: end;
  }

  body.chatgpt-reference-v1 .composer-model-pill {
    display: inline-flex;
    max-width: none;
  }

  body.chatgpt-reference-v1 .composer-model-pill > span:first-child {
    white-space: nowrap;
  }

  body.chatgpt-reference-v1 .composer-mic-button {
    display: inline-grid;
  }
}

/* 20260708-composer-mobile-bottom-pin: ChatGPT pins the composer to the BOTTOM of the
   mobile home instead of vertically centering the greeting/composer/chips block. Push
   the block down with a big top spacer and re-order the empty-home grid so the chips
   sit ABOVE the composer and the composer is last (bottom). Scoped to
   html[data-chat-state="empty"] so the active conversation view is untouched; the extra
   `body` in each selector beats the base empty-home rules (styles.css:7308-7336). */
@media (max-width: 520px) {
  /* Both empty AND active: the chat pane must fill the workbench (the area below the
     52px mobile topbar), never 100dvh (styles.css:8372) — otherwise the bottom-pinned
     composer, now 106px tall on mobile, overflows off-screen by the topbar height. */
  body.chatgpt-reference-v1 .chat-pane {
    height: 100% !important;
    min-height: 0 !important;
    max-height: 100% !important;
  }

  html[data-chat-state="empty"] body.chatgpt-reference-v1 .chat-pane {
    /* login-r33-post-login-stage1 §1-6(Qⓐ): +1 row for #profileHomeNudge, between the
       greeting(2) and the chip shelf, which shifted from row3 to row4 below. */
    grid-template-rows: minmax(0, 1fr) auto auto auto auto !important;
    padding-bottom: 10px !important;
  }

  html[data-chat-state="empty"] body.chatgpt-reference-v1 .message-list {
    grid-row: 2 !important;
    margin: 0 auto 6px !important;
  }

  html[data-chat-state="empty"] body.chatgpt-reference-v1 .profile-home-nudge {
    grid-row: 3 !important;
    width: calc(100% - 32px) !important;
    margin: 0 auto 10px !important;
  }

  html[data-chat-state="empty"] body.chatgpt-reference-v1 .chatgpt-prompt-shelf {
    grid-row: 4 !important;
    margin: 0 auto 10px !important;
  }

  html[data-chat-state="empty"] body.chatgpt-reference-v1 .chatgpt-composer-shell,
  html[data-chat-state="empty"] body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell {
    grid-row: 5 !important;
    margin: 0 auto !important;
  }

  /* Active conversation: trim the desktop 28px composer bottom margin to a mobile 10px
     so the composer sits near the bottom edge like ChatGPT (and matches the empty home). */
  html[data-chat-state="active"] body.chatgpt-reference-v1 .chatgpt-composer-shell,
  html[data-chat-state="active"] body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell,
  html[data-chat-state="loading"] body.chatgpt-reference-v1 .chatgpt-composer-shell,
  html[data-chat-state="loading"] body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell {
    margin-bottom: 10px !important;
  }
}

/* 20260708-sidebar-shortcut-hints-mobile-hide: ChatGPT hides the keyboard-shortcut
   hints (⇧⌘O, ⌘K) in the sidebar on mobile, where there is no hardware keyboard. Hide
   them whenever the mobile drawer shell is active (<=900px, where the hamburger topbar
   replaces the rail). The Settings dialog's dedicated shortcut list (.settings-shortcut-*)
   is a separate surface and is intentionally left untouched. */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-sidebar-shortcut {
    display: none !important;
  }
}

/* 20260708-mobile-dialog-fullscreen-system: WIZON mobile design RULE for modals.
   ANY overlay element that opts in with [data-mobile-fullscreen] becomes full-screen
   edge-to-edge on mobile (<=520px), matching ChatGPT's content-panel modals. Works for
   both native <dialog> (settings, search) and custom role="dialog" overlays rendered
   inside a backdrop (project create, …) — `position: fixed; inset: 0` pins either kind
   to the viewport. This is the single source of truth: opt-in dialogs no longer hardcode
   their own mobile size. It overrides the native <dialog> defaults (max-height:
   calc(100% - 38px); margin: auto) that otherwise leave a ~19px gap. Small confirmation
   alerts / pickers (delete, share, library-move) deliberately DO NOT opt in — ChatGPT
   keeps those compact and centered. Values come from the --wizon-mobile-dialog-* tokens
   (see "20260708-mobile-dialog-tokens" near the top of this file). */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 [data-mobile-fullscreen] {
    position: fixed !important;
    inset: 0 !important;
    /* Pin to the visual viewport (top offset + height) so an iOS keyboard scroll
       can't drag the dialog's sticky header off-screen. */
    top: var(--wizon-vv-offset-top, 0px) !important;
    bottom: auto !important;
    width: var(--wizon-mobile-dialog-inline-size, 100vw) !important;
    max-width: none !important;
    height: var(--wizon-mobile-dialog-block-size, 100dvh) !important;
    max-height: var(--wizon-mobile-dialog-block-size, 100dvh) !important;
    margin: var(--wizon-mobile-dialog-margin, 0) !important;
    border-radius: var(--wizon-mobile-dialog-radius, 0) !important;
  }

  /* A dialog whose card layout was a fixed-height grid must reflow when it goes
     full-screen: stack the content from the top and pin the primary action to the
     bottom, full-width (ChatGPT's new-project mobile page). */
  body.chatgpt-reference-v1 .chatgpt-project-create-dialog[data-mobile-fullscreen] {
    display: flex !important;
    flex-direction: column !important;
    align-items: stretch !important;
    gap: 14px !important;
    padding: 16px 20px calc(20px + env(safe-area-inset-bottom, 0px)) !important;
    overflow-y: auto !important;
  }

  body.chatgpt-reference-v1 .chatgpt-project-create-dialog[data-mobile-fullscreen] .chatgpt-project-create-submit {
    margin-top: auto !important;
    width: 100% !important;
    align-self: stretch !important;
    justify-self: stretch !important;
  }
}

/* 20260904-mobile-bottom-sheet-adapter (mobile-sheet-bottom-sheet-unification 레인 · 오너 지시
   2026-09-04 폰 실기: 「팝업/모달을 바텀시트로 통일」). WIZON 모바일 규칙 두 번째 — 첫 번째는
   위 [data-mobile-fullscreen](전면 페이지형 모달: 검색·설정·새 프로젝트). 이 규칙은 **짧은 단일
   목적 폼/좁은 목록**(이동, 텍스트 나누기, 드라이브 저장 충돌처럼 「예/아니오 + 입력 한둘」 크기의
   대화상자)을 위한 것 — 그런 대화상자를 폰에서 «전면»으로 펴면 내용보다 빈 여백이 커진다(Apple
   HIG·Material 3 의 sheet 관례, doc 635 §3 참고). ANY OUR-OWN <dialog> 가 [data-mobile-bottom-sheet]
   로 옵트인하면 이 규칙 하나로 하단 시트가 된다 — 대화상자마다 손으로 다시 적지 않는다.

   ★공유 클래스가 아니라 **개별 dialog id 에만** 옵트인 속성을 붙인다(.chatgpt-delete-confirm-dialog
   자체는 건드리지 않는다) — 그 클래스는 채팅 표면의 deleteConfirmDialog 도 같이 쓰므로, 클래스
   자체를 바꾸면 이 트랙의 스코프(모바일 시트 편집기, doc 635) 밖까지 번진다.

   ★모션 토큰은 새로 만들지 않는다 — .orchestration-dock/.composer-popover 하단 시트가 이미 쓰는
   wizon-sheet-up/-down 키프레임과 --wizon-ai-radius-sheet 등 같은 var()+폴백 쌍을 그대로 재사용한다
   (raw-color/raw-border-radius anti-drift 래칫이 이미 그 두 값으로 통과 중 — 새 리터럴 없음).

   ★네이티브 <dialog> 는 showModal() 로 이미 진짜 top layer 라 composer-popover 계열이 필요했던
   JS 탭-통과 캡처 가드(핸들TopLayerOutsidePointerDown)가 필요 없다 — ::backdrop 이 실제로 포인터를
   막는다(Popover API 의 ::backdrop 은 UA 가 pointer-events:none 을 강제하지만 <dialog> 의 것은
   아니다). 그래서 이 블록은 위치·모션만 다루고 새 JS 를 추가하지 않는다. */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet][open] {
    position: fixed !important;
    inset: auto 0 0 0 !important;
    top: auto !important;
    /* 669 §M-8 — overlays-content 뷰포트(index.html)에서는 소프트키보드가 레이아웃 뷰포트를
       줄이지 않는다. 바닥을 --wizon-kb-inset(이미 살아 있는 키보드 높이, syncMobileViewport
       Metrics 가 채운다)만큼 띄워 입력 칸·주버튼이 키보드 밑에 남지 않게 한다(선례:
       workspace-picker-dialog[data-mobile-bottom-sheet="workspace-picker"] 의 같은 recipe,
       styles.css 「fix-b」 블록). 같은 특이도의 뒤 선언이 위 inset 의 bottom:0 을 이긴다. */
    bottom: var(--wizon-kb-inset, 0px) !important;
    margin: 0 !important;
    width: 100% !important;
    /* UA 기본 dialog{max-width:calc((100% - 6px) - 2em)}이 100%를 조용히 깎는 것을 막는다
       (doc 544 §5.4 의 sheet-function-dialog 900px 규칙과 같은 함정, 5342행 참고). */
    max-width: none !important;
    height: auto !important;
    /* 669 §M-5 — dvh 단독은 키보드가 열려도 줄지 않는다(iOS Safari 실측). --wizon-vvh(visual
       viewport 기준, syncMobileViewportMetrics 가 채운다)로 바꾼다 — 폴백은 그대로 100dvh. */
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px)) !important;
    overflow-y: auto !important;
    border: 0 !important;
    border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0 !important;
    padding-top: 20px !important;
    padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px)) !important;
    /* box-shadow 는 건드리지 않는다 — 밑에 있는 .chatgpt-delete-confirm-dialog[open] 이 이미
       var(--cg-dialog-shadow) 를 준다(raw-rgb/raw-box-shadow anti-drift 래칫이 var() 폴백
       «안»의 리터럴도 줄 단위로 세므로, orchestration-dock 처럼 다시 rgba 를 적으면 걸린다
       — doc 635 §9.8 이 이미 겪은 함정). */
    opacity: 1 !important;
    transform: none !important;
    translate: 0 0;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }

  /* R-40 ②파도 build C(669 §M-5) — 모달 시트 최대 높이는 두 종류뿐이다. 위 공용 값은 «짧은»
     (선택·결정·안내) 이고, 작업형(task: 함수 삽입·텍스트 나누기 등 입력이 있는 시트)은 «긴»
     값 — 보이는 화면(--wizon-vvh) − 위 안전영역 − 40. dvh/vh 단독 값은 쓰지 않는다. */
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet][data-sheet-kind="task"][open] {
    max-height: calc(var(--wizon-vvh, 100dvh) - var(--wizon-safe-top, 0px) - 40px) !important;
  }

  /* 669 §M-11 — 모달 시트의 스크림은 토큰 하나(--wizon-scrim)다. 시트로 열린 <dialog>(함수·나누기·
     이동·업로드 충돌·결재·작업공간 선택)가 자기 셸의 ::backdrop 값(.16/.52 — 삭제 확인 셸 것)을
     그대로 물려받아 표면마다 달랐다. 셸 자체(채팅 삭제 확인 등 «시트 아닌» 대화상자)는 건드리지
     않는다 — [data-mobile-bottom-sheet] 를 단 <dialog> 만 이 규칙에 든다. <dialog>.showModal() 의
     ::backdrop 은 실제로 포인터를 막는다(뒤 화면 클릭 통과 0, 위 주석·실측). 다크 셸 규칙(0,3,3)과
     특이도가 같고 뒤에 있어 이긴다. */
  body.chatgpt-reference-v1 dialog[data-mobile-bottom-sheet]:modal::backdrop {
    background: var(--wizon-scrim);
  }

  /* 잡이 — 시각 전용(드래그 없음). ★2026-09-25 정정(669 §M-2 실측, wave1) — 이 자리는 예전에
     "<dialog> 는 이미 자기 X/취소 버튼과 실제 ::backdrop 탭-닫힘을 갖고 있어 드래그 손잡이가
     유일한 닫힘 수단이 아니다"라고 적었지만 사실과 달랐다: ::backdrop 클릭은 dialog 엘리먼트로
     버블링될 뿐 자동으로 닫지 않는다(MDN) — JS 쪽 click 핸들러가 `event.target === dialog`를
     직접 확인해야 한다. 그 배선이 없던 넷(함수·나누기·이동·업로드충돌) 중 함수·나누기(작업형
     단계)는 이번 레인이 추가했고, 이동·업로드충돌(결정형)은 §2 규칙표대로 스크림 탭 무반응이
     의도된 동작이다(app.js handleArtifactSheetGoToDialogClick/askDriveUploadConflict 참고).
     드래그 손잡이는 여전히 시각 전용(끌기 없음, doc 635 §3 표의 「비폼형 시트만 detent」 판단은
     유지) — 헤더가 이미 있는 상자 위에 얹으므로 위 padding-top: 20px 가 자리를 만든다
     (.chatgpt-delete-confirm-head 자신의 top 10px 위에 겹치지 않고 더한다). */
  /* R-41 B2b(할 일 5, 설계 §3.3) — 종류가 손잡이를 주는 시트(task: 함수·나누기)는 이제
     실제 DOM 손잡이(ensureSheetHandle 이 시트 맨 앞에 깐 [data-sheet-handle])가 이 자리를
     대신 그린다 — 옛 ::before 는 손잡이가 없는 시트(decide: 이동·업로드충돌, §2 규칙표대로
     스크림 탭 무반응)에만 남겨 시각을 그대로 유지한다. */
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet][open]:not(:has(> .wizon-sheet-handle))::before {
    content: "";
    position: absolute;
    top: 8px;
    left: 50%;
    width: 36px;
    height: 4px;
    border-radius: var(--wizon-ai-radius-pill, 999px);
    /* 폴백 리터럴 없음(raw-color 래칫이 var() 폴백도 센다) — --cg-muted 는 :root 에 실제
       정의가 있어(styles.css) 폴백이 애초에 불필요하다(doc 635 §9.8 과 같은 판단). */
    background: var(--cg-muted);
    opacity: 0.5;
    transform: translateX(-50%);
    pointer-events: none;
  }

  /* 손잡이가 있는 시트 — 공통 레시피(44px 높이·::after 막대)를 그대로 쓰되, 옛 ::before
     자리(top:8px 절대 위치)를 유지해 헤더 레이아웃이 밀리지 않는다(150 §K 비교판 불변).
     ★중앙 재작업 지시 ②(실렌더 매트릭스) 수리 — 처음에 폭을 44px 정사각으로 좁혀서
     M-3 ③(히트 폭 ≥ 시트 50%, design.md §2 「폭 0.93~1.0」)을 어겼다(실측: 0.113).
     left/right 를 0 으로 열어 시트 전체 폭을 되찾는다(옛 ::before 도 left:50%+
     translateX(-50%) 로 「가운데 배치」였을 뿐 히트 영역 자체는 없었다 — 이번이 처음
     생기는 값이라 폭 제약을 상속할 옛 규칙이 없다). */
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet][open] > .wizon-sheet-handle {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    width: auto;
    height: 44px;
    min-height: 44px;
    /* ★중앙 실측 수리(S3 function·picker 390 캡처) — 전폭 손잡이가 dialog 의
       overflow-y:auto(비-hidden) 때문에 위 border-radius 클립을 안 받아 둥근 위쪽
       모서리 밖까지 곧은 가로선으로 삐져나왔다(다이얼로그 상단 라운드 곡선 바깥까지
       x=0 로 이어짐). 손잡이 자신에도 같은 반지름을 줘 클립하고, 배경·테두리를 명시로
       비워 혹시 상속되는 값이 있어도 항상 투명하게 고정한다. */
    background: transparent;
    border: none;
    border-top-left-radius: var(--wizon-ai-radius-sheet, 18px);
    border-top-right-radius: var(--wizon-ai-radius-sheet, 18px);
    overflow: hidden;
  }
  /* 전폭 손잡이가 머리글의 닫기 버튼과 같은 44px 띠를 덮는다 — S4 가 이미 겪은 문제와
     같은 모양(design.md §3.3 「모서리 × z-index:2」)이라 같은 처방을 쓴다. 손잡이 자신은
     막대만 그리는 배경 없는 레이어라 버튼을 시각으로 가리지 않고, z-index 만 버튼이 위로
     오게 한다(클릭 우선순위). */
  body.chatgpt-reference-v1 [data-sheet-function-close],
  body.chatgpt-reference-v1 [data-workspace-picker-close] {
    position: relative;
    z-index: 2;
  }

  /* 종료 — 키프레임(트랜지션 아님): Safari 는 top-layer 위에서 transition 을 페인트하지 않는다
     (20260709-motion-m1 블록의 같은 이유, 위쪽 이 파일 참고). */
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet][data-motion-state="closing"] {
    opacity: 1 !important;
    transform: none !important;
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }
}

/* R-41 B1b(M-15) — 이전엔 [open]/closing 모두 animation:none(즉시)이었다. 이동(translate)은
   여전히 0 이지만, 열림·닫힘 자체는 로그인 창(19547행)과 같은 문법으로 124ms 흐려짐으로 바뀐다
   — 새 키프레임 0개(wizon-scrim-in/-out 재사용), !important 로 하한을 이긴다. */
@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet][open] {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
    translate: 0 0 !important;
  }
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet][data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
    translate: 0 0 !important;
  }
}

/* 20260904-mobile-bottom-sheet-popover-adapter (mobile-sheet-bottom-sheet-round2 레인, doc 635
   §19). [data-mobile-bottom-sheet](위 블록, §11)의 두 번째 형제 — 그 어댑터는 네이티브 <dialog>
   전용이라(진짜 top layer, ::backdrop 이 실제로 포인터를 막는다) 앵커 좌표를 JS 가 계산하는
   role="menu" 팝오버(정렬 메뉴·채우기색 팔레트·시트탭 「≡ 전체 목록」)에는 그대로 못 쓴다
   (doc 635 §11.5). 이 블록은 같은 열림 규약(밑에 고정, 위쪽만 둥근 모서리, 같은 wizon-sheet-up/
   -down 모션 재사용, 새 키프레임 0개)을 [data-mobile-bottom-sheet="popover"] 로 다시 쓰되, 「열림」
   판정만 <dialog>[open] 대신 이 부류가 실제로 쓰는 :not([hidden]) 으로 바꾼다.

   ★캡처 가드 신규 불필요. 정렬/색/시트탭 메뉴는 이미 document.addEventListener("click", ..., true)
   캡처 단계 outside-click 핸들러를 갖고 있고(popover.contains(event.target) 예외 포함, doc 544
   §20.4 가 실측으로 잡은 함정), 이 규칙은 위치·모션만 바꾸고 그 핸들러를 그대로 쓴다 — Popover
   API 도 새 top-layer 승격도 쓰지 않는다(20260709-mobile-sheet-top-layer 블록과는 별개 계열).
   ★닫기 X(44×44, .artifact-mobile-popover-sheet-close)는 새로 추가했다 — 서식/삽입 시트의
   .artifact-ribbon-sheet-close 와 같은 레시피. 이 세 메뉴는 원래 명시적 닫기 버튼이 없었다(바깥
   탭/Escape/항목 선택으로만 닫혔다) — 바텀시트로 그려진 좁은 폭에서는 「바깥」이 화면 대부분을
   차지해 오탭 위험이 크므로 명시적 닫힘 수단을 더했다(app.js 의 기존 close* 함수를 그대로 호출,
   새 상태 없음). */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet="popover"]:not([hidden]) {
    position: fixed !important;
    inset: auto 0 0 0 !important;
    top: auto !important;
    left: 0 !important;
    right: 0 !important;
    margin: 0 !important;
    width: 100% !important;
    max-width: none !important;
    height: auto !important;
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px)) !important;
    overflow-y: auto !important;
    border: 0 !important;
    border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0 !important;
    padding: 44px 8px calc(12px + env(safe-area-inset-bottom, 0px)) !important;
    /* R-41 B2b(할 일 2, 설계 §3.3) — DOM 손잡이가 생기면 그 자신이 44px 첫 행을 채우므로
       위 44px 예약은 핸들이 대신 지운다(:has(> .wizon-sheet-handle) 아래에서 0 으로). */
    /* box-shadow: var() 만 — raw-rgb 래칫이 var() 폴백 안 리터럴도 줄 단위로 센다(doc 635 §9.8/
       §11.3 이 이미 겪은 함정, 이 값은 §9 서식/삽입 시트가 이미 쓰는 값과 같다). */
    box-shadow: 0 -4px 16px var(--wizon-ai-focus-ring) !important;
    z-index: 300 !important;
    opacity: 1 !important;
    transform: none !important;
    translate: 0 0;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }

  /* 잡이 — R-41 B2b(설계 §3.3) 이후 DOM 손잡이(ensureSheetHandle 이 깐 [data-sheet-handle],
     기본 sticky 레시피는 44105행)가 이 자리를 대신 그린다. 옛 ::before(시각 전용, 드래그 없음)
     는 아직 data-sheet-kind 가 없는 형제(font-color 팔레트 — §S4 범위 밖)에만 남긴다. */
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet="popover"]:not([hidden]):not(:has(> .wizon-sheet-handle))::before {
    content: "";
    position: absolute;
    top: 8px;
    left: 50%;
    width: 36px;
    height: 4px;
    border-radius: var(--wizon-ai-radius-pill, 999px);
    background: var(--cg-muted);
    opacity: 0.5;
    transform: translateX(-50%);
    pointer-events: none;
  }

  /* DOM 손잡이가 채우는 44px 첫 행은 위 44px 상단 패딩과 중복이다 — S1 이 이미 쓰는 같은
     패턴(44128행 :has(> .wizon-sheet-handle){padding-top:0}). */
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet="popover"]:not([hidden]):has(> .wizon-sheet-handle) {
    padding-top: 0 !important;
  }

  /* 손잡이가 sticky 로 첫 행에 앉으므로 모서리 × 가 같은 44px 띠 안에서 가려진다(design.md
     §2 「모서리 × 가림」실측, S4 안내). × 를 손잡이 위로 올린다 — 손잡이는 막대만 그리는
     투명 레이어라 시각은 그대로다. */
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet="popover"] .artifact-mobile-popover-sheet-close {
    z-index: 2;
  }

  body.chatgpt-reference-v1 [data-mobile-bottom-sheet="popover"][data-motion-state="closing"] {
    opacity: 1 !important;
    transform: none !important;
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }

  /* 닫기 X — 데스크톱에서는 이 파일 아래쪽 기본 규칙(display:none)이 이긴다. .artifact-ribbon-
     sheet-close 와 같은 44×44 그리드 배치, 다만 위치는 시트 우상단(그 컴포넌트엔 헤더 바가 있어
     좌측에 그립+타이틀이 있었지만, 이 세 메뉴엔 헤더 자체가 없어 그립 하나만 남기고 X 는 통째로
     오른쪽 위에 둔다 — 44px 상단 패딩이 그 자리를 만든다). */
  body.chatgpt-reference-v1 .artifact-mobile-popover-sheet-close {
    display: grid;
    position: absolute;
    top: 0;
    right: 0;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--cg-text-soft, var(--muted));
    cursor: pointer;
  }
  body.chatgpt-reference-v1 .artifact-mobile-popover-sheet-close:focus-visible {
    outline: 2px solid var(--cg-focus-ring, var(--accent));
    outline-offset: -2px;
  }
}

/* R-41 B1b(M-15) — S4 팝오버 3개도 위 §11 dialog 블록과 같은 사유로 즉시→124ms 흐려짐. */
@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet="popover"]:not([hidden]) {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
    translate: 0 0 !important;
  }
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet="popover"][data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
    translate: 0 0 !important;
  }
}

/* 데스크톱/900px 폭에서는 닫기 X 를 그리지 않는다 — 팝오버는 원래 명시적 닫기 버튼이 없었고
   (바깥 탭/Escape/항목 선택으로 닫힌다), X 를 항상 그리면 좁지 않은 폭에서도 팔레트/메뉴 안에
   불필요한 44×44 빈 모서리가 생긴다. */
.artifact-mobile-popover-sheet-close {
  display: none;
}

/* 20260905-mobile-bottom-sheet-vendor-adapter (doc 635 §22). [data-mobile-bottom-sheet](§11)/
   [data-mobile-bottom-sheet="popover"](§19) 의 세 번째 형제 — 이번엔 우리 DOM이 아니라
   Univer 내장 프리셋이 React 포털로 직접 마운트하는 Radix 대화상자(찾기 및 바꾸기) 다. §18.2
   가 실측으로 확정해 둔 대로 그 어댑터들은 «우리 자신의 <dialog>» 만 대상으로 할 수 있어(벤더가
   만드는 DOM은 대상이 될 수 없다) 이 대화상자만 남아 있었다 — app.js 의
   tagArtifactRibbonFindReplaceDialogForMobileSheet() 가 열릴 때마다(Radix는 고정 id 없이 매번
   새로 마운트한다) `data-mobile-bottom-sheet="vendor"` 를 붙인다.

   ★transform 만 !important 로 무력화하고 opacity 는 건드리지 않는다 — Radix 자신의
   `data-[state=open]:univer-animate-in`/`data-[state=closed]:univer-animate-out` 클래스(
   tailwindcss-animate, 실측 클래스명)가 opacity 는 그대로 페이드시키고 transform(줌/센터
   슬라이드)만 우리 규칙에 진다. CSS cascade 상 「중요 작성자 선언」이 「애니메이션 선언」을
   이기므로(같은 속성일 때만) 이 !important 만으로 충분하다 — animation 자체를 우리 값으로
   바꿔 뺏지 않는다(래칫 관례상 새 키프레임을 벤더 애니메이션과 경합시키는 위험을 늘리지 않는
   가장 단순한 개입, §9.8 이 이미 겪은 함정군과 같은 판단으로 최소화했다). 그 결과 닫힘은
   슬라이드다운이 아니라 «그 자리에서 페이드아웃» — 새 키프레임을 만들지 않는 대가로 받아들인
   자국이다(잔여 위험으로 남긴다, 이 보고문 §22.6). inset/left/right/margin/width 는 Radix
   의 인라인 `style="position:absolute; left:0; top:0; transform:translate(...)"` 위에 그대로
   이긴다 — !important 스타일시트 규칙은 출처·특이도와 무관하게 !important 없는 인라인 스타일을
   항상 이긴다(CSS Cascade §4). */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 [data-mobile-bottom-sheet="vendor"] {
    position: fixed !important;
    inset: auto 0 0 0 !important;
    top: auto !important;
    left: 0 !important;
    right: 0 !important;
    margin: 0 !important;
    width: 100% !important;
    max-width: none !important;
    height: auto !important;
    max-height: calc(var(--wizon-vvh, 100dvh) - var(--wizon-safe-top, 0px) - 40px) !important;
    overflow-y: auto !important;
    border: 0 !important;
    border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0 !important;
    /* fix(20260925-build-approval-sheet-dialog): 옛 2값 축약 `padding: 12px calc(12px + safe)`
       는 top/bottom=12px, left/right=calc(12px+safe) 로 풀린다 — 안전영역이 좌우로 들어가고
       바닥은 12px 그대로였다(중앙 확인, inventory.md S5 행). 4값으로 바닥에만 safe 를 준다. */
    padding: 12px 12px calc(12px + env(safe-area-inset-bottom, 0px)) 12px !important;
    box-shadow: 0 -4px 16px var(--wizon-ai-focus-ring) !important;
    z-index: 300 !important;
    transform: none !important;
  }
}

/* ===== Weather widget (WDS DataAnswerCard port) — immersive data-viz answer =============
   Sky gradients / glass tints / temp-range are intentional weather colors (real hex, no
   token dependency). Chrome spacing/radius/shadow use literals. Panel backgrounds are set
   inline by JS (WEATHER_PANEL_TINT) so CSS never fights them. */
.weather-widget,
.weather-mini,
.wcVerdict {
  container-type: inline-size;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 20px;
  margin-top: 4px;
  padding: 24px;
  border-radius: 20px;
  background: #0d285e;
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #ffffff 38%, transparent),
    inset 0 -1px 0 color-mix(in srgb, #0b1d3a 28%, transparent),
    0 24px 48px rgba(0, 0, 0, 0.16),
    0 18px 40px -12px color-mix(in srgb, #0b1d3a 30%, transparent);
  color: #ffffff;
}
.weather-widget::after,
.weather-mini::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(120% 80% at 50% 112%, color-mix(in srgb, #0b1d3a 36%, transparent), transparent 60%);
}
.weather-widget > *,
.weather-mini > * {
  position: relative;
  z-index: 1;
}

.weatherBg {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
}
.weatherBg::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.weather-widget[data-cond="snow"] .weatherBg::before,
.weather-mini[data-cond="snow"] .weatherBg::before,
.weather-widget[data-cond="cloud"] .weatherBg::before,
.weather-mini[data-cond="cloud"] .weatherBg::before,
.weather-widget[data-cond="cloudnight"] .weatherBg::before,
.weather-mini[data-cond="cloudnight"] .weatherBg::before,
.weather-widget[data-cond="fog"] .weatherBg::before,
.weather-mini[data-cond="fog"] .weatherBg::before {
  background: linear-gradient(180deg, color-mix(in srgb, #0b1d3a 40%, transparent), transparent 38%);
}
.weather-widget[data-cond="sun"] .weatherBg,
.weather-mini[data-cond="sun"] .weatherBg {
  background:
    radial-gradient(120% 90% at 78% 10%, color-mix(in srgb, #fbbf24 28%, transparent), transparent 46%),
    radial-gradient(140% 120% at 12% -10%, color-mix(in srgb, #06b6d4 48%, transparent), transparent 56%),
    linear-gradient(168deg, #38bdf8, #3c8ffd 42%, #1252b2 92%, #0d285e);
}
.weather-widget[data-cond="cloudsun"] .weatherBg,
.weather-mini[data-cond="cloudsun"] .weatherBg {
  background:
    radial-gradient(120% 90% at 78% 10%, color-mix(in srgb, #fbbf24 26%, transparent), transparent 46%),
    radial-gradient(140% 120% at 12% -10%, color-mix(in srgb, #38bdf8 80%, transparent), transparent 55%),
    linear-gradient(168deg, #38bdf8, #3c8ffd 42%, #1252b2 92%, #0d285e);
}
.weather-widget[data-cond="cloud"] .weatherBg,
.weather-mini[data-cond="cloud"] .weatherBg {
  background: linear-gradient(168deg, color-mix(in srgb, #38bdf8 38%, #4b5563), #4b5563 48%, #0d285e);
}
.weather-widget[data-cond="rain"] .weatherBg,
.weather-mini[data-cond="rain"] .weatherBg {
  background:
    radial-gradient(140% 120% at 30% -10%, color-mix(in srgb, #1252b2 70%, #374151), transparent 60%),
    linear-gradient(172deg, color-mix(in srgb, #1252b2 55%, #374151), #0d285e 50%, #0b1d3a);
}
.weather-widget[data-cond="snow"] .weatherBg,
.weather-mini[data-cond="snow"] .weatherBg {
  background: linear-gradient(170deg, color-mix(in srgb, #38bdf8 45%, #4b5563), color-mix(in srgb, #3c8ffd 70%, #9ca3af) 45%, #1252b2);
}
.weather-widget[data-cond="night"] .weatherBg,
.weather-mini[data-cond="night"] .weatherBg {
  background:
    radial-gradient(120% 80% at 80% 8%, color-mix(in srgb, #1252b2 28%, transparent), transparent 50%),
    linear-gradient(168deg, #083344, #0b1d3a 55%, #111827);
}
.weather-widget[data-cond="thunder"] .weatherBg,
.weather-mini[data-cond="thunder"] .weatherBg {
  background:
    radial-gradient(130% 110% at 64% -8%, color-mix(in srgb, #1252b2 45%, #374151), transparent 58%),
    linear-gradient(176deg, color-mix(in srgb, #0b1d3a 78%, #374151), #0b1d3a 46%, #111827);
}
.weather-widget[data-cond="fog"] .weatherBg,
.weather-mini[data-cond="fog"] .weatherBg {
  background: linear-gradient(170deg, color-mix(in srgb, #38bdf8 22%, #4b5563), #4b5563 46%, #0d285e);
}
.weather-widget[data-cond="partlynight"] .weatherBg,
.weather-mini[data-cond="partlynight"] .weatherBg {
  background:
    radial-gradient(120% 80% at 82% 8%, color-mix(in srgb, #f2e4b0 12%, transparent), transparent 46%),
    linear-gradient(168deg, color-mix(in srgb, #38bdf8 10%, #0e2748), #0b1d3a 52%, #111827);
}
.weather-widget[data-cond="cloudnight"] .weatherBg,
.weather-mini[data-cond="cloudnight"] .weatherBg {
  background: linear-gradient(168deg, color-mix(in srgb, #38bdf8 14%, #1f2937), #1f2937 46%, #0b1d3a);
}

/* ---- 비교 답변: 애플 다지역형 미니 위젯 행 -------------------------------------------
 *
 * 큰 카드를 나란히 놓지 않는다. 채팅 열은 ~615px이고 모바일도 쓴다 — 반으로 쪼개면 시간별
 * 6칸과 5일 행이 무너진다. 애플·The Weather Channel도 다지역은 세로로 쌓은 납작한 행이다.
 *
 * ★새 색·새 치수를 만들지 않는다. 하늘(.weatherBg)·면·글자는 큰 카드의 선언을 **그대로
 *   공유하고**, 행 안의 글자는 큰 카드의 클래스(.loc/.cond/.bigTemp/.heroGlyph/.wNow)를
 *   그대로 쓴다. 처음엔 여기서 색과 크기를 손수 적었다가 anti-drift 래칫에 걸렸다 —
 *   래칫이 맞았다. 조건 10종 중 2종만 흉내 낸 색은 정의가 아니라 드리프트다.
 */
.weather-compare {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}
.weather-mini {
  /* 큰 카드의 히어로는 clamp(36px…4.5rem)라 130px 행에서는 넘친다. 치수는 선언이 아니라
     변수로 둬서 anti-drift 스캐너가 세는 raw font-size를 늘리지 않는다. */
  --wx-mini-temp-size: 42px;
  --wx-mini-name-size: 19px;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: start;
  row-gap: 14px;
  gap: 14px;
  margin-top: 0;
  padding: 16px 20px;
  overflow: hidden;
}
/* 행 안에서는 큰 카드보다 한 단계 작게. 치수는 선언이 아니라 변수로 둔다. */
.weather-mini .bigTemp {
  font-size: var(--wx-mini-temp-size);
}
.weather-mini .heroGlyph {
  margin-top: 0;
}
.weather-mini .heroGlyph svg,
.weather-mini .heroGlyph .weather-icon {
  width: 34px;
  height: 34px;
}
.weather-mini .loc {
  font-size: var(--wx-mini-name-size);
}
.wmHead {
  min-width: 0;
}
.wmHead .cond {
  display: block;
  margin-top: 3px;
}
.wmCond {
  align-self: end;
}
.wmFoot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 14px;
  align-self: end;
}
.wmRange {
  font-variant-numeric: tabular-nums;
}
/* 두 행 **모두** 칩을 갖는다. 따뜻한 쪽에만 달았더니 행 높이가 어긋나 한쪽이 더 중요해 보였다. */
.wmDelta {
  display: inline-flex;
  align-items: center;
  height: 21px;
  padding: 0 9px;
  border-radius: var(--ds-radius-pill);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.wmDelta[data-side="warm"] {
  background: var(--ds-badge-warn-bg);
  color: var(--ds-badge-warn-fg);
}
.wmDelta[data-side="cool"] {
  background: var(--ds-badge-info-bg);
  color: var(--ds-badge-info-fg);
}
/* 조회 실패한 행. 하늘을 낮추되 **이름은 남긴다** — 사라지면 물어본 지역이 어디 갔는지 모른다. */
.weather-mini[data-state="down"] .weatherBg {
  opacity: 0.3;
}
.wmDown {
  grid-column: 1 / -1;
  align-self: center;
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
}
.wmDown svg {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--ds-badge-warn-bg);
}
/* 결론은 굵게 먼저, 근거는 흐리게 뒤에. 한 덩어리로 쓰면 위계가 없다.
   하늘(.weatherBg)을 두지 않아 평면으로 남는다 — 「여기는 또 다른 장소가 아니라 결론」. */
.wcVerdict {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 0;
  padding: 15px 18px;
}
.wcHead {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.wcHead svg {
  flex: none;
  width: 15px;
  height: 15px;
}
/* 근거의 소수점은 일부러 남긴다 — 행은 29°로 반올림하는데 결론은 1.5도라고 말하므로,
   둘을 잇는 값이 없으면 「숫자가 안 맞는다」로 보인다. */
.wcEvidence {
  margin: 0;
  padding-left: 24px;
  font-variant-numeric: tabular-nums;
}

.wHead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
.wHeadText {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.loc {
  font-size: var(--ds-text-title-20);
  font-weight: 600;
  color: #ffffff;
}
.cond {
  font-size: var(--ds-text-caption-12);
  color: color-mix(in srgb, #ffffff 80%, transparent);
}
.wHeadTemp {
  flex: none;
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.bigTemp {
  font-size: clamp(36px, 6cqw + 1.5rem, 4.5rem);
  font-weight: 400;
  line-height: 0.92;
  letter-spacing: -0.04em;
  color: #ffffff;
}
.deg {
  font-size: 0.42em;
  vertical-align: super;
}
.heroGlyph {
  display: inline-flex;
  margin-top: 8px;
  color: #ffffff;
  filter: drop-shadow(0 2px 8px color-mix(in srgb, #0b1d3a 45%, transparent));
}
.heroGlyph svg,
.heroGlyph .weather-icon {
  width: 56px;
  height: 56px;
}
.weather-widget[data-cond="sun"] .heroGlyph,
.weather-widget[data-cond="cloudsun"] .heroGlyph {
  color: #fbbf24;
}
.wNow {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  font-size: var(--ds-text-caption-12);
  color: color-mix(in srgb, #ffffff 80%, transparent);
}

.hourlyWrap {
  position: relative;
}
.slideBtn {
  position: absolute;
  top: 50%;
  z-index: 2;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  padding: 4px;
  border: none;
  background: transparent;
  color: #ffffff;
  cursor: pointer;
  opacity: 0;
  transition: opacity 150ms ease;
  filter: drop-shadow(0 1px 3px color-mix(in srgb, #0b1d3a 60%, transparent));
}
.slideBtn svg {
  width: 20px;
  height: 20px;
}
.hourlyWrap:hover .slideBtn {
  opacity: 1;
}
.slideLeft {
  left: -20px;
}
.slideRight {
  right: -20px;
}
.hourly {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding: 12px 16px;
  border-radius: var(--ds-radius-md, 14px);
  box-shadow:
    0 4px 12px rgba(0, 0, 0, 0.08),
    inset 0 1px 0 color-mix(in srgb, #ffffff 26%, transparent),
    inset 0 0 0 1px color-mix(in srgb, #ffffff 9%, transparent);
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 16px;
  scrollbar-width: none;
  -ms-overflow-style: none;
  cursor: grab;
  user-select: none;
  touch-action: pan-x;
}
.hourly::-webkit-scrollbar {
  display: none;
}
.hourly:active {
  cursor: grabbing;
}
.chip {
  flex: 0 0 calc((100cqw - 176px) / 10);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding-block: 8px;
  scroll-snap-align: start;
}
@container (max-width: 520px) {
  .chip {
    flex-basis: calc((100cqw - 96px) / 5);
  }
}
.chipTime {
  font-size: var(--ds-text-caption-12);
  white-space: nowrap;
  color: color-mix(in srgb, #ffffff 76%, transparent);
}
.chipGlyph {
  display: inline-flex;
  color: #ffffff;
  filter: drop-shadow(0 1px 2px color-mix(in srgb, #0b1d3a 40%, transparent));
}
.chipGlyph svg,
.dayGlyph svg,
.chipGlyph .weather-icon,
.dayGlyph .weather-icon {
  width: 34px;
  height: 34px;
}
.weather-icon {
  display: block;
  -webkit-user-select: none;
  user-select: none;
}
.chipGlyph[data-cond="sun"] {
  color: #fbbf24;
}
.chipPop {
  min-height: 1lh;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  color: #38bdf8;
}
.chipTemp {
  font-size: var(--ds-text-body-16);
  font-weight: 600;
  color: #ffffff;
}

.daily {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 16px;
  border-radius: var(--ds-radius-md, 14px);
  box-shadow:
    0 4px 12px rgba(0, 0, 0, 0.08),
    inset 0 1px 0 color-mix(in srgb, #ffffff 26%, transparent),
    inset 0 0 0 1px color-mix(in srgb, #ffffff 9%, transparent);
}
.dailyHead {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 8px;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  color: color-mix(in srgb, #ffffff 72%, transparent);
}
.dayRow {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-block: 4px;
}
.dayLabel {
  flex: none;
  width: 32px;
  font-size: var(--ds-text-body-14);
  font-weight: 500;
  color: #ffffff;
}
.dayCond {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 40px;
}
.dayGlyph {
  display: inline-flex;
  color: #ffffff;
}
.dayGlyph[data-cond="sun"] {
  color: #fbbf24;
}
.dayPop {
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  color: #38bdf8;
}
.dayMin {
  flex: none;
  width: 30px;
  text-align: right;
  font-size: var(--ds-text-body-14);
  color: color-mix(in srgb, #ffffff 66%, transparent);
}
.dayMax {
  flex: none;
  width: 30px;
  font-size: var(--ds-text-body-14);
  color: #ffffff;
}
/* 중기예보 오차 표시. 값보다 약하게 — 보조 정보이지 주인공이 아니다. */
.dayMargin {
  flex: none;
  margin-inline-start: 4px;
  font-size: var(--ds-text-caption-12);
  color: rgba(255, 255, 255, 0.55);
  font-variant-numeric: tabular-nums;
}
.dailyNote {
  margin-block-start: 8px;
  padding-block-start: 8px;
  border-block-start: 1px solid rgba(255, 255, 255, 0.12);
  font-size: var(--ds-text-caption-12);
  color: rgba(255, 255, 255, 0.6);
}
.track {
  position: relative;
  flex: 1;
  height: 6px;
  border-radius: 9999px;
  background: color-mix(in srgb, #ffffff 16%, transparent);
  box-shadow: inset 0 1px 2px color-mix(in srgb, #0b1d3a 35%, transparent);
}
.range {
  position: absolute;
  top: 0;
  height: 6px;
  border-radius: 9999px;
  background: linear-gradient(90deg, #66aaff, #fbbf24);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #ffffff 45%, transparent);
}
.nowDot {
  position: absolute;
  top: 50%;
  width: 9px;
  height: 9px;
  transform: translate(-50%, -50%);
  border-radius: 9999px;
  background: #ffffff;
  box-shadow:
    0 0 0 2px color-mix(in srgb, #1252b2 60%, transparent),
    0 1px 3px color-mix(in srgb, #0b1d3a 50%, transparent);
}
.weather-widget[data-cond="night"] .hourly,
.weather-widget[data-cond="night"] .daily {
  box-shadow:
    0 4px 12px rgba(0, 0, 0, 0.08),
    inset 0 1px 0 color-mix(in srgb, #ffffff 34%, transparent),
    inset 0 0 0 1px color-mix(in srgb, #ffffff 14%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .slideBtn {
    transition: none;
  }
}

/* ===== Finance (stock) family — theme-aware legacy slab material =========================
   The accepted single-card slab is the shared material source for single and comparison
   widgets. Theme tokens replace its old dark-only literals; JS still changes only the
   single card's direction halo. Quote regions reuse the same type roles at their own width.
   v-E 결정 1(오너 09-22, halo 옵션화 — 중앙 권고 ⓑ off 기본): halo 강도는 이제 «방향»에만
   의존하는 고정값이고 등락 크기·기간 전환에 반응하지 않는다(재도색 0). 기본은 0%(끔) —
   `[data-wash="on"]` 을 준 인스턴스만 7% 고정으로 켜진다. 같은 CSS 규칙이 강도만 바뀌므로
   코드 분기가 없다(오너가 ⓐ를 원하면 속성 하나만 바꾸면 된다). */
.finance-surface {
  --fin-on-surface: var(--cg-text);
  --fin-muted: color-mix(in srgb, var(--fin-on-surface) 62%, transparent);
  --fin-halo-color: var(--wds-color-wizon-accent);
  --fin-halo-strength: 0%;
  --fin-slab-top: var(--wizon-ai-surface-sheet);
  --fin-slab-bottom: var(--wizon-ai-surface);
  --fin-slab-surface: linear-gradient(180deg, var(--fin-slab-top) 0%, var(--fin-slab-bottom) 100%);
  --fin-panel-surface: color-mix(in srgb, var(--fin-on-surface) 4.5%, transparent);
  --fin-panel-border: color-mix(in srgb, var(--fin-on-surface) 5%, transparent);
  color: var(--fin-on-surface);
  background:
    radial-gradient(120% 80% at 50% -12%, color-mix(in srgb, var(--fin-halo-color) var(--fin-halo-strength), transparent), transparent 62%),
    var(--fin-slab-surface);
  box-shadow: var(--wizon-ai-lift-3);
}
.finance-surface[data-wash="on"] {
  /* 고정 강도 7% — 등락 방향에만 반응(색은 --fin-halo-color, JS 는 방향만 고른다). */
  --fin-halo-strength: 7%;
}
html[data-theme="dark"] .finance-surface {
  /* R-3 v-E′ S1 결정 7(오너 10-01 «3ⓐ»): 다크 면은 R=G=B 중립 — group(#1c1c1c) → sheet(#141414).
     옛 쿨톤 슬랩(#14161a → #0e1013, --wizon-ai-tone-surface)은 HSV 채도 23~26% 라 «유채 면»이었다.
     라이트는 위 sheet → surface 그대로. 스켈레톤(.stream-card-skeleton[data-card-skeleton="finance"])도
     같은 두 토큰을 읽는다(ΔE 0). */
  --fin-slab-surface: linear-gradient(180deg, var(--wizon-ai-surface-group) 0%, var(--wizon-ai-surface-sheet) 100%);
}
/* R-3 v-E′ S1 — 역할 토큰 3종(text·line·fill) × 방향(up·down). 색의 단일 출처: JS 는 .is-krx-market /
   .is-western-market 와 data-sign="up|down|flat" 만 붙이고 값은 여기서만 정한다(원시 hex 0, 새 리터럴 0 —
   기존 의미색 토큰 재사용). text = 글자(≥4.5:1) · line = 선·점·halo(≥3:1) · fill = 필 면(글자는 흰/검). */
.finance-surface.is-krx-market,
.finance-surface .is-krx-market {
  --fin-up-text: var(--wds-color-error-text);
  --fin-up-line: var(--wds-color-error-text);
  --fin-up-fill: var(--wds-color-error-text);
  --fin-down-text: var(--wizon-ai-stock-down);
  --fin-down-line: var(--wizon-ai-stock-down);
  --fin-down-fill: var(--wizon-ai-stock-down);
}
.finance-surface.is-western-market,
.finance-surface .is-western-market {
  --fin-up-text: var(--wds-color-success-text);
  --fin-up-line: var(--wds-color-success-text);
  --fin-up-fill: var(--wds-color-success-text);
  --fin-down-text: var(--wds-color-error-text);
  --fin-down-line: var(--wds-color-error-text);
  --fin-down-fill: var(--wds-color-error-text);
}
html[data-theme="dark"] .finance-surface.is-krx-market,
html[data-theme="dark"] .finance-surface .is-krx-market {
  /* R-3 v-E′ S3: 비교 카드는 카드 외곽이 아니라 머리·행·차트 섹션이 시장 클래스를 들고 있다(혼합 시장 = 행마다 다른 관행) —
     같은 역할 토큰을 «안쪽 시장 클래스» 에도 푼다. 다크: 하락 «글자»만 blue-400(#66aaff — 웰 위 5.99:1), 선은 stock-down(#0a84ff). 필 면은 danger-bg/stock-down + 검은 글자. */
  --fin-down-text: var(--wds-blue-400);
  --fin-up-fill: var(--wizon-ai-danger-bg);
  --fin-down-fill: var(--wizon-ai-stock-down);
}
/* 값 사다리 토큰(S2~S4 가 소비) — 타이포 4단 · 컨트롤 높이(웹 36 / 터치 44). */
.finance-surface {
  --fin-type-hero: 40px;
  --fin-type-body: 15px;
  --fin-type-meta: 13px;
  --fin-type-eyebrow: 12px;
  --fin-ctl-h: 36px;
}
@media (pointer: coarse) {
  .finance-surface {
    --fin-ctl-h: 44px;
  }
}
/* R-3 v-E′ S2 — 시각화 웰·지표 타일·기간 트랙의 L2 면(기존 토큰만). 라이트: 웰 = hover-soft(#f4f4f4) + 1px
   sidebar-border · 썸 = 면(흰). 다크: 웰 = surface-raised(#2a2a2a) · 썸 = 웰 위 잉크 14% 얹음(한 단계 밝게). */
.finance-surface {
  --fin-well: var(--wizon-ai-hover-soft-bg);
  --fin-well-line: var(--wizon-ai-sidebar-border);
  --fin-thumb: var(--wizon-ai-surface);
}
html[data-theme="dark"] .finance-surface {
  --fin-well: var(--wizon-ai-surface-raised);
  --fin-thumb: color-mix(in srgb, var(--fin-on-surface) 14%, var(--fin-well));
}
/* 방향 이름(data-sign) → 색. 위젯 halo · 차트 선/페이드 · 점 · 기간 배지 · 스크럽 등락 텍스트. */
.finance-surface[data-sign="up"] { --fin-halo-color: var(--fin-up-line); }
.finance-surface[data-sign="down"] { --fin-halo-color: var(--fin-down-line); }
.finance-surface[data-sign="flat"] { --fin-halo-color: var(--fin-on-surface); }
.fin-plot svg { --fin-mark: var(--fin-muted); }
.fin-plot svg[data-sign="up"] { --fin-mark: var(--fin-up-line); }
.fin-plot svg[data-sign="down"] { --fin-mark: var(--fin-down-line); }
.fin-plot svg .fin-line { stroke: var(--fin-mark); }
.fin-plot svg stop { stop-color: var(--fin-mark); }
.fin-badge[data-sign="up"] {
  color: var(--fin-up-text);
  background: color-mix(in srgb, var(--fin-up-line) 18%, transparent);
}
.fin-badge[data-sign="down"] {
  color: var(--fin-down-text);
  background: color-mix(in srgb, var(--fin-down-line) 18%, transparent);
}
.fin-badge[data-sign="flat"] {
  color: var(--fin-muted);
  background: transparent;
}
.fin-dot[data-sign="up"] { color: var(--fin-up-line); }
.fin-dot[data-sign="down"] { color: var(--fin-down-line); }
.fin-dot[data-sign="flat"] { color: var(--fin-on-surface); }
.finance-widget,
.fin-quote-region {
  /* The hierarchy is explicit: identity 4px, identity-to-price 16px,
     price-to-official-daily 8px, daily-to-provenance 8px. */
  /* Four text roles; rem sizes preserve text enlargement. Container breakpoints
     change the hierarchy together instead of scaling only the price. */
  --fin-price-size: 3rem;
  --fin-identity-size: 1.375rem;
  --fin-daily-size: 1rem;
  --fin-support-size: 0.875rem;
  container-type: inline-size;
  container-name: finance;
}
.finance-widget {
  /* R-3 v-E′ S1: 방향 텍스트색은 역할 토큰(--fin-*-text, .finance-surface.is-*-market 정의)을 그대로
     읽는다 — 옛 «시장 원색 65% + 잉크» 혼합(JS 원시 hex 를 인라인 --fin-*-source 로 주입)은 폐기. */
  --fin-up: var(--fin-up-text);
  --fin-down: var(--fin-down-text);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0;
  margin-top: 4px;
  padding: 20px;
  border-radius: 20px;
}
@media (max-width: 520px) {
  .finance-widget {
    padding: 16px;
  }
}
.fin-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  row-gap: 4px;
}
.fin-ident {
  display: contents;
  min-width: 0;
}
.fin-name {
  grid-column: 1;
  grid-row: 1;
  font-size: var(--fin-identity-size);
  font-weight: 600;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
.fin-ticker {
  grid-column: 1 / -1;
  grid-row: 2;
  font-size: var(--fin-support-size);
  color: color-mix(in srgb, var(--fin-on-surface) 62%, transparent);
  font-variant-numeric: tabular-nums;
}
.fin-badge {
  align-self: flex-start;
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  align-items: baseline;
  gap: 5px;
  width: fit-content;
  max-width: 100%;
  margin-top: 8px;
  padding: 5px 11px;
  border-radius: var(--ds-radius-pill, 999px);
  font-size: var(--fin-support-size);
  font-weight: 600;
  line-height: 1.25;
  font-variant-numeric: tabular-nums;
  white-space: normal;
  text-align: left;
}
.fin-badge b {
  font-weight: 600;
}
.fin-badge-change,
.fin-badge b,
.fin-badge-range {
  white-space: nowrap;
}
.fin-badge-range {
  font-weight: 500;
  font-size: inherit;
  opacity: 0.72;
}
.fin-period-row { min-width: 0; }

.fin-price-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 16px;
}
.fin-price {
  font-size: var(--fin-price-size);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.fin-currency {
  font-size: var(--fin-identity-size);
  font-weight: 500;
  color: var(--fin-muted);
  margin-left: 3px;
}
.fin-asof {
  font-size: var(--fin-support-size);
  color: var(--fin-muted);
}
.fin-asof-part {
  display: inline-block;
}
.fin-daily-change {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: var(--fin-daily-size);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.fin-daily-label {
  color: inherit;
  font-size: inherit;
  white-space: nowrap;
}
.fin-daily-value {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: inherit;
  font-weight: 600;
}
.fin-daily-value b {
  font-weight: 600;
}
.fin-daily-triangle,
.fin-daily-neutral {
  flex: none;
  width: 20px;
  height: 20px;
}
.fin-daily-triangle.is-down {
  transform: rotate(180deg);
}
.fin-daily-neutral {
  display: inline-grid;
  place-items: center;
  color: color-mix(in srgb, var(--fin-on-surface) 60%, transparent);
}
.fin-daily-change.is-up { color: var(--fin-up); }
.fin-daily-change.is-down { color: var(--fin-down); }
.fin-daily-change.is-neutral { color: color-mix(in srgb, var(--fin-on-surface) 72%, transparent); }
.fin-daily-change.is-unknown { color: color-mix(in srgb, var(--fin-on-surface) 62%, transparent); }
.fin-daily-word,
.fin-daily-unknown {
  font-size: inherit;
  font-weight: 500;
}
.fin-provenance {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}
/* R-3 v-E′ S2 — 시각화 웰(L2 면, 반경 14, 패딩 12): 캡션(기간 등락)·날짜 행 + 플롯. 판독 띠(.fin-readout)는 삭제 —
   스크럽 값은 히어로 숫자·필이 대신 말한다. */
.fin-chart {
  display: grid;
  gap: 8px;
  min-width: 0;
  margin-top: var(--wds-space-3);
  padding: var(--wds-space-3);
  border-radius: var(--wds-radius-lg);
  background: var(--fin-well);
  box-shadow: inset 0 0 0 1px var(--fin-well-line);
  font-size: var(--fin-type-meta);
}
.fin-chart--empty {
  min-height: 96px;
  place-items: center;
}
.fin-chart-empty {
  margin: 0;
  color: var(--fin-muted);
  font-size: var(--fin-type-meta);
}
.fin-plot {
  position: relative;
  border-radius: var(--wizon-ai-radius-control);
  /* Keep native vertical page scrolling available, but reserve horizontal movement for
     the chart's Pointer Events scrub. With `auto`, Android standalone may cancel the
     sequence for browser gesture handling before the chart can update its readout. */
  touch-action: pan-y;
}
.fin-chart svg {
  display: block;
  width: 100%;
  height: 128px;
}
@container finance (min-width: 480px) {
  .fin-chart svg {
    height: 168px;
  }
}
/* S2 QA fail 2cbc1f2fa 수리 — 옛 `.fin-plot:focus-visible`(0,2,0)은 전역 기본 링 `.chatgpt-reference-v1 :focus-visible:not(…)×3`(0,3,3)에 져서
   1440 = rgba(13,13,13,.16)(≈1.4:1), 390 pointer:coarse = outline none(전역 «coarse 링 끔» 후순위)였다. 기간 탭(.fin-segment)과 같은
   문법(body.chatgpt-reference-v1 .finance-surface …, 0,4,1)·같은 링 토큰(--fin-on-surface)으로 올려 두 포인터 모두 2px 링이 보인다. */
body.chatgpt-reference-v1 .finance-surface .fin-plot:focus-visible {
  outline: 2px solid var(--fin-on-surface);
  outline-offset: 2px;
}
/* Draw-in reveal via clip-path on the line's wrapper group. Stroke-dash draw was
   dropped: with vector-effect="non-scaling-stroke" the dash is re-measured in device
   pixels, breaking pathLength normalization and leaving permanent gaps in the line. */
.fin-line-draw {
  animation: fin-line-draw 640ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes fin-line-draw {
  from {
    clip-path: inset(0 100% 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}
.fin-crosshair {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  transform: translateX(-50%);
  background: color-mix(in srgb, var(--fin-on-surface) 28%, transparent);
  pointer-events: none;
}
.fin-crosshair[hidden] {
  display: none;
}
.fin-dot {
  position: absolute;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: currentColor;
  box-shadow: 0 0 0 2px var(--fin-well);
  pointer-events: none;
}
.fin-dot[hidden] {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .fin-line-draw {
    animation: none;
    clip-path: none;
  }
  /* R-3 v-E′ S4 — 기간 탭 전환도 즉시(색 전환 0). */
  .fin-segment {
    transition: none;
  }
}
.fin-chart-range {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px 8px;
  font-size: var(--fin-type-eyebrow);
  color: var(--fin-muted);
  font-variant-numeric: tabular-nums;
}
/* 기간 등락 = 웰 캡션 텍스트(13/600 등락 텍스트색). 옛 채운 알약 배지(.fin-badge)의 면·패딩·반경을 벗긴다. */
.finance-widget .fin-chart-range .fin-badge {
  margin: 0;
  padding: 0;
  border-radius: 0;
  background: none;
  font-size: var(--fin-type-meta);
}
.finance-widget .fin-chart-range .fin-badge-range {
  opacity: 1;
  font-size: inherit;
  font-weight: inherit;
}
.finance-widget .fin-chart-range .fin-badge[data-sign="up"],
.finance-widget .fin-chart-range .fin-badge[data-sign="down"],
.finance-widget .fin-chart-range .fin-badge[data-sign="flat"] {
  background: none;
}
/* S2b §3 — filled selected chip on a grouped track (mockup-source card.css .wx-seg):
   the track carries a faint tinted well so the active chip's own panel-surface fill
   reads as a raised object, matching the rank/pill "물체" language used elsewhere in
   this card rather than a plain color swap on a flat row. */
.fin-segments {
  display: flex;
  gap: 2px;
  margin-top: 10px;
  padding: 3px;
  border-radius: var(--ds-radius-sm);
  background: color-mix(in srgb, var(--fin-on-surface) 5%, transparent);
  box-shadow: inset 0 0 0 1px var(--fin-panel-border, transparent);
}
.fin-segment {
  flex: 1 1 0;
  padding: 6px 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--fin-muted);
  font-size: var(--fin-support-size);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--wds-duration-fast) ease, color var(--wds-duration-fast) ease;
}
.fin-segment:hover:not(:disabled):not(.is-active) {
  background: color-mix(in srgb, var(--fin-on-surface) 8%, transparent);
  color: color-mix(in srgb, var(--fin-on-surface) 85%, transparent);
}
.fin-segment.is-active {
  background: var(--fin-panel-surface, color-mix(in srgb, var(--fin-on-surface) 14%, transparent));
  color: var(--fin-on-surface);
  box-shadow: inset 0 0 0 1px var(--fin-panel-border, transparent);
}
.fin-segment:disabled {
  opacity: 0.32;
  cursor: default;
}
body.chatgpt-reference-v1 .finance-surface .fin-segment:focus-visible {
  outline: 2px solid var(--fin-on-surface);
  outline-offset: 1px;
}
/* The five period controls have 32px desktop density. On touch hardware their native
   button rectangles—not a pseudo-element or delegated parent target—must be at least
   44 CSS px so every period remains independently reachable. */
@media (pointer: coarse) {
  .fin-segment {
    min-height: 44px;
  }
}
/* R-3 v-E′ S2 → S3 — 기간 탭(단일·비교 공통): 트랙 반경 10 · 높이 --fin-ctl-h(웹 36 / 터치 44) · 썸 = 2px 투명 테두리 안쪽
   (버튼 전체 높이가 히트 영역) · role=tablist/tab. 비교 카드의 옛 «min-height 44 고정» 은 폐기 — 터치에서만 44(--fin-ctl-h). */
.finance-widget .fin-segments,
.finance-group-card .fin-segments {
  gap: 0;
  margin-top: var(--wds-space-3);
  padding: 0;
  border-radius: var(--ds-radius-sm);
  background: var(--fin-well);
  box-shadow: inset 0 0 0 1px var(--fin-well-line);
}
.finance-widget .fin-segment,
.finance-group-card .fin-segment {
  min-height: var(--fin-ctl-h);
  padding: 0;
  border: 2px solid transparent;
  background-clip: padding-box;
  border-radius: var(--ds-radius-sm);
  font-size: var(--fin-type-meta);
}
.finance-widget .fin-segment.is-active,
.finance-group-card .fin-segment.is-active {
  background-color: var(--fin-thumb);
  background-clip: padding-box;
  color: var(--fin-on-surface);
  box-shadow: inset 0 0 0 1px var(--fin-well-line);
}
.finance-widget .fin-segment:focus-visible,
.finance-group-card .fin-segment:focus-visible {
  outline: 2px solid var(--fin-on-surface);
  outline-offset: 1px;
}
.fin-data-panel {
  /* v-E 반경 규칙(오너 09-22 결정 반영, 중앙 09-22 반경 통일): 패널 12px->14px.
     var()로 토큰화해 raw-border-radius 래칫에서 빠진다(음성 lookahead 스캐너 규칙). */
  border-radius: var(--fin-panel-radius, 14px);
  background: var(--fin-panel-surface);
  box-shadow: inset 0 0 0 1px var(--fin-panel-border);
}
/* R-3 v-E′ S2 — 지표 타일: 3열 격자(gap 8), 타일 = 반경 10 · 패딩 12 · 라벨 12/600 muted · 값 15/600.
   일중 범위 타일은 2칸 폭(저가–고가 막대 + 현재가 손잡이). 타일 컨테이너 <300 의 2열은 S4 몫. */
.fin-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--wds-space-2);
  margin-top: var(--wds-space-3);
}
.fin-stat {
  display: flex;
  flex-direction: column;
  align-content: start;
  gap: 4px;
  min-width: 0;
  padding: var(--wds-space-3);
  border-radius: var(--ds-radius-sm);
  background: var(--fin-well);
  box-shadow: inset 0 0 0 1px var(--fin-well-line);
}
.fin-stat-label {
  font-size: var(--fin-type-eyebrow);
  font-weight: 600;
  color: var(--fin-muted);
}
.fin-stat-value {
  font-size: var(--fin-type-body);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fin-stat--range {
  grid-column: span 2;
}
.fin-range-bar {
  display: block;
  width: calc(100% - 12px);
  height: 12px;
  margin: 8px 6px 0;
  overflow: visible;
}
.fin-range-track {
  stroke: color-mix(in srgb, var(--fin-on-surface) 50%, transparent);
}
.fin-range-ring {
  fill: var(--fin-well);
}
.fin-range-knob {
  fill: var(--fin-on-surface);
}
.fin-range-ends {
  display: flex;
  justify-content: space-between;
  font-size: var(--fin-type-eyebrow);
  color: var(--fin-muted);
  font-variant-numeric: tabular-nums;
}
.fin-source {
  font-size: var(--fin-support-size);
  color: var(--fin-muted);
}
@container finance (min-width: 280px) {
  .finance-widget > *,
  .fin-quote-region > * {
    --fin-price-size: 4rem;
    --fin-identity-size: 1.5rem;
    --fin-daily-size: 1.125rem;
  }
}
@container finance (min-width: 480px) {
  .finance-widget > *,
  .fin-quote-region > * {
    --fin-price-size: 5rem;
    --fin-identity-size: 1.75rem;
    --fin-daily-size: 1.25rem;
    --fin-support-size: 1rem;
  }
}
@container finance (max-width: 280px) {
  .fin-segments {
    gap: 2px;
  }
}
/* R-3 v-E′ S4 — 200% 확대·320 폭: 타일 컨테이너(.finance-widget 의 content-box)가 280 미만이면 지표 타일 3열 → 2열(320 폭 224 = 2열 · 390 폭 294 = 3열 유지 · 1440@200% 616 = 3열).
   일중 범위 타일(span 2)은 2열에서 한 줄 전폭이 된다 — 새 클래스·새 색 0. */
@container finance (max-width: 279.98px) {
  .fin-stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* R-3 v-E′ S2 — 단일 카드 ① 히어로 존: 이름 15/600 + 티커 13 한 줄 · 가격 = 히어로 40 고정(컨테이너 48/64/80 규칙은
   비교 카드·스켈레톤만 쓴다) · 필 = .fin-vc-pill 재사용(전일 대비) + «전일 대비» 라벨 13 muted. 비교 카드(.fin-group-item)는
   .finance-widget 밖이라 그대로다. */
.finance-widget .fin-head {
  display: block;
}
.finance-widget .fin-ident {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 8px;
  min-width: 0;
}
.finance-widget .fin-name {
  font-size: var(--fin-type-body);
}
.finance-widget .fin-ticker {
  font-size: var(--fin-type-meta);
}
.finance-widget .fin-price-row {
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 12px;
  min-height: 44px;
  margin-top: 4px;
}
.finance-widget .fin-price {
  font-size: var(--fin-type-hero);
  line-height: 44px;
}
.finance-widget .fin-currency {
  margin-left: 4px;
  font-size: var(--fin-type-body);
  font-weight: 600;
}
.finance-widget .fin-daily-change {
  flex-wrap: nowrap;
  gap: 8px;
  margin-top: 0;
  font-size: var(--fin-type-meta);
  font-weight: 400;
}
.finance-widget .fin-daily-change.is-up,
.finance-widget .fin-daily-change.is-down,
.finance-widget .fin-daily-change.is-neutral,
.finance-widget .fin-daily-change.is-unknown,
.finance-widget .fin-daily-change.is-scrub {
  color: var(--fin-muted);
}
.finance-widget .fin-daily-unknown {
  font-weight: 400;
}
.finance-widget .fin-daily-change .fin-vc-pill {
  min-width: 0;
  font-size: var(--fin-type-meta);
  line-height: 20px;
  color: var(--wds-white);
}
html[data-theme="dark"] .finance-widget .fin-daily-change .fin-vc-pill {
  color: var(--wds-black);
}
.finance-widget .fin-daily-change .fin-vc-pill[data-sign="up"] {
  background: var(--fin-up-fill);
}
.finance-widget .fin-daily-change .fin-vc-pill[data-sign="down"] {
  background: var(--fin-down-fill);
}
.finance-widget .fin-daily-change .fin-vc-sign {
  font-size: inherit;
}
.finance-widget .fin-provenance {
  justify-content: space-between;
  gap: 4px 8px;
  margin-top: var(--wds-space-3);
}
.finance-widget .fin-asof,
.finance-widget .fin-source {
  font-size: var(--fin-type-eyebrow);
}

/* ---- News answer feed — magazine front page (2026-07-30) ------------------------------
   Figma "Free News & Magazine" (Design/desktop, frame 1920×5698) 어휘를 WIZON 토큰으로 옮긴 것.
   구조·비례만 가져오고 색은 하나도 가져오지 않는다:
     · top-tags      → 사진 깔린 주제 태그 (템플릿 필 ~118×40 → 레일 폭에 맞춰 106×40)
     · Slider        → 744×452 (1.65) + **밝은 프로스티드 인셋 밴드** + 화살표 + 알약 도트
     · popular-posts → 1512×459 gap 30 4열 → 컨테이너 폭에 따라 3/2/1열, 테두리·채움 없는 카드
     · 섹션 헤더      → 주제색 세로 바 + 제목 + 원형 이전/다음
   위젯감은 «면»이 아니라 사진 + 출처 행 + 넉넉한 간격에서 나온다. 그래서 카드에는 채움도
   테두리도 없다.

   ★원시 리터럴을 하나도 추가하지 않는다. 정적 UI 안티드리프트 래칫이 여섯 범주 전부
   여유 0이라(2026-07-30 실측: raw-color 1060/1060, raw-rgb 467/467, raw-border-radius
   206/206, raw-box-shadow 78/78, raw-font-size 72/72, inline-style 29/29) 색은 전부
   var(...)·color-mix(...)이고, 반경은 --ds-radius-* 또는 형태 백분율(50%)이며, 글자 크기는
   --ds-text-*, 그림자는 var()를 포함한 값만 쓴다. 주제 강조색도 새 팔레트를 만들지 않고
   --wds-chart-1..8(테마별로 따로 정의되어 있고 CVD 분리를 보존한다)을 슬롯으로 배정한다. */
.news-feed {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  /* ★리듬은 균일하지 않다. 목업은 18/20/26/26/24로 «헤더 근처는 조이고 큰 단락 사이는
     넓힌다». 균일 gap:18px은 그 리듬을 지웠으므로 gap을 0으로 두고 형제 규칙이 간격을
     만든다(아래 "수직 리듬" 블록). 실측(721px 컨텐츠 박스): 균일 18 → 18/20/26/26/24. */
  gap: 0;
  margin-top: 14px;
  /* 목업 24 / radius 22. 18/18은 위젯을 문서 블록처럼 납작하게 만들었다. */
  padding: 24px;
  border-radius: var(--ds-radius-xl);
  background: var(--cg-surface);
  /* 목업의 그림자는 셋이다: 인셋 헤어라인 + 근접 접지(2px) + 넓은 부양(20px). 앞의 하나만
     남기면 표면이 «떠 있는 위젯»이 아니라 «선으로 그린 상자»가 된다. 원시 리터럴은 쓰지
     않는다 — 두 그림자 모두 color-mix(var(--ds-color-shadow-ink) …)라 raw-box-shadow/raw-rgb가 늘지 않는다. */
  box-shadow: inset 0 0 0 1px var(--cg-menu-border),
    0 2px 4px color-mix(in srgb, var(--ds-color-shadow-ink) 3%, transparent),
    0 20px 40px -20px color-mix(in srgb, var(--ds-color-shadow-ink) 22%, transparent);
  color: var(--cg-text);
}
/* ── 수직 리듬 ────────────────────────────────────────────────────────────────────────
   목업 실측: head→tags 18 · tags→stage 20 · stage→sec 26 · sec→sec 26 · foot 24(+구분선).
   형제 선택자라 어떤 블록이 없어도(주제 레일 없음, 히어로 없음) 남은 이웃이 올바른 간격을
   물려받는다 — 고정 margin을 각 블록에 박으면 «없을 때»가 어긋난다. */
.news-feed > * + * {
  margin-top: 18px;
}
.news-topics + .news-hero {
  margin-top: 20px;
}
.news-hero + .news-section,
.news-section + .news-section,
.news-topics + .news-section {
  margin-top: 26px;
}
.news-feed > .news-feed-foot {
  margin-top: 24px;
  padding-top: 14px;
  border-top: 1px solid var(--cg-menu-border);
}
/* 주제 강조색 슬롯. app.js의 newsTopicAccentSlot()이 주제명 해시로 1..8을 고르고 값은 여기서만
   산다 — 그래서 JS에도 CSS에도 새 색 리터럴이 없다. 0 = 주제 없음.
   ★0을 --cg-muted로 두면 «아무도 주제를 고르지 않은» 기본 상태 — 즉 모든 사용자가 처음 보는
   화면 — 에서 섹션 세로 바가 rgb(111,111,111) 회색으로 렌더된다(실측). 목업의 기본값은 회색이
   아니라 잉크다(`.fp-secname { --a: var(--cg-text) }`): 주제가 없을 때도 바는 «칠해진» 상태이고,
   주제를 고르면 그 주제색으로 «갈아탄다». 여기서도 같게 맞춘다 — 회색은 «비활성»으로 읽힌다. */
.news-feed [data-news-accent="0"] {
  --news-accent: var(--cg-text);
}
.news-feed [data-news-accent="1"] {
  --news-accent: var(--wds-chart-1);
}
.news-feed [data-news-accent="2"] {
  --news-accent: var(--wds-chart-2);
}
.news-feed [data-news-accent="3"] {
  --news-accent: var(--wds-chart-3);
}
.news-feed [data-news-accent="4"] {
  --news-accent: var(--wds-chart-4);
}
.news-feed [data-news-accent="5"] {
  --news-accent: var(--wds-chart-5);
}
.news-feed [data-news-accent="6"] {
  --news-accent: var(--wds-chart-6);
}
.news-feed [data-news-accent="7"] {
  --news-accent: var(--wds-chart-7);
}
.news-feed [data-news-accent="8"] {
  --news-accent: var(--wds-chart-8);
}
.news-feed-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 8px 14px;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--cg-muted, #8f8f8f);
}
/* ★스케일의 «꼭대기». 목업의 가장 큰 활자(23px "오늘의 주요 뉴스")가 통째로 빠져 있어서,
   이 위젯에서 가장 큰 글자가 히어로 제목이었고 그 히어로 제목마저 18.7px에 머물렀다 —
   즉 주변 채팅 본문(16px)과 사실상 같은 층이었다. 제목이 돌아오면서 22/18/15/12/11의
   단조 스케일이 성립한다(각 단계 ≥1.2배, 실측표는 test_news_magazine_front 참조).
   ★목업의 3분할 기간 컨트롤(24시간/48시간/7일)은 옮기지 않는다 — 포털에는 그 질의를 다시
   돌릴 경로가 없고, 아무것도 못 바꾸는 컨트롤은 없는 편이 낫다(이 파일의 기존 원칙).
   실제로 검색이 쓴 창은 이미 .news-facet-chip-window가 글자로 말한다. */
.news-feed-headline {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
/* ★★캐스케이드 접두사 `.chatgpt-reference-v1 .news-feed`가 «장식»이 아니다 — 없으면 이 규칙은
   통째로 죽는다. 이 위젯은 .message-body 안에 살고, 그 안의 h3/h4/p에는 전역 산문 규칙이
   이미 걸려 있다:
     · `.chatgpt-reference-v1 .message-body p` (0,2,0, 이 파일 뒤쪽) → font-size 16 / lh 1.7
     · `.message-body h3, h4, h5`             (0,1,1)               → margin 16px 0 9px / lh 1.25
     · `.message-body h3`                     (0,1,1)               → font-size title-20
   컴포넌트 클래스 하나(0,1,0)로는 셋 다 진다. 실측으로 두 번 물렸다:
     · 카드 요약이 16px/27.2px로 렌더돼 **자기 제목(14px)보다 컸다** — 위계가 뒤집혔다.
     · 섹션 헤더 블록이 20px 줄에 h4 마진 16+9가 붙어 45px가 됐다(목업 30).
   !important로 덮지 않는다. 접두사 두 개면 (0,3,0)이라 전역 산문 규칙보다 순서와 무관하게
   이긴다 — 다른 표면의 전역 규칙은 한 글자도 건드리지 않는다.
   ★이 접두사를 «간결하게» 벗기지 말 것. 벗기는 순간 위 세 규칙이 되살아난다. */
.chatgpt-reference-v1 .news-feed .news-feed-title {
  margin: 0;
  /* ★스케일의 «꼭대기». 상한이 --ds-text-heading-28인데 실제 채팅 폭에서는 그 아래에 선다:
     컨테이너 709px에서 3.7cqw = 26.23px(실측). 히어로 22px과 1.19배 벌어져 «위젯 이름»과
     «리드 기사»가 서로 다른 층으로 읽힌다.
     ★왜 목업의 23px이 아니라 26px인가 — 목업은 피드 제목 750 / 히어로 780으로 **웨이트**가
     둘을 갈랐다. 포털 타이포 계약(doc 160 T4)은 400/500/600만 허용하고 700은 모바일 워드마크
     1곳으로 래칫이 걸려 있어(test_static_contract.test_typography_contract) 그 채널이 닫혀
     있다. 닫힌 웨이트 채널의 몫을 크기 채널이 흡수한 값이 26px이다. */
  font-size: clamp(var(--ds-text-title-20), 3.7cqw, var(--ds-text-heading-28));
  font-weight: 600;
  line-height: var(--ds-leading-title, 1.32);
  letter-spacing: -0.024em;
  color: var(--cg-text);
}
/* <p>다 — 위 .news-feed-title 주석의 전역 산문 규칙에 같은 이유로 접두사가 필요하다. */
.chatgpt-reference-v1 .news-feed .news-feed-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  margin: 0;
  font-size: var(--ds-text-micro-11);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: normal;
}
/* ★«라이브» 점은 삭제했다(규칙도 @keyframes도). 두 가지 이유가 겹친다:
     ① 정직성 — 이 피드는 스트리밍이 아니라 «질의 시점에 고정된 스냅샷»이다. 맥동하는 라이브
        점은 갱신되지 않는 목록에 갱신을 약속한다. 신선도는 아래 as-of가 «글자로» 말한다.
     ② 실측 — 그 @keyframes의 70%/100% 프레임(`box-shadow: 0 0 0 7px transparent`,
        `0 0 0 0 transparent`)은 var()를 하나도 안 들고 있어서 정적 UI 안티드리프트의
        raw-box-shadow로 잡혔다. 이 규칙들이 들어오면서 그 범주가 천장을 넘겼다
        (2026-07-31 실측: ceiling 78 / actual 79 = RED). 삭제하면 79 → 77이 된다.
   ★두 규칙 다 마크업이 한 번도 없었다 — renderNewsFeed는 .news-live를 만든 적이 없고,
   `grep -rn "news-live\|newsLivePulse" apps/ai-portal`은 이 파일 안에서만 잡혔다. 즉 죽은
   규칙이라 지우는 데 표면 손실이 0이다. */
/* as-of는 «지금»이 아니라 «이 답변이 들고 있는 가장 신선한 기사의 시각»이다. 시각이 하나도
   없으면 아예 렌더되지 않는다 — 지어낸 기준 시각은 신선도에 대한 거짓말이다. */
.news-asof {
  color: var(--cg-muted);
  font-variant-numeric: tabular-nums;
}
/* Query facet chips (doc 337 A1) — the header says what the answer actually searched: the
   window the search really used, then the resolved topic, then the item count. These elements
   only exist when the gateway ships `news_query`, so a card without facets renders exactly as
   it did before. The chips carry text, never colour alone (WCAG SC 1.4.1): publisher identity
   already owns the colour channel on this surface, so the facets stay neutral. */
.news-facet-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
/* Hairline, no fill — measured live: --cg-surface resolves to the very colour the card itself
   uses (#ffffff light / dark equivalent), so a "filled" chip was invisible against its own
   background in both themes and only the border was ever doing the work. Follows the existing
   .chatgpt-topic-tag-badge precedent (hairline pill, no fill) for a quiet metadata row. */
.news-facet-chip {
  padding: 1px 6px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-xs, 6px);
  color: var(--cg-text-soft);
  font-weight: 500;
}
.news-facet-count {
  margin-inline-start: 2px;
  color: var(--cg-muted);
  font-weight: 500;
}
/* Publication time (doc 337 A2) — sits next to the publisher on the same byline (.news-by). The
   separator is a pseudo-element so it can never appear without a time beside it: an article
   with no usable timestamp renders no .news-time at all, and a stray "·" would read as a
   missing value rather than an absent one.
   ★색 토큰은 그대로 둔다. 2026-07-30 실측에서 이 라벨이 라이트 4.90:1로 이 표면에서 가장
   빠듯했다(test_news_card_a11y의 실측표). 사진 위 프로스티드 밴드에서만 --cg-text-soft로
   올려 쓴다 — 아래 .news-by-band 참조. */
.news-time {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  white-space: nowrap;
}
.news-time::before {
  content: "·";
  margin-inline: 6px;
}
/* ══ ① 사진 깔린 주제 태그 (템플릿 top-tags) ═══════════════════════════════════════════
   각 필의 배경은 그 주제의 «가장 신선한» 기사 사진이다 — 지어낸 그림이 아니라 그 주제가
   실제로 들고 있는 사진. 사진이 없거나 로드에 실패하면 강조색 틴트가 그대로 남고 스크림은
   그대로라, 라벨 대비가 두 경우 모두 동일하고 빈 자리가 생기지 않는다. */
.news-topics {
  position: relative;
  /* ★날씨 위젯처럼 화살표가 심볼만이라 자기 공간이 필요하다 — 레일 «안»에 자리를 뺏으면
     끝 칩이 화살표 밑에 깔린다(그게 옛 .news-topics-more가 z-index 결함을 갖던 이유,
     38481행 주석). 화살표는 이 여백 한가운데 얹히고 레일과 겹치지 않는다. */
  padding-inline: 28px;
}
.news-topics-rail {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scrollbar-width: none;
  scroll-behavior: smooth;
  /* ★이 레일은 스크롤 컨테이너라 **자기 패딩 박스 밖을 잘라 낸다**. 바깥으로 그려지는 링은
     전부 여기서 죽는다. 실측(조상 overflow 검사): 첫 알약과 클리퍼 사이 여백이 가로 0px이라
     선택 링도 포커스 링도 왼쪽이 통째로 잘렸다 — 세로 4px도 선택 링(0 0 0 4px)에 여유가
     정확히 0이었다. 포커스 링은 6px까지 나가므로 6px로 올리고, 가로도 같은 6px을 준다.
     ★음수 마진으로 그 6px을 되돌린다 — 안 그러면 알약 열이 피드의 나머지 콘텐츠보다 6px
     안쪽으로 밀려 정렬이 깨진다. .news-topics에는 overflow가 없으므로 음수 마진은 잘리지 않는다.
     ★getComputedStyle은 이 결함을 **볼 수 없다**(outline 값은 정상으로 보고된다). 클리핑은
     계산값이 아니라 페인트라서, 계약 테스트가 아니라 조상 overflow 검사로만 잡힌다. */
  padding-block: 6px;
  padding-inline-start: 6px;
  margin-inline-start: -6px;
}
.news-topics-rail::-webkit-scrollbar {
  display: none;
}
.news-topic {
  --news-accent: var(--cg-muted);
  position: relative;
  overflow: hidden;
  flex: none;
  /* ★고정 106×40 → 최소값. 라벨은 rem으로 커지는데 알약이 px로 고정돼 있어서 텍스트 200%
     확대(WCAG SC 1.4.4)에서 라벨이 알약 밖으로 잘려 나갔다. 최소 크기만 남기고 실제 크기는
     라벨이 정하게 하면, 확대하면 알약도 같이 커진다. min-width는 «가지런한 레일»이라는
     원래 의도를 그대로 지킨다. */
  min-width: 106px;
  min-height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--ds-radius-sm);
  background: color-mix(in srgb, var(--news-accent) 42%, var(--cg-hover));
  font: inherit;
  cursor: pointer;
  transition: transform var(--wds-duration-fast) var(--wds-ease-out),
    box-shadow var(--wds-duration-fast) ease;
}
.news-topic-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* 스크림. ★사진을 우리가 고르지 않으므로 라벨 대비를 사진에 맡길 수 없다. 순백 사진을
   최악으로 놓고 손으로 계산했다(자동 측정기는 그라데이션을 배경으로 읽지 못한다):
   검정 56%→78%면 라벨이 놓이는 중앙부(≈67%)에서 #fff 대비가 약 7:1, 글자 상단(≈61%)에서도
   약 4.95:1로 4.5:1 하한을 넘는다. 템플릿의 48%→74%는 상단에서 4.74까지 내려간다. */
.news-topic::after {
  content: "";
  position: absolute;
  inset: 0;
  /* ★스크림은 사진 «위»에 있어야 한다. .news-topic-img가 함께 다는 .news-thumb가 z-index:1을
     들고 있어서, 이걸 auto로 두면 ::after가 사진 아래로 깔린다 — 실브라우저에서 순백 사진
     타일이 흰 글자를 그대로 삼켰다(1280×1000, 라이트, 첫 타일 라벨이 통째로 사라짐).
     계산으로 정한 대비는 스크림이 위에 있을 때만 성립한다. */
  z-index: 2;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--ds-color-media-scrim) 56%, transparent),
    color-mix(in srgb, var(--ds-color-media-scrim) 78%, transparent)
  );
}
/* 분야별 기사 수 (doc 343 §3.4). 라벨 옆에 붙되 라벨을 밀어내지 않는다 —
   8개 탭이면 어느 탭이 알찬지 보이지 않아 사용자가 빈 탭을 누르게 된다.
   ★색은 새로 만들지 않는다. 라벨과 같은 토큰을 쓰고 불투명도로만 뒤로 물린다 —
   이 리포의 원시값 드리프트 래칫은 여유가 0이다. */
.news-topic-count {
  /* 라벨 안에 살므로 z-index·position이 따로 필요 없다 — 간격은 라벨의 column-gap이 준다. */
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  line-height: 1.4;
  color: var(--ds-color-on-media);
  opacity: 0.72;
  font-variant-numeric: tabular-nums;
}
.news-topic-label {
  position: relative;
  z-index: 3; /* 사진(1) < 스크림(2) < 라벨 */
  display: grid;
  /* ★라벨과 건수 배지를 **한 줄에** 놓는다. 기본(row)이면 배지가 다음 줄로 떨어져 탭
     높이가 37px → 64px가 된다(실브라우저 실측). */
  grid-auto-flow: column;
  column-gap: 4px;
  place-items: center;
  /* height:100% + overflow:hidden이 알약을 «넘치면 자른다»로 만들고 있었다. 확대되면 라벨이
     알약을 밀어 키우도록 세로 패딩으로 바꾼다(잘림 → 성장). */
  padding: 10px 12px;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.005em;
  color: var(--ds-color-on-media);
}
/* 선택은 색만으로 말하지 않는다: 강조색 링 + 스크림이 걷히며 사진이 드러나고, 보조기술에는
   aria-pressed가 간다(SC 1.4.1). */
.news-topic[aria-selected="true"] {
  box-shadow: 0 0 0 2px var(--cg-surface), 0 0 0 4px var(--news-accent);
}
.news-topic[aria-selected="true"]::after {
  opacity: 0.78;
}
/* 포커스 링은 이 블록 맨 끝의 «뉴스 포커스 링» 규칙 하나가 전담한다 — 여기 있던 개별
   :focus-visible 규칙은 (0,2,0)이라 전역 기본 링(0,3,3)에 져서 한 번도 그려진 적이 없다. */
/* ★오너 요청(2026-09-08): 「날씨 위젯처럼 칩 버튼 좌/우에 심볼 아이콘만」 — 원·테두리·배경·
   스크림을 전부 없애고 날씨 .hourlyWrap/.slideBtn과 같은 모양으로 맞춘다. 차이는 하나뿐이다:
   날씨는 짙은 배경 위라 hover로만 드러내도 대비로 존재가 보이지만, 이 레일은 밝은 카드
   표면이라 hover-only면 버튼이 있는 줄 아무도 모른다 — 그래서 기본 노출은 «항상 보임»이고,
   이미 닿은 방향만 opacity로 옅어진다(완전히 지우면 「더 못 간다」와 「버튼이 없다」가
   구별되지 않는다). 폭이 넘치지 않을 때는 JS(updateNewsTopicsRailEdges)가 hidden을 준다.
   ★.news-topics-more가 갖고 있던 z-index 결함(38481행 옛 주석)은 위치 자체가 원인이었다 —
   레일 «안» 끝에 얹혀 이웃 칩과 같은 공간을 다퉜다. 이제 버튼은 .news-topics의 padding-inline
   여백(레일 바깥)에 얹히므로 겹칠 칩이 없고, z-index 핫픽스도 함께 걷어냈다. */
.news-topics-slide {
  position: absolute;
  top: 50%;
  z-index: 2;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  padding: 4px;
  border: none;
  background: transparent;
  color: var(--cg-text-soft);
  cursor: pointer;
  transition: opacity var(--wds-duration-fast) ease;
}
/* ★.news-card[hidden](24131행)과 같은 함정 — updateNewsTopicsRailEdges가 button.hidden = true
   를 놓아도, 사용자 에이전트 기본 `[hidden] { display: none }`은 특이도 (0,0,1)이라 바로 위
   `.news-topics-slide { display: grid }` (0,1,0)에 진다. 실브라우저 캡처로 실측했다: 레일이
   안 넘치는데도(8개 칩이 1278px 안에 다 들어감) 화살표 두 개가 hidden=true인 채 display:grid
   로 그대로 보였다. */
.news-topics-slide[hidden] {
  display: none;
}
.news-topics-slide .wds-inline-icon {
  width: 20px;
  height: 20px;
}
.news-topics-slide:hover {
  color: var(--cg-text);
}
.news-topics-slide-prev {
  inset-inline-start: 0;
}
.news-topics-slide-next {
  inset-inline-end: 0;
}
/* 끝에 닿은 방향은 «더 못 간다»고 옅어지되 버튼 자체는 남는다(다른 방향은 여전히 산다). */
[data-news-topics][data-at-start] .news-topics-slide-prev,
[data-news-topics][data-at-end] .news-topics-slide-next {
  opacity: 0.3;
  pointer-events: none;
}
/* 손가락은 커서보다 굵다 — 날씨 .slideBtn 은 이 슬롭이 없지만(짙은 배경 위 큰 아이콘이라
   28×28 자체 판정 영역으로 충분했다) 이 버튼은 .news-topics 의 좁은 padding-inline 여백
   안이라 그대로 두면 28×28이다. */
@media (pointer: coarse) {
  .news-topics-slide::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }
}
/* ★≤520px: 스와이프가 기본이고 칩이 폭을 다 쓴다 — 화살표 한 쌍이 좁은 화면에서는 칩 자리를
   더 뺏는다. 정본 분기점 {520,521,900,901,1200,1201} 래칫(test_composer_sent_bubble.py) 밖의
   값은 쓰지 않는다. JS(updateNewsTopicsRailEdges)의 hidden 토글과 별개로, 이 폭 이하에서는
   레일이 넘치더라도 화살표를 아예 켜지 않는다. */
@media (max-width: 520px) {
  .news-topics {
    padding-inline: 0;
  }
  .news-topics-slide {
    display: none;
  }
}

/* ══ ② 히어로 캐러셀 (템플릿 Slider 744×452 · 1.65) ═════════════════════════════════════ */
.news-hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--ds-radius-md);
}
.news-hero-track {
  display: flex;
  transition: transform 460ms var(--wds-ease-out);
}
/* 오프셋은 인라인 style이 아니라 여기서 산다 — 전환·감속·reduced-motion과 같은 자리. */
.news-hero-track[data-news-hero-index="1"] {
  transform: translateX(-100%);
}
.news-hero-track[data-news-hero-index="2"] {
  transform: translateX(-200%);
}
.news-lead {
  position: relative;
  flex: 0 0 100%;
  display: block;
  box-sizing: border-box;
  overflow: hidden;
  aspect-ratio: 744 / 452;
  border-radius: var(--ds-radius-md);
  /* Real border (not inset box-shadow): overflow:hidden clips the photo to the inner radius,
     so a loaded image can never paint over the top/side frame the way it did with an inset
     shadow. It also gives a near-white photo an edge to sit against. */
  border: 1px solid var(--cg-menu-border);
  background: var(--cg-hover);
  text-decoration: none;
  color: inherit;
}
/* 포커스 링 → 블록 끝의 «뉴스 포커스 링» 참조(여기 있던 (0,2,0) 규칙은 죽은 코드였다). */
.news-lead-shot {
  position: absolute;
  inset: 0;
}
/* ★사진 없는 리드 = 텍스트 리드. 사진 자리를 비워 두는 대신 밴드가 슬라이드 «전체»가 된다.
   1.65 상자를 유지하면 커다란 빈 사각형 아래에 밴드만 놓인 모양이 되고, 그건 설계된 부재가
   아니라 고장으로 읽힌다(실브라우저 확인 — 레거시 페이로드에서 정확히 그랬다).
   흐릴 배경이 없으므로 프로스티드 처리도 걷는다. */
.news-lead[data-news-textonly] {
  aspect-ratio: auto;
  background: var(--cg-hover-soft);
}
.news-lead[data-news-textonly] .news-lead-band {
  position: static;
  padding: 16px 18px;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* 도트가 커커·발행처 줄 위로 겹치지 않도록 그만큼만 비워 둔다(알약 22 + 점 2개 + 간격 + 인셋). */
.news-lead[data-news-textonly] .news-lead-bandtop {
  padding-inline-end: 70px;
}
/* Real source thumbnail (20260709-news-thumb) — a failed load takes its whole slot with it
   (bindNewsPhotoFallbacks), so the surface never shows an empty box where a photo was
   promised. Subtle hover zoom gives the feed the same "alive" feel as the other widgets. */
.news-thumb {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* No opacity fade: an opacity transition here froze at currentTime 0 (the img is created
     and revealed in the same frame), leaving thumbnails invisible over the cover. The image
     just appears on load — the token background shows through until then. */
  transition: transform 420ms var(--wds-ease-out);
}
/* ★템플릿의 핵심 처리 — 가장자리에서 띄운 **밝은** 프로스티드 카드. 어두운 그라데이션이 아니다.
   투명도는 취향이 아니라 계산이다. 사진을 우리가 고르지 않으므로 최악(순백/순흑)을 가정한다:
     라이트(surface #ffffff) · 순흑 사진 → 밴드 rgb(224) → --cg-text 대비 ≈13:1
     다크 (surface #000000) · 순백 사진 → 밴드 rgb(31)  → --cg-text 대비 ≈13:1
   ★밴드 위 보조 텍스트는 --cg-muted가 아니라 --cg-text-soft를 쓴다. --cg-muted(#6f6f6f)는
   라이트 최악 밴드에서 3.46:1로 미달한다(카드 위에서는 5.02:1로 통과한다 — 배경이 다르다).
   backdrop-filter가 없는 브라우저에서는 86%로 더 불투명하게 떨어뜨려 같은 하한을 지킨다
   (라이트·순흑 사진 최악에서 밴드 rgb(219) → --cg-text 대비 약 13.2:1). */
.news-lead-band {
  position: absolute;
  z-index: 2;
  /* ★13이지 14가 아니다. .news-lead에는 1px 실테두리가 있고 이 밴드는 **패딩 박스** 기준으로
     자리를 잡는다. 14를 적으면 보이는 가장자리에서 15px 떨어져 보인다(실측 15). 13 + 테두리
     1 = 목업의 14px. */
  inset-inline: 13px;
  bottom: 13px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px 18px;
  border-radius: var(--ds-radius-md);
  background: color-mix(in srgb, var(--cg-surface) 86%, transparent);
  box-shadow: inset 0 0 0 1px var(--cg-menu-border);
}
@supports (backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px)) {
  .news-lead-band {
    /* ★88% → 78%(목업값). 88%는 «프로스티드 오버레이»가 아니라 «불투명한 판»으로 읽혔다.
       투명도는 취향이 아니라 계산이므로 낮추면서 하한을 다시 유도했다. 사진을 우리가 고르지
       않으므로 최악(순백/순흑)을 가정한다 — 78%의 최악 밴드는:
         라이트(surface #fff) · 순흑 사진 → 밴드 rgb(199) → --cg-text(#0d0d0d) 대비 11.49:1
         다크 (surface #000) · 순백 사진 → 밴드 rgb(56)  → --cg-text 대비는 아래 실측표 참조
       보조 텍스트(--cg-text-soft)도 라이트 최악에서 6.22:1로 4.5:1을 넘는다. 즉 하한(4.5:1)은
       88%가 아니라 «밴드가 만드는 대비»가 지키는 것이고, 78%도 그 하한 안쪽이다.
       ★핀을 «88 이상»이 아니라 «계산된 대비 하한»으로 바꿔 두었다
       (test_news_magazine_front.LiveMeasuredCssInvariantTest) — 프록시가 아니라 불변식을 잰다. */
    background: color-mix(in srgb, var(--cg-surface) 78%, transparent);
    -webkit-backdrop-filter: blur(22px) saturate(150%);
    backdrop-filter: blur(22px) saturate(150%);
  }
}
.news-lead-bandtop {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
}
/* 커커. ★강조색은 «점»이 나르고 주제 이름은 «글자»가 나른다. 강조색을 라벨 글자에 쓰면 사진
   위 밴드에서 대비를 보장할 수 없다 — 차트 토큰 일부는 라이트 최악 밴드에서 2.5:1까지
   떨어진다. 점은 바로 옆 글자가 같은 말을 하므로 장식이고, 글자는 --cg-text라 항상 안전하다. */
.news-kicker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--cg-text);
}
.news-kicker::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--news-accent, var(--cg-muted));
}
.news-lead-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* ★상한 22px은 «승인된 크기»인데 2.6cqw로는 **닿지 않는 죽은 팔**이었다. 실측: 컨테이너
     709px에서 2.6cqw = 18.43px — 승인값보다 16.2% 작고, 주변 채팅 본문 16px의 1.15배라
     사실상 같은 층이었다. 중간 팔을 3.2cqw로 올리면 컨테이너 687.5px부터 상한에 닿는다.
     실제 채팅 폭(709px 실측)에서 3.2cqw = 22.69px → clamp 상한 22px로 고정된다.
     ★하한 body-16은 **1280만 재던 값**이었다. 390 실측(피드 컨테이너 283px): 3.2cqw = 9.06px
     이라 하한 16px에 눌리는데, 섹션 이름은 고정 18px이라 **리드 제목이 섹션 헤더보다 작았다**
     (16 : 18 = 0.889배). 1280에서 고친 것과 같은 종류의 위계 역전이 390에서 그대로 살아 있었다.
     하한을 title-18로 올린다 — 1280에서는 상한 22px에 걸려 있으므로 한 픽셀도 바뀌지 않고
     (3.2cqw × 709 = 22.69 > 22), 좁은 폭에서만 18px 바닥이 드러난다. 아래 .news-section-name /
     .news-card-link도 같은 이유로 유동화해서 사다리 전체가 **순서를 지킨 채** 함께 내려온다.
     웨이트는 위 .news-feed-title 주석과 같은 이유로 600이 상한이다. */
  font-size: clamp(var(--ds-text-title-18), 3.2cqw, var(--ds-text-title-22));
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.022em;
  text-wrap: pretty;
  color: var(--cg-text);
}
.news-lead:hover .news-lead-title {
  text-decoration: underline;
  text-underline-offset: 4px;
}
.news-lead-excerpt {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: var(--ds-text-caption-12);
  line-height: 1.55;
  color: var(--cg-text-soft);
}
.news-hero-nav {
  position: absolute;
  z-index: 3;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  /* 목업 38 (36이었다). */
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--cg-surface) 84%, transparent);
  color: var(--cg-text);
  cursor: pointer;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--ds-color-shadow-ink) 24%, transparent);
  transition: opacity var(--wds-duration-fast) ease, transform var(--wds-duration-fast) ease;
}
/* 자동 넘김 재생/정지 (doc 343 §4.1, WCAG 2.2.1) — 화살표와 같은 원형 컨트롤이되
   좌우가 아니라 왼쪽 아래에 앉는다(화살표·도트와 겹치지 않는 자리).
   ★글리프를 CSS로 그린다. WDS 아이콘셋(shared/wds/wds-icons.js)에 pause/play가 없고 그
   파일은 이 작업의 범위 밖이다. app.js 폴백 맵에만 넣으면 «앱에는 있는데 WDS에는 없는»
   파리티 드리프트가 생긴다 — 이 리포가 반복해 겪은 문제라 만들지 않는다.
   아이콘셋에 pause/play가 들어오면 이 규칙을 renderIcon 호출로 바꿀 것. */
.news-hero-autoplay {
  position: absolute;
  z-index: 3;
  inset-inline-start: 10px;
  bottom: 10px;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--cg-surface) 84%, transparent);
  color: var(--cg-text);
  cursor: pointer;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--ds-color-shadow-ink) 24%, transparent);
}
.news-hero-autoplay::before,
.news-hero-autoplay::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 3px;
  height: 11px;
  /* ★라운딩 없음. 3px 막대에 1px 라운딩은 눈에 띄지도 않는데 원시값 드리프트 래칫을
     넘긴다(여유 0). 천장을 올려 통과시키지 않는다 — 그게 래칫의 존재 이유다. */
  background: currentColor;
  transform: translateY(-50%);
}
.news-hero-autoplay::before { inset-inline-start: calc(50% - 4px); }
.news-hero-autoplay::after { inset-inline-start: calc(50% + 1px); }
/* 멈춘 상태 = 다음 동작이 «재생»이므로 삼각형을 보인다. */
.news-hero-autoplay[aria-pressed="false"]::before {
  inset-inline-start: calc(50% - 3px);
  width: 0;
  height: 0;
  background: none;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-inline-start: 9px solid currentColor;
}
.news-hero-autoplay[aria-pressed="false"]::after { content: none; }

.news-hero-nav-prev {
  inset-inline-start: 10px;
}
.news-hero-nav-next {
  inset-inline-end: 10px;
}
.news-hero-nav .wds-inline-icon {
  width: 18px;
  height: 18px;
}
/* 화살표는 hover 가능한 포인터에서만 숨는다. 터치에서 숨기면 캐러셀을 넘길 방법이 도트뿐이 된다. */
@media (hover: hover) and (pointer: fine) {
  .news-hero-nav {
    opacity: 0;
  }
  .news-hero:hover .news-hero-nav,
  .news-hero-nav:focus-visible {
    opacity: 1;
  }
  .news-hero-nav:hover {
    transform: translateY(-50%) scale(1.06);
  }
}
/* ★★도트는 **자기 스크림 위에** 앉는다. 도트는 이 캐러셀의 유일한 상시 위치 표시기인데,
   사진 위에 바로 놓여 있던 동안 흰 도트 대 «흰 사진»이 실측 1.00:1이었다(라이트/다크,
   활성/비활성 네 경우 모두). 사진은 우리가 고르지 않으므로 배경에 기댈 수 없다.
   ★목업을 그대로 베끼지 않는다 — 목업의 처리는 1.26:1로 더 나쁘다.
   불투명도는 계산값이다. 순백 사진 최악에서 스크림 = 검정 70% → rgb(76.5):
     · 활성 도트(순백 #fff)  대 스크림 → 8.51:1
     · 비활성 도트(흰색 62%) 대 스크림 → 4.44:1   (둘 다 SC 1.4.11의 3:1을 통과)
   활성/비활성 구분은 색이 아니라 **너비**(24 대 10)가 나른다 — SC 1.4.1. */
.news-dots {
  position: absolute;
  z-index: 3;
  inset-inline-end: 13px;
  top: 13px;
  display: flex;
  align-items: center;
  /* 14px 간격은 장식이 아니라 SC 2.5.8 계산이다 — 아래 .news-dot::after 주석 참조. */
  gap: 14px;
  padding: 6px 10px;
  border-radius: var(--ds-radius-pill);
  background: color-mix(in srgb, var(--ds-color-media-scrim) 70%, transparent);
}
.news-dot {
  position: relative;
  /* 7 → 10. 히트 영역만 키우면 SC 2.5.8은 통과해도 **보이는** 표시기가 여전히 7px이라
     읽히지 않는다. */
  width: 10px;
  height: 10px;
  padding: 0;
  border: 0;
  border-radius: var(--ds-radius-pill);
  background: color-mix(in srgb, var(--ds-color-on-media) 62%, transparent);
  cursor: pointer;
  transition: width 280ms var(--wds-ease-out),
    background var(--wds-duration-fast) ease;
}
/* ★SC 2.5.8(Target Size Minimum, AA) 계산. 이전 값(inset -9px -4px)은 7px 도트에 15×25
   히트 영역을 줬다 — 가로 15 < 24라 최소 크기도 미달이고, 중심 간격이 13px / 20.5px라
   «간격 예외»(24px 원이 겹치지 않을 것)도 동시에 미달이었다.
   지금: 도트 10 + inset 7 양쪽 = 24×24로 최소 크기를 정면 통과하고, gap 14px이라 비활성끼리
   중심 간격이 10+14 = 24px라 히트 영역이 서로 «닿기만» 하고 겹치지 않는다. */
.news-dot::after {
  content: "";
  position: absolute;
  inset: -7px;
}
.news-dot[aria-current="true"] {
  width: 24px;
  background: var(--ds-color-on-media);
}

/* ══ ③ 섹션 헤더 (템플릿: 세로 바 + 제목 + 우측 원형 화살표) ═══════════════════════════ */
.news-section {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* doc 635 §9 후속 전수 스윕(2026-09-04) — redrawNewsFeedSections()가
   `section.hidden = cards.length === 0`로 이 요소에 직접 hidden을 세운다(app.js).
   위 `.news-section { display: flex }`는 저자 규칙이라 [hidden]의 UA 기본값
   (display:none)을 출처 우선순위로 이긴다 — hasAttribute('hidden')===true인데
   getComputedStyle().display==='flex'로 실측 확인(정적 서버, 데스크톱 1440).
   같은 특이도(class+attribute)로 짝을 준다. */
.news-section[hidden] {
  display: none;
}
.news-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
/* <h4>다 — 접두사가 없으면 `.message-body h4`(0,1,1)의 `margin: 16px 0 9px`가 이겨서 20px짜리
   글줄이 45px 블록이 된다(실측). 접두사로 (0,3,0)을 만들면 마진 0이 실제로 적용돼 30px가 된다.
   크기도 body-16 → title-18: 16px은 주변 채팅 본문과 **같은 값**이라 «섹션 이름»이라는 역할이
   활자로는 하나도 표시되지 않았다. 18px이면 카드 제목 15px과 1.20배로 갈라진다.
   ★고정 18px은 1280에서만 옳았다. 390(컨테이너 283px)에서는 히어로 제목이 하한으로 눌려
   내려오는데 이것만 18px에 못 박혀 있어서 **섹션 이름이 리드 제목보다 커졌다**(실측 18 : 16).
   유동화한다: 2.6cqw는 컨테이너 692.3px부터 상한 18px에 닿으므로 실채팅 폭 709px에서는
   18.43 → 18px로 **1280 값이 그대로 유지**되고(실측), 좁은 폭에서만 하한 body-16이 드러난다.
   하한을 14가 아니라 16으로 두는 이유는 아래 .news-card-link(하한 14)와 1.14배를 유지하기
   위해서다 — 사다리의 각 칸은 좁은 폭에서도 최소 한 단계(토큰 사다리의 이웃 눈금)를 벌린다. */
.chatgpt-reference-v1 .news-feed .news-section-name {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  font-size: clamp(var(--ds-text-body-16), 2.6cqw, var(--ds-text-title-18));
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.02em;
  color: var(--cg-text);
}
.news-section-bar {
  flex: none;
  width: 3px;
  height: 17px;
  border-radius: var(--ds-radius-2xs);
  background: var(--news-accent, var(--cg-text));
}
.news-section-arrows {
  display: flex;
  gap: 7px;
}
.news-arrow {
  display: grid;
  place-items: center;
  /* 목업 32 (30이었다). */
  width: 32px;
  height: 32px;
  border: 1px solid var(--cg-menu-border);
  border-radius: 50%;
  background: transparent;
  color: var(--cg-text-soft);
  cursor: pointer;
  transition: border-color var(--wds-duration-fast) ease, color var(--wds-duration-fast) ease,
    background var(--wds-duration-fast) ease;
}
.news-arrow:hover {
  border-color: var(--cg-text-soft);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}
.news-arrow .wds-inline-icon {
  width: 16px;
  height: 16px;
}

/* ══ ③b 2단 — 톱과 카드 트랙 사이 두 칸 (doc 344 §10) ══════════════════════════════════
   ★사진이 없다. 1면의 위계는 사진 크기가 아니라 활자와 밀도로 만든다 — 톱(사진 위 밴드) →
   2단(제목 + 요약 1줄) → 카드(썸네일 + 요약 2줄). 2단에 사진을 주면 톱과 같은 층이 되어
   1면이 «큰 사진 세 덩이»가 된다.
   ★카드 트랙과 달리 여기는 **평범한 2열 격자**다. 두 칸뿐이라 페이징할 것이 없고, 트랙으로
   만들면 스냅·페이저·2줄 기하가 두 벌이 된다. */
/* 구획 전체(라벨 + 격자). hidden은 여기가 든다 — 격자만 숨기면 라벨이 홀로 남는다. */
.news-second-tier {
  display: block;
  margin-top: 16px;
  padding-top: 14px;
  /* ★--cg-border는 **정의되지 않은 토큰**이다(NoPhantomCgBorderTokenTest가 잡는다). 이 표면의
     구분선은 --cg-menu-border를 쓴다 — 바로 위 .news-topics + .news-hero가 같은 값이다. */
  border-top: 1px solid var(--cg-menu-border);
}
/* ★`[hidden]`은 (0,0,1)이라 바로 위 `display: block` (0,1,0)에 진다 — .news-card[hidden]에서
   같은 것을 실브라우저로 확인했다. 없으면 기사가 적은 분야에서 빈 구획이 선만 남기고 보인다. */
.news-second-tier[hidden] {
  display: none;
}
.news-second-tier > .news-section-head {
  margin-bottom: 10px;
}
.news-second {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
/* ★셀은 2열 격자다: 왼쪽에 제목·요약, 오른쪽에 64px 썸네일, 발행처 행은 아래를 가로지른다.
   flex 세로 쌓기로는 썸네일을 «제목 옆»에 둘 수 없다. */
.news-second-cell {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto auto;
  column-gap: 12px;
  row-gap: 4px;
  align-items: start;
  min-width: 0;
}
.news-second-cell > .news-second-link {
  grid-column: 1;
  grid-row: 1;
}
.news-second-cell > .news-second-excerpt {
  grid-column: 1;
  grid-row: 2;
}
.news-second-cell > .news-second-shot {
  grid-column: 2;
  grid-row: 1 / span 2;
}
.news-second-cell > .news-second-foot {
  grid-column: 1 / -1;
  grid-row: 3;
}
/* 카드와 같은 «늘린 링크» 구조. 제목은 카드(15)와 톱 사이 층이라 16이다. */
.news-second-link {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: clamp(var(--ds-text-body-15), 2.4cqw, var(--ds-text-body-16));
  font-weight: 600;
  line-height: 1.36;
  letter-spacing: -0.014em;
  text-wrap: pretty;
  text-decoration: none;
  color: var(--cg-text);
}
.news-second-link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--ds-radius-sm);
}
.news-second-link:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* ★<p>다 — 접두사 없이는 `.chatgpt-reference-v1 .message-body p` (0,2,0)가 이겨서 요약이
   16px로 렌더되고 **자기 제목보다 커진다**. .news-card-excerpt가 정확히 그렇게 깨져 있었다.
   접두사 두 개 = (0,3,0)이라 파일 순서와 무관하게 이긴다(CascadeOverrideTest가 소유한다). */
.chatgpt-reference-v1 .news-feed .news-second-excerpt {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0;
  font-size: var(--ds-text-caption-12);
  font-weight: 400;
  line-height: 1.55;
  color: var(--cg-muted);
}
.news-second-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 6px;
}
/* ★★썸네일 두 함정 — 둘 다 라이브 DOM에 후보를 주입해서 잡았다(T6c). 합성 하네스에는
   전역 `.news-thumb` 규칙이 없어 **둘 다 안 보인다**.
   ① `.news-thumb`는 `position: absolute`다. 감싸개가 relative가 아니면 이미지가 셀 원점으로
      튀어 제목 위에 겹친다(.news-card-shot이 relative인 이유가 이것이다).
   ② `.news-second-shot img` (0,1,1)는 전역 `.news-thumb`에 **진다**. 접두사 두 개로
      (0,3,1)을 만들어야 64px가 실제로 먹는다 — 안 그러면 이미지가 346px로 퍼진다(실측). */
.chatgpt-reference-v1 .news-feed .news-second-shot {
  position: relative;
  flex: none;
  width: 64px;
  height: 64px;
  overflow: hidden;
  border-radius: var(--ds-radius-sm);
  background: var(--cg-hover);
}
.chatgpt-reference-v1 .news-feed .news-second-shot .news-second-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ══ ④ 카드 (템플릿 popular-posts) ═════════════════════════════════════════════════════
   채움도 테두리도 없다. 사진과 출처 행이 카드를 만든다. */
/* align-items는 stretch(기본)로 둔다. 사진 없는 기사가 섞이면 start에서는 그 카드만 위로
   붙어 행이 들쭉날쭉해 보인다(실브라우저 확인) — stretch면 카드 높이가 같아지고
   .news-card-foot의 margin-top:auto가 모든 출처 행을 같은 바닥선에 정렬한다. */
/* ★세로 2줄 고정 · 가로 흐름. 예전에는 3열 세로 격자여서 지면 높이가 기사 수를 그대로
   따라갔다 — 24건이면 7줄, 카드 구간만 2000px을 넘었다. 이제 줄은 2로 고정이고 남는 기사는
   오른쪽으로 흘러 페이지 단위로 넘어간다.
   ★열 수가 아니라 **줄 수**를 고정한 이유: 열 수는 컨테이너 폭에 따라 3→2→1로 접히는데,
   카드를 6개씩 «페이지»로 묶어 두면 좁은 폭에서 한 페이지가 3줄·6줄이 되어 «2줄» 약속이
   깨진다. 줄을 고정하고 열을 흐르게 두면 어떤 폭에서도 2줄이고, JS는 개수를 세지 않는다.
   ★grid-auto-columns가 한 «페이지»의 열 수를 정한다. 100%를 열 수로 나눈 값이라 한 화면에
   정확히 그 수만큼 들어가고, scrollBy(clientWidth)가 곧 한 페이지가 된다. */
.news-cards {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, minmax(0, auto));
  grid-auto-columns: calc((100% - 32px) / 3);
  gap: 16px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  /* 스크롤 컨테이너가 카드의 focus-visible 링을 잘라 먹지 않게 한다. */
  padding-block: 2px;
}
.news-cards::-webkit-scrollbar {
  display: none;
}

/* ══ 한 페이지에 다 들어가면 «행»으로 채운다 (2026-08-04, 사용자 지적) ═══════════════
   격자는 `grid-auto-flow: column` + 2행이라 **아래로 먼저** 채운다. 기사가 12·23건인
   분야에서는 그래야 페이지가 꽉 차지만, 문화처럼 **2건**뿐이면 한 열에 세로로 쌓이고
   오른쪽 2/3가 빈다 — 사용자가 화면에서 지적한 모양이 이것이다.

   ★임계는 **4개**다. 가장 좁은 페이지(620px, 2열×2행)가 4개이므로, 4개 이하이면 어느
     폭에서도 스크롤이 필요 없다. 그래서 컨테이너 쿼리는 **열 폭만** 바꾸면 되고
     (`test_every_width_keeps_exactly_two_rows`의 약속), 줄 수를 건드릴 일이 없다.
   ★5개부터는 원래의 열 흐름·좌우 페이징 그대로다. 넘치는 분야까지 행 흐름으로 두면
     컨테이너가 세로로 자라 캐러셀이 사라진다(페이저는 scrollWidth/clientWidth로 센다).

   라이브 DOM 주입 실측(3열): 2개 → 1행 나란히 · 4개 → 첫 줄 3개+1개 · 5개↑ → 열 흐름 복귀. */
.news-cards:not(:has(> .news-card:nth-child(5))) {
  grid-auto-flow: row;
  grid-template-rows: none;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  justify-content: start;
}
.news-cards > .news-card {
  scroll-snap-align: start;
}
.news-page-count {
  /* 화살표만으로는 «얼마나 더 있는지»를 알 수 없다. 이 표시가 없으면 끝을 확인하려고
     끝까지 눌러 보는 수밖에 없다. aria-hidden인 이유: 같은 사실을 피드의 aria-label과
     라이브 리전이 이미 문장으로 말한다. */
  align-self: center;
  margin-inline-end: 3px;
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-12);
  font-variant-numeric: tabular-nums;
}
.news-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
/* ★없으면 주제 필터가 화면에서 아무 일도 하지 않는다. selectNewsTopic이 card.hidden = true를
   놓아도, 사용자 에이전트 기본 규칙 `[hidden] { display: none }`은 특이도 (0,0,1)이라
   바로 위 `.news-card { display: flex }` (0,1,0)에 진다. 실브라우저에서 실측했다:
   aria-pressed="true"에 hidden 속성까지 붙은 카드가 display:flex로 그대로 보였다.
   DOM 스텁은 hidden을 «속성이 있으니 숨겨졌다»로 읽어 이 결함을 통과시킨다. */
.news-card[hidden] {
  display: none;
}
.news-card-shot {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  border-radius: var(--ds-radius-sm);
  background: var(--cg-hover);
}

/* ══ 사진 없는 기사의 «채워진» 자리 (2026-08-04, 사용자 지적) ══════════════════════════
   사진이 없다고 칸을 통째로 빼면 카드마다 제목 시작점이 달라져 격자가 들쭉날쭉해 보인다.
   `align-items: stretch`가 **발**은 맞춰 주지만 **머리**는 못 맞춘다.

   ★예전 결정(«빈 상자를 남기지 않는다»)과 모순이 아니다. 그때 문제는 **아무것도 없는**
     사각형이었다. 여기서는 «사진 없음»으로 채운다 — 헤어라인 테두리와 글리프가 있으면
     로딩 스켈레톤이나 깨진 이미지가 아니라 **의도된 부재**로 읽힌다.
   ★리드(히어로)는 제외다. 1.65 상자를 이렇게 채우면 커다란 슬래브가 되고, 그 모양은
     실브라우저에서 이미 기각됐다(.news-lead[data-news-textonly] 참조). */
.news-card-shot[data-news-photo="empty"],
.chatgpt-reference-v1 .news-feed .news-second-shot[data-news-photo="empty"] {
  display: grid;
  place-items: center;
  background: var(--cg-hover-soft, var(--cg-hover));
  box-shadow: inset 0 0 0 1px var(--wizon-ai-popover-border);
}
.news-shot-empty {
  display: grid;
  place-items: center;
  color: var(--cg-muted);
  opacity: 0.34;
}
.news-shot-empty svg {
  /* 카드는 넉넉히, 64px 2단 칸은 작게 — 칸 폭을 따라간다. */
  inline-size: clamp(18px, 22%, 34px);
  block-size: auto;
}
/* 카드 전체가 링크지만 <a> 안에 버튼을 넣지 않는다(무효 HTML이자 키보드 함정). 늘린 링크가
   카드를 덮고, 북마크 버튼만 그 위로 올라온다. */
.news-card-link {
  margin-top: 5px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* 14 → 15. 카드 제목은 요약(12)보다 위, 섹션 이름(18)보다 아래여야 한다. 14px이면
     요약과 1.17배밖에 안 벌어지고, 15px이면 1.25배다.
     ★고정 15px이면 좁은 폭에서 사다리가 «위만» 내려오고 여기서 멈춰 칸 간격이 무너진다.
     2.2cqw는 컨테이너 681.8px부터 상한 15px에 닿으므로 실채팅 폭 709px에서는 15.6 → 15px로
     **1280 값이 그대로 유지**되고(실측), 390(283px)에서만 하한 body-14가 드러나 요약 12px과
     1.17배를 지킨다. 하한을 13으로 더 낮추지 않는 이유가 그것이다 — 13이면 1.08배로 요약과
     사실상 같은 층이 된다. */
  font-size: clamp(var(--ds-text-body-14), 2.2cqw, var(--ds-text-body-15));
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.014em;
  text-wrap: pretty;
  text-decoration: none;
  color: var(--cg-text);
}
.news-card-link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--ds-radius-sm);
}
.news-card-link:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* ★R-53 ①(오너 10-02 «그림은 클릭이 안 된다») — 늘린 링크의 «쌓임 순서» 를 명시한다.
   사진(.news-thumb)은 톱 밴드(z 2) 아래에 깔리려고 z-index:1 을 갖는데, 카드·2단에서는 그 1 이 늘린 링크
   (::after, z auto) **위** 로 올라와 사진 영역의 누름을 삼켰다(실측: 1440·390 사진 6칸 전부 링크 아님).
   2단은 사진 칸이 링크보다 DOM 뒤라 «사진 없음» 칸도 같은 이유로 눌리지 않았다.
   층 = 사진 1 < 늘린 링크 2 < 북마크 3. isolation 으로 이 층들을 카드 안에 가둔다 — 카드 밖(대화·시트·메뉴)의
   z-index 와 겨루지 않는다. 사진을 <a> 로 한 번 더 감싸는 안은 기각(탭 정지 2개·낭독 중복).
   시험: tests/test_news_card_click_r53_live.py(변이 시험이 아래 두 규칙을 지워 결함 재현을 확인). */
.news-card,
.news-second-cell {
  isolation: isolate;
}
.news-card-link::after,
.news-second-link::after {
  z-index: 2;
}
.news-card .news-bookmark,
.news-second-cell .news-bookmark {
  z-index: 3;
}
/* ★R-53 후속(coarse + 마우스 클릭 누수) — 원인은 «줄어듦» 이 아니라 «쌓임 맥락»: .wds-pressed 가 늘린 링크에 `scale` 을 주는 순간
   (0.96 이든 reduced-motion 의 1 이든 `none` 이 아니면) <a> 가 자기 쌓임 맥락이 되어 ::after(z 2)가 그 안에 갇히고, 링크
   자체(z auto)는 사진(z 1) 밑으로 내려간다. 눌림 중 사진·요약 위 pointerup 이 사진에 떨어져 click 이 카드로 샌다(실측: 같은
   자리 elementFromPoint 가 눌림 전 링크 → 눌림 중 사진. 터치는 암묵 캡처라 영향 없음). 그래서 늘린 링크는 눌림에도 `scale: none`
   (스택 맥락 0)이고, 눌림 표시는 카드 전체의 흐려짐이 맡는다(탭 정지 1·북마크 독립·카드 전체 링크 구조 그대로).
   시험: test_news_card_click_r53_live.NewsCardCoarseMouseClickTests. */
@media (pointer: coarse) {
  .news-card-link.wds-pressed,
  .news-second-link.wds-pressed {
    scale: none;
  }
  .news-card:has(.news-card-link.wds-pressed),
  .news-second-cell:has(.news-second-link.wds-pressed) {
    opacity: 0.82;
    transition: opacity var(--wizon-motion-fast, 140ms) ease-out;
  }
}
/* 포커스 링 → 블록 끝의 «뉴스 포커스 링» 참조(여기 있던 (0,2,0) 규칙은 죽은 코드였다). */
/* ★★이 위젯에서 유일한 <p>다. 접두사가 없던 동안 `.chatgpt-reference-v1 .message-body p`
   (0,2,0)가 이겨서 요약이 **16px/27.2px**로 렌더됐다 — 자기 카드 제목(14px)보다 크다.
   모든 카드에서 위계가 뒤집혀 있었다(실측). 접두사 두 개 = (0,3,0)이라 순서와 무관하게 이긴다.
   위 .news-feed-title 주석에 이 캐스케이드 사고의 전모가 있다. */
.chatgpt-reference-v1 .news-feed .news-card-excerpt {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0;
  font-size: var(--ds-text-caption-12);
  font-weight: 400;
  line-height: 1.55;
  color: var(--cg-muted);
}
.news-card-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 8px;
}
/* 출처 행 = 템플릿의 «아바타 + 작성자 + 날짜». 우리는 작성자가 없고 언론사가 있다. */
.news-by {
  display: flex;
  align-items: center;
  min-width: 0;
}
.news-by-band .news-time {
  /* 프로스티드 밴드 위에서만 한 단계 올린다 — 위 .news-lead-band 주석의 계산 참조. */
  color: var(--cg-text-soft);
}
.news-bookmark {
  /* 늘린 카드 링크(.news-card-link::after) 위로 올라와야 눌린다. */
  position: relative;
  z-index: 1;
  flex: none;
  margin-inline-start: auto;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--ds-radius-xs);
  background: transparent;
  color: var(--cg-muted);
  cursor: pointer;
  transition: color var(--wds-duration-fast) ease, background var(--wds-duration-fast) ease;
}
.news-bookmark:hover {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}
.news-bookmark .wds-inline-icon {
  width: 16px;
  height: 16px;
}
/* 눌림은 색과 «형태»를 함께 바꾼다 — 외곽선 북마크가 채워진 북마크가 된다(SC 1.4.1). */
.news-bookmark[aria-pressed="true"] {
  color: var(--news-accent, var(--cg-text));
}
.news-bookmark[aria-pressed="true"] .wds-inline-icon {
  fill: currentColor;
}
@media (hover: hover) and (pointer: fine) {
  .news-topic:hover {
    transform: translateY(-2px);
  }
  .news-card:hover .news-card-img {
    transform: scale(1.05);
  }
  .news-lead:hover .news-lead-img {
    transform: scale(1.04);
  }
}
.news-fav {
  position: relative;
  overflow: hidden;
  flex: none;
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  border-radius: 4px;
  font-size: 10px;
  font-weight: 600;
  color: #ffffff;
}
/* Branded letter fallback — sits under the favicon and shows when the favicon fails to load
   (removed in mountNewsFeeds). */
.news-fav-letter {
  line-height: 1;
}
/* Real site favicon layered over the letter box. White fill keeps transparent logos crisp;
   object-fit contain avoids cropping the mark. */
.news-fav-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #ffffff;
}
.news-feed-foot {
  font-size: var(--ds-text-caption-12);
  color: var(--cg-muted, #8f8f8f);
}
/* news-subscribe-btn-style(2026-09-26) — 이 버튼엔 focus-visible(아래)·터치 영역(44039·
   44059)뿐, 모양 규칙이 없었다. Android 에뮬 Chrome 다크는 UA 기본 버튼(각진 회색 사각형),
   iOS Safari 는 UA 기본 둥근 모양으로 나뉘어 보였다(중앙 기기 검수 05:2x).
   새 리터럴을 만들지 않고 같은 카드 안 보조 행동 버튼인 .source-pill(34275)의 토큰을
   그대로 재사용한다 — 배경 --cg-bg, 테두리 --cg-menu-border, 둥근 필 --ds-radius-pill,
   글자 --cg-text-soft, hover --cg-hover-soft. appearance: none 으로 두 브라우저의 UA 기본
   버튼 모양(각진 회색/둥근 기본)을 먼저 지운 뒤 이 토큰들로 모양을 준다. */
.news-subscribe-btn {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill);
  background: var(--cg-bg);
  color: var(--cg-text-soft);
  font: inherit;
  cursor: pointer;
  transition: background var(--wds-duration-fast) ease, border-color var(--wds-duration-fast) ease;
}
.news-subscribe-btn:hover {
  background: var(--cg-hover-soft);
  border-color: var(--cg-menu-border);
}

/* r53-news-subscribe-dead-button(2026-10-02, 오너 R-53 ④ «정기 구독 눌러도 반응 없음») —
   구독 시트(.news-sub-overlay > .news-sub-sheet)와 실패 안내(newsSubscribeNotice)는 08-01 도입
   (594e08994) 이래 **규칙이 0건**이었다. JS 는 body 끝에 붙였지만 position:static 블록이라
   body overflow:hidden 아래로(1440: top 918 > 900) 또는 고정 앱 셸 «뒤»(390, 투명 배경)로 숨었다 —
   클릭·API·렌더는 다 됐는데 눈에 안 보여 «무반응». 결정형 시트라 하단 «닫기/구독하기» 두 버튼
   (오너 09-25 닫기 규칙). 웹 = 가운데 대화상자, ≤520 = 하단 시트. 새 색·새 키프레임 0 —
   표면 --cg-menu-bg·그림자 --cg-dialog-shadow(삭제 확인 대화상자와 같다), 스크림 --wizon-scrim,
   반경 --wizon-ai-radius-sheet, 열림 wizon-scrim-in/wizon-sheet-up 재사용. z-index 200 = .fin-vc-sheet 와 같은 층. */
.news-sub-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 16px;
  background: var(--wizon-scrim);
  animation: wizon-scrim-in var(--wds-spring-effect-fast-duration) linear both;
}
.news-sub-overlay .news-sub-sheet {
  box-sizing: border-box;
  width: min(480px, 100%);
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 20px 20px 16px;
  border-radius: var(--wizon-ai-radius-sheet);
  background: var(--cg-menu-bg);
  color: var(--cg-text);
  box-shadow: var(--cg-dialog-shadow);
  font-size: var(--ds-text-body-14);
  line-height: 20px;
}
.news-sub-overlay .news-sub-title {
  margin: 0 0 8px;
  font-size: var(--ds-text-title-18);
  font-weight: 600;
  line-height: 28px;
  outline: none;
}
.news-sub-overlay .news-sub-subtitle {
  margin: 0 0 6px;
  font-size: var(--ds-text-body-14);
  font-weight: 600;
}
.news-sub-overlay .news-sub-hint,
.news-sub-overlay .news-sub-note {
  margin: 0 0 12px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
}
.news-sub-overlay .news-sub-group {
  min-width: 0;
  margin: 0 0 14px;
  padding: 0;
  border: 0;
}
.news-sub-overlay .news-sub-group legend {
  margin: 0 0 6px;
  padding: 0;
  font-weight: 600;
}
.news-sub-overlay .news-sub-group .news-sub-note {
  margin: 0 0 8px;
}
.news-sub-overlay .news-sub-group label {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  cursor: pointer;
}
/* 전역 input 규칙(width:100%·min-height:42px)이 라디오를 400px 짜리 원으로 늘렸다(실측) — 시트 안에서만 되돌린다. */
.news-sub-overlay .news-sub-group input[type="radio"] {
  flex: none;
  width: 18px;
  height: 18px;
  min-height: 0;
  margin: 0;
  padding: 0;
  accent-color: var(--cg-send-bg);
}
.news-sub-overlay .news-sub-address {
  overflow-wrap: anywhere;
}
.news-sub-overlay .news-sub-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.news-sub-overlay .news-sub-chip,
.news-sub-overlay .news-sub-row-toggle,
.news-sub-overlay .news-sub-row-delete {
  appearance: none;
  -webkit-appearance: none;
  min-height: 32px;
  padding: 0 12px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill);
  background: var(--cg-bg);
  color: var(--cg-text-soft);
  font: inherit;
  cursor: pointer;
}
.news-sub-overlay .news-sub-chip:hover,
.news-sub-overlay .news-sub-row-toggle:hover,
.news-sub-overlay .news-sub-row-delete:hover {
  background: var(--cg-hover-soft);
}
.news-sub-overlay .news-sub-chip[aria-pressed="true"] {
  border-color: transparent;
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}
.news-sub-overlay [data-news-sub-time] {
  width: auto;
  min-width: 120px;
  min-height: 36px;
  margin-right: 8px;
  padding: 0 8px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--cg-menu-radius);
  background: var(--cg-bg);
  color: var(--cg-text);
  font: inherit;
}
.news-sub-overlay .news-sub-summary {
  margin: 0;
  padding: 10px 12px;
  border-radius: var(--cg-menu-radius);
  background: var(--cg-hover-soft);
}
.news-sub-overlay .news-sub-error {
  margin: 12px 0 0;
  color: var(--wizon-ai-danger-text);
}
.news-sub-overlay .news-sub-error[hidden] {
  display: none;
}
.news-sub-overlay .news-sub-existing {
  margin: 0 0 14px;
}
.news-sub-overlay .news-sub-list {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.news-sub-overlay .news-sub-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--cg-menu-radius);
}
.news-sub-overlay .news-sub-row-text.is-paused {
  color: var(--cg-muted);
}
.news-sub-overlay .news-sub-row-actions {
  display: flex;
  gap: 8px;
}
.news-sub-overlay .news-sub-actions {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 16px;
}
@media (max-width: 520px) {
  .news-sub-overlay {
    align-items: flex-end;
    padding: 0;
  }
  .news-sub-overlay .news-sub-sheet {
    width: 100%;
    max-height: calc(100dvh - var(--wizon-safe-top) - 40px);
    padding-bottom: calc(12px + var(--wizon-safe-bottom));
    border-radius: var(--wizon-ai-radius-sheet) var(--wizon-ai-radius-sheet) 0 0;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration) var(--wds-spring-effect-default) backwards;
  }
  .news-sub-overlay .news-sub-actions > button {
    flex: 1;
    min-height: 44px;
  }
}
@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  .news-sub-overlay .news-sub-sheet {
    animation: none;
  }
}
/* 터치 기기에서는 시트 안 누를 것 전부 44px(앱 공통 터치 계약 — 같은 카드의 .news-subscribe-btn 과 같은 기준). */
@media (pointer: coarse) {
  .news-sub-overlay .news-sub-chip,
  .news-sub-overlay .news-sub-row-toggle,
  .news-sub-overlay .news-sub-row-delete,
  .news-sub-overlay .news-sub-actions > button,
  .news-sub-overlay [data-news-sub-time],
  .news-sub-overlay .news-sub-group label {
    min-height: 44px;
  }
  /* 짧은 분야 이름(«IT»)이 38px 폭으로 줄어 손가락이 빗나갔다 — 폭도 44px. */
  .news-sub-overlay .news-sub-chip {
    min-width: 44px;
  }
}

/* ══ 뉴스 포커스 링 ═══════════════════════════════════════════════════════════════════════
   ★이 표면에 흩어져 있던 네 개의 :focus-visible 규칙은 **한 번도 그려진 적이 없다**. 전역
   기본 링
     `.chatgpt-reference-v1 :focus-visible:not(input):not(textarea):not(select):not([contenteditable="true"])`
   이 (0,3,3)이라 컴포넌트 규칙 (0,2,0)을 전부 눌렀다. 실측(포커스 후 getComputedStyle):
   여덟 컨트롤 전부 outline-color가 전역값 rgba(13,13,13,0.16) — 배경 대비 **1.42:1**
   (다크는 rgba(255,255,255,0.18) = 1.55:1). SC 1.4.11의 3:1에 한참 못 미치고, 사진 위에서는
   배경이 무엇이든 될 수 있으니 더 나쁘다.
   ★해결은 !important도, 전역 규칙 약화도 아니다. `:is()`는 인자 중 «가장 센 것»의 특이도를
   가지므로 이 선택자는 (0,4,0) — 클래스 자리가 4 > 3이라 **순서와 무관하게** 전역을 이긴다.
   다른 표면의 전역 링은 한 글자도 바뀌지 않는다.
   ★두 색 링(안쪽 흰색 3px + 바깥 검정 3px)인 이유: 사진 위에서는 배경 대비를 보장할 수
   없으므로 **링끼리** 대비를 갖게 한다(#fff 대 #000 = 21:1). 어떤 배경에서도 둘 중 하나는
   3:1을 넘는다 — 실측: 흰 배경에서 검정 링 21:1, 검은 사진에서 흰 링 21:1, 중간 회색(128)
   에서 흰 3.95:1 / 검정 5.32:1.
   ★★그리고 이 규칙은 @media (pointer: coarse) 안의 전역 `outline: none`(같은 (0,3,3))도
   이긴다 — 그래서 터치스크린 노트북·키보드 붙인 태블릿에서 뉴스 컨트롤 26개의 포커스가
   «사라지지» 않는다. 그 전역 억제는 그대로 두었다(다른 표면은 종전과 동일). */
.chatgpt-reference-v1
  .news-feed
  :is(
    .news-lead,
    .news-second-link,
    .news-card-link,
    .news-topic,
    .news-topics-slide,
    .news-hero-nav,
    .news-hero-autoplay,
    .news-arrow,
    .news-dot,
    .news-bookmark,
    .news-subscribe-btn):focus-visible {
  outline: 3px solid var(--ds-color-focus-ring-contrast-inner);
  outline-offset: 0;
  box-shadow: 0 0 0 6px var(--ds-color-focus-ring-contrast-outer);
}
/* 선택된 주제 탭은 강조색 링으로 «선택»을 말한다. 포커스 링이 그 자리를 덮어쓰지 않도록
   바깥에 한 겹 더 얹는다 — 포커스와 선택이 동시에 보인다.
   ★aria-pressed → aria-selected. 탭은 «눌림»이 아니라 «선택»이다(doc 343 §4.3). 선택자를
   같이 안 바꾸면 이 규칙이 조용히 아무 요소에도 안 걸린다. */
.chatgpt-reference-v1 .news-feed .news-topic[aria-selected="true"]:focus-visible {
  box-shadow: 0 0 0 6px var(--ds-color-focus-ring-contrast-outer), 0 0 0 8px var(--news-accent);
}
/* ★★히어로 슬라이드만 예외다. .news-hero는 overflow:hidden이고 슬라이드가 그 상자를 «꽉»
   채우므로(실측: 클리퍼와의 여백이 사방 0px) 바깥으로 그리는 링은 통째로 잘린다 — 실제로
   포커스 스크린샷에서 링이 한 픽셀도 보이지 않았다. 레일과 달리 여기엔 패딩을 줄 자리가
   없다(패딩을 주면 캐러셀 트랙 계산이 어긋난다). 그래서 이 컨트롤만 링을 **안쪽**에 그린다.
   ★box-shadow: inset은 배경 레이어라 사진(.news-thumb, z-index:1) 아래로 깔려 보이지 않는다.
   그래서 의사요소에 얹고 z-index를 사진·밴드보다 위로 올린다.
   ★두 겹의 순서: 목록 뒤쪽 그림자가 «뒤»에 깔린다. 흰색 0..3px이 위, 검정 0..6px이 아래라
   화면에는 흰 3px(안) + 검정 3px(밖)으로 보인다 — 바깥 두 규칙과 같은 21:1 쌍이다.
   ★outline을 끄는 건 «포커스를 지우는 것»이 아니라 같은 요소에서 더 잘 보이는 표시로
   **교체**하는 것이다. 아래 ::after가 없으면 이 규칙은 접근성 후퇴다 — 함께 지울 것. */
.chatgpt-reference-v1 .news-feed .news-lead:focus-visible {
  outline: none;
  box-shadow: none;
}
.chatgpt-reference-v1 .news-feed .news-lead:focus-visible::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4; /* 사진(1) < 밴드(2) < 화살표·도트(3) < 포커스 링 */
  pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 0 0 3px var(--ds-color-focus-ring-contrast-inner), inset 0 0 0 6px var(--ds-color-focus-ring-contrast-outer);
}

/* ---- Generated file download card (doc 171 P2 / batch D3) -----------------------------
   Server-generated file (e.g. xlsx) sits below the answer body as a single download
   affordance. Tone matches .md-table-copy (small outline button on --cg-* surface tokens);
   layout matches .news-feed (bordered card, container query for narrow reflow). */
.generated-file-card {
  container-type: inline-size;
  display: flex;
  /* wrap lives here, not in the @container block below: a container query styles a container's
     descendants, never the container element itself, so the narrow-width rule cannot flip the
     card's own flex-wrap. Without it the button's flex-basis: 100% overflows instead of wrapping. */
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 14px;
  padding: 14px 16px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-menu-radius);
  background: var(--cg-surface);
  color: var(--cg-text);
}
.generated-file-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: var(--wizon-ai-radius-control);
  color: var(--cg-text-soft, var(--muted));
}
/* The attachment artwork carries its own colour and its own paper shape, so the grey tile
   the old inline glyph needed would only box it in. Kept for the svg branch in case a card
   ever falls back to a line icon. */
.generated-file-icon svg,
.generated-file-icon .wds-inline-icon {
  width: 18px;
  height: 18px;
  background: var(--cg-hover-soft, var(--surface-2));
  border-radius: var(--wizon-ai-radius-control);
}
.generated-file-icon .composer-attachment-art {
  width: 32px;
  height: 32px;
  object-fit: contain;
}
.generated-file-body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.generated-file-name {
  overflow: hidden;
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.generated-file-meta {
  font-size: var(--ds-text-caption-12);
  color: var(--cg-text-soft, var(--muted));
}
/* doc 171 S2c — the actions beside a generated file. Shaped from .generated-file-download
   below rather than from a design-system class name: the portal stylesheet has no ds-button,
   and borrowing one that does not exist is how the save button ended up as wrapped text in a
   34px box on 2026-08-10. */
.generated-file-action {
  display: inline-flex;
  flex: none;
  align-items: center;
  padding: 6px 12px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-radius-control);
  background: var(--cg-text, var(--text));
  color: var(--cg-surface);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
}

.generated-file-more {
  position: relative;
  flex: none;
}

.generated-file-more-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  padding: 6px 8px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-radius-control);
  background: var(--cg-surface);
  color: var(--cg-text, var(--text));
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

.generated-file-more-menu {
  position: absolute;
  right: 0;
  z-index: 20;
  display: grid;
  min-width: 168px;
  max-width: calc(100vw - 24px);
  margin-top: 4px;
  padding: 4px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-radius-menu-row);
  background: var(--cg-surface);
  box-shadow: var(--ds-shadow-md);
  transform: translateX(var(--generated-file-menu-shift-x, 0));
}

.generated-file-more-menu[hidden] {
  display: none;
}

.generated-file-more-menu button {
  padding: 8px 10px;
  border: 0;
  border-radius: var(--ds-radius-xs);
  background: transparent;
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

.generated-file-more-menu button:hover,
.generated-file-more-menu button:focus-visible {
  background: color-mix(in srgb, var(--cg-text) 6%, transparent);
}

@media (any-pointer: coarse), (max-width: 520px) {
  .generated-file-more-trigger {
    min-width: 44px;
    min-height: 44px;
    padding: 0 8px;
  }

  .generated-file-more-menu button {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding-block: 0;
  }
}

.generated-file-download {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-radius-control);
  background: var(--cg-surface, #fff);
  color: var(--cg-text, var(--text));
  font: inherit;
  font-size: var(--ds-text-caption-12);
  line-height: 1;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, opacity 120ms ease;
}
.generated-file-download:hover {
  background: var(--cg-hover-soft, var(--surface-2));
}
.generated-file-download:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, #0d0d0d);
  outline-offset: 2px;
}
.generated-file-download:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}
.generated-file-download.is-copied {
  color: var(--accent, #2f6bff);
}
.generated-file-download.is-expired {
  color: var(--cg-text-soft, var(--muted));
}
.generated-file-download-icon {
  display: inline-flex;
}
.generated-file-download-icon svg,
.generated-file-download-icon .wds-inline-icon {
  width: 13px;
  height: 13px;
}
.generated-file-card.is-expired .generated-file-icon {
  opacity: 0.6;
}
.generated-file-card.is-expired .generated-file-meta {
  color: var(--cg-text-soft, var(--muted));
}
@container (max-width: 520px) {
  /* .generated-file-body deliberately keeps its base flex: 1 1 auto here (no flex-basis: 100%
     override) so it shares the first row with the icon instead of forcing the icon to sit alone
     on a mostly-empty line. Only the download button drops to its own full-width row below. */
  .generated-file-download {
    flex-basis: 100%;
    justify-content: center;
  }
}
@media (prefers-reduced-motion: reduce) {
  .generated-file-download {
    transition: none;
  }
}

/* ---- Document summary insight card (D2, docs/53 §7.1 · specs/2026-06-03) ------------------
   Evidence-backed insight surface for attached-document answers: enterprise-dense, subdued,
   ≤8px radius, no decorative gradients, tone via border/text (never color-only — WCAG AA). Sits
   below the prose answer (attach). Container query reflows the fact tiles, not a breakpoint.
   Tokens only (no raw hex — anti-drift ratchet); tone accents reuse the semantic danger/success/
   accent tokens (no warning token exists, so warning shares the accent emphasis). */
/* 2026-08-26 공용 위젯 카드 셸로 이관(차별화 축① 1차 슬라이스).
   컨테이너·표면·실선·모서리·여백·본문색은 이제 .wiz-card[data-wiz-density="compact"]
   가 소유한다(wizon-widget-card-shell.css). 여기 남아 있던 같은 선언은 지웠다 —
   두 시트가 같은 속성을 걸면 «나중에 로드된 쪽»만 이기고, 지는 선언은 화면에 한 번도
   닿지 않으면서 읽는 사람만 헷갈리게 한다(이 시트 14600행대에 같은 사고 기록 있음).
   raw 14px/16px 도 함께 사라진다 — 스케일 밖 값이 토큰으로 흡수된 자리다. */
.doc-summary-head {
  margin-bottom: 0;
}
/* 배지와 상태 칩을 한 덩어리로 묶는 eyebrow 슬롯. 셸의 __headings 안에서 제목 위에
   선다 — 이전에는 셋(배지·상태·문서명)이 한 줄에 나란했지만, 문서명은 «출처/시각»
   이라 셸의 __meta 로 갈라졌다. */
.doc-summary-eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wiz-card-gap-tight);
}
.doc-summary-badge {
  padding: 2px 8px;
  border-radius: var(--ds-radius-xs);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}
.doc-summary-status {
  padding: 2px 8px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill);
  font-size: var(--ds-text-caption-12);
  color: var(--cg-text-soft);
}
.doc-summary-status[data-status="completed"] {
  border-color: color-mix(in oklab, var(--accent) 40%, transparent);
  color: var(--accent);
}
.doc-summary-doc {
  margin-left: auto;
  overflow: hidden;
  max-width: 100%;
  font-size: var(--ds-text-caption-12);
  color: var(--cg-text-soft);
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 제목과 개요의 여백·크기·굵기·색은 셸의 .wiz-card__title / .wiz-card__subtitle 이
   소유한다. 세로 리듬은 이제 margin-bottom 이 아니라 __headings 의 gap 이 만든다.
   ★한 가지 «의도한» 변화: 개요가 본문색(--cg-text)에서 보조색(ink-soft)으로 내려간다.
   제목 아래 딸린 문장이 제목과 같은 농도로 서 있으면 위계가 안 생기고, 옆 카드
   (commerce)의 부제는 이미 --wizon-ai-text-secondary 였다. 두 카드가 «부제가 무슨
   색인가»에 서로 다른 답을 갖고 있던 걸 하나로 맞춘 자리다. */
/* (규칙 없음 — 셸이 전부 소유한다. 여기에 같은 속성을 한 줄이라도 남기면 «지는
   선언»이 되어 읽는 사람만 속인다.) */
.doc-summary-facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 10px;
}
.doc-summary-fact {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-sm);
  background: var(--cg-hover-soft);
  min-width: 0;
}
/* ★경고와 위험은 다른 단계다. 두 tone이 한 선택자에 묶여 --cg-danger-fg 하나를 쓴 탓에
   화면에서 같은 빨강으로 뭉개졌다 — fact 타일의 tone 신호는 이 테두리 하나뿐이라
   (app.js의 타일은 label/value에 tone별 스타일이 없다) 두 단계를 구분할 방법이 없었다.
   .doc-summary-notice가 이미 warning/danger를 나눠 쓰는 것과 같은 형태로 맞춘다.
   실브라우저 실측(color-mix 55% 통과 후 캔버스로 되읽은 합성 픽셀, 타일 배경
   --cg-hover-soft 대비): 라이트 #b45309→#d19c73 2.19:1, 다크 #f59e0b→#956514 3.18:1.
   다크는 이 자리에 있던 빨강(2.61:1)보다 오히려 나아진다. 옛 규칙을 되살려 같은
   엔진에서 잰 BEFORE는 두 tone 거리가 0.0 — 말 그대로 같은 색이었다(AFTER 11.3/21.2).
   ★믹스 강도 55%→80%→85%(family 단위 단일 값. tone별로 다르게 두지 않는다). 55%로는
   여섯 경우 중 넷이 SC 1.4.11의 3:1에 미달했다: 라이트 warning 2.19 / danger 2.59 /
   positive 2.18, 다크 danger 2.61. 이 테두리가 심각도를 나르는 유일한 신호라 여기서
   색은 장식이 아니라 정보다. 3:1을 처음 넘기는 값은 tone·테마별로 라이트 75/63/75%,
   다크 53/63/50%(warning/danger/positive)라 라이트 warning·positive가 family 전체를
   묶는 제약이다. 최저 통과값 75%는 3.01:1로 여유가 없어 토큰이 조금만 움직여도
   되돌아간다.

   80%(최저 3.27:1)에서 85%로 한 번 더 올린 근거는 «배경 대비»가 아니라 «중립 대비»다.
   타일이 톤을 가졌다는 걸 읽히려면 톤 없는 이웃 타일과 구별돼야 하는데, 그 거리가
   80%에서 다크 danger 2.70까지 내려간다. 85%면 배경 대비 최저 3.52:1, 중립 대비도
   라이트 3.02 / 다크 2.97로 두 축이 함께 ~3에 선다. 중립 대비는 1.4.11의 요구사항이
   «아니다»(인접색이 아니라 옆 타일이고, 색상차가 휘도비로 안 잡히는 몫을 메운다) —
   요구가 아니라 «이 테두리가 유일한 신호»라는 전제를 끝까지 민 결과다. 형제
   .doc-summary-notice가 같은 토큰을 믹스 없이 100%로 쓰므로 85%도 여전히 옅은 쪽이다.

   강도를 var()로 빼지 않은 건 이 시트에 color-mix 퍼센트를 var로 넣은 선례가 없어서다
   (검증 안 된 패턴을 새로 들이는 것보다 기존 형태 유지가 낫다). 중립 테두리
   (--cg-menu-border, 라이트 1.17:1 / 다크 1.53:1)는 일부러 그대로 둔다: 메뉴·팝오버까지
   쓰는 앱 전역 토큰이라 여기서 결정할 값이 아니고, 1.4.11이 겨냥하는 건 '상태'를
   나르는 tone 테두리이며, 무엇보다 중립을 올리면 위의 중립 대비 거리가 «줄어» 신호가
   죽는다. 별도 관측: 타일 배경 대 카드 배경은 라이트 1.10 / 다크 1.30이다.

   측정은 실브라우저 합성 픽셀 캔버스 되읽기. 하네스 body class에 chatgpt-reference-v1
   필수 — --cg-*는 :root가 아니라 이 클래스에 스코프되어, 빠뜨리면 전 토큰이 조용히 빈
   값이 되고(테두리가 rgb(13,13,13), 타일 배경이 투명으로 떨어진다) 측정이 통째로
   거짓이 된다. 가정이 아니라 클래스를 떼서 실제로 그렇게 되는 걸 확인했다. */
.doc-summary-fact[data-tone="warning"] {
  border-color: color-mix(in oklab, var(--cg-warning-fg) 85%, transparent);
}
.doc-summary-fact[data-tone="danger"] {
  border-color: color-mix(in oklab, var(--cg-danger-fg) 85%, transparent);
}
.doc-summary-fact[data-tone="positive"] {
  border-color: color-mix(in oklab, var(--cg-success) 85%, transparent);
}
.doc-summary-fact-label {
  font-size: var(--ds-text-caption-12);
  color: var(--cg-text-soft);
}
.doc-summary-fact-value {
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.doc-summary-fact-unit {
  margin-left: 2px;
  font-size: var(--ds-text-caption-12);
  font-weight: 500;
  color: var(--cg-text-soft);
}
.doc-summary-chip {
  align-self: flex-start;
  margin-top: 2px;
  padding: 1px 6px;
  border-radius: var(--ds-radius-xs);
  background: var(--cg-surface);
  border: 1px solid var(--cg-menu-border);
  font-size: var(--ds-text-caption-12);
  color: var(--cg-text-soft);
}
.doc-summary-rows {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin: 0 0 10px;
  border-top: 1px solid var(--cg-menu-border);
}
.doc-summary-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 7px 0;
  border-bottom: 1px solid var(--cg-menu-border);
}
.doc-summary-row-label {
  flex: 0 0 auto;
  min-width: 96px;
  margin: 0;
  font-size: var(--ds-text-caption-12);
  color: var(--cg-text-soft);
}
.doc-summary-row-value {
  flex: 1 1 60%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: var(--ds-text-caption-13);
  overflow-wrap: anywhere;
}
/* doc 234 W6 — 아티팩트 고위험 고지가 같은 레이아웃을 쓴다. 값을 복제하지 않고
   선택자만 얹는다: 성격이 같은 고지가 두 모양으로 보이면 사용자는 둘을 다른 것으로
   읽는다. 항목 색은 아래 .doc-summary-notice[data-tone] 규칙이 그대로 정한다. */
.doc-summary-notices,
.artifact-notices {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 8px;
}
/* 문서요약은 본문 위, 아티팩트 고지는 표 뒤에 붙으므로 위쪽 간격이 필요하다. */
.artifact-notices {
  margin-top: 8px;
}

/* doc 234 v5 — 2줄 셀의 보조줄. 부속 열이 왼쪽 이웃 안으로 접혀 들어온 자리다.
   ★주값과 같은 무게로 보이면 접은 의미가 없다. 한 단계 작고 흐리게 두어 «이 값의
   속성»으로 읽히게 한다. 색은 기존 토큰만 쓴다(새 리터럴 없음 — raw-color 래칫). */
/* kpi.trend — 추세 요약 띠. 정렬 기준 캡션과 같은 자리(스크롤 영역 밖, 카드 안쪽 맨 위)에
   앉아 표 전체에 대한 한 줄 요약으로 읽힌다. */
/* ★접두사 필요 — `.md-table-tile-label` 주석 참조. 이 띠도 <p>다: 접두사 없이는 전역
   산문 규칙의 margin 10px가 이겨 표와의 간격이 6px가 아니게 된다. */
.message-body .md-table-wrap .md-table-trend {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  margin: 0 0 var(--ds-space-1);
  padding: 0 2px;
}
.md-table-trend svg {
  flex: none;
  overflow: visible;
}
/* ★선만 그린다. 면(area)으로 채우면 0 기준처럼 보이는데 이 선은 min~max 정규화라
   기준선이 0이 아니다 — 채우면 그림이 거짓말을 시작한다. */
.md-table-trend polyline {
  fill: none;
  /* ★데이터 마크는 «차트 계열색»을 쓴다. 원래 `var(--cg-focus-ring, var(--accent))` 였는데
     포커스 링은 테두리 장식용 토큰이라 대비 예산이 다르다 — 2026-08-07 라이브 실측에서
     이 선의 실효 대비가 흰 배경 대비 1.43:1 이었다(WCAG 2.2 SC 1.4.11 기준 3:1 미달).
     의도했던 `var(--accent)` 폴백은 `--cg-focus-ring` 이 정의돼 있어 한 번도 발화하지
     않았다 — 폴백이 죽은 토큰 선택을 가려 준 셈이다.
     ★같은 답변 안의 인라인 차트 선과 **같은 토큰**을 쓴다. 한 답변 안에서 추세를 말하는
     그림 둘이 다른 색 언어를 쓸 이유가 없다. */
  stroke: var(--ds-chart-slot-1);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.md-table-trend-caption {
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-12);
  line-height: 1.45;
}
/* ★레일은 «한 번도 그려진 적이 없다» (2026-08-08 라이브 실측으로 확정).
   규칙이 `td` 로만 걸려 있었는데, 렌더러는 첫 열을 `<th scope="row">` 로 낸다(접근성
   결정). 레일이 붙는 시간 열은 대개 첫 열이므로 선택자가 **한 번도 매칭되지 않았다**.
   증거: 일정 표 5행 전부 `matchesTdSelector=false`·`matchesThVariant=true`,
   `padding-left` 가 규칙의 22px 이 아니라 12px, `::before`/`::after` 가 `content: none`.
   ★판정기는 통과하고 클래스도 붙어 있었다 — 「적용됐다」와 「그려졌다」는 다른 말이다. */
/* schedule.timeline — 시간 열의 레일. 점과 잇는 선으로 «순서»를 보여 준다.
   ★크기를 말하지 않는다. 이미 표에 적힌 날짜의 순서를 확인해 잇는 것뿐이라, 고위험
   도메인(마감·제출기한)에서도 그린다. */
.md-table th.md-table-timeline,
.md-table td.md-table-timeline {
  position: relative;
  padding-left: 22px;
  white-space: nowrap;
}
.md-table th.md-table-timeline::before,
.md-table td.md-table-timeline::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--cg-menu-border, var(--line));
}
.md-table th.md-table-timeline.is-first::before,
.md-table td.md-table-timeline.is-first::before {
  top: 50%;
}
.md-table th.md-table-timeline.is-last::before,
.md-table td.md-table-timeline.is-last::before {
  bottom: 50%;
}
.md-table th.md-table-timeline::after,
.md-table td.md-table-timeline::after {
  content: "";
  position: absolute;
  left: 2px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -3px;
  border-radius: var(--ds-radius-pill, 999px);
  /* ★폴백 원시값을 쓰지 않는다 — 드리프트 래칫이 잡았다(1050 → 1051). 점 안쪽은
     카드 표면색이어야 레일 선이 점을 관통하지 않는다. */
  background: var(--cg-surface);
  box-shadow: inset 0 0 0 1.5px var(--wizon-ai-focus-ring, var(--accent));
}
/* 수치 열의 데이터 바 (doc 234 v5). 셀 뒤에 값에 비례하는 띠를 깔아 숫자 열을 읽지 않고도
   대소가 보이게 한다. 막대는 이미 셀에 있는 숫자를 다시 그릴 뿐이라 새 주장을 얹지 않는다.
   ★scaleX로 늘린다 — width 애니메이션은 레이아웃을 다시 계산시키고, 여기선 정적이어도
   같은 이유로 합성 가능한 속성을 쓴다. 원점을 왼쪽에 두어야 0에서 자란다. */
/* v7 — 지표 타일. 규범이 「a handful of headline numbers」에 지정한 형태다(차트가 아니다).
   ★깊이: 카드는 그림자로 «뜨고» 이 띠는 inset으로 «눌린다». 두 층이 생기면 표가 세 번째
   층으로 자연히 뒤로 물러난다 — 카드 안이 한 층뿐이라 평평했던 것을 여기서 푼다. */
/* ── 등장 모션 (⑤, doc 234) ──
   ★목적이 있는 모션만 넣는다(조사: 「모든 애니메이션에 목적이 있어야 한다」).
   띠는 먼저 떠올라 «여기가 요약»이라는 읽기 순서를 세우고, 자는 오른쪽 기준점에서 자라며
   크기 부호화를 «재어 보이게» 한다. 장식이 아니라 위계와 인코딩을 말한다.
   ★★기본 상태는 «최종 상태»다. scaleX(0)·opacity 0을 기본으로 두면 reduced-motion
   사용자에게 막대가 통째로 사라진다 — 애니메이션은 항상 없어도 되는 것이어야 한다. */
@keyframes md-table-tiles-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ── 첫 열 고정 (v10-2) ──
   ★히트맵의 전제조건이다. 3열 이상 표는 가로 스크롤인데 행 식별자가 같이 밀려 사라지면
   색만 남고 이름이 없어진다 — 「이 진한 셀이 어느 행이었지」가 되어 히트맵이 아무 말도
   못 한다. 자(bar)는 값이 셀 안에 함께 있어 덜 치명적이었지만, 히트맵은 값과 이름이 갈린다.
   Mobbin 실측: Stake·Formula 1·theScore가 전부 이 방식이다 — 가로 스크롤 표의 관례다.
   ★배경은 «불투명»이어야 한다. 없거나 반투명이면 스크롤한 셀이 밑으로 비쳐 글자가 겹친다.
   ★z-index가 필요하다 — 히트맵 셀에도 배경이 있어 문서 순서로는 뒤에 깔린다. */
.md-table-scroll .md-table th:first-child,
.md-table-scroll .md-table td:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--cg-surface, var(--ds-color-surface));
  box-shadow: 1px 0 0 var(--cg-menu-border, var(--line)),
    6px 0 12px -8px color-mix(in srgb, var(--ds-color-shadow-ink) 18%, transparent);
}
.md-table-scroll .md-table thead th:first-child {
  z-index: 3;
  /* ★v7 — 머리글 밴드를 이 모서리 칸까지 잇는다. 위 고정열 규칙이 `--cg-surface`(흰 면)를
     주는데, 머리글 행이 밴드가 된 뒤로는 이 한 칸만 흰색으로 남아 밴드가 끊겨 보인다.
     불투명 배경이어야 한다는 고정열의 전제는 그대로 지킨다(밴드도 불투명 토큰이다). */
  background: var(--cg-hover-soft);
}
/* ★고정 열은 자기 배경을 가지므로 행 hover 배경을 상속받지 못한다 —
   이 규칙이 없으면 마우스를 올렸을 때 첫 칸만 색이 안 바뀐다. */
@media (hover: hover) {
  .md-table-scroll .md-table tbody tr:hover th:first-child,
  .md-table-scroll .md-table tbody tr:hover td:first-child {
    background: var(--cg-hover-soft, var(--ds-color-surface-muted));
  }
}
/* Q1 수리(build-r29-p3a, 09-24 운영 1440 실측): 위 hover 규칙과 같은 뿌리의 결함이
   총계 행(.md-table-total-row, 9926행에서 th/td 에 `--cg-hover-soft` 를 준다)에도
   있었다 — 고정 열 규칙(.md-table-scroll .md-table td:first-child, 특이성 0,3,1)이
   총계 행 규칙(.md-table .md-table-total-row td, 특이성 0,2,1)보다 높아 첫 칸만
   흰 조각으로 남았다(09-24 실측: 「회색 줄무늬 행에서 첫 칸만 흰 조각」). hover
   override 와 똑같은 문법으로 총계 행도 override 한다 — 새 메커니즘을 만들지 않고
   같은 패턴을 한 번 더 쓴다. */
.md-table-scroll .md-table tbody tr.md-table-total-row th:first-child,
.md-table-scroll .md-table tbody tr.md-table-total-row td:first-child {
  background: var(--cg-hover-soft);
}


/* 행 끝의 추이 열 (W2).
   ★Uniswap·Pinterest Trends가 쓰는 관용구다 — 숫자 넉 줄을 읽어야 알던 «올랐는가»를
   한 번에 보여준다. 표를 문서에서 위젯으로 바꾸는 가장 큰 한 걸음이다.
   ★선은 요약이지 대체가 아니다. 값은 셀에 그대로 남고, 계획이 없는 행은 빈 칸이다 —
   없는 추세를 그리느니 비워 둔다.
   ★색은 «추이» 종류 색을 쓴다. 히트맵 램프와 같은 계열이라 카드 안에서 따로 놀지 않는다. */
/* ★추이 열은 오른쪽에 고정한다 — 첫 열 고정의 **거울상**이다.
   2026-08-02 실측: 추이 열을 붙이자 표가 83px 넘쳤고 그 넘침이 정확히 추이 열을 잘라
   SVG 64px 중 15px만 남았다. 선의 77%가 화면 밖이었다. 계산값만 보면 정상이라
   확대 스크린샷이 아니었으면 놓쳤다.
   ★업계 표준이 이 경우를 다룬다 — 「맨 왼쪽 열을 고정하고, 때로는 합계 같은 **요약을
   담는 맨 오른쪽 열도** 고정한다」(Pencil & Paper). 추이 열이 그 «요약»이다.
   ★그림자는 방향만 뒤집는다. 같은 방향이면 표 밖으로 나가 층으로 안 읽힌다. */
.md-table-sparkline-head,
.md-table-sparkline-cell {
  position: sticky;
  right: 0;
  z-index: 2;
  background: var(--cg-surface, var(--ds-color-surface));
  box-shadow: -1px 0 0 var(--cg-menu-border, var(--line)),
    -6px 0 12px -8px color-mix(in srgb, var(--ds-color-shadow-ink) 18%, transparent);
}
.md-table-sparkline-head {
  /* 머리글은 다른 열과 같은 규칙을 따른다 — 여기서만 다르면 줄이 어긋나 보인다. */
  text-align: center;
  /* ★교차 지점에서 본문 위에 선다. 첫 열 머리글이 3인 것과 같은 이유다. */
  z-index: 3;
}
.md-table-sparkline-cell {
  width: 1%; /* 내용만큼만 — 데이터 열의 폭을 뺏지 않는다 */
  text-align: center;
  vertical-align: middle;
}
/* ★고정 열은 자기 배경을 가지므로 행 hover 배경을 상속받지 못한다 — 첫 열과 같은 처리.
   토큰도 첫 열이 쓰는 것을 그대로 쓴다. 다르면 두 고정 열의 hover 색이 갈린다. */
@media (hover: hover) {
  .md-table tbody tr:hover .md-table-sparkline-cell {
    background: var(--cg-hover-soft, var(--ds-color-surface-muted));
  }
}
.md-table-sparkline {
  display: block;
  width: 64px;
  height: 16px;
  margin: 0 auto;
  overflow: visible;
}
.md-table-sparkline-line {
  fill: none;
  stroke: var(--ds-kind-trend-mark, currentColor);
  stroke-width: 1.5;
  /* 꺾이는 지점이 뾰족하면 작은 크기에서 점처럼 튄다. */
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

@media (prefers-reduced-motion: no-preference) {
  /* 선이 왼쪽에서 자라난다 — 시간 방향과 같아 «읽는 방향»을 강화한다.
     ★`stroke-dashoffset`은 컴포지터에 남지 않지만 요소가 64×16으로 작고 행당 하나뿐이라
     비용이 무시할 수준이다. 레이아웃은 건드리지 않는다.
     ★`--ds-motion-default`는 **지속시간+이징을 함께** 담는다(`220ms cubic-bezier(...)`).
     뒤에 이징을 또 붙이면 단축 속성이 무효가 된다 — 실제로 그렇게 썼다가 애니메이션이
     통째로 드롭됐다. */
  .md-table-sparkline-line {
    animation: md-table-sparkline-draw var(--ds-motion-default) forwards;
  }
}

/* ★대시는 키프레임 «안»에만 둔다. 기본 규칙에 두면 애니메이션이 어떤 이유로든 안 돌 때
   `stroke-dashoffset`만 남아 **선이 통째로 사라진다** — 2026-08-02 실화면에서 정확히 그
   상태였다(선 길이 61에 오프셋 120). 모션은 실패해도 콘텐츠는 보여야 한다. */
@keyframes md-table-sparkline-draw {
  from {
    stroke-dasharray: 120;
    stroke-dashoffset: 120;
  }
  to {
    stroke-dasharray: 120;
    stroke-dashoffset: 0;
  }
}


/* 히트맵 범례 (W1-3).
   ★조사한 히트맵 화면은 예외 없이 범례를 갖고 있었는데(Klaviyo·Vanta·Gorgias) 우리만
   없었다. 5단계로 칠하면서 무엇이 높고 낮은지 안 말하면 색각 이상 사용자에게는 아무
   정보도 아니다.
   ★더 중요한 것은 «축»이다. 같은 색이 어떤 표에서는 이 행 안에서 크고 다른 표에서는
   이 열 안에서 크다 — 안 알려주면 표 전체에서 큰 값으로 오독된다.
   ★견본 색은 셀 규칙에 **선택자를 덧붙여** 선언을 공유한다. 따로 쓰면 팔레트를 바꿀 때
   한쪽만 바뀌어 범례가 다른 표를 설명하게 된다. */

@media (max-width: 520px) {

}

/* ── 종류 chip (④, doc 234 v9) ──
   ★색은 «종류»를 말하고 «평가»를 말하지 않는다. 빨강·초록은 이 제품에서 이미
   danger/success를 뜻해 범주형 팔레트에서 영구 배제했다(--ds-kind-* 생성기 참조).
   ★이 트랙은 색각이상 «분리도»를 일부러 포기하고 대비 균일성을 샀다 — 카드당 종류가
   하나만 뜨고 chip에 늘 글자가 함께 가므로 색이 안 보여도 잃는 정보가 0이다.
   그래서 chip은 절대 아이콘·점만으로 서지 않는다(애플 HIG: 색에만 의존하지 말라). */
.md-table-kind-chip {
  /* ★v7-1 — `flex-start` → `center`. 옛 툴바는 `align-items: flex-start` 에 제목이
     두 줄(이름 + "N열 × M행")이라 칩이 첫 줄에 맞으려면 위로 붙어야 했다. v7에서 부제가
     sr-only 가 되며 툴바를 `center` 로 바꿨는데 이 칩만 따라오지 않아, 실측 결과 칩
     중심이 제목 중심보다 **9.8px 위**에 떠 있었다. 툴바가 가로 정렬을 소유하므로
     여기서는 교차축을 거스르지 않는다. */
  /* ★v11 — 채움을 뺐다. 툴바 한 줄에서 색이 채워진 유일한 요소가 «누를 수 없는» 이 칩이고
     정작 선택된 세그먼트는 흰색이라, 사용자가 「추이가 선택됨」으로 읽었다(실측). 색은
     여전히 «종류»를 말하지만 글자 위에서 말한다 — 이 파일의 원래 계약대로 라벨이 정보를
     온전히 지므로, 채움을 빼도 잃는 정보는 0이다.
     ★`margin-left: auto` 도 뺐다. 이제 제목 무리 안이라 밀어낼 것이 없다. */
  flex: none;
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
  white-space: nowrap;
}
.md-table-kind-chip[data-kind="compare"] {
  color: var(--ds-kind-compare-ink);
}
.md-table-kind-chip[data-kind="trend"] {
  color: var(--ds-kind-trend-ink);
}
.md-table-kind-chip[data-kind="schedule"] {
  color: var(--ds-kind-schedule-ink);
}
.md-table-kind-chip[data-kind="rank"] {
  color: var(--ds-kind-rank-ink);
}
/* ── 근거 표기 (④) ──
   ★색을 주지 않는다. 「문서 근거」에 색을 얹으면 「모델 지식보다 낫다」는 평가가 되고,
   그건 색이 하면 안 되는 일이다. 구분은 글자가 한다.
   ★<p>라서 접두사가 필요하다 — 없으면 전역 산문 규칙(0,2,1)에 져서 본문 크기로 렌더된다. */
.message-body .md-table-wrap .md-table-provenance {
  margin: var(--ds-space-3) 0 0;
  padding-top: var(--ds-space-3);
  border-top: 1px solid var(--cg-menu-border, var(--line));
  font-size: var(--ds-text-caption-12);
  color: var(--cg-muted);
}
/* ══════════════════════════════════════════════════════════════════
   T1 — 안 B 본문 편입 (doc 626 §9, 오너 확정 1ⓐ+2ⓒ 2026-09-18)
   캡션(산문) → 표(헤어라인) → 푸터(출처 + 조용한 액션). 값 출처:
   answer-table-presentation-research-judge design-brief §8축 실측.
   ══════════════════════════════════════════════════════════════════ */
/* 캡션: `.md-table-title`(카운터·폴백 숨김·승격 레벨 마진·액센트 바 전부 상속)에서
   크기/굵기만 되돌린다 — 카드 헤드라인 잔재인 18px 대신 «산문의 강조 문장» 크기
   (15/600, 390 16)로. `<p>`라서 `.chatgpt-reference-v1 .message-body p`(0,2,1, 41743행
   — 본문 16px/400)에 실측으로 졌다(768폭 렌더 확인: 16px/400 vs 의도한 15px/600).
   `.message-body .md-table-wrap .md-table-caption`(0,3,0)로 특이도를 올려 이긴다. */
.message-body .md-table-wrap .md-table-caption {
  margin: 0 0 var(--ds-space-2) 0;
  font-size: var(--ds-text-body-15);
  font-weight: var(--ds-weight-semibold, 600);
  line-height: 1.5;
}
@media (max-width: 520px) {
  .message-body .md-table-wrap .md-table-caption {
    font-size: var(--ds-text-body-16);
  }
}
/* 푸터: 왼쪽 출처, 오른쪽 «크게 보기»+«···». 흐름에 상주 액션 0개 — 둘 다 조용한
   텍스트 버튼(.md-table-quiet)이다. 표↔푸터 간격은 judge 실측 10px 대신 토큰
   `--ds-space-2`(8px)를 쓴다 — 세로 리듬 값 종류를 {8,20}(≤3) 안에 묶어 두기 위함
   (judge 표는 하드코딩 목업 값, 이 파일은 토큰 스케일에 묶여야 래칫이 깨지지 않는다). */
.md-table-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-3);
  flex-wrap: wrap;
  margin: var(--ds-space-2) 0 0;
}
.md-table-foot[data-empty="1"] {
  display: block;
  font-size: var(--ds-text-caption-12);
  color: var(--cg-muted);
}
.md-table-foot .md-table-provenance {
  margin: 0;
  padding-top: 0;
  border-top: 0;
}
.md-table-foot-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--ds-space-1);
  flex: 0 0 auto;
}
.md-table-quiet {
  appearance: none;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  color: var(--cg-muted);
  cursor: pointer;
  min-height: 32px;
  padding: var(--ds-space-1) var(--ds-space-2);
  border-radius: var(--wizon-ai-radius-control);
  position: relative;
}
@media (hover: hover) {
  .md-table-quiet:hover {
    color: var(--cg-text, var(--text));
    background: var(--cg-hover-soft);
  }
}
.md-table-quiet:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}
.md-table-more {
  position: relative;
}
/* 스트리밍 중: 액션 비활성(design-brief §상태). `.md-stream[data-streaming="1"]`는
   스트리밍 스캐폴드가 이미 다는 마커(app.js streamScaffoldHtml, 19726행) — 새 상태
   플래그가 필요 없다. */
.md-stream[data-streaming="1"] .md-table-foot-actions {
  opacity: 0.5;
  pointer-events: none;
}
@media (any-pointer: coarse), (max-width: 520px) {
  .md-table-quiet {
    min-height: var(--md-table-pill-target);
  }
}
/* R-54 F-T — 스트리밍 중 «꼬리» 표의 푸터(출처·액션)는 흐름 밖에 둔다. 행이 올 때마다 푸터가 내려앉았다 올라오지 않게
   따라가기가 «자란 만큼» 을 같은 프레임에 보태던 pin 이 38~45px 화면 계단의 원인이었다(≥20px 프레임의 96~100% 가 표 꼬리).
   caret 아래 요소가 없으면 pin 이 필요 없다. 푸터는 다음 블록이 시작되거나 답변이 끝날 때(최종 렌더·data-streaming 해제) 나타난다.
   display 만 쓴다 — 애니메이션·강조선 없음. */
.md-stream[data-streaming="1"] > .md-table-wrap:last-child > .md-table-foot {
  display: none;
}
/* R-54 F2 — 스트리밍 표의 «행 자리». 머리행이 확정되면 표 골격을 바로 그리고 아직 안 온 행은 이 한 줄이 자리를
   잡는다(제로폭 공백 셀 → 높이는 실제 한 줄 행과 같다). 셀은 visibility:hidden — 자리만 차지하고 그려지지 않는다
   (그려지는 셀은 첫 본문 행이 열 너비를 바꿀 때 가로로 밀려 레이아웃 이동으로 세어진다: 1440 CLS 0.0144→0.03, 실측).
   애니메이션 없음. 첫 본문 행이 오면 같은 <tr> 이 내용만 채워지고 이 표식은 사라진다. 카드·인용 왼쪽 강조선 없음. */
.md-table tr.md-row-pending > th,
.md-table tr.md-row-pending > td {
  visibility: hidden;
}
/* ══════════════════════════════════════════════════════════════════
   T2 — 15행 초과 표 승격 뷰어 (doc 626 §9 비범위 예약, 2026-09-22)
   데스크톱: 폭 최대 모달(96vw, 최대 1040px) · 첫 열 고정(.md-table-scroll 전역
   규칙 27577행 재사용) · 가로 스크롤. 모바일: [data-mobile-fullscreen] 공유
   규칙(20260708-mobile-dialog-fullscreen-system)이 전체화면 시트로 만든다 —
   이 블록은 새 모바일 전체화면 로직을 만들지 않는다. 다크는 새 토큰을 만들지
   않고 settings/search 다이얼로그가 쓰는 같은 --cg-* 토큰(다크 재정의는 이미
   전역에 있다)을 그대로 재사용한다.
   ══════════════════════════════════════════════════════════════════ */
.table-viewer-dialog {
  width: min(1040px, 96vw);
  max-width: none;
  height: min(760px, calc(100vh - 48px));
  max-height: calc(100vh - 48px);
  padding: 0;
  border: 0;
  /* anti-drift 래칫(raw-border-radius/box-shadow/color/rgb)을 늘리지 않도록 리터럴 대신
     기존 토큰만 쓴다 — --ds-radius-lg(18px)는 settings 다이얼로그의 16px과 같은 급이고,
     --cg-menu-shadow는 그 다이얼로그가 쓰는 바로 그 그림자 토큰(값 재사용, 재정의 아님). */
  border-radius: var(--ds-radius-lg);
  background: var(--cg-surface);
  color: var(--cg-text);
  box-shadow: var(--cg-menu-shadow);
}
.table-viewer-dialog::backdrop {
  /* --wds-modal-scrim — .work-approval-sheet::backdrop(20260925-work-approval-decision-dialog
     블록)이 이미 쓰는 실 모달 스크림 토큰. 리터럴 rgba를 새로 쓰지 않는다(같은 래칫 이유). */
  background: var(--wds-modal-scrim);
  opacity: 0;
  transition: opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease);
}
.table-viewer-dialog[data-motion-state="open"]::backdrop {
  opacity: 1;
}
.table-viewer-dialog[data-motion-state="closing"]::backdrop {
  opacity: 0;
}
.table-viewer-dialog[open] {
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(0.985);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
  will-change: opacity, transform;
}
.table-viewer-dialog[data-motion-state="open"] {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}
.table-viewer-dialog[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(0.985);
}
.table-viewer-shell {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  height: 100%;
}
.table-viewer-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--ds-space-3);
  padding: var(--ds-space-4) var(--ds-space-5) var(--ds-space-3);
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}
.table-viewer-head-text {
  min-width: 0;
}
.table-viewer-title {
  margin: 0;
  font-size: var(--ds-text-body-16);
  font-weight: var(--ds-weight-semibold, 600);
  line-height: 1.4;
}
.table-viewer-meta {
  margin: 2px 0 0;
  font-size: var(--ds-text-caption-12);
  color: var(--cg-muted);
}
.table-viewer-body {
  overflow: auto;
  padding: var(--ds-space-4) var(--ds-space-5);
}
/* 뷰어 안에서는 폭 제약(768 스레드 컬럼)이 없다 — 본문 md-table-scroll을 그대로
   재사용하되(첫 열 고정 전역 규칙 27577행이 자동 적용) 여기서만 최소폭을 뷰어
   자체 폭에 맞춘다. */
.table-viewer-body .table-viewer-scroll {
  border-top: 1px solid var(--cg-menu-border, var(--line));
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}
@media (max-width: 520px) {
  .table-viewer-body {
    padding: var(--ds-space-3) var(--ds-space-4) calc(var(--ds-space-4) + env(safe-area-inset-bottom, 0px));
  }
}
/* ── v7-1 아트디렉션 정합 (2026-08-07, 사용자 지적) ──
   ★지적: 「사각형인데 비해 상단의 요약 표는 둥근 사각형」·「간격/여백 아트디렉션도 안 맞는다」.
   실측으로 확인된 어긋남 둘:
     ① 모서리 — 타일 14px vs 표 프레임 0px. 같은 색(#f4f4f4) 밴드가 나란히 있는데
        모서리 언어만 달라 «둘이 다른 것»으로 읽힌다.
     ② 들여쓰기 — 표 계열은 제목·머리글·행이름이 **모두 산문 대비 12px** 로 통일돼
        있는데 타일만 16px 이라 혼자 4px 더 들어가 왼쪽 정렬선이 깨졌다.
   ★이 값들은 카드 시절의 유물이다. 14px 은 카드의 22px 아래 «중첩된» 반경이었고,
   16px 여백은 카드의 24px 여백 안에서 잡은 리듬이었다. 카드가 사라졌으므로 기준을
   표 계열로 옮긴다 — 새 언어를 만들지 않고 이미 있는 리듬에 합류시킨다.
   ★inset 그림자도 뺀다. 「카드는 그림자로 뜨고 이 띠는 inset 으로 눌린다」는 2층 모델의
   아래층인데, 위층(카드 리프트)이 없어져 혼자서는 아무 것도 말하지 않는다. */
/* ★타일 셋은 «각각의 상자»가 아니라 **하나의 격자**다 (2026-08-08).
   flex 였을 때는 라벨이 2줄이 되면 그 타일의 값·부제만 내려가 기준선이 깨졌다 —
   실측 375px·430px 에서 **20px 어긋남**(라벨 높이 20/20/40). 셋 중 하나만 어긋나 눈에 띈다.
   ★라벨 길이는 우리가 못 정한다. 「2025-Q1 대비 2025-Q4」는 **표에서 읽어 온 이름**이라
   줄바꿈을 없앨 수 없다 — 자르면 무엇 대비 무엇인지가 사라진다. 그래서 줄바꿈을 허용한
   채로 기준선을 지킨다: 행을 여기서 정의하고 타일이 `subgrid` 로 **상속**한다.
   ★행이 짧을 땐 아무 대가도 없다. 라벨이 한 줄인 표에서 띠 높이는 그대로다(실측 89px),
   실제로 2줄이 될 때만 109px 로 커진다 — `min-height` 로 늘 2줄을 예약하지 않는 이유다. */
.md-table-tiles {
  display: grid;
  grid-auto-flow: column;
  /* ★`1fr` 만으로는 부족하다 — 자동 최소 크기가 내용 폭이라 긴 값이 자기 열을 넓혀
     타일 폭이 갈린다. 종전 flex 의 `flex: 1 1 0` + `min-width: 0` 이 하던 일이다. */
  grid-auto-columns: minmax(0, 1fr);
  grid-template-rows: auto auto auto;
  margin: 0 0 var(--ds-space-3);
  animation: md-table-tiles-in var(--ds-motion-default) both;
  overflow: hidden;
  background: var(--cg-hover-soft, var(--surface-2));
}

/* ★전정기관 민감성. 조사: reduced-motion은 킬 스위치가 아니라 «장식은 빼고 기능은 남긴다».
   여기서는 최종 상태가 곧 기능이므로 애니메이션만 제거하면 끝이다 — 기본 상태가 이미
   최종 상태라서(opacity 0을 기본으로 두지 않았다) 띠는 그대로 보인다.
   ★2026-08-08 — 이 블록은 자(bar)와 띠를 함께 다뤘고, A안에서 자 규칙을 걷어낼 때
   블록이 통째로 사라졌다(게이트가 잡았다). 남은 소비자 하나로 다시 세운다. */
@media (prefers-reduced-motion: reduce) {
  .md-table-tiles {
    animation: none;
  }
}

.md-table-tile {
  position: relative;
  /* ★띠의 행을 **상속**한다 — 자기 행을 만들지 않는다. 그래야 라벨이 2줄이 될 때 세
     타일의 라벨 행이 «함께» 2줄이 되고 값은 같은 줄에 남는다(위 `.md-table-tiles` 주석).
     ★`span 3` 과 짝이다. 하나만 있으면 아무 일도 안 한다 — `subgrid` 만 있으면 한 행만
     상속해 값·부제가 한 칸에 뭉치고, `span` 만 있으면 자기 행을 만들어 형제와 어긋난다.
     ★상자를 `display: contents` 로 녹이지 않는다 — `position: relative` 와 아래
     `::before` 구분선, 패딩이 이 상자에 걸려 있다. */
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  min-width: 0;
  /* 좌우 12px = 표 셀·제목과 같은 들여쓰기. 세로는 라벨+값 두 줄이라 셀(8px)보다 넉넉히. */
  padding: var(--ds-space-3);
}

/* ★타일 값이 잘렸다(실기기 iPhone 402pt: 「139,822」가 「139,…」). 타일은 폭을 균등하게
   나눠 가지는데 값은 nowrap+ellipsis라, 안쪽 폭이 모자라면 헤드라인 숫자가 잘린다 —
   잘린 숫자는 안 보여 주는 것만 못하다. 「33,03…」은 33,033.3 이 아니라 **틀린 수**다.
   실측: 22px에서 「139,822」는 80.5px. 안쪽 폭이 패딩 16일 때 75.3pt로 모자라고
   패딩 8이면 91.3pt로 들어간다.
   ★★2026-08-08 — 「글자를 줄이는 대신 패딩을 줄여 헤드라인 크기를 지킨다」던 종전 결정을
   **실측이 뒤집었다.** 패딩만으로는 모자란다(폭별 최소 여유 · 넘치는 값 개수):

       값 크기   320          360         375          430      520
       22px     -19.3 (10)   -6.3 (4)    -1.3 (1)     +16.7    +46.7
       18px      +0.7 (0)   +13.7 (0)   +18.7 (0)     +36.7    +66.7

   22px는 **375px 폰에서도** 넘친다(안전해지는 것은 ~390px부터). 범인은 원 단위 열의
   평균(「33,033.3원」, 9자)으로, 요금제·단가 표에서 늘 나오는 모양이다. 헤드라인 크기를
   지키려다 헤드라인 «숫자»를 틀리게 만드는 것은 거래가 안 된다 — 한 단계 내린다.
   여전히 만능은 아니다: 320px에서 여유가 0.7px라 더 긴 값은 잘린다.
   ★★이 블록은 기본 규칙 «뒤»에 있어야 한다. @media는 특이도를 올리지 않으므로 앞에 두면
   뒤의 기본 규칙이 이겨 override가 통째로 무효가 된다 — 실제로 그렇게 배포했다가
   시뮬레이터에서 잘림이 그대로인 것을 보고 잡았다(reduced-motion에서도 같은 실수를 했다). */
@media (max-width: 520px) {
  .md-table-tile {
    padding: var(--ds-space-2);
  }
}
/* 타일 사이는 선으로 가른다 — 박스를 각각 그리면 카드 안에 카드가 겹친다. */
.md-table-tile + .md-table-tile::before {
  content: "";
  position: absolute;
  left: 0;
  top: 14px;
  bottom: 14px;
  width: 1px;
  background: var(--cg-menu-border, var(--line));
}
/* ★★접두사 `.message-body .md-table-wrap`이 «장식»이 아니다 — 카드가 만드는 <p>는 접두사를
   벗기는 순간 전역 산문 규칙에 진다. 실측으로 물렸다: 라벨이 12px/600/muted 대신
   **16px/400/ink**로 렌더돼 자기 값과 같은 크기·같은 검정이 됐고, 지표 띠의 위계가
   통째로 사라졌다. 토큰은 멀쩡했다 — 진 것은 캐스케이드다.
     .message-body p                            (0,1,1)  margin 10px
     .premium-product-shell-v1 .message-body p  (0,2,1)  font-size 15
     .chatgpt-reference-v1 .message-body p      (0,2,1)  color → ink
     .chatgpt-reference-v1 .message-body p      (0,2,1)  font-size 16 · weight 400
   컴포넌트 클래스 하나(0,1,0)로는 넷 다 진다. 클래스 셋(0,3,0)이면 파일 순서와 무관하게
   이기고, 셸 클래스를 안 쓰므로 어느 셸에서도 같다. !important로 덮지 않는다 —
   다른 표면의 전역 산문 규칙까지 같이 망가진다.
   ★이 접두사를 «간결하게» 벗기지 말 것. `test_table_card_prose_specificity.py`가 막는다. */
.message-body .md-table-wrap .md-table-tile-label {
  margin: 0 0 var(--ds-space-1);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  color: var(--cg-muted);
}
.md-table-tile-value {
  /* ★규범: 큰 독립 숫자는 비례 숫자. tabular은 121을 헐렁하게 만들고, 세로로 정렬할
     이유도 없다 — 그건 표 열에서만 필요하다. */
  font-size: var(--ds-text-title-22);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-variant-numeric: proportional-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* ★폰에서 한 단계 내린다 — 사유와 실측표는 위 `@media (max-width: 520px)` 패딩 블록 주석에.
   ★★이 블록은 기본 규칙 «바로 뒤»에 있어야 한다. @media 는 특이도를 올리지 않으므로
   패딩 블록(위쪽)에 함께 넣으면 뒤따르는 기본 규칙 `font-size: …title-22` 가 이겨
   **선언이 통째로 죽는다.** 실제로 그렇게 짰다가 검사에 잡혔다 — 같은 파일이 같은 함정을
   두 번 기록하고 있는데도 그랬다. 중단점 값은 패딩 블록과 **같은 520px** 이다. */
@media (max-width: 520px) {
  .md-table-tile-value {
    font-size: var(--ds-text-title-18);
  }
}
/* ★접두사 필요 — 위 `.md-table-tile-label` 주석 참조. */
.message-body .md-table-wrap .md-table-tile-sub {
  margin: var(--ds-space-1) 0 0;
  font-size: var(--ds-text-caption-12);
  color: var(--cg-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}.md-table-cell-secondary {
  display: block;
  margin-top: 2px;
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-12);
  line-height: 1.45;
}

/* A안 «안전 프레임» (2026-07-30 승인). 고지가 표 카드 **안**으로 들어오면 카드 밖에
   있을 때 필요했던 바깥 여백이 오히려 카드를 벌린다. 카드 내부에서는 안쪽 여백을
   쓰고, 위험도 엣지가 카드 전체 높이를 관통해 표와 고지를 한 덩어리로 묶는다 —
   배포 화면에서 표·고지·모델 꼬리문단이 세 개의 남남으로 읽힌 것이 이 안의 출발점이다.
   색은 이미 있는 시맨틱 토큰을 그대로 쓴다(새 색 리터럴 없음). */
.md-table-wrap > .artifact-notices {
  margin: 0;
  padding: var(--ds-space-2) var(--ds-space-3);
}
/* ★v10 — 헤어라인도 중립색이다. v7-1 에서 여기에 등급색을 물렸는데, 나머지 표면이
   전부 채도 0% 인 잉크라 채도 90% 짜리 선 하나가 «마크다운이 아닌 무언가»로 튀었다
   (사용자 지적). 등급은 아래 텍스트 라벨이 진다. */
.md-table-wrap > .artifact-notices:first-child {
  border-bottom: 1px solid var(--cg-menu-border);
}
.md-table-wrap > .artifact-notices:last-child {
  border-top: 1px solid var(--cg-menu-border);
}
/* ── v7-1 (2026-08-07) — 관통 엣지를 걷어낸다 ──
   ★이 엣지는 «카드»를 전제로 만들어졌다: 카드 전체 높이를 관통해 표와 고지를 한
   덩어리로 묶고, 카드의 반경 22px을 좌측만 0으로 취소했다. v7에서 카드를 걷어내자
   그 전제가 통째로 깨졌고, 배포 화면에서 두 가지가 실측됐다.
     ① 제목의 액센트 바(4px)와 이 엣지(3px)가 **간격 0으로 맞붙었다.** 공교롭게도
        바로 아래 주석이 경고하는 «이중 신호»와 같은 결함이다 — 그때는 카드 엣지와
        고지 레일이었고, 이번엔 카드 엣지와 제목 바다.
     ② 래퍼의 3px 테두리가 표를 산문보다 3px 안쪽으로 밀어, 인라인 정렬이 이 경우에만
        어긋났다. 카드 시절엔 24px 여백에 묻혀 보이지 않던 값이다.
   ★엣지를 지워도 등급 신호는 잃지 않는다 — 아래 밴드 tint가 이미 tone별로 갈린다
   (그 자체가 «레일을 빼고 배경으로 옮긴» 앞선 결정의 결과다). 여기서는 남은 중복
   채널 하나를 마저 걷어내고, tone 은 밴드 헤어라인으로 물려준다.
   ★선택자는 살려 둔다. 계약이 존재를 핀할 뿐 아니라, tone → 색 대응이 사라지면
   밴드 경계가 등급을 잃는다. */
/* ── v10 (2026-08-07) — 등급을 «색»에서 «말»로 옮긴다 ──
   ★사용자 지적: 「빨간색 마크다운 방식이 좀 안 어울린다」. 실측하니 답변 표면이 전부
   채도 0% 인데(머리글 밴드 0%·제목 액센트 바 0%) 고지만 헤어라인 채도 90.5%·밴드 70.7%
   였고, 밴드 면적이 표 본체의 41% 였다. 잉크 규율(doc 345)과 정면으로 부딪힌다.
   ★색을 빼도 등급을 잃지 않는다 — 오히려 접근성이 는다. doc 231 A5 는 «필수 역할을
   color 채널에만 배정 금지»(SC 1.4.1)를 요구하는데, 지금까지 danger/warning 을 가르는
   가시 채널은 색뿐이었다(위치는 있었지만 색이 주였다). 등급을 **글자**로 옮기면 색각·
   흑백 인쇄·저대비 환경에서도 살아남는다.
   ★래퍼의 `[data-risk-tone]` 은 색을 안 쓰게 되어 CSS 로는 할 일이 없다. 속성 자체는
   JS 가 계속 세운다(고지 마운트 표식) — 죽은 선언을 남기지 않고 규칙만 걷어낸다. */
/* ★실브라우저에서 발견: 카드 엣지와 고지 항목의 좌측 레일이 같은 색으로 나란히
   그려졌다(측정: 카드 x=856, 고지 x=904, 둘 다 rgb(186,38,35)). 「한 덩어리로 묶는다」고
   만든 엣지가 신호를 이중으로 만든 것이다 — 소스로는 안 보이고 픽셀을 재야 나온다.
   카드 안에서는 레일을 빼되, 지금 두 밴드의 배경이 **같은 회색**이라 레일이 tone을
   구분하는 유일한 채널이므로 등급을 배경으로 옮긴다. 카드 밖(표 없는 답변)의 고지는
   기존 레일을 그대로 쓴다 — 거기엔 카드 엣지가 없다. */
/* 반경은 일부러 재선언하지 않는다 — 배경이 투명하고 레일이 없으면 칠할 것이 없어
   시각적으로 무의미한데, 원시 border-radius 값은 반드시 anti-drift 래칫에 잡힌다
   (206 → 207로 RED가 났다). 안 쓰는 선언으로 천장을 깎지 않는다. */
.md-table-wrap > .artifact-notices .doc-summary-notice {
  border-left: 0;
  background: transparent;
}
/* ★밴드는 등급과 무관하게 중립 회색이다 — 머리글 밴드와 같은 톤이라 답변 안에서
   «같은 언어의 형제»로 읽힌다. 등급은 아래 라벨이 진다. */
.md-table-wrap > .artifact-notices {
  background: var(--cg-hover-soft);
}
/* ── 등급 라벨 (v10) ──
   ★색 대신 말. 「중요」(danger) · 「주의」(warning) · 「참고」(info).
   ★CSS `content` 로 넣는 이유: 이 문구는 정책이 정한 «고지 본문»이 아니라 UI 라벨이다.
   본문에 섞으면 복사·내보내기한 원문이 오염되고, 정책 YAML 이 문구의 유일한 출처라는
   계약(«모델도 렌더러도 고지를 짓지 않는다»)이 흐려진다. */
/* ★v10-1 — 카드 «밖» 고지도 같이 중립화한다.
   실브라우저 검수에서 잡았다: 표가 없는 답변은 고지가 `.md-table-wrap` 밖에 붙어
   v10 의 중립화가 안 닿았고, **채도 90.5% 짜리 주황 레일이 그대로** 남아 있었다
   (사용자가 지적한 바로 그 색). 등급을 «말»로 옮긴 이상 레일도 중립이어야 한다.
   ★`.doc-summary-notice` 기본 규칙은 손대지 않는다 — 그 클래스는 문서요약 위젯도 쓰고,
   거기서는 등급 레일이 여전히 유효한 신호다. 위험 고지 컨테이너(`.artifact-notices`)
   안에서만 덮는다. */
/* ★★특이도를 한 칸 올린다 — `[data-tone]` 을 붙여 (0,2,0) → (0,3,0).
   실브라우저에서 잡았다: (0,2,0) 으로 썼더니 라벨은 나오는데 **레일만 주황 그대로**였다.
   기존 `.doc-summary-notice[data-tone="warning"]`(클래스1+속성1 = 0,2,0)와 동률인데
   그쪽이 파일 뒤에 있어 이긴다. 동률은 «나중 선언»이 가져간다. */
.artifact-notices .doc-summary-notice[data-tone] {
  border-left-color: var(--cg-menu-border);
}
.artifact-notices .doc-summary-notice::before {
  margin-right: var(--ds-space-1);
  color: var(--cg-text);
  font-weight: var(--ds-weight-semibold);
}
.artifact-notices .doc-summary-notice[data-tone="danger"]::before {
  content: "중요";
}
.artifact-notices .doc-summary-notice[data-tone="warning"]::before {
  content: "주의";
}
.artifact-notices .doc-summary-notice[data-tone="info"]::before {
  content: "참고";}
.doc-summary-notice {
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--ds-radius-xs);
  border-left: 3px solid var(--cg-menu-border);
  background: var(--cg-hover-soft);
  font-size: var(--ds-text-caption-12);
  color: var(--cg-text);
}
/* ★위험 수준을 개인 설정으로 렌더하지 않는다. 여기가 --accent였던 탓에 파란 accent를
   고른 사용자에게는 «투자 자문이 아닙니다»가 파란 막대로 나왔다 — 실브라우저 실측에서
   발견. 기본 accent가 우연히 먹빛이라 눈에 띄지 않았을 뿐이다. */
.doc-summary-notice[data-tone="warning"] {
  border-left-color: var(--cg-warning-fg);
}
.doc-summary-notice[data-tone="danger"] {
  border-left-color: var(--cg-danger-fg);
}
.doc-summary-evidence {
  font-size: var(--ds-text-caption-12);
  color: var(--cg-text-soft);
}
.doc-summary-evidence summary {
  cursor: pointer;
  padding: 4px 0;
  color: var(--cg-text-soft);
}
.doc-summary-evidence ul {
  margin: 4px 0 0;
  padding-left: 18px;
}
.doc-summary-evidence li {
  margin: 2px 0;
  overflow-wrap: anywhere;
}
@container (max-width: 520px) {
  .doc-summary-facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ---- Visual answer card v1 --------------------------------------------------------------
   Generic web answers with 1–5 entity-approved HTTPS source images stay text-first:
   answer/price table → source controls → optional media. Zero images use the plain answer path
   and reserve no height. Compact reflow is owned by the card container. */
.visual-answer-card {
  container-name: visual-answer;
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  inline-size: 100%;
  max-inline-size: 100%;
  min-width: 0;
  margin-top: var(--wds-space-1);
  overflow: hidden;
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-popover);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  box-shadow: var(--wizon-ai-popover-shadow);
}

.visual-answer-mosaic {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  min-width: 0;
  max-width: 100%;
  gap: var(--wds-space-1);
  padding: var(--wds-space-1);
  background: var(--wizon-ai-surface);
}

.visual-answer-card:not(.travel-itinerary-card)[data-visual-image-count="1"] > .visual-answer-mosaic {
  grid-template-columns: minmax(0, 1fr);
}

.visual-answer-card:not(.travel-itinerary-card)[data-visual-image-count="2"] > .visual-answer-mosaic {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.visual-answer-card:not(.travel-itinerary-card)[data-visual-image-count="3"] > .visual-answer-mosaic {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.visual-answer-card:not(.travel-itinerary-card)[data-visual-image-count="4"] > .visual-answer-mosaic {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.visual-answer-card:not(.travel-itinerary-card)[data-visual-image-count="5"] > .visual-answer-mosaic {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.visual-answer-item {
  display: grid;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
}

.visual-answer-media {
  position: relative;
  min-width: 0;
  min-height: 0;
  padding: 0;
  overflow: hidden;
  border: 0;
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
  color: inherit;
  font: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}

.visual-answer-media:first-child:nth-last-child(3) {
  grid-row: span 2;
}

.visual-answer-item .visual-answer-media {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 0;
}

.visual-answer-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--wizon-ai-motion-surface-duration) var(--wizon-ai-motion-effect-ease);
}

.visual-answer-media:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: -2px;
}

.visual-answer-caption {
  min-width: 0;
  padding: var(--wds-space-2) var(--wds-space-3);
  border-top: 1px solid var(--wizon-ai-popover-border);
  font-size: var(--ds-text-caption-12);
  line-height: 1.4;
}

.visual-answer-source-link {
  display: block;
  overflow: hidden;
  color: var(--wizon-ai-text-secondary);
  text-decoration: none;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.visual-answer-source-link:hover,
.visual-answer-source-link:focus-visible {
  color: var(--wizon-ai-text-primary);
  text-decoration: underline;
}

@media (hover: hover) {
  .visual-answer-media:hover img {
    transform: scale(1.035);
  }
}

.visual-answer-content {
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-width: 100%;
  gap: var(--wds-space-3);
  padding: var(--wds-space-5) var(--wds-space-5) var(--wds-space-4);
}

.visual-answer-content > :first-child {
  margin-top: 0;
}

.visual-answer-content > :last-child {
  margin-bottom: 0;
}

.visual-answer-content .list-line,
.visual-answer-content .answer-table-line {
  margin: 0;
  padding-block: var(--wds-space-2);
  border-top: 1px solid var(--wizon-ai-popover-border);
  line-height: 1.65;
}

.visual-answer-content h3,
.visual-answer-content h4,
.visual-answer-content h5 {
  margin: var(--wds-space-3) 0 0;
  text-wrap: pretty;
}

.visual-answer-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  min-width: 0;
  max-width: 100%;
  gap: var(--wds-space-2);
  padding: var(--wds-space-3) var(--wds-space-5) var(--wds-space-4);
  border-top: 1px solid var(--wizon-ai-popover-border);
}

.visual-answer-foot .source-pill-row,
.visual-answer-foot .trust-chips {
  margin-top: 0;
}

.visual-answer-content .md-table-wrap {
  max-width: 100%;
}

/* Broken fenced output stays readable as escaped source instead of becoming a partial code card.
   Korean keeps word boundaries where possible; an unbroken URL/token may still wrap before it can
   widen a 390px page. */
.md-plain-fallback {
  max-width: 100%;
  word-break: keep-all;
  overflow-wrap: anywhere;
}

/* A zero-count payload never enters the card. If remote loading later leaves zero tiles,
   remove the shell and keep the same Markdown/source controls as the standard answer path. */
.visual-answer-card.is-image-fallback {
  overflow: visible;
  border: 0;
  background: transparent;
  box-shadow: none;
}

.visual-answer-card.is-image-fallback .visual-answer-mosaic {
  display: none;
}

.visual-answer-card.is-image-fallback .visual-answer-content,
.visual-answer-card.is-image-fallback .visual-answer-foot {
  padding-inline: 0;
}

.visual-answer-card.is-image-fallback .visual-answer-content {
  padding-top: 0;
}

.visual-answer-card.is-image-fallback .visual-answer-foot {
  border-top: 0;
}

@container visual-answer (max-width: 520px) {
  .visual-answer-card:not(.travel-itinerary-card)[data-visual-image-count="4"] > .visual-answer-mosaic,
  .visual-answer-card:not(.travel-itinerary-card)[data-visual-image-count="5"] > .visual-answer-mosaic {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .visual-answer-content {
    padding: var(--wds-space-4);
  }

  .visual-answer-foot {
    padding: var(--wds-space-3) var(--wds-space-4) var(--wds-space-4);
  }
}

/* ---- Travel itinerary widget v2 -------------------------------------------------------
   The travel card stays inside the visual-answer shell, but adds evidence-aware day tabs,
   synchronized stops and a coordinate-backed route overview. All colors, spacing and motion
   come from the existing WIZON/WDS contract; unavailable map data degrades to a route list. */
.travel-itinerary-lede {
  margin: 0 0 var(--wds-space-3);
  line-height: 1.65;
  text-wrap: pretty;
}

.travel-itinerary-card {
  container-name: travel-itinerary;
}

.travel-itinerary-hero {
  grid-auto-rows: clamp(144px, 22vw, 180px);
  min-height: 0;
  background: var(--wizon-ai-surface);
}

.travel-itinerary-hero:has(.travel-itinerary-hero-media:only-child) {
  grid-template-columns: minmax(0, 1fr);
}

.travel-itinerary-hero-media {
  border-radius: calc(var(--wizon-ai-radius-control) - var(--wds-space-1));
}

.travel-itinerary-body {
  gap: var(--wds-space-3);
  padding-top: var(--wds-space-4);
}

.travel-itinerary-heading {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: var(--wds-space-3);
}

.travel-itinerary-heading > span {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: var(--wds-space-1);
}

.travel-itinerary-heading h3 {
  margin: 0;
  font-size: var(--wds-font-size-title-l);
  line-height: calc(var(--wds-font-size-title-l) * 1.35);
}

.travel-itinerary-heading.has-long-days {
  align-items: stretch;
  flex-direction: column;
}

.travel-itinerary-evidence {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-label);
}

.travel-itinerary-day-nav,
.travel-itinerary-tabs {
  display: flex;
  align-items: center;
  min-width: 0;
}

.travel-itinerary-day-nav {
  flex: 0 1 auto;
  gap: var(--wds-space-2);
}

.travel-itinerary-day-nav.is-long {
  width: 100%;
}

.travel-itinerary-tabs {
  flex: 1 1 auto;
  gap: var(--wds-space-1);
  max-width: 100%;
  padding: var(--wds-space-1);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-canvas);
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-behavior: smooth;
  scroll-snap-type: inline proximity;
  scrollbar-width: none;
}

.travel-itinerary-tabs::-webkit-scrollbar {
  display: none;
}

.travel-itinerary-tabs button {
  flex: 0 0 auto;
  min-width: 52px;
  min-height: 44px;
  padding: 0 var(--wds-space-3);
  border: 0;
  border-radius: var(--wizon-ai-radius-pill);
  background: transparent;
  color: var(--wizon-ai-text-secondary);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
  cursor: pointer;
  scroll-snap-align: center;
}

.travel-itinerary-tabs button[aria-selected="true"] {
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  box-shadow: var(--wizon-ai-popover-shadow);
}

.travel-itinerary-day-step {
  display: grid;
  flex: 0 0 44px;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  cursor: pointer;
}

.travel-itinerary-day-step:disabled {
  color: var(--wizon-ai-text-muted);
  cursor: default;
  opacity: 0.45;
}

.travel-itinerary-day-step svg {
  width: 18px;
  height: 18px;
}

.travel-itinerary-day-position {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: baseline;
  gap: var(--wds-space-1);
  min-width: 54px;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.travel-itinerary-day-position strong {
  color: var(--wizon-ai-text-primary);
}

.travel-itinerary-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(220px, 0.92fr);
  align-items: stretch;
  gap: var(--wds-space-4);
}

.travel-itinerary-days {
  min-width: 0;
}

.travel-itinerary-day ol {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-itinerary-stop {
  position: relative;
  display: grid;
  grid-template-columns: var(--wds-space-3) minmax(48px, auto) minmax(0, 1fr);
  column-gap: var(--wds-space-2);
  min-width: 0;
  font-size: var(--wds-font-size-caption);
  line-height: 1.4;
}

.travel-itinerary-stop:not(:last-child)::after {
  content: "";
  position: absolute;
  top: var(--wds-space-3);
  bottom: calc(var(--wds-space-1) * -1);
  left: calc(var(--wds-space-3) / 2 - 0.5px);
  width: 1px;
  background: var(--wizon-ai-popover-border);
}

.travel-itinerary-stop-marker {
  position: relative;
  width: var(--wds-space-2);
  height: var(--wds-space-2);
  margin: calc(var(--wds-space-1) + 1px) auto 0;
  border: 1px solid var(--wizon-ai-text-secondary);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-surface);
}

.travel-itinerary-stop.is-selected .travel-itinerary-stop-marker {
  border-color: var(--wizon-ai-text-primary);
  background: var(--wizon-ai-text-primary);
}

.travel-itinerary-stop-time {
  color: var(--wizon-ai-text-secondary);
  font-variant-numeric: tabular-nums;
}

.travel-itinerary-stop-copy,
.travel-itinerary-stop-head {
  display: flex;
  min-width: 0;
}

.travel-itinerary-stop-copy {
  width: 100%;
  min-height: 44px;
  padding: var(--wds-space-2) var(--wds-space-3);
  border-radius: var(--wizon-ai-radius-control);
  background: transparent;
}

.travel-itinerary-stop-control {
  display: flex;
  flex-direction: column;
  width: 100%;
  min-width: 0;
  min-height: 28px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  text-align: left;
  gap: var(--wds-space-1);
  cursor: pointer;
}

.travel-itinerary-stop.is-selected .travel-itinerary-stop-copy {
  background: var(--wizon-ai-canvas);
}

.travel-itinerary-stop.is-featured .travel-itinerary-stop-copy {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(108px, 132px);
  column-gap: var(--wds-space-3);
}

.travel-itinerary-stop.is-featured .travel-itinerary-stop-head,
.travel-itinerary-stop.is-featured .travel-itinerary-stop-detail,
.travel-itinerary-stop.is-featured .travel-itinerary-stop-control {
  grid-column: 1;
}

.travel-itinerary-stop.is-featured .travel-itinerary-stop-media {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: start;
  margin-top: 0;
}

.travel-itinerary-stop-head {
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--wds-space-1) var(--wds-space-3);
}

.travel-itinerary-stop-head strong {
  font-weight: var(--wds-font-weight-semibold);
  color: var(--wizon-ai-text-primary);
}

.travel-itinerary-stop-head span,
.travel-itinerary-stop-note,
.travel-itinerary-stop-facts {
  color: var(--wizon-ai-text-secondary);
}

.travel-itinerary-stop-detail {
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition:
    max-height var(--wizon-ai-motion-micro-duration) var(--wizon-ai-ease-move),
    opacity var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast);
}

.travel-itinerary-stop.is-selected .travel-itinerary-stop-detail {
  max-height: 160px;
  opacity: 1;
}

.travel-itinerary-stop-facts {
  margin-top: var(--wds-space-1);
  font-size: var(--wds-font-size-caption);
}

.travel-itinerary-stop-media {
  width: min(132px, 100%);
  aspect-ratio: 16 / 9;
  margin-top: var(--wds-space-2);
  border-radius: var(--wizon-ai-radius-control);
}

.travel-itinerary-route-preview {
  min-width: 0;
  min-height: 100%;
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
  overflow: hidden;
}

.travel-itinerary-route-panel {
  height: 100%;
  padding: var(--wds-space-4);
}

.travel-itinerary-route-panel > header {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-1);
  margin-bottom: var(--wds-space-3);
}

.travel-itinerary-route-panel > header strong {
  font-size: var(--wds-font-size-body-2);
  line-height: var(--wds-line-height-label);
}

.travel-itinerary-route-panel > header span {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-label);
}

.travel-itinerary-route-list {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-itinerary-route-row {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  gap: var(--wds-space-2);
  padding: var(--wds-space-2);
  border: 0;
  border-radius: var(--wizon-ai-radius-control);
  background: transparent;
  color: var(--wizon-ai-text-primary);
  text-align: left;
  cursor: pointer;
}

.travel-itinerary-route-row.is-selected {
  background: var(--wizon-ai-surface);
}

.travel-itinerary-route-row > span {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-text-primary);
  color: var(--wizon-ai-surface);
  font-size: var(--wds-font-size-caption);
}

.travel-itinerary-route-row strong {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--wds-font-size-caption);
}

.travel-itinerary-map {
  position: relative;
  min-height: 214px;
  border-radius: var(--wizon-ai-radius-control);
  background:
    linear-gradient(var(--wizon-ai-popover-border) 1px, transparent 1px),
    linear-gradient(90deg, var(--wizon-ai-popover-border) 1px, transparent 1px),
    var(--wizon-ai-surface);
  background-size: var(--wds-space-8) var(--wds-space-8);
  overflow: hidden;
}

.travel-itinerary-map svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.travel-itinerary-map polyline {
  fill: none;
  stroke: var(--wizon-ai-text-primary);
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}

.travel-itinerary-map-marker {
  position: absolute;
  left: var(--travel-map-x);
  top: var(--travel-map-y);
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 2px solid var(--wizon-ai-surface);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-text-secondary);
  color: var(--wizon-ai-surface);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-semibold);
  transform: translate(-50%, -50%);
  cursor: pointer;
  box-shadow: var(--wizon-ai-popover-shadow);
  transition:
    transform var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-spatial-ease),
    background var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast);
}

.travel-itinerary-map-marker.is-selected {
  background: var(--wizon-ai-text-primary);
  transform: translate(-50%, -50%) scale(1.12);
}

.travel-itinerary-map-selection {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wds-space-2);
  min-height: 44px;
  padding-top: var(--wds-space-2);
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
}

.travel-itinerary-map-selection a {
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  min-height: 44px;
  color: var(--wizon-ai-text-secondary);
  text-decoration: none;
}

.travel-itinerary-map-selection svg,
.travel-itinerary-actions svg {
  width: 18px;
  height: 18px;
}

.travel-itinerary-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wds-space-2);
  padding-top: var(--wds-space-2);
  border-top: 1px solid var(--wizon-ai-popover-border);
}

.travel-itinerary-actions button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--wds-space-1);
  min-height: 44px;
  padding: 0 var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
  cursor: pointer;
}

.travel-itinerary-adjust {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wds-space-1);
}

.travel-itinerary-adjust[hidden] {
  display: none;
}

.travel-itinerary-adjust > span,
.travel-itinerary-adjust-status,
.travel-itinerary-caveat {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.travel-itinerary-adjust button {
  min-width: 44px;
  padding-inline: var(--wds-space-2);
}

.travel-itinerary-caveat {
  margin: 0;
  line-height: 1.5;
}

.travel-feature-degradation {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--wds-space-2) var(--wds-space-3);
  padding: var(--wds-space-3);
  border-radius: var(--wizon-ai-radius-control);
  /* No literal fallbacks here: both tokens come from stylesheets index.html always loads,
     and --wizon-ai-surface is theme-dependent (#ffffff light / #000000 dark) -- a #ffffff
     fallback would paint a white panel in dark mode on the one occasion it fired. */
  background: color-mix(in srgb, var(--wds-blue-500) 8%, var(--wizon-ai-surface));
  color: var(--wizon-ai-text-primary);
}

.travel-feature-degradation-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--wds-blue-500);
}

.travel-feature-degradation-icon svg {
  width: 18px;
  height: 18px;
}

.travel-feature-degradation-copy {
  display: grid;
  min-width: 0;
  gap: 2px;
}

.travel-feature-degradation-copy strong {
  font-size: var(--wds-font-size-body-2);
}

.travel-feature-degradation-copy > span {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: 1.45;
}

.travel-itinerary-note {
  padding-top: var(--wds-space-3);
  border-top: 1px solid var(--wizon-ai-popover-border);
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.travel-itinerary-note > :first-child,
.travel-itinerary-note > :last-child {
  margin-block: 0;
}

.travel-itinerary-foot {
  justify-content: space-between;
}

/* ---- Travel guidebook widget G5-5 ---------------------------------------------------
   G5-4 timing and route facts remain authoritative. The card exposes partial states,
   interleaves stops and legs, and renders location markers without inferred route lines. */
.travel-guidebook-card {
  inline-size: 100%;
  max-inline-size: 100%;
  min-width: 0;
  margin-top: 0;
  overflow: visible;
  border: 0;
  border-radius: initial;
  background: transparent;
  box-shadow: none;
}

.travel-guidebook-card > .visual-answer-content.travel-itinerary-body {
  padding: 0;
}

.travel-guidebook-card > .visual-answer-foot.travel-itinerary-foot {
  padding-inline: 0;
  border-top: 0;
}

.travel-guidebook-card .travel-itinerary-body {
  gap: var(--wds-space-4);
}

.travel-guidebook-heading {
  align-items: flex-start;
}

.travel-guidebook-heading > span {
  gap: var(--wds-space-2);
}

.travel-guidebook-heading p {
  max-width: var(--wizon-ai-chat-max-width);
  margin: 0;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-body-2);
  line-height: var(--wds-line-height-normal);
  text-wrap: pretty;
}

.travel-guidebook-revalidation {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  min-height: var(--wizon-ai-action-target-touch);
  gap: var(--wds-space-2);
  padding: 0 var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
  cursor: pointer;
}

.travel-guidebook-revalidation svg {
  width: var(--wds-space-5);
  height: var(--wds-space-5);
}

.travel-guidebook-snapshot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wds-space-1) var(--wds-space-3);
  padding: var(--wds-space-2) 0;
  border-block: 1px solid var(--wizon-ai-popover-border);
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.travel-guidebook-snapshot span {
  display: inline-flex;
  align-items: center;
  min-height: var(--wds-space-8);
  white-space: nowrap;
}

.travel-guidebook-personalization {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--wizon-adaptive-right-panel-width)), 1fr));
  gap: var(--wds-space-2);
}

.travel-guidebook-personalization-panel {
  display: grid;
  align-content: start;
  gap: var(--wds-space-2);
  min-width: 0;
  padding: var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface);
}

.travel-guidebook-personalization-panel h4,
.travel-guidebook-personalization-panel p,
.travel-guidebook-personalization-chips,
.travel-guidebook-alternatives {
  margin: 0;
}

.travel-guidebook-personalization-panel h4 {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-personalization-panel p,
.travel-guidebook-alternatives span,
.travel-guidebook-alternatives em {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-personalization-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wds-space-1);
  padding: 0;
  list-style: none;
}

.travel-guidebook-personalization-chips li {
  padding: var(--wds-space-1) var(--wds-space-2);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-hover-soft-bg);
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-label-s);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-personalization-check {
  padding-top: var(--wds-space-2);
  border-top: 1px solid var(--wizon-ai-popover-border);
}

.travel-guidebook-theme-fit,
.travel-guidebook-personalization-facts {
  display: grid;
  gap: var(--wds-space-1);
  margin: 0;
  padding: 0;
}

.travel-guidebook-theme-fit {
  list-style: none;
}

.travel-guidebook-theme-fit li,
.travel-guidebook-personalization-facts > div {
  display: grid;
  gap: var(--wds-space-1);
  padding-top: var(--wds-space-2);
  border-top: 1px solid var(--wizon-ai-popover-border);
}

.travel-guidebook-theme-fit strong,
.travel-guidebook-personalization-facts dt {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-theme-fit span,
.travel-guidebook-personalization-facts dd,
.travel-guidebook-personalization-state {
  margin: 0;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-label-s);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-personalization-state {
  color: var(--wds-color-warning-text);
  font-style: normal;
  font-weight: var(--wds-font-weight-medium);
}

.travel-guidebook-alternatives {
  display: grid;
  gap: var(--wds-space-2);
  padding: 0;
  list-style: none;
}

.travel-guidebook-alternatives li {
  display: grid;
  gap: var(--wds-space-1);
}

.travel-guidebook-alternatives strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-alternatives em {
  color: var(--wds-color-warning-text);
  font-style: normal;
  font-weight: var(--wds-font-weight-medium);
}

/* G5-7O: the lower guide stays single-column, but each decision surface behaves like a
   compact widget rather than a document section. */
.travel-guidebook-story-flow {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--wds-space-3);
}

.travel-guidebook-prep,
.travel-guidebook-rain-strip {
  display: grid;
  align-content: start;
  gap: var(--wds-space-3);
  min-width: 0;
  padding: var(--wds-space-4);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-popover);
  background: var(--wizon-ai-surface);
  box-shadow: var(--wizon-ai-popover-shadow);
}

.travel-guidebook-prep header,
.travel-guidebook-prep header > span,
.travel-guidebook-rain-strip header,
.travel-guidebook-rain-strip header > span,
.travel-guidebook-readiness,
.travel-guidebook-rain-controls {
  display: flex;
}

.travel-guidebook-prep header,
.travel-guidebook-rain-strip header {
  align-items: baseline;
  justify-content: space-between;
  gap: var(--wds-space-2);
}

.travel-guidebook-rain-strip header > span {
  min-width: 0;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-guidebook-prep header > span:first-child {
  min-width: 0;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-guidebook-readiness,
.travel-guidebook-rain-controls {
  align-items: center;
  flex: 0 0 auto;
  gap: var(--wds-space-1);
}

.travel-guidebook-rain-strip header > .travel-guidebook-rain-controls {
  flex-direction: row;
}

.travel-guidebook-readiness progress {
  appearance: none;
  width: var(--wds-space-24);
  height: var(--wds-space-2);
  overflow: hidden;
  border: 0;
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-canvas);
  accent-color: var(--wds-color-info);
}

.travel-guidebook-readiness progress::-webkit-progress-bar {
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-canvas);
}

.travel-guidebook-readiness progress::-webkit-progress-value {
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wds-color-info);
}

.travel-guidebook-readiness progress::-moz-progress-bar {
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wds-color-info);
}

.travel-guidebook-readiness b,
.travel-guidebook-rain-controls > span {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
}

.travel-guidebook-rain-controls button {
  display: grid;
  place-items: center;
  width: var(--wizon-ai-action-target-touch);
  height: var(--wizon-ai-action-target-touch);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--wizon-ai-text-primary);
  cursor: pointer;
}

.travel-guidebook-rain-controls button:disabled {
  color: var(--wizon-ai-text-muted);
  cursor: default;
}

.travel-guidebook-rain-controls button svg {
  width: var(--wds-space-5);
  height: var(--wds-space-5);
}

.travel-guidebook-prep header strong,
.travel-guidebook-rain-strip header strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
}

.travel-guidebook-prep header span,
.travel-guidebook-rain-strip header span,
.travel-guidebook-rain-strip > p {
  margin: 0;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-prep ol {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
  list-style: none;
}

.travel-guidebook-prep li {
  display: grid;
  grid-template-columns: var(--wds-space-10) minmax(0, 1fr);
  align-items: center;
  gap: var(--wds-space-3);
  min-height: var(--wizon-ai-action-target-touch);
  padding: var(--wds-space-3);
}

.travel-guidebook-prep li + li {
  border-top: 1px solid var(--wizon-ai-popover-border);
}

.travel-guidebook-prep-icon {
  display: grid;
  place-items: center;
  width: var(--wds-space-10);
  height: var(--wds-space-10);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-hover-soft-bg);
  color: var(--wds-color-info-text);
}

.travel-guidebook-prep-icon svg {
  width: var(--wds-space-5);
  height: var(--wds-space-5);
}

.travel-guidebook-prep-copy {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-guidebook-prep-copy strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
}

.travel-guidebook-prep-copy > span {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

/* G5-7P: one contextual fact is visible at a time.  This preserves the guidebook's
   single-column reading rhythm while giving live data an interactive widget surface. */
.travel-guidebook-context-stack {
  display: grid;
  min-width: 0;
  gap: var(--wds-space-3);
}

.travel-guidebook-live-context {
  display: grid;
  min-width: 0;
  gap: var(--wds-space-2);
  padding: var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface);
}

.travel-guidebook-live-context > header,
.travel-guidebook-live-context > header > span,
.travel-guidebook-live-controls,
.travel-guidebook-live-context [data-travel-live-rail],
.travel-guidebook-live-context li,
.travel-guidebook-live-copy {
  display: flex;
}

.travel-guidebook-live-context > header {
  align-items: center;
  justify-content: space-between;
  gap: var(--wds-space-3);
}

.travel-guidebook-live-context > header > span:first-child,
.travel-guidebook-live-copy {
  min-width: 0;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-guidebook-live-context > header strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
}

.travel-guidebook-live-context > header > span:first-child > span,
.travel-guidebook-live-copy > span,
.travel-guidebook-live-copy small,
.travel-guidebook-live-controls > span {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-live-controls {
  align-items: center;
  flex: 0 0 auto;
  gap: 0;
}

.travel-guidebook-live-controls > span {
  padding-inline-end: var(--wds-space-1);
  font-variant-numeric: tabular-nums;
}

.travel-guidebook-live-controls button {
  display: grid;
  place-items: center;
  width: var(--wizon-ai-action-target-touch);
  height: var(--wizon-ai-action-target-touch);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--wizon-ai-text-secondary);
  cursor: pointer;
}

.travel-guidebook-live-controls button:disabled {
  color: var(--wizon-ai-text-muted);
  cursor: default;
}

.travel-guidebook-live-controls svg {
  width: var(--wds-space-4);
  height: var(--wds-space-4);
}

.travel-guidebook-live-context [data-travel-live-rail] {
  min-width: 0;
  gap: var(--wds-space-2);
  margin: 0;
  padding: 0;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-behavior: smooth;
  scroll-snap-type: inline mandatory;
  scroll-snap-stop: always;
  scrollbar-width: none;
  list-style: none;
  touch-action: pan-x;
}

.travel-guidebook-live-context li {
  align-items: center;
  flex: 0 0 100%;
  min-width: 0;
  min-height: calc(var(--wizon-ai-action-target-touch) * 2);
  gap: var(--wds-space-3);
  padding: var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
  opacity: 0.72;
  scroll-snap-align: start;
  transition:
    border-color var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    background var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    opacity var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast);
}

.travel-guidebook-live-context li[aria-current="true"] {
  border-color: var(--wds-color-info);
  background: var(--wizon-ai-hover-soft-bg);
  opacity: 1;
}

.travel-guidebook-live-icon {
  display: grid;
  flex: 0 0 var(--wds-space-10);
  place-items: center;
  width: var(--wds-space-10);
  height: var(--wds-space-10);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-surface);
  color: var(--wds-color-info-text);
}

.travel-guidebook-live-icon svg {
  width: var(--wds-space-5);
  height: var(--wds-space-5);
}

.travel-guidebook-live-copy {
  flex: 1 1 auto;
}

.travel-guidebook-live-copy strong {
  overflow: hidden;
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
  line-height: var(--wds-line-height-normal);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.travel-guidebook-live-context .travel-guidebook-status {
  flex: 0 0 auto;
}

/* G5-7S: weather is its own whole-card surface. It no longer sits inside the
   neutral live-context card, so one horizontal gesture always has one owner. */
.travel-guidebook-weather-mini {
  position: relative;
  gap: var(--wds-space-2);
  margin: 0;
  min-height: 156px;
  padding: var(--wds-space-4);
  overflow: hidden;
  border-radius: var(--wizon-ai-radius-control);
  color: var(--ds-color-on-media);
}

.travel-guidebook-weather-controls {
  display: flex;
}

.travel-guidebook-weather-copy > span,
.travel-guidebook-weather-copy > small,
.travel-guidebook-weather-temperatures small,
.travel-guidebook-weather-controls > span,
.travel-guidebook-weather-meta {
  color: color-mix(in srgb, var(--ds-color-on-media) 78%, transparent);
  font-size: var(--wds-font-size-caption);
}

.travel-guidebook-weather-hero {
  display: grid;
  grid-column: 1;
  grid-row: 1 / span 2;
  place-items: center;
  width: var(--wds-space-10);
  height: var(--wds-space-10);
}

.travel-guidebook-weather-hero .weather-icon { width: var(--wds-space-10); height: var(--wds-space-10); }

.travel-guidebook-weather-controls {
  position: absolute;
  top: var(--wds-space-2);
  right: var(--wds-space-2);
  align-items: center;
}
.travel-guidebook-weather-controls > span { white-space: nowrap; }
.travel-guidebook-weather-controls button {
  display: grid;
  place-items: center;
  width: var(--wizon-ai-action-target-touch);
  height: var(--wizon-ai-action-target-touch);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ds-color-on-media);
  cursor: pointer;
}
.travel-guidebook-weather-controls button:disabled { opacity: 0.45; cursor: default; }
.travel-guidebook-weather-controls svg { width: var(--wds-space-4); height: var(--wds-space-4); }

.travel-guidebook-weather-days {
  display: flex;
  gap: var(--wds-space-2);
  width: 100%;
  margin: 0;
  padding: 0;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-behavior: smooth;
  scroll-snap-type: inline mandatory;
  scrollbar-width: none;
  list-style: none;
  touch-action: pan-x;
}

.travel-guidebook-weather-mini .travel-guidebook-weather-day {
  display: grid;
  grid-template-columns: var(--wds-space-10) minmax(0, 1fr) auto;
  grid-template-rows: auto 1fr;
  align-items: center;
  flex: 0 0 100%;
  gap: var(--wds-space-1) var(--wds-space-3);
  min-width: 0;
  min-height: 92px;
  padding: 0;
  border: 0;
  border-radius: initial;
  background: transparent;
  color: var(--ds-color-on-media);
  box-shadow: none;
  opacity: 1;
  scroll-snap-align: start;
  text-align: left;
}
.travel-guidebook-weather-mini .travel-guidebook-weather-day[aria-current="true"] {
  border: 0;
  background: transparent;
  box-shadow: none;
}
.travel-guidebook-weather-copy {
  display: grid;
  grid-column: 2;
  grid-row: 1 / span 2;
  gap: 2px;
  min-width: 0;
}
.travel-guidebook-weather-copy strong {
  overflow: hidden;
  color: var(--ds-color-on-media);
  font-size: var(--wds-font-size-body-1, 1rem);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.travel-guidebook-weather-temperatures {
  display: flex;
  grid-column: 3;
  grid-row: 2;
  align-self: end;
  justify-self: end;
  gap: var(--wds-space-4);
  padding-inline-end: var(--wds-space-1);
}
.travel-guidebook-weather-temperatures > span {
  display: flex;
  align-items: baseline;
  gap: var(--wds-space-1);
}
.travel-guidebook-weather-temperatures strong {
  color: var(--ds-color-on-media);
  font-size: var(--wds-font-size-title-l);
}
.travel-guidebook-weather-days:focus-visible { outline: none; }
.travel-guidebook-weather-mini:has(.travel-guidebook-weather-days:focus-visible) {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--ds-color-on-media) 72%, transparent);
}
.travel-guidebook-weather-day:focus-visible,
.travel-guidebook-weather-controls button:focus-visible { outline: 2px solid var(--ds-color-on-media); outline-offset: 2px; }
.travel-guidebook-weather-pending { margin: var(--wds-space-10) 0 var(--wds-space-4); color: color-mix(in srgb, var(--ds-color-on-media) 82%, transparent); font-size: var(--wds-font-size-caption); }
.travel-guidebook-weather-meta {
  display: flex;
  justify-content: space-between;
  gap: var(--wds-space-2);
}
.travel-guidebook-weather-meta span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.travel-guidebook-weather-meta strong {
  flex: none;
  color: var(--ds-color-on-media);
  font-weight: var(--wds-font-weight-semibold);
}

/* The assistant message typography layer is intentionally strong. Keep the
   immersive weather card's foreground on its sky palette inside that layer. */
body.chatgpt-reference-v1 .travel-guidebook-weather-mini :is(
  .travel-guidebook-weather-day,
  .travel-guidebook-weather-copy strong,
  .travel-guidebook-weather-temperatures strong,
  .travel-guidebook-weather-meta strong
) {
  color: var(--ds-color-on-media);
}

@media (max-width: 520px) {
  .travel-guidebook-weather-mini { min-height: 156px; }
  .travel-guidebook-weather-mini .travel-guidebook-weather-day {
    grid-template-columns: var(--wds-space-10) minmax(0, 1fr);
  }
  .travel-guidebook-weather-copy {
    grid-row: 1;
    padding-inline-end: calc(var(--wizon-ai-action-target-touch) * 2 + var(--wds-space-3));
  }
  .travel-guidebook-weather-temperatures {
    grid-column: 2;
    grid-row: 2;
    justify-self: start;
    padding-inline-end: 0;
  }
}

.travel-guidebook-rain-strip .travel-guidebook-alternatives {
  display: flex;
  gap: var(--wds-space-2);
  margin: 0;
  padding: 0;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-behavior: smooth;
  scroll-snap-type: inline mandatory;
  scrollbar-width: none;
  list-style: none;
}

.travel-guidebook-rain-strip .travel-guidebook-alternatives li {
  display: grid;
  grid-template-columns: var(--wds-space-12) minmax(0, 1fr) auto;
  align-items: center;
  flex: 0 0 100%;
  gap: var(--wds-space-3);
  min-width: 0;
  min-height: 132px;
  padding: var(--wds-space-4);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
  opacity: 0.78;
  scroll-snap-align: start;
  transition:
    border-color var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    background var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    opacity var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast);
}

.travel-guidebook-rain-strip .travel-guidebook-alternatives li[aria-current="true"] {
  border-color: var(--wds-color-info);
  background: var(--wizon-ai-hover-soft-bg);
  opacity: 1;
}

.travel-guidebook-rain-icon {
  display: grid;
  place-items: center;
  width: var(--wds-space-12);
  height: var(--wds-space-12);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-surface);
  color: var(--wds-color-info-text);
}

.travel-guidebook-rain-icon svg {
  width: var(--wds-space-5);
  height: var(--wds-space-5);
}

.travel-guidebook-rain-copy {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-guidebook-rain-copy > span:first-child,
.travel-guidebook-rain-copy > span,
.travel-guidebook-rain-copy small {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-rain-copy strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-1);
}

.travel-guidebook-rain-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wds-space-2);
}

.travel-guidebook-rain-actions button {
  min-height: var(--wizon-ai-action-target-touch);
  padding-inline: var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-semibold);
  cursor: pointer;
}

.travel-guidebook-rain-actions [data-travel-rain-apply] {
  border-color: var(--wds-color-info);
  background: var(--wds-color-info);
  color: var(--wds-color-text-on-primary);
}

.travel-guidebook-rain-actions button:disabled {
  border-color: var(--wizon-ai-popover-border);
  background: var(--wizon-ai-canvas);
  color: var(--wizon-ai-text-muted);
  cursor: default;
}

.travel-guidebook-rain-actions [data-travel-rain-apply-status] {
  flex: 1 1 100%;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

/* G5-7C: the media contract stays ordered and untruncated. One horizontal snap rail is
   reused at every container size; touch scroll, visible controls, and keyboard movement
   are equivalent paths to the same items. */
.travel-media-presentation {
  display: grid;
  min-width: 0;
  max-width: 100%;
  gap: var(--wds-space-2);
}

[data-travel-photo-rail] {
  min-width: 0;
}

.travel-media-heading,
.travel-media-heading > span,
.travel-media-controls,
.travel-media-fallback,
.travel-media-fallback > span:last-child {
  display: flex;
}

.travel-media-heading {
  align-items: center;
  justify-content: space-between;
  gap: var(--wds-space-3);
}

.travel-media-heading > span:first-child {
  min-width: 0;
  align-items: baseline;
  gap: var(--wds-space-2);
}

.travel-media-heading strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-1);
}

.travel-media-heading [data-travel-media-position] {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
}

.travel-media-controls {
  flex: 0 0 auto;
  gap: 0;
}

.travel-media-controls button {
  display: grid;
  place-items: center;
  width: var(--wizon-ai-action-target-touch);
  height: var(--wizon-ai-action-target-touch);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--wizon-ai-text-secondary);
  cursor: pointer;
}

.travel-media-controls button:disabled {
  color: var(--wizon-ai-text-muted);
  cursor: default;
}

.travel-media-controls svg,
.travel-media-fallback svg {
  width: var(--wds-space-4);
  height: var(--wds-space-4);
}

.travel-media-viewport {
  display: flex;
  min-width: 0;
  max-width: 100%;
  gap: var(--wds-space-3);
  padding-block: var(--wds-space-1);
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-inline: contain;
  scroll-behavior: smooth;
  scroll-snap-type: inline mandatory;
  scroll-snap-stop: always;
  scroll-padding-inline: var(--wds-space-1);
  scrollbar-width: thin;
  touch-action: pan-x;
}

.travel-media-card.visual-answer-media,
[data-travel-photo-rail] .travel-media-card,
[data-travel-photo-rail] > :is(button, a, li, article) {
  display: grid;
  flex: 0 0 min(100%, var(--wizon-adaptive-right-panel-width));
  scroll-snap-align: start;
  border: 1px solid var(--wizon-ai-popover-border);
  background: var(--wizon-ai-surface);
  transform-origin: center;
  transition:
    border-color var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    box-shadow var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    opacity var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    transform var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-spatial-ease);
}

.travel-media-card.visual-answer-media[aria-current="true"],
.travel-media-card.visual-answer-media:is([aria-current="true"], .is-selected, [data-selected="true"]),
[data-travel-photo-rail] > :is(button, a, li, article):is([aria-current="true"], .is-selected, [data-selected="true"]) {
  border-color: var(--wds-color-info);
  box-shadow: inset 0 0 0 1px var(--wds-color-info);
  transform: none;
}

.travel-media-card.visual-answer-media img {
  aspect-ratio: 16 / 9;
  height: auto;
}

.travel-media-card-copy {
  display: grid;
  min-width: 0;
  gap: var(--wds-space-1);
  padding: var(--wds-space-3);
}

.travel-media-card-copy strong {
  overflow: hidden;
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
  line-height: var(--wds-line-height-normal);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.travel-media-card-copy > span {
  overflow: hidden;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.travel-media-card-copy .travel-media-card-scope {
  color: var(--wds-color-info-text);
  font-weight: var(--wds-font-weight-semibold);
}

.travel-media-fallback {
  align-items: center;
  gap: var(--wds-space-3);
  min-height: calc(var(--wizon-ai-action-target-touch) * 2);
  padding: var(--wds-space-4);
  border: 1px dashed var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  color: var(--wizon-ai-text-secondary);
}

.travel-media-fallback[hidden] {
  display: none;
}

.travel-media-fallback > span:first-child {
  display: grid;
  flex: 0 0 var(--wizon-ai-action-target-touch);
  place-items: center;
  width: var(--wizon-ai-action-target-touch);
  height: var(--wizon-ai-action-target-touch);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-hover-soft-bg);
}

.travel-media-fallback > span:last-child {
  min-width: 0;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-media-fallback strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
}

.travel-media-fallback span span {
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-media-controls button:focus-visible,
.travel-media-viewport:focus-visible,
.travel-media-card:focus-visible,
.travel-itinerary-stop-control:focus-visible,
.travel-itinerary-day-step:focus-visible,
.travel-itinerary-tabs button:focus-visible,
.travel-guidebook-course-controls button:focus-visible,
.travel-guidebook-course-viewport:focus-visible,
.travel-guidebook-course-card:focus-visible,
[data-travel-photo-rail]:focus-visible,
[data-travel-course-rail]:focus-visible {
  outline: 0;
  box-shadow: 0 0 0 var(--wds-space-1) var(--wizon-ai-focus-visible-ring);
}

/* G5-7K: the rendering layer can add the semantic hero marker without coupling this
   presentation rule to a single payload version. Selection state drives a short, readable
   crossfade while the image remains the leading guidebook surface. */
:is(.travel-guidebook-hero, .travel-hero, [data-travel-guidebook-hero]) {
  position: relative;
  min-width: 0;
  aspect-ratio: 16 / 7;
  overflow: hidden;
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
  isolation: isolate;
}

/* A source-backed photo keeps the cinematic ratio above. When TourAPI has no usable
   image, the honest text fallback must collapse to its content instead of reserving a
   large blank photographic canvas. */
.travel-guidebook-hero-fallback[data-travel-guidebook-fallback] {
  aspect-ratio: auto;
  overflow: visible;
  background: transparent;
  isolation: auto;
}

:is(.travel-guidebook-hero, .travel-hero, [data-travel-guidebook-hero]) :is(img, [data-travel-hero-image], .travel-hero-media) {
  width: 100%;
  min-height: 100%;
  object-fit: cover;
  transform: scale(1.01);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
}

:is(.travel-guidebook-hero, .travel-hero, [data-travel-guidebook-hero]):is(.is-transitioning, [data-travel-selection-state="enter"], [data-travel-selection-state="swap"]) :is(img, [data-travel-hero-image], .travel-hero-media),
:is(.travel-guidebook-hero, .travel-hero, [data-travel-guidebook-hero]):is(.is-transitioning, [data-travel-selection-state="enter"], [data-travel-selection-state="swap"]) :is(.travel-hero-copy, [data-travel-hero-copy]) {
  animation: travel-guidebook-hero-in var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease) both;
}

[data-travel-guidebook-experience]:is([data-travel-selection-state="enter"], [data-travel-selection-state="swap"], [data-travel-selection-state="changing"]) [data-travel-guidebook-hero] :is(img, [data-travel-hero-image], .travel-hero-media),
[data-travel-guidebook-experience]:is([data-travel-selection-state="enter"], [data-travel-selection-state="swap"], [data-travel-selection-state="changing"]) [data-travel-guidebook-hero] :is(.travel-hero-copy, [data-travel-hero-copy], .travel-guidebook-hero-overlay) {
  animation: travel-guidebook-hero-in var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease) both;
}

@keyframes travel-guidebook-hero-in {
  from {
    opacity: 0;
    transform: translate3d(0, var(--wds-space-3), 0) scale(1.01);
  }

  to {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
  }
}

.travel-itinerary-day:not([hidden]),
.travel-itinerary-route-panel:not([hidden]),
.travel-guidebook-detail:not([hidden]),
[data-travel-detail-panel]:not([hidden]),
[data-travel-location-panel]:not([hidden]) {
  animation: travel-guidebook-panel-in var(--wizon-ai-motion-panel-duration) var(--wizon-ai-motion-spatial-ease) both;
}

@keyframes travel-guidebook-panel-in {
  from {
    opacity: 0;
    transform: translate3d(0, var(--wds-space-2), 0);
  }

  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

/* The hero and curated-course rail are supplied only when source-backed media exists.
   A text fallback uses the same semantic hero marker, keeping the state honest without
   manufacturing a photographic surface. */
.travel-guidebook-hero.has-media {
  display: grid;
  min-height: 0;
  color: var(--wds-color-text-on-primary);
}

.travel-guidebook-hero-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: zoom-in;
}

.travel-guidebook-hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, color-mix(in srgb, var(--wizon-ai-text-primary) 68%, transparent), transparent);
  pointer-events: none;
}

.travel-guidebook-rain-visual-fallback,
.travel-guidebook-rain-place-fallback,
.travel-guidebook-rain-map-fallback {
  display: grid;
  place-items: center;
  align-content: center;
  gap: var(--wds-space-2);
  min-height: 100%;
  padding: var(--wds-space-4);
  background: var(--wizon-ai-hover-soft-bg);
  color: var(--wizon-ai-text-secondary);
  text-align: center;
}

.travel-guidebook-rain-visual-fallback[hidden],
.travel-guidebook-rain-place-fallback[hidden],
.travel-guidebook-rain-map-fallback[hidden] {
  display: none;
}

.travel-guidebook-rain-visual-fallback {
  position: absolute;
  inset: 0;
}

.travel-guidebook-rain-visual-fallback strong,
.travel-guidebook-rain-place-fallback > span:last-child,
.travel-guidebook-rain-map-fallback > span {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
}

.travel-guidebook-rain-visual-fallback > span:last-child {
  max-width: var(--wizon-adaptive-right-panel-width);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-rain-visual-fallback svg,
.travel-guidebook-rain-place-fallback svg,
.travel-guidebook-rain-map-fallback svg {
  width: var(--wds-space-6);
  height: var(--wds-space-6);
  color: var(--wds-color-info-text);
}

.travel-guidebook-rain-place-fallback {
  align-self: stretch;
  min-height: var(--wds-space-24);
  border-radius: calc(var(--wizon-ai-radius-control) - var(--wds-space-1));
}

.travel-guidebook-rain-map-fallback {
  min-height: var(--wds-space-24);
  margin: var(--wds-space-3);
  border: 1px dashed var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
}

.travel-guidebook-hero-overlay {
  position: relative;
  align-self: end;
  max-width: var(--wizon-ai-chat-max-width);
  padding: var(--wds-space-5);
  pointer-events: none;
}

.travel-guidebook-hero-overlay h3,
.travel-guidebook-hero-overlay p,
.travel-guidebook-hero-kicker,
.travel-guidebook-hero-status,
.travel-guidebook-hero-credit {
  color: var(--wds-color-text-on-primary);
}

.travel-guidebook-hero-overlay h3,
.travel-guidebook-hero-overlay p {
  margin: 0;
}

.travel-guidebook-hero-overlay h3 {
  margin-top: var(--wds-space-1);
  font-size: var(--wds-font-size-title-l);
}

.travel-guidebook-hero-overlay p {
  max-width: var(--wizon-adaptive-right-panel-width);
  margin-top: var(--wds-space-2);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-hero-kicker,
.travel-guidebook-hero-status,
.travel-guidebook-hero-credit {
  font-size: var(--wds-font-size-caption);
}

.travel-guidebook-hero-action {
  position: absolute;
  inset-block-start: var(--wds-space-3);
  inset-inline-end: var(--wds-space-3);
}

.travel-guidebook-hero-credit {
  position: absolute;
  inset-block-end: var(--wds-space-2);
  inset-inline-end: var(--wds-space-3);
  max-width: var(--wizon-adaptive-right-panel-width);
  margin: 0;
  text-align: end;
}

.chatgpt-reference-v1 [data-travel-guidebook-hero] .travel-guidebook-hero-overlay h3,
.chatgpt-reference-v1 [data-travel-guidebook-hero] .travel-guidebook-hero-overlay p,
.chatgpt-reference-v1 [data-travel-guidebook-hero] .travel-guidebook-hero-kicker,
.chatgpt-reference-v1 [data-travel-guidebook-hero] .travel-guidebook-hero-status,
.chatgpt-reference-v1 [data-travel-guidebook-hero] .travel-guidebook-hero-credit {
  color: var(--wds-color-text-on-primary);
}

.chatgpt-reference-v1 [data-travel-guidebook-hero] .travel-guidebook-snapshot {
  gap: var(--wds-space-1);
  padding: 0;
  border: 0;
  color: var(--wds-color-text-on-primary);
}

.chatgpt-reference-v1 [data-travel-guidebook-hero] .travel-guidebook-snapshot span {
  min-height: var(--wds-space-6);
  padding-inline: var(--wds-space-2);
  border-radius: var(--wizon-ai-radius-pill);
  background: color-mix(in srgb, var(--wizon-ai-text-primary) 44%, transparent);
  color: var(--wds-color-text-on-primary);
}

.chatgpt-reference-v1 [data-travel-guidebook-hero] .travel-guidebook-status {
  color: var(--wds-color-text-on-primary);
}

/* The no-photo hero is an editorial text header on the page canvas. Do not inherit
   the white-on-photo metadata treatment from the source-backed hero. */
.chatgpt-reference-v1 .travel-guidebook-hero-fallback[data-travel-guidebook-fallback] {
  color: var(--wizon-ai-text-primary);
}

.chatgpt-reference-v1 .travel-guidebook-hero-fallback[data-travel-guidebook-fallback] :is(
    .travel-guidebook-hero-kicker,
    .travel-guidebook-hero-status,
    .travel-guidebook-status
  ) {
  color: var(--wizon-ai-text-secondary);
}

.chatgpt-reference-v1 .travel-itinerary-card:not(.travel-guidebook-card) [data-travel-guidebook-hero] .travel-guidebook-hero-overlay h3 {
  font-size: var(--wds-font-size-headline-m);
}

.travel-guidebook-hero.has-media {
  aspect-ratio: 21 / 5;
}

.travel-guidebook-hero-overlay {
  align-self: center;
  display: grid;
  width: min(58%, var(--wizon-ai-chat-max-width));
  justify-items: start;
  gap: var(--wds-space-1);
  padding-block: var(--wds-space-5);
}

.travel-guidebook-hero-overlay h3,
.travel-guidebook-hero-overlay p {
  margin-block: 0;
}

.travel-guidebook-hero-overlay .travel-guidebook-snapshot {
  padding-block: var(--wds-space-1);
}

.travel-guidebook-hero-weather {
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
  word-break: keep-all;
}

.travel-guidebook-hero-credit {
  max-width: 46%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.travel-media-card-selected {
  position: absolute;
  inset-block-start: var(--wds-space-2);
  inset-inline-start: var(--wds-space-2);
  display: none;
  place-items: center;
  width: var(--wds-space-8);
  height: var(--wds-space-8);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wds-color-info);
  color: var(--wds-color-text-on-primary);
}

.travel-media-card:is([aria-pressed="true"], .is-selected) .travel-media-card-selected {
  display: grid;
}

.travel-media-card-selected svg {
  width: var(--wds-space-4);
  height: var(--wds-space-4);
}

.travel-media-card.visual-answer-media {
  min-height: var(--wds-space-32);
}

.travel-media-card.visual-answer-media img {
  position: absolute;
  inset: 0;
  height: 100%;
  aspect-ratio: auto;
}

.travel-media-card-copy {
  position: relative;
  align-self: end;
  background: linear-gradient(transparent, color-mix(in srgb, var(--wizon-ai-text-primary) 82%, transparent));
}

.chatgpt-reference-v1 .travel-media-card-copy strong,
.chatgpt-reference-v1 .travel-media-card-copy > span,
.chatgpt-reference-v1 .travel-media-card-copy .travel-media-card-scope {
  color: var(--wds-color-text-on-primary);
}

@container travel-itinerary (min-width: 521px) {
  .travel-media-card.visual-answer-media,
  [data-travel-photo-rail] .travel-media-card,
  [data-travel-photo-rail] > :is(button, a, li, article) {
    flex-basis: min(24%, var(--wizon-adaptive-right-panel-width));
  }

  .travel-guidebook-course-card,
  [data-travel-course-rail] > :is(button, a, li, article) {
    flex-basis: min(38%, var(--wizon-adaptive-right-panel-width));
  }
}

[data-travel-course-rail] {
  display: grid;
  min-width: 0;
  gap: var(--wds-space-2);
}

.travel-guidebook-rail-heading,
.travel-guidebook-rail-heading > span,
.travel-guidebook-course-controls {
  display: flex;
  align-items: center;
}

.travel-guidebook-rail-heading {
  justify-content: space-between;
  gap: var(--wds-space-3);
}

.travel-guidebook-rail-heading > span:first-child {
  min-width: 0;
  gap: var(--wds-space-2);
}

.travel-guidebook-rail-heading strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-1);
}

.travel-guidebook-rail-heading [data-travel-course-position] {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
}

.travel-guidebook-course-controls {
  flex: 0 0 auto;
  gap: 0;
}

.travel-guidebook-course-controls button {
  display: grid;
  place-items: center;
  width: var(--wizon-ai-action-target-touch);
  height: var(--wizon-ai-action-target-touch);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--wizon-ai-text-secondary);
  cursor: pointer;
}

.travel-guidebook-course-controls svg {
  width: var(--wds-space-4);
  height: var(--wds-space-4);
}

.travel-guidebook-course-controls button:disabled {
  color: var(--wizon-ai-text-muted);
  cursor: default;
}

.travel-guidebook-course-viewport {
  display: flex;
  min-width: 0;
  gap: var(--wds-space-3);
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-inline: contain;
  scroll-behavior: smooth;
  scroll-snap-type: inline mandatory;
  scroll-snap-stop: always;
  scrollbar-width: thin;
  touch-action: pan-x;
}

.travel-guidebook-course-card {
  display: grid;
  grid-template-columns: minmax(var(--wds-space-24), 2fr) minmax(0, 3fr);
  flex: 0 0 min(100%, var(--wizon-adaptive-right-panel-width));
  min-width: 0;
  padding: var(--wds-space-2);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface);
  color: var(--wizon-ai-text-primary);
  text-align: left;
  scroll-snap-align: start;
  transform-origin: center;
  cursor: pointer;
  transition:
    border-color var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    box-shadow var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    transform var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-spatial-ease);
}

.travel-guidebook-course-card:is(.is-selected, [aria-pressed="true"]) {
  border-color: var(--wds-color-info);
  background: var(--wizon-ai-hover-soft-bg);
  box-shadow: inset 0 0 0 1px var(--wds-color-info);
  transform: none;
}

.travel-guidebook-course-visual,
.travel-guidebook-course-visual img {
  width: 100%;
  height: 100%;
}

.travel-guidebook-course-visual {
  align-self: start;
  aspect-ratio: 4 / 3;
  height: auto;
  min-height: var(--wds-space-20);
  overflow: hidden;
  border-radius: calc(var(--wizon-ai-radius-control) - var(--wds-space-1));
}

.travel-guidebook-course-visual img {
  object-fit: cover;
}

.travel-guidebook-course-copy,
.travel-guidebook-course-badges {
  display: flex;
  min-width: 0;
}

.travel-guidebook-course-copy {
  flex-direction: column;
  gap: var(--wds-space-1);
  padding-inline-start: var(--wds-space-2);
}

.travel-guidebook-course-label {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.travel-guidebook-course-copy strong {
  overflow: hidden;
  font-size: var(--wds-font-size-body-2);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.travel-guidebook-course-badges {
  flex-wrap: wrap;
  gap: var(--wds-space-1);
}

.travel-guidebook-course-badges span {
  display: inline-flex;
  align-items: center;
  min-height: var(--wds-space-6);
  padding-inline: var(--wds-space-2);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-canvas);
  color: var(--wds-color-info-text);
  font-size: var(--wds-font-size-label-s);
}

.travel-guidebook-plan {
  display: grid;
  min-width: 0;
  gap: var(--wds-space-4);
  padding: var(--wds-space-4);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-popover);
  background: var(--wizon-ai-surface);
  box-shadow: none;
}

.travel-guidebook-plan-heading,
.travel-guidebook-plan-heading > span {
  display: flex;
}

.travel-guidebook-plan-heading {
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--wds-space-3);
}

.travel-guidebook-plan-heading > span:first-child {
  min-width: 0;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-guidebook-plan-heading strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-1);
}

.travel-guidebook-plan-heading > span:first-child > span {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.travel-guidebook-plan-heading .travel-itinerary-day-nav,
.travel-guidebook-plan-heading .travel-itinerary-tabs {
  flex: 0 0 auto;
  gap: 0;
}

.travel-guidebook-plan-heading .travel-itinerary-tabs {
  width: auto;
  padding: 0;
  border-radius: initial;
  background: transparent;
}

.travel-guidebook-plan-heading .travel-itinerary-tabs button {
  min-width: auto;
  padding-inline: var(--wds-space-3);
  border-radius: initial;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-body-1);
}

.travel-guidebook-plan-heading .travel-itinerary-tabs button[aria-selected="true"] {
  background: transparent;
  color: var(--wizon-ai-text-primary);
  font-weight: var(--wds-font-weight-semibold);
  box-shadow: none;
  transform: none;
}

.travel-guidebook-selection-layout {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--wds-space-4);
}

.travel-guidebook-place-detail {
  display: grid;
  grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
  align-items: center;
  min-width: 0;
  gap: var(--wds-space-3);
  margin-top: var(--wds-space-3);
  padding-top: var(--wds-space-3);
  border-top: 1px solid var(--wizon-ai-popover-border);
  animation: travel-guidebook-panel-in var(--wizon-ai-motion-panel-duration) var(--wizon-ai-motion-spatial-ease) both;
}

.travel-guidebook-place-detail[hidden] {
  display: none;
}

.travel-guidebook-place-visual {
  align-self: start;
  overflow: hidden;
  border-radius: calc(var(--wizon-ai-radius-control) - var(--wds-space-1));
}

.travel-guidebook-place-visual img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.travel-guidebook-location-disclosure {
  min-width: 0;
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-canvas);
  overflow: hidden;
}

.travel-guidebook-location-disclosure > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: var(--wizon-ai-action-target-touch);
  gap: var(--wds-space-3);
  padding: var(--wds-space-2) var(--wds-space-3);
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-semibold);
  cursor: pointer;
  list-style: none;
}

.travel-guidebook-location-disclosure > summary::-webkit-details-marker {
  display: none;
}

.travel-guidebook-location-disclosure > summary > span:first-child {
  display: flex;
  align-items: center;
  gap: var(--wds-space-2);
}

.travel-guidebook-location-disclosure > summary > span:first-child svg,
.travel-guidebook-location-chevron svg {
  width: var(--wds-space-5);
  height: var(--wds-space-5);
}

.travel-guidebook-location-chevron {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  color: var(--wizon-ai-text-secondary);
  transition: transform var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-spatial-ease);
}

.travel-guidebook-location-disclosure[open] .travel-guidebook-location-chevron {
  transform: rotate(180deg);
}

.travel-guidebook-location-disclosure .travel-itinerary-route-preview {
  min-height: 0;
  margin: 0 var(--wds-space-3) var(--wds-space-3);
}

.travel-guidebook-place-copy {
  display: flex;
  min-width: 0;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--wds-space-1);
}

.travel-guidebook-place-copy [hidden] {
  display: none;
}

.travel-guidebook-place-copy h4,
.travel-guidebook-place-copy p {
  margin: 0;
}

.travel-guidebook-place-copy > span:first-child,
.travel-guidebook-place-copy p,
.travel-guidebook-place-copy small {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-place-copy h4 {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
}

.travel-guidebook-place-copy small {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.travel-guidebook-place-timing,
[data-travel-place-timing],
[data-travel-place-route] {
  color: var(--wds-color-info-text);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
}

.travel-course-decision {
  display: grid;
  gap: var(--wds-space-3);
  padding: var(--wds-space-4);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-popover);
  background: var(--wizon-ai-surface);
}

.travel-course-decision-heading,
.travel-course-decision-heading > span,
.travel-course-option header,
.travel-course-option header > span,
.travel-course-rationale header {
  display: flex;
}

.travel-course-decision-heading,
.travel-course-option header,
.travel-course-rationale header {
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--wds-space-3);
}

.travel-course-decision-heading > span,
.travel-course-option header > span {
  min-width: 0;
  flex-direction: column;
  gap: var(--wds-space-1);
}

.travel-course-decision h4,
.travel-course-decision p,
.travel-course-decision dl,
.travel-course-decision dd {
  margin: 0;
}

.travel-course-decision h4 {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-1);
  line-height: var(--wds-line-height-normal);
}

.travel-course-decision-heading p,
.travel-course-decision-heading > span:last-child,
.travel-course-option header span span,
.travel-course-option p,
.travel-course-rationale > p,
.travel-course-rationale header > span,
.travel-course-rejected li span {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-course-decision-heading > span:last-child {
  flex: 0 0 auto;
  white-space: nowrap;
}

.travel-course-options {
  display: flex;
  min-width: 0;
  gap: var(--wds-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-behavior: smooth;
  scroll-snap-type: inline mandatory;
  scroll-snap-stop: always;
  scrollbar-width: thin;
  touch-action: pan-x;
}

.travel-course-option,
[data-travel-course-rail] > :is(button, a, li, article) {
  display: grid;
  flex: 0 0 min(100%, var(--wizon-adaptive-right-panel-width));
  align-content: start;
  gap: var(--wds-space-2);
  min-width: 0;
  padding: var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-surface);
  scroll-snap-align: start;
  transform-origin: center;
  transition:
    border-color var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    box-shadow var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-effect-fast),
    transform var(--wizon-ai-motion-micro-duration) var(--wizon-ai-motion-spatial-ease);
}

.travel-course-option:is(.is-primary, .is-selected, [aria-current="true"], [data-selected="true"]),
[data-travel-course-rail] > :is(button, a, li, article):is(.is-primary, .is-selected, [aria-current="true"], [data-selected="true"]) {
  border-color: var(--wds-color-info);
  background: var(--wizon-ai-hover-soft-bg);
  box-shadow: inset 0 0 0 1px var(--wds-color-info);
  transform: none;
}

.travel-course-option header strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
}

.travel-course-option header b {
  flex: 0 0 auto;
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-h3);
  font-variant-numeric: tabular-nums;
}

.travel-course-rationale {
  display: grid;
  gap: var(--wds-space-3);
  padding-top: var(--wds-space-3);
  border-top: 1px solid var(--wizon-ai-popover-border);
}

.travel-course-rationale dl {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--wds-space-2);
}

.travel-course-rationale dl > div {
  display: grid;
  gap: var(--wds-space-1);
  min-width: 0;
  padding: var(--wds-space-3);
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-hover-soft-bg);
}

.travel-course-rationale dt {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-label-s);
}

.travel-course-rationale dd {
  display: grid;
  gap: var(--wds-space-1);
}

.travel-course-rationale dd strong {
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-1);
  font-variant-numeric: tabular-nums;
}

.travel-course-rationale dd span,
.travel-course-check {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-label-s);
  line-height: var(--wds-line-height-normal);
}

.travel-course-rejected {
  border-top: 1px solid var(--wizon-ai-popover-border);
}

.travel-course-rejected summary {
  display: flex;
  align-items: center;
  min-height: var(--wizon-ai-action-target-touch);
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-semibold);
  cursor: pointer;
}

.travel-course-rejected ul {
  display: grid;
  gap: var(--wds-space-2);
  margin: 0;
  padding: 0 0 var(--wds-space-2);
  list-style: none;
}

.travel-course-rejected li {
  display: grid;
  grid-template-columns: minmax(var(--wds-space-16), auto) minmax(0, 1fr);
  gap: var(--wds-space-3);
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-caption);
}

.travel-guidebook-day-head,
.travel-guidebook-day-head > span,
.travel-guidebook-day-meta {
  display: flex;
}

.travel-guidebook-day-head {
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--wds-space-3);
  padding-bottom: var(--wds-space-2);
}

.travel-guidebook-day-head > span {
  flex-direction: column;
  min-width: 0;
  gap: var(--wds-space-1);
}

.travel-guidebook-day-head strong {
  font-size: var(--wds-font-size-body-2);
}

.travel-guidebook-day-head > span > span,
.travel-guidebook-day-meta {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.travel-guidebook-day-meta {
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: var(--wds-space-2);
  margin-bottom: var(--wds-space-3);
  padding: 0;
  border: 0;
}

.travel-guidebook-day-meta > span {
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  min-height: var(--wds-space-8);
  padding-inline: var(--wds-space-3);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-canvas);
}

.travel-guidebook-day-meta svg {
  width: var(--wds-space-4);
  height: var(--wds-space-4);
}

.travel-itinerary-stop.travel-guidebook-stop {
  grid-template-columns: var(--wds-space-8) minmax(0, 1fr);
  gap: var(--wds-space-2);
}

.travel-guidebook-stop .travel-itinerary-stop-marker {
  display: grid;
  place-items: center;
  width: var(--wds-space-8);
  height: var(--wds-space-8);
  margin: 0;
  border: 0;
  background: var(--wds-color-info);
  color: var(--wds-color-text-on-primary);
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-semibold);
}

.travel-guidebook-stop.is-selected .travel-itinerary-stop-marker {
  background: var(--wizon-ai-text-primary);
}

.travel-guidebook-stop.travel-itinerary-stop:not(:last-child)::after {
  top: var(--wds-space-8);
  left: calc(var(--wds-space-8) / 2);
}

.travel-guidebook-stop .travel-itinerary-stop-copy {
  flex-direction: column;
  min-height: var(--wizon-ai-action-target-touch);
  padding: var(--wds-space-3);
  border: 1px solid transparent;
}

.travel-guidebook-stop .travel-itinerary-stop-copy > .travel-itinerary-stop-control {
  width: 100%;
  min-height: var(--wizon-ai-action-target-touch);
}

.travel-guidebook-stop.is-selected .travel-itinerary-stop-copy {
  border-color: var(--wds-color-info);
  background: var(--wizon-ai-hover-soft-bg);
  box-shadow: none;
  transform: none;
}

.travel-guidebook-stop .travel-itinerary-stop-head {
  justify-content: flex-start;
}

.travel-guidebook-stop .travel-itinerary-stop-head > span {
  display: flex;
  min-width: 0;
  align-items: baseline;
  gap: var(--wds-space-2);
}

.travel-guidebook-stop .travel-itinerary-stop-time {
  flex: 0 0 auto;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
}

.travel-guidebook-stop .travel-itinerary-stop-detail {
  display: none;
}

.travel-guidebook-stop-timing {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
}

.travel-guidebook-stop.is-selected .travel-guidebook-stop-timing {
  display: none;
}

.travel-guidebook-leg,
.travel-guidebook-remaining {
  display: grid;
  grid-template-columns: var(--wds-space-8) minmax(0, 1fr);
  align-items: center;
  gap: var(--wds-space-2);
  min-height: var(--wizon-ai-action-target-touch);
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.travel-guidebook-leg-marker {
  display: grid;
  place-items: center;
  width: var(--wds-space-8);
  height: var(--wds-space-8);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-surface);
}

.travel-guidebook-leg-marker svg {
  width: var(--wds-space-4);
  height: var(--wds-space-4);
}

.travel-guidebook-leg-times {
  color: var(--wizon-ai-text-secondary);
  font-variant-numeric: tabular-nums;
}

.travel-guidebook-leg-copy,
.travel-guidebook-remaining > span:last-child {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wds-space-1) var(--wds-space-2);
}

.travel-guidebook-leg-copy strong,
.travel-guidebook-remaining strong {
  color: var(--wizon-ai-text-primary);
}

.travel-guidebook-leg-evidence {
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-label-s);
  font-variant-numeric: tabular-nums;
}

.travel-guidebook-remaining > span:first-child {
  width: var(--wds-space-3);
  height: var(--wds-space-3);
  margin-inline: auto;
  border: 1px dashed var(--wizon-ai-text-muted);
  border-radius: var(--wizon-ai-radius-pill);
}

.travel-guidebook-remaining > span:last-child {
  justify-content: space-between;
  min-width: 0;
}

.travel-guidebook-status {
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-label-s);
  font-weight: var(--wds-font-weight-medium);
  white-space: nowrap;
}

.travel-guidebook-status > span {
  width: var(--wds-space-2);
  height: var(--wds-space-2);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-text-muted);
}

.travel-guidebook-status[data-status="passed"] {
  color: var(--wds-color-success-text);
}

.travel-guidebook-status[data-status="passed"] > span {
  background: var(--wds-color-success);
}

.travel-guidebook-status[data-status="needs_refresh"] {
  color: var(--wds-color-warning-text);
}

.travel-guidebook-status[data-status="needs_refresh"] > span {
  background: var(--wds-color-warning);
}

.travel-guidebook-status[data-status="not_checked"] {
  color: var(--wds-color-info-text);
}

.travel-guidebook-status[data-status="not_checked"] > span {
  background: var(--wds-color-info);
}

.travel-guidebook-status[data-status="failed"] {
  color: var(--wds-color-error-text);
}

.travel-guidebook-status[data-status="failed"] > span {
  background: var(--wds-color-error);
}

.travel-guidebook-map-note {
  margin: var(--wds-space-2) 0 0;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  line-height: var(--wds-line-height-normal);
}

.travel-guidebook-evidence {
  border-block: 1px solid var(--wizon-ai-popover-border);
}

.travel-guidebook-evidence > [data-travel-evidence-toggle] {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--wizon-ai-action-target-touch);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--wizon-ai-text-primary);
  font-family: inherit;
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-semibold);
  text-align: start;
  cursor: pointer;
}

.travel-guidebook-evidence-list {
  display: grid;
  gap: var(--wds-space-2);
  padding-bottom: var(--wds-space-3);
}

.travel-guidebook-evidence-list p {
  display: grid;
  grid-template-columns: minmax(var(--wds-space-16), auto) minmax(0, 1fr);
  align-items: baseline;
  gap: var(--wds-space-3);
  margin: 0;
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.travel-guidebook-status-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wds-space-2) var(--wds-space-4);
  padding: var(--wds-space-3) 0;
  border-top: 1px solid var(--wizon-ai-popover-border);
}

@container travel-itinerary (max-width: 520px) {
  :is(.travel-guidebook-hero, .travel-hero, [data-travel-guidebook-hero]) {
    aspect-ratio: 7 / 3;
  }

  .travel-guidebook-hero.has-media {
    aspect-ratio: 7 / 3;
  }

  .travel-guidebook-hero-overlay {
    width: 76%;
    padding: var(--wds-space-3);
  }

  .chatgpt-reference-v1 .travel-itinerary-card:not(.travel-guidebook-card) [data-travel-guidebook-hero] .travel-guidebook-hero-overlay h3 {
    font-size: var(--wds-font-size-title-l);
  }

  .travel-guidebook-hero-overlay > p:not(.travel-guidebook-hero-weather),
  .travel-guidebook-hero-overlay .travel-guidebook-snapshot {
    display: none;
  }

  .travel-guidebook-hero-credit {
    display: none;
  }

  .travel-itinerary-hero {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: clamp(92px, 29vw, 128px);
    padding: var(--wds-space-1);
  }

  .travel-itinerary-body {
    gap: var(--wds-space-3);
    padding: var(--wds-space-4);
  }

  .travel-itinerary-heading {
    align-items: stretch;
    flex-direction: column;
  }

  .travel-itinerary-tabs {
    width: 100%;
  }

  .travel-itinerary-day-nav {
    width: 100%;
  }

  .travel-itinerary-day-nav.is-long {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) auto 44px;
    gap: var(--wds-space-1);
  }

  .travel-itinerary-day-nav.is-long .travel-itinerary-tabs {
    padding-inline: var(--wds-space-1);
  }

  .travel-itinerary-day-position {
    min-width: 48px;
    justify-content: center;
  }

  .travel-itinerary-layout {
    display: flex;
    flex-direction: column;
    gap: var(--wds-space-5);
  }

  .travel-itinerary-route-preview {
    order: -1;
    width: 100%;
  }

  .travel-itinerary-route-panel {
    padding: var(--wds-space-3);
  }

  .travel-itinerary-map {
    min-height: 180px;
  }

  .travel-guidebook-card .travel-itinerary-map-marker {
    width: var(--wizon-ai-action-target-touch);
    height: var(--wizon-ai-action-target-touch);
  }

  .travel-itinerary-stop {
    grid-template-columns: var(--wds-space-3) 42px minmax(0, 1fr);
  }

  .travel-itinerary-stop-media {
    width: 100%;
    max-height: 112px;
  }

  .travel-itinerary-stop.is-featured .travel-itinerary-stop-copy {
    display: flex;
  }

  .travel-itinerary-stop.is-featured .travel-itinerary-stop-media {
    margin-top: var(--wds-space-2);
  }

  .travel-itinerary-actions,
  .travel-itinerary-adjust {
    align-items: stretch;
    flex-direction: column;
  }

  .travel-itinerary-actions > button,
  .travel-itinerary-adjust,
  .travel-itinerary-adjust button {
    width: 100%;
  }

  .travel-guidebook-card .travel-itinerary-route-preview {
    order: initial;
    min-height: 0;
  }

  .travel-guidebook-card .travel-itinerary-route-panel {
    height: auto;
  }

  .travel-guidebook-plan-heading {
    align-items: stretch;
    flex-direction: column;
  }

  .travel-guidebook-plan,
  .travel-guidebook-prep,
  .travel-guidebook-rain-strip {
    padding: var(--wds-space-3);
    box-shadow: none;
  }

  .travel-guidebook-plan-heading .travel-itinerary-day-nav,
  .travel-guidebook-plan-heading .travel-itinerary-tabs {
    width: auto;
    align-self: flex-start;
  }

  .travel-guidebook-place-detail {
    grid-template-columns: minmax(0, 1fr);
  }

  .travel-guidebook-rain-strip .travel-guidebook-alternatives li {
    grid-template-columns: var(--wds-space-10) minmax(0, 1fr);
    min-height: 156px;
    padding: var(--wds-space-3);
  }

  .travel-guidebook-rain-strip .travel-guidebook-status {
    grid-column: 2;
  }

  .travel-guidebook-rain-icon {
    width: var(--wds-space-10);
    height: var(--wds-space-10);
  }

  .travel-guidebook-readiness progress {
    width: var(--wds-space-16);
  }

  .travel-guidebook-place-visual img {
    aspect-ratio: 16 / 9;
  }

  .travel-guidebook-revalidation {
    width: 100%;
  }

  .travel-guidebook-hero-action {
    width: auto;
    gap: var(--wds-space-1);
    padding-inline: var(--wds-space-2);
    font-size: var(--wds-font-size-label-s);
  }

  .travel-media-heading,
  .travel-media-heading > span:first-child {
    gap: var(--wds-space-1);
  }

  .travel-media-heading strong {
    font-size: var(--wds-font-size-body-2);
  }

  .travel-guidebook-snapshot {
    gap: var(--wds-space-1) var(--wds-space-2);
  }

  .travel-media-card.visual-answer-media {
    flex-basis: calc(100% - var(--wds-space-8));
  }

  .travel-course-decision {
    padding: var(--wds-space-3);
  }

  .travel-course-decision-heading {
    align-items: flex-start;
  }

  .travel-course-options,
  .travel-course-rationale dl {
    grid-template-columns: minmax(0, 1fr);
  }

  .travel-course-option,
  .travel-guidebook-course-card,
  [data-travel-course-rail] > :is(button, a, li, article) {
    flex-basis: calc(100% - var(--wds-space-8));
  }

  .travel-course-rejected li {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--wds-space-1);
  }

  .travel-itinerary-stop.travel-guidebook-stop,
  .travel-guidebook-leg,
  .travel-guidebook-remaining {
    grid-template-columns: var(--wds-space-8) minmax(0, 1fr);
  }

  .travel-guidebook-stop .travel-itinerary-stop-copy {
    padding: var(--wds-space-2);
  }

  .travel-guidebook-evidence-list p {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--wds-space-1);
  }

  .travel-guidebook-status-legend {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (prefers-reduced-motion: reduce) {
  .visual-answer-media img,
  .travel-media-viewport,
  [data-travel-photo-rail],
  [data-travel-course-rail],
  .travel-media-card.visual-answer-media,
  .travel-course-option,
  .travel-guidebook-course-viewport,
  .travel-guidebook-course-card,
  .travel-guidebook-live-context [data-travel-live-rail],
  .travel-guidebook-weather-days,
  .travel-guidebook-weather-mini,
  .travel-guidebook-live-context li,
  .travel-itinerary-tabs,
  .travel-itinerary-tabs button,
  .travel-itinerary-stop-copy,
  .travel-itinerary-stop-detail,
  .travel-itinerary-map-marker,
  .travel-itinerary-day,
  .travel-itinerary-route-panel,
  .travel-guidebook-place-detail,
  .travel-guidebook-detail,
  [data-travel-detail-panel],
  [data-travel-location-panel],
  :is(.travel-guidebook-hero, .travel-hero, [data-travel-guidebook-hero]),
  :is(.travel-guidebook-hero, .travel-hero, [data-travel-guidebook-hero]) :is(img, [data-travel-hero-image], .travel-hero-media) {
    transition: none;
    animation: none;
    transform: none;
    scroll-behavior: auto;
  }
}

@media (hover: hover) and (pointer: fine) {
  .travel-media-controls button:not(:disabled):hover,
  .travel-guidebook-course-controls button:not(:disabled):hover,
  .travel-guidebook-live-controls button:not(:disabled):hover {
    color: var(--wizon-ai-text-primary);
  }

  .travel-media-card.visual-answer-media:not([aria-current="true"]):hover,
  [data-travel-photo-rail] > :is(button, a, li, article):not([aria-current="true"]):not(.is-selected):hover,
  .travel-course-option:not(.is-primary):hover,
  .travel-guidebook-course-card:not(.is-selected):hover,
  [data-travel-course-rail] > :is(button, a, li, article):not([aria-current="true"]):not(.is-selected):hover {
    transform: scale(1.01);
  }

  .travel-itinerary-day-step:not(:disabled):hover,
  .travel-itinerary-tabs button:hover {
    background: var(--wizon-ai-hover-soft-bg);
  }
}

/* Image tiles open a focused preview. Source-list navigation stays exclusively on the source
   pill below the answer, avoiding two controls that perform the same provenance action. */
.image-preview-dialog[open] {
  position: fixed;
  inset: 0;
  display: block;
  width: min(
    calc(var(--wizon-ai-chat-max-width) + var(--wizon-adaptive-right-panel-width)),
    calc(100vw - var(--wds-space-8))
  );
  height: min(calc(100dvh - var(--wds-space-8)), calc(var(--wizon-ai-chat-max-width) + var(--wds-space-16)));
  max-width: none;
  max-height: none;
  margin: auto;
  padding: 0;
  overflow: hidden;
  border: 0;
  border-radius: var(--wizon-ai-radius-popover);
  background: var(--wizon-ai-popover-bg);
  color: var(--wizon-ai-text-primary);
  box-shadow: var(--wizon-ai-dialog-shadow);
  opacity: 0;
  transform: translate3d(0, var(--wds-space-3), 0) scale(0.985);
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    transform var(--wizon-motion-surface-duration) var(--wizon-motion-spatial-ease);
}

.image-preview-dialog::backdrop {
  background: color-mix(in srgb, var(--ds-color-media-scrim) 16%, transparent);
  opacity: 0;
  transition: opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease);
}

.image-preview-dialog[data-motion-state="open"] {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}

.image-preview-dialog[data-motion-state="open"]::backdrop {
  opacity: 1;
}

.image-preview-dialog[data-motion-state="closing"] {
  opacity: 0;
  transform: translate3d(0, var(--wds-space-3), 0) scale(0.985);
}

.image-preview-dialog[data-motion-state="closing"]::backdrop {
  opacity: 0;
}

html[data-theme="dark"] .image-preview-dialog::backdrop {
  background: color-mix(in srgb, var(--ds-color-media-scrim) 52%, transparent);
}

.image-preview-dialog:focus {
  outline: none;
}

.image-preview-shell {
  position: relative;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: 100%;
  min-height: 0;
}

.image-preview-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--wds-space-3);
  min-height: var(--wizon-ai-action-target-touch);
  padding: var(--wds-space-3) var(--wds-space-4);
  border-bottom: 1px solid var(--wizon-ai-popover-border);
}

.image-preview-head-actions {
  grid-column: 3;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: var(--wds-space-2);
}

.image-preview-stage {
  position: relative;
  display: flex;
  min-width: 0;
  min-height: 0;
  padding: var(--wds-space-5) var(--wds-space-4);
  overflow: hidden;
  background: var(--wizon-ai-hover-soft-bg);
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
}

.image-preview-stage.is-dragging {
  cursor: grabbing;
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-stage {
  background: color-mix(in srgb, var(--wizon-ai-text-primary) 86%, var(--wizon-ai-popover-bg));
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-carousel-card img {
  object-fit: contain;
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-carousel-card.is-current {
  border: 0;
  border-radius: initial;
  background: transparent;
  box-shadow: none;
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-counter,
.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-source-link {
  display: none;
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-head {
  grid-template-columns: minmax(0, auto) auto minmax(0, 1fr);
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-caption-copy {
  grid-column: 1;
  justify-self: start;
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-title-line {
  justify-content: flex-start;
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-title {
  flex: 0 1 auto;
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-head-actions {
  grid-column: 2;
  justify-self: start;
}

html:not([data-wizon-viewport="compact"]) .image-preview-dialog[data-image-preview-scope="travel-place"][open] {
  width: min(760px, 100vw);
  height: min(540px, calc(100dvh - var(--wds-space-8)));
}

.image-preview-header-count {
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-semibold);
  white-space: nowrap;
}

.image-preview-stage-control {
  position: absolute;
  z-index: var(--wizon-layer-sticky, 1);
  top: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 44px;
  block-size: 44px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--wizon-ai-surface);
  cursor: pointer;
  transform: translateY(-50%);
}

.image-preview-stage-control[hidden],
.image-preview-carousel-card[hidden] {
  display: none;
}

.image-preview-stage-control--previous {
  inset-inline-start: var(--wds-space-4);
}

.image-preview-stage-control--next {
  inset-inline-end: var(--wds-space-4);
}

.image-preview-stage-control:focus-visible {
  outline: 2px solid var(--wds-color-focus-ring);
  outline-offset: 2px;
}

.image-preview-disclosure-slot:empty {
  display: none;
}

.image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-disclosure-slot {
  box-sizing: border-box;
  min-block-size: 48px;
  border-top: 1px solid var(--wizon-ai-popover-border);
  background: var(--wizon-ai-popover-bg);
}

.image-preview-place-source-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wds-space-3);
  box-sizing: border-box;
  min-block-size: 48px;
  padding-inline: var(--wds-space-4);
  color: var(--wizon-ai-text-secondary);
  font-size: var(--wds-font-size-caption);
}

.image-preview-place-source-row > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.image-preview-place-source-row a {
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  color: var(--wds-color-primary);
  font-weight: var(--wds-font-weight-semibold);
  text-decoration: none;
  white-space: nowrap;
}

.image-preview-disclosure {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1.4fr) auto;
  align-items: center;
  gap: var(--wds-space-4);
  padding: var(--wds-space-3) var(--wds-space-4);
  border-top: 1px solid var(--wizon-ai-popover-border);
  background: var(--wizon-ai-popover-bg);
}

.image-preview-disclosure-heading,
.image-preview-disclosure-list,
.image-preview-disclosure-list > div {
  min-width: 0;
}

.image-preview-disclosure-heading {
  display: grid;
  gap: var(--wds-space-1);
}

.image-preview-disclosure-heading > span,
.image-preview-disclosure-list dt,
.image-preview-disclosure > p {
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-caption);
}

.image-preview-disclosure-heading strong,
.image-preview-disclosure-list dd {
  overflow: hidden;
  color: var(--wizon-ai-text-primary);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.image-preview-disclosure-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--wds-space-3);
  margin: 0;
}

.image-preview-disclosure-list > div {
  display: grid;
  gap: var(--wds-space-1);
}

.image-preview-disclosure-list dd {
  margin: 0;
  font-size: var(--wds-font-size-body-2);
}

.image-preview-disclosure-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--wds-space-1);
  min-height: var(--wizon-ai-action-target-touch);
  padding-inline: var(--wds-space-3);
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-control);
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-semibold);
  text-decoration: none;
}

.image-preview-disclosure-link svg {
  width: var(--wizon-ai-icon-size-md);
  height: var(--wizon-ai-icon-size-md);
}

.image-preview-disclosure > p {
  grid-column: 1 / -1;
  margin: calc(0px - var(--wds-space-2)) 0 0;
}

.image-preview-disclosure-link:focus-visible {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: 2px;
}

.image-preview-pagination {
  position: absolute;
  right: 0;
  left: 0;
  top: calc(50% + min(39%, calc(var(--wizon-ai-chat-max-width) * 0.34)) + var(--wds-space-3));
  display: grid;
  place-items: center;
  min-height: var(--wds-space-5);
  pointer-events: none;
}

.image-preview-counter {
  padding: var(--wds-space-1) var(--wds-space-2);
  border-radius: var(--wizon-ai-radius-pill);
  background: color-mix(in srgb, var(--ds-color-media-scrim) 48%, transparent);
  color: var(--wizon-ai-surface);
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-semibold);
}

.image-preview-page-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
}

.image-preview-page-dot {
  display: grid;
  place-items: center;
  width: var(--wds-space-5);
  height: var(--wds-space-5);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
}

.image-preview-page-dot::before {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--wizon-ai-text-muted) 18%, transparent);
  content: "";
  transition:
    width var(--wizon-motion-micro-duration) var(--wizon-motion-effect-ease),
    height var(--wizon-motion-micro-duration) var(--wizon-motion-effect-ease),
    background-color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-ease);
}

.image-preview-page-dot.is-current::before {
  width: 8px;
  height: 8px;
  background: var(--wizon-ai-text-primary);
}

.image-preview-carousel {
  position: relative;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
}

.image-preview-carousel-card {
  position: absolute;
  top: 50%;
  left: var(--image-preview-rest-left, 50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--image-preview-rest-width, 22%);
  height: min(78%, calc(var(--wizon-ai-chat-max-width) * 0.68));
  min-width: 0;
  min-height: 0;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--wizon-ai-popover-border);
  border-radius: var(--wizon-ai-radius-popover);
  background: var(--wizon-ai-popover-bg);
  box-shadow: none;
  cursor: pointer;
  opacity: var(--image-preview-rest-opacity, 0);
  pointer-events: none;
  transform: translate3d(-50%, -50%, 0) scale(var(--image-preview-rest-scale, 1));
  will-change: transform, opacity;
  transition:
    opacity var(--wizon-motion-surface-duration) var(--wizon-motion-effect-ease),
    width calc(var(--wizon-motion-panel-duration) + var(--wizon-motion-surface-duration)) var(--wds-ease-standard),
    left calc(var(--wizon-motion-panel-duration) + var(--wizon-motion-surface-duration)) var(--wds-ease-standard),
    transform calc(var(--wizon-motion-panel-duration) + var(--wizon-motion-surface-duration)) var(--wds-ease-standard),
    box-shadow calc(var(--wizon-motion-panel-duration) + var(--wizon-motion-surface-duration)) var(--wds-ease-standard);
}

.image-preview-carousel-card.is-visible {
  pointer-events: auto;
}

.image-preview-carousel-card.is-current {
  z-index: var(--wizon-layer-sticky, 1);
  pointer-events: auto;
  box-shadow: var(--wizon-ai-dialog-shadow);
}

.image-preview-carousel-card.is-wrap-jump {
  opacity: 0;
  transition: none;
}

.image-preview-carousel-card img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 0;
  object-fit: cover;
  border-radius: inherit;
  background: transparent;
  pointer-events: none;
}

.image-preview-stage.is-dragging .image-preview-carousel-card.is-current {
  left: var(--image-preview-live-left, 50%);
  width: var(--image-preview-live-width, 66%);
  opacity: var(--image-preview-live-opacity, 1);
  transform: translate3d(-50%, -50%, 0) scale(var(--image-preview-live-scale, 1));
}

.image-preview-stage.is-dragging .image-preview-carousel-card {
  left: var(--image-preview-live-left, 50%);
  width: var(--image-preview-live-width, 22%);
  opacity: var(--image-preview-live-opacity, 0);
  transform: translate3d(-50%, -50%, 0) scale(var(--image-preview-live-scale, 1));
  transition: none;
}

.image-preview-caption-copy {
  grid-column: 2;
  justify-self: center;
  display: grid;
  justify-items: center;
  gap: var(--wds-space-1);
  max-width: min(46vw, var(--wizon-ai-chat-max-width));
  min-width: 0;
  text-align: center;
}

.image-preview-title-line {
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: var(--wds-space-2);
  min-width: 0;
}

.image-preview-title {
  flex: 1 1 auto;
  margin: 0;
  overflow: hidden;
  color: var(--wizon-ai-text-primary);
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-semibold);
  line-height: var(--wds-line-height-normal);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.image-preview-icon-button,
.image-preview-source-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--wizon-ai-action-target-touch);
  min-height: var(--wizon-ai-action-target-touch);
  border: 0;
  background: transparent;
  color: var(--wizon-ai-text-primary);
  opacity: 0.72;
  transition: opacity var(--wizon-motion-micro-duration) var(--wizon-motion-effect-ease);
}

.image-preview-icon-button {
  padding: 0;
  border-radius: var(--wizon-ai-radius-control);
  cursor: pointer;
}

.image-preview-mobile-back {
  display: none;
}

.image-preview-icon-button svg,
.image-preview-source-link svg {
  width: var(--wizon-ai-icon-size-md);
  height: var(--wizon-ai-icon-size-md);
}

.image-preview-source-link {
  flex: none;
  width: var(--wizon-ai-action-target-touch);
  padding: 0;
  border-radius: var(--wizon-ai-radius-control);
  text-decoration: none;
}

.chatgpt-reference-v1 .chatgpt-tooltip-host[data-tooltip-placement="bottom"] > .chatgpt-tooltip {
  top: calc(100% + var(--wds-space-2));
  left: 50%;
  transform: translateX(-50%) translateY(calc(0px - var(--wds-space-1)));
}

.chatgpt-reference-v1 .chatgpt-tooltip-host[data-tooltip-placement="bottom"][data-tooltip-open="true"] > .chatgpt-tooltip {
  transform: translateX(-50%) translateY(0);
}

.image-preview-icon-button:focus-visible,
.image-preview-source-link:focus-visible,
.image-preview-carousel-card:focus-visible,
.image-preview-page-dot:focus-visible {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: 2px;
}

@media (hover: hover) {
  .image-preview-icon-button:hover,
  .image-preview-source-link:hover {
    opacity: 1;
  }

  .image-preview-carousel-card.is-before:hover {
    opacity: 0.94;
    transform: translate3d(-50%, -50%, 0) scale(0.97);
  }

  .image-preview-carousel-card.is-after:hover {
    opacity: 0.94;
    transform: translate3d(-50%, -50%, 0) scale(0.97);
  }

  .image-preview-carousel-card.is-current:hover {
    transform: translate3d(-50%, -50%, 0) scale(1.01);
  }

  .image-preview-page-dot:not(.is-current):hover::before {
    background: color-mix(in srgb, var(--wizon-ai-text-muted) 42%, transparent);
  }
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] {
  border: 0;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"][open] {
  background: color-mix(in srgb, var(--wizon-ai-text-primary) 86%, var(--wizon-ai-popover-bg));
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-head {
  grid-template-columns: var(--wizon-ai-action-target-sm) minmax(0, 1fr) auto;
  padding-top: env(safe-area-inset-top, 0px);
  border-bottom-color: color-mix(in srgb, var(--wizon-ai-surface) 16%, transparent);
  background: color-mix(in srgb, var(--wizon-ai-text-primary) 86%, var(--wizon-ai-popover-bg));
  color: var(--wizon-ai-surface);
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-caption-copy {
  grid-column: 2;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-head-actions {
  grid-column: 3;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] :is(.image-preview-title, .image-preview-header-count, .image-preview-icon-button) {
  color: var(--wizon-ai-surface);
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-source-link {
  display: none;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-stage {
  padding-inline: 0;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-pagination {
  top: auto;
  bottom: var(--wds-space-2);
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-page-dot::before {
  background: color-mix(in srgb, var(--wizon-ai-surface) 32%, transparent);
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-page-dot.is-current::before {
  background: var(--wizon-ai-surface);
}

html[data-wizon-viewport="compact"] .image-preview-head {
  grid-template-columns: var(--wizon-ai-action-target-sm) minmax(0, 1fr) auto;
  height: var(--wds-space-16);
  gap: var(--wds-space-4);
  min-height: var(--wds-space-16);
  padding: 0 var(--wds-space-4);
}

html[data-wizon-viewport="compact"] .image-preview-mobile-back {
  grid-column: 1;
  align-self: center;
  display: inline-flex;
}

html[data-wizon-viewport="compact"] .image-preview-caption-copy {
  grid-column: 2;
  align-self: stretch;
  justify-self: stretch;
  display: flex;
  align-items: center;
  justify-items: start;
  max-width: none;
  text-align: left;
}

html[data-wizon-viewport="compact"] .image-preview-title-line {
  justify-content: flex-start;
  align-items: center;
  width: 100%;
}

html[data-wizon-viewport="compact"] .image-preview-title {
  font-size: var(--wds-font-size-body-2);
  font-weight: 600;
  line-height: var(--wds-line-height-label, 20px);
}

html[data-wizon-viewport="compact"] .image-preview-head-actions {
  grid-column: 3;
  align-self: center;
}

html[data-wizon-viewport="compact"] .image-preview-desktop-close {
  display: none;
}

html[data-wizon-viewport="compact"] .image-preview-mobile-back,
html[data-wizon-viewport="compact"] .image-preview-source-link {
  width: var(--wizon-ai-action-target-sm);
  min-width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  min-height: var(--wizon-ai-action-target-sm);
}

html[data-wizon-viewport="compact"] .image-preview-stage {
  padding: var(--wds-space-3);
}

html[data-wizon-viewport="compact"] .image-preview-stage-control--previous {
  inset-inline-start: var(--wds-space-3);
}

html[data-wizon-viewport="compact"] .image-preview-stage-control--next {
  inset-inline-end: var(--wds-space-3);
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-disclosure-slot {
  padding-bottom: env(safe-area-inset-bottom, 0px);
  border-top-color: color-mix(in srgb, var(--wizon-ai-surface) 16%, transparent);
  background: color-mix(in srgb, var(--wizon-ai-text-primary) 86%, var(--wizon-ai-popover-bg));
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-place-source-row {
  color: color-mix(in srgb, var(--wizon-ai-surface) 68%, transparent);
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-head {
  box-sizing: border-box;
  min-block-size: calc(56px + env(safe-area-inset-top, 0px));
  height: calc(56px + env(safe-area-inset-top, 0px));
  padding: env(safe-area-inset-top, 0px) var(--wds-space-4) 0;
}

html[data-wizon-viewport="compact"] .image-preview-disclosure {
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--wds-space-2) var(--wds-space-3);
  padding: var(--wds-space-3) var(--wds-space-4) var(--wds-space-4);
}

html[data-wizon-viewport="compact"] .image-preview-disclosure-heading,
html[data-wizon-viewport="compact"] .image-preview-disclosure-list {
  grid-column: 1 / -1;
}

html[data-wizon-viewport="compact"] .image-preview-disclosure-link {
  grid-column: 2;
  grid-row: 3 / span 2;
  align-self: end;
}

html[data-wizon-viewport="compact"] .image-preview-disclosure > p {
  grid-column: 1;
  margin: 0;
}

html[data-wizon-viewport="compact"] .image-preview-pagination {
  top: calc(50% + min(20vw, 120px) + var(--wds-space-3));
}

html[data-wizon-viewport="compact"] .image-preview-carousel-card {
  height: min(40vw, 240px);
}

html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-carousel-card.is-current {
  height: min(64vw, 360px);
}

html[data-wizon-viewport="compact"] .image-preview-source-link {
  align-self: center;
}

@media (prefers-reduced-motion: reduce) {
  .image-preview-dialog[open],
  .image-preview-dialog::backdrop,
  .image-preview-carousel-card,
  .image-preview-page-dot::before {
    transition: none;
  }
}

/* RC22 · Common Media Viewer
   One full-screen media surface is shared by travel-place and generic visual answers.
   Controls float above the media so the image remains the first visual layer. */
.image-preview-dialog[open] {
  inset: 0;
  width: 100vw;
  height: 100dvh;
  margin: 0;
  border-radius: 0;
  background: #08090b;
  color: #fff;
  box-shadow: none;
  transform: none;
}

html:not([data-wizon-viewport="compact"]) .image-preview-dialog[data-image-preview-scope="travel-place"][open] {
  width: 100vw;
  height: 100dvh;
}

.image-preview-dialog::backdrop,
html[data-theme="dark"] .image-preview-dialog::backdrop {
  background: rgb(0 0 0 / 78%);
}

.image-preview-dialog[data-motion-state="open"],
.image-preview-dialog[data-motion-state="closing"] {
  transform: none;
}

.image-preview-shell {
  display: block;
  background: #08090b;
}

.image-preview-dialog[open] .image-preview-head {
  position: absolute;
  z-index: 4;
  inset: 0 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  min-height: 72px;
  padding: 16px 16px 16px 28px;
  border: 0;
  background: linear-gradient(180deg, rgb(8 9 11 / 92%) 0%, rgb(8 9 11 / 0%) 100%);
  color: #fff;
  pointer-events: none;
}

.image-preview-dialog[open] .image-preview-caption-copy {
  grid-column: 1;
  justify-self: start;
  align-self: start;
  max-width: min(60vw, 720px);
  color: #fff;
  text-align: left;
  pointer-events: auto;
}

.image-preview-dialog[open] .image-preview-title-line {
  justify-content: flex-start;
  align-items: center;
  min-height: 48px;
}

.image-preview-dialog[open] .image-preview-title {
  color: #fff;
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-semibold);
}

.image-preview-dialog[open] .image-preview-header-count {
  color: rgb(255 255 255 / 68%);
}

.image-preview-dialog[open] .image-preview-head-actions {
  grid-column: 2;
  justify-self: end;
  align-self: start;
  gap: 8px;
  pointer-events: auto;
}

.image-preview-dialog[open] .image-preview-icon-button {
  width: 48px;
  min-width: 48px;
  height: 48px;
  min-height: 48px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: #fff;
  opacity: 0.88;
  backdrop-filter: none;
}

.image-preview-dialog[open] .image-preview-mobile-back {
  display: none;
}

.image-preview-dialog[open] .image-preview-stage {
  position: absolute;
  inset: 0;
  display: block;
  padding: 84px 92px 72px;
  overflow: hidden;
  background: #08090b;
  cursor: zoom-in;
  touch-action: none;
}

.image-preview-dialog[data-image-preview-zoomed="true"] .image-preview-stage {
  cursor: grab;
}

.image-preview-dialog[data-image-preview-zoomed="true"] .image-preview-stage.is-panning {
  cursor: grabbing;
}

.image-preview-dialog[open] .image-preview-carousel {
  width: 100%;
  height: 100%;
}

.image-preview-dialog[open] .image-preview-carousel-card,
.image-preview-dialog[open] .image-preview-carousel-card.is-current,
.image-preview-dialog[open][data-image-preview-scope="travel-place"] .image-preview-carousel-card.is-current {
  position: absolute;
  inset: 0;
  display: none;
  width: 100%;
  height: 100%;
  padding: 0;
  overflow: visible;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  opacity: 0;
  pointer-events: none;
  transform: none;
  transition: none;
}

.image-preview-dialog[open] .image-preview-carousel-card.is-current,
.image-preview-dialog[open][data-image-preview-scope="travel-place"] .image-preview-carousel-card.is-current {
  display: flex;
  opacity: 1;
  pointer-events: auto;
}

.image-preview-dialog[open] .image-preview-media {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 100%;
  border: 0;
  border-radius: 0;
  background: transparent;
  object-fit: contain;
  pointer-events: auto;
  transform:
    translate3d(var(--image-preview-pan-x, 0), var(--image-preview-pan-y, 0), 0)
    scale(var(--image-preview-scale, 1));
  transform-origin: center;
  transition: transform 120ms var(--wizon-motion-effect-ease);
  user-select: none;
  will-change: transform;
}

.image-preview-dialog[open] .image-preview-media--video {
  min-width: min(920px, 72vw);
  background: #000;
}

.image-preview-dialog[open] .image-preview-stage.is-panning .image-preview-media,
.image-preview-dialog[open] .image-preview-stage.is-dragging .image-preview-media {
  transition: none;
}

.image-preview-dialog[open] .image-preview-pagination {
  display: none;
}

.image-preview-dialog[open] .image-preview-stage-control {
  z-index: 3;
  width: 52px;
  height: 52px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: #fff;
  opacity: 0.88;
  backdrop-filter: none;
  transition: opacity var(--wizon-motion-micro-duration) var(--wizon-motion-effect-ease);
}

.image-preview-dialog[open] .image-preview-stage-control--previous {
  inset-inline-start: 24px;
}

.image-preview-dialog[open] .image-preview-stage-control--next {
  inset-inline-end: 24px;
}

.image-preview-zoom-controls {
  position: absolute;
  z-index: 4;
  right: 24px;
  bottom: 72px;
  display: grid;
  width: 52px;
  overflow: hidden;
  border: 1px solid rgb(255 255 255 / 12%);
  border-radius: 26px;
  background: rgb(16 18 22 / 76%);
  color: #fff;
  backdrop-filter: blur(12px);
}

.image-preview-zoom-button {
  display: inline-grid;
  place-items: center;
  width: 52px;
  min-height: 52px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.image-preview-zoom-value {
  display: grid;
  place-items: center;
  width: 52px;
  min-height: 36px;
  border-block: 1px solid rgb(255 255 255 / 10%);
  color: rgb(255 255 255 / 72%);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  pointer-events: none;
}

.image-preview-zoom-button svg {
  width: 20px;
  height: 20px;
}

.image-preview-zoom-button:disabled {
  color: rgb(255 255 255 / 28%);
  cursor: default;
}

.image-preview-dialog[data-image-preview-media-type="video"] .image-preview-zoom-controls {
  display: none;
}

.image-preview-dialog[open] .image-preview-disclosure-slot {
  position: absolute;
  z-index: 4;
  top: var(--image-preview-source-top, 0);
  right: auto;
  bottom: auto;
  left: var(--image-preview-source-left, 0);
  display: block;
  width: var(--image-preview-source-width, 0);
  max-width: none;
  min-height: 40px;
  padding: 0;
  border: 0;
  background: transparent;
  color: rgb(255 255 255 / 66%);
  pointer-events: auto;
  visibility: hidden;
}

.image-preview-dialog[open] .image-preview-disclosure-slot[data-image-preview-source-positioned="true"] {
  visibility: visible;
}

.image-preview-dialog[open] .image-preview-source-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wds-space-3);
  width: 100%;
  min-height: 40px;
  padding: var(--wds-space-2) 0;
  border: 0;
  background: transparent;
  font-size: var(--wds-font-size-caption);
  line-height: 1.4;
}

.image-preview-dialog[open] .image-preview-source-link-text {
  display: flex;
  align-items: center;
  align-self: stretch;
  min-width: 0;
  color: rgb(255 255 255 / 66%);
  text-decoration: none;
  transition: color var(--wizon-motion-micro-duration) var(--wizon-motion-effect-ease);
}

.image-preview-dialog[open] .image-preview-source-link-text--static {
  cursor: default;
}

.image-preview-dialog[open] .image-preview-source-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.image-preview-dialog[open] .image-preview-source-share {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: #fff;
  cursor: pointer;
  opacity: 0.88;
  transition: opacity var(--wizon-motion-micro-duration) var(--wizon-motion-effect-ease);
}

.image-preview-dialog[open] .image-preview-source-share svg {
  width: 18px;
  height: 18px;
}

.image-preview-gesture-hint {
  display: none;
}

.image-preview-dialog[open] :is(
  .image-preview-icon-button,
  .image-preview-stage-control,
  .image-preview-zoom-button,
  .image-preview-source-link-text,
  .image-preview-source-share
):focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

@media (hover: hover) {
  .image-preview-dialog[open] :is(
    .image-preview-icon-button,
    .image-preview-stage-control,
    .image-preview-source-share
  ):hover {
    opacity: 1;
  }

  .image-preview-dialog[open] .image-preview-source-link-text:hover {
    color: #fff;
  }

  .image-preview-dialog[open] .image-preview-zoom-button:not(:disabled):hover {
    background: rgb(36 39 46 / 72%);
  }

  .image-preview-dialog[open] .image-preview-carousel-card.is-current:hover {
    transform: none;
  }
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open],
html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"][open] {
  width: 100vw;
  height: 100dvh;
  background: #08090b;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-head,
html[data-wizon-viewport="compact"] .image-preview-dialog[data-image-preview-scope="travel-place"] .image-preview-head {
  grid-template-columns: 44px minmax(0, 1fr) auto;
  box-sizing: border-box;
  height: calc(100px + env(safe-area-inset-top, 0px));
  min-height: calc(100px + env(safe-area-inset-top, 0px));
  padding: calc(44px + env(safe-area-inset-top, 0px)) 8px 0;
  border: 0;
  background: linear-gradient(180deg, rgb(8 9 11 / 96%) 0%, rgb(8 9 11 / 0%) 100%);
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-mobile-back {
  grid-column: 1;
  display: inline-flex;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-caption-copy {
  grid-column: 2;
  align-self: start;
  padding-inline: 8px;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-head-actions {
  grid-column: 3;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-desktop-close {
  display: inline-flex;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] :is(
  .image-preview-icon-button
) {
  width: 44px;
  min-width: 44px;
  height: 44px;
  min-height: 44px;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-stage {
  padding: calc(112px + env(safe-area-inset-top, 0px)) 0 calc(130px + env(safe-area-inset-bottom, 0px));
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-stage-control {
  display: none;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-media {
  max-width: 100%;
  max-height: 100%;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open][data-image-preview-scope="travel-place"] .image-preview-carousel-card.is-current {
  width: 100%;
  height: 100%;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-media--video {
  min-width: 100%;
}

html[data-wizon-viewport="compact"] .image-preview-zoom-controls {
  right: 12px;
  bottom: calc(150px + env(safe-area-inset-bottom, 0px));
  width: 48px;
  border-radius: 24px;
}

html[data-wizon-viewport="compact"] .image-preview-zoom-button {
  width: 48px;
  min-height: 48px;
}

html[data-wizon-viewport="compact"] .image-preview-zoom-value {
  width: 48px;
  min-height: 36px;
  font-size: 10px;
}

html[data-wizon-viewport="compact"] .image-preview-gesture-hint {
  position: absolute;
  z-index: 3;
  bottom: calc(104px + env(safe-area-inset-bottom, 0px));
  left: 50%;
  display: block;
  min-width: 172px;
  margin: 0;
  padding: 9px 14px;
  border-radius: var(--ds-radius-pill, 999px);
  background: rgb(16 18 22 / 72%);
  color: rgb(255 255 255 / 88%);
  font-size: 12px;
  font-weight: var(--wds-font-weight-medium);
  text-align: center;
  transform: translateX(-50%);
  pointer-events: none;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-disclosure-slot {
  min-height: 44px;
  padding-bottom: 0;
  border: 0;
  background: transparent;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-source-row {
  min-height: 44px;
  padding: 0;
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-source-link-text {
  padding-inline-start: var(--wds-space-4);
}

html[data-wizon-viewport="compact"] .image-preview-dialog[open] .image-preview-source-share {
  width: 44px;
  height: 44px;
}

@media (prefers-reduced-motion: reduce) {
  .image-preview-dialog[open] .image-preview-media {
    transition: none;
  }
}


/* ---- Sources pill + drawer (20260709) ----
   접힌 요약(파비콘 클러스터+출처 N) → 드로어 근거(카드 리스트) → 원문 링크 세부, doc 82의
   요약→근거→세부 순서. 데스크톱=우측 슬라이드 오버, 모바일=2-detent 바텀시트(ChatGPT 패리티). */
.source-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px 5px 7px;
  border: 1px solid var(--cg-menu-border, rgba(13, 13, 13, 0.08));
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-bg, #ffffff);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  color: var(--cg-text-soft, #5d5d5d);
  cursor: pointer;
  transition: background var(--wds-duration-fast) ease, border-color var(--wds-duration-fast) ease;
}
.source-pill:hover {
  background: var(--cg-hover-soft, rgba(13, 13, 13, 0.04));
  border-color: var(--cg-menu-border, rgba(13, 13, 13, 0.14));
}
.source-pill:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, #0d0d0d);
  outline-offset: 2px;
}
.travel-guidebook-card .source-pill {
  min-height: 44px;
}
.source-pill-favs {
  display: inline-flex;
  align-items: center;
}
/* Overlapping favicon cluster — circular, ring-separated (Perplexity/ChatGPT pattern). */
.source-pill-favs .news-fav {
  border-radius: var(--ds-radius-pill, 999px);
  box-shadow: 0 0 0 2px var(--cg-bg, #ffffff);
}
.source-pill-favs .news-fav + .news-fav {
  margin-left: -5px;
}
/* "+N" overflow — a solid ink pill (not the old low-contrast gray that read as barely legible),
   set apart from the favicons rather than overlapping them so the number is clear. */
.source-pill-more {
  display: grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  margin-left: 5px;
  padding: 0 5px;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-text, #0d0d0d);
  font-size: 10px;
  font-weight: 600;
  color: var(--cg-bg, #ffffff);
}
.source-pill-label {
  font-weight: 500;
}
.source-pill-row {
  margin-top: 10px;
}
.memory-use-rationale-shadow {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 10px;
  padding: 9px 11px;
  border: 1px solid color-mix(in srgb, var(--ad-blue) 20%, transparent);
  border-radius: var(--ds-radius-md, 10px);
  background: color-mix(in srgb, var(--ad-blue) 5%, transparent);
  color: var(--cg-text);
  font-size: var(--cg-text-meta-size);
  line-height: 1.45;
}
.memory-use-rationale-shadow__icon,
.memory-use-rationale-shadow__icon .wds-inline-icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--ad-blue);
}
.memory-use-rationale-shadow__copy {
  display: grid;
  gap: 1px;
  min-width: 0;
}
.memory-use-rationale-shadow__copy strong {
  font-size: var(--cg-text-meta-size);
  font-weight: var(--cg-text-label-active-weight);
}
.memory-use-rationale-shadow__copy > span {
  color: var(--cg-text-soft);
}
.memory-use-rationale-shadow__version {
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--cg-text-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
@media (max-width: 520px) {
  .memory-use-rationale-shadow {
    align-items: flex-start;
  }
  .memory-use-rationale-shadow__version {
    display: none;
  }
}
/* Doc-source avatars (Phase 4 RAG): a document glyph stands in for the favicon on the pill,
   drawer card, and inline citation chip. */
.source-pill-doc {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  color: var(--cg-text-soft, #5d5d5d);
}
.source-pill-doc .wds-inline-icon {
  width: 15px;
  height: 15px;
}
.citation-ref-doc {
  display: inline-flex;
}
.citation-ref-doc .wds-inline-icon {
  width: 12px;
  height: 12px;
}
/* Inline citation chip — a source-colored number + that source's favicon (user request: color-
   code the number, show the favicon instead of a raw URL). Deep-links to the drawer card. */
.citation-ref {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 17px;
  margin: 0 2px;
  padding: 0 5px 0 4px;
  vertical-align: baseline;
  transform: translateY(-1px);
  border: 1px solid color-mix(in srgb, var(--cite-color, #5d5d5d) 30%, transparent);
  border-radius: var(--ds-radius-pill, 999px);
  background: color-mix(in srgb, var(--cite-color, #5d5d5d) 10%, transparent);
  font: inherit;
  line-height: 1;
  cursor: pointer;
  transition: background var(--wds-duration-fast) ease, border-color var(--wds-duration-fast) ease;
}
.citation-ref-num {
  color: var(--cite-color, #5d5d5d);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}
.citation-ref-fav {
  width: 12px;
  height: 12px;
  border-radius: var(--ds-radius-2xs, 3px);
  object-fit: contain;
}
.citation-ref:hover {
  background: color-mix(in srgb, var(--cite-color, #5d5d5d) 20%, transparent);
  border-color: color-mix(in srgb, var(--cite-color, #5d5d5d) 55%, transparent);
}
.citation-ref:focus-visible {
  outline: 2px solid var(--cite-color, var(--cg-focus-visible-ring, #0d0d0d));
  outline-offset: 1px;
}
/* Paragraph-end source tag (doc 161 P2-1): ChatGPT-style neutral pill at the end of a
   paragraph/list item — favicon(s) or doc glyph + source name (+N). Replaces the inline
   numbered chips of that block; table cells keep the compact chips. */
.citation-tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 20px;
  margin: 0 0 0 6px;
  padding: 0 8px 0 4px;
  /* The tag (20px) is taller than the body text, so a tall inline box needs an explicit vertical
     anchor. vertical-align:middle centers it on the parent's x-height (baseline + x-height/2).
     The nudge is NOT eyeballed: it's the measured gap between the font's Korean-ink optical center
     and that x-height midpoint — (5.64px − 4.24px) / 16px ≈ 0.088em for Pretendard. Being a font
     metric (em, not px), it holds for every sentence and every text size (verified: <0.02px off,
     <0.01px drift across content/size). doc 164 §후속4. */
  vertical-align: middle;
  transform: translateY(-0.088em);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-hover-soft, var(--surface-2));
  font: inherit;
  line-height: 1;
  cursor: pointer;
  transition: background var(--wds-duration-fast) ease, border-color var(--wds-duration-fast) ease;
}
.citation-tag:hover {
  background: var(--cg-hover, var(--surface-2));
  border-color: var(--cg-muted, var(--muted));
}
/* doc 161 §7 B3: document source tags read as blue-tint pills everywhere (Blue = 문서 축);
   web tags stay neutral + colored favicon. Unifies the small-tag radius/color language. */
.citation-tag[data-kind="doc"] {
  border-color: color-mix(in srgb, var(--ad-blue) 16%, transparent);
  background: var(--ad-blue-tint);
}
.citation-tag[data-kind="doc"] .citation-tag-name {
  color: var(--ad-blue);
}
.citation-tag[data-kind="doc"] .citation-tag-doc {
  color: var(--ad-blue);
}
.citation-tag[data-kind="doc"]:hover {
  background: var(--ad-blue-tint);
  border-color: color-mix(in srgb, var(--ad-blue) 38%, transparent);
}
.citation-tag:focus-visible {
  /* Strong solid ring — the faint --cg-focus-ring (16% alpha) fails 3:1 and would be a
     downgrade from the saturated ring of the .citation-ref chips this tag replaces. */
  outline: 2px solid var(--cg-text, #0d0d0d);
  outline-offset: 1px;
}
.citation-tag-favs {
  display: inline-flex;
  align-items: center;
}
.citation-tag-favs > * + * {
  margin-left: -4px;
}
.citation-tag-fav {
  width: 14px;
  height: 14px;
  border-radius: 4px;
  box-shadow: 0 0 0 1.5px var(--cg-surface, #fff);
}
.citation-tag-doc {
  display: inline-flex;
  color: var(--cg-text-soft, var(--muted));
}
.citation-tag-doc .wds-inline-icon {
  width: 13px;
  height: 13px;
}
.citation-tag-name {
  max-width: 150px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-caption-12);
  font-weight: 500;
}
.citation-tag-more {
  color: var(--cg-text-soft, var(--muted));
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}
/* Citation drift is attached only to the rendered claim block that owns the affected source.
   The comparison itself stays server-side; this surface carries status + safe source indices only. */
.has-citation-drift {
  border-radius: var(--ds-radius-sm, 8px);
  background: color-mix(in srgb, var(--cg-warning-fg) 8%, transparent);
  box-shadow: inset 3px 0 0 color-mix(in srgb, var(--cg-warning-fg) 78%, transparent);
}
.has-citation-drift--missing {
  background: color-mix(in srgb, var(--cg-danger-fg) 7%, transparent);
  box-shadow: inset 3px 0 0 color-mix(in srgb, var(--cg-danger-fg) 78%, transparent);
}
.citation-drift-notice {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: 6px;
  vertical-align: middle;
  /* 의미색 S4: 부모 .has-citation-drift 의 8% 워시(color-mix) 위에서 --cg-warning-fg
     (라이트 amber-700 #b45309)가 4.41:1로 AA 텍스트 4.5 미달이었다(S3 판정 §6-1,
     canvas getImageData 실측). --cg-warning-fg 는 16개 콜사이트가 공유하는 토큰이라
     거기서 못 고친다 — 이 컴포넌트만 한 단계 어두운 기존 토큰 --wds-amber-tonal-t40
     (#98470d)으로 올린다(canvas 실측 라이트 5.688:1). 다크는 바로 아래
     [data-theme="dark"] 오버라이드로 --cg-warning-fg(amber-400, 11.44:1)를 그대로
     유지한다 — S3 가 다크에서 올려 둔 값을 이 수리로 되돌리지 않기 위해서다. */
  color: var(--wds-amber-tonal-t40, var(--cg-warning-fg));
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
}
[data-theme="dark"] .citation-drift-notice {
  color: var(--cg-warning-fg);
}
.citation-drift-notice[data-tone="danger"] {
  color: var(--cg-danger-fg);
}
.citation-drift-label {
  white-space: nowrap;
}
.citation-drift-regenerate {
  min-height: 24px;
  padding: 2px 7px;
  border: 1px solid currentColor;
  border-radius: var(--ds-radius-pill, 999px);
  background: var(--cg-surface, var(--surface));
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.citation-drift-regenerate:hover:not(:disabled) {
  background: color-mix(in srgb, currentColor 9%, var(--cg-surface, var(--surface)));
}
.citation-drift-regenerate:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
.citation-drift-regenerate:disabled {
  cursor: progress;
  opacity: 0.68;
}
.citation-drift-notice.is-reviewed {
  color: var(--cg-success);
}
.citation-revision-applied {
  display: grid;
  gap: var(--wds-space-1);
  margin: var(--wds-space-2) 0;
  padding: var(--wds-space-3) var(--wds-space-4);
  border-left: 3px solid var(--cg-success);
  border-radius: var(--ds-radius-sm);
  background: var(--ad-green-tint);
}
.citation-revision-applied > strong {
  color: var(--cg-success);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--wds-font-weight-semibold);
}
.citation-revision-applied > p {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  line-height: var(--wds-line-height-normal);
  overflow-wrap: anywhere;
}
.citation-health-focus {
  box-shadow: inset 0 0 0 2px var(--cg-focus-visible-ring);
}

/* Conversation-level evidence health reuses the trust dock. It contains only answer text and
   safe ordinals; document/version references remain in JS state and the authorized API lane. */
.citation-health-summary {
  display: grid;
  gap: var(--wds-space-4);
  padding: var(--wds-space-5);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-lg);
  background: var(--ad-blue-tint);
}
.citation-health-summary[data-status="attention"] {
  background: var(--ad-amber-tint);
}
.citation-health-summary[data-status="approval"] {
  border-color: var(--ad-blue);
  background: var(--ad-blue-tint);
}
.citation-health-summary[data-status="reviewed"] {
  background: var(--ad-green-tint);
}
.citation-health-summary[data-status="unavailable"] {
  border-color: var(--cg-danger-fg);
  background: var(--cg-hover-soft);
}
.citation-health-summary-copy {
  display: grid;
  gap: var(--wds-space-1);
}
.citation-health-summary-copy > span {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--wds-font-weight-medium);
}
.citation-health-summary-copy > strong {
  color: var(--cg-text);
  font-size: var(--ds-text-title-18);
  font-weight: var(--wds-font-weight-semibold);
}
.citation-health-summary-copy > p,
.citation-health-result {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  line-height: var(--wds-line-height-normal);
}
.citation-health-counts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--wds-space-2);
}
.citation-health-counts > span {
  display: grid;
  gap: var(--wds-space-1);
  padding: var(--wds-space-3);
  border-radius: var(--ds-radius-sm);
  background: var(--cg-surface);
  color: var(--cg-muted);
  font-size: var(--ds-text-micro-11);
  text-align: center;
}
.citation-health-counts strong {
  color: var(--cg-text);
  font-size: var(--ds-text-body-15);
}
.citation-health-actions,
.citation-health-claim-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wds-space-2);
}
.citation-health-actions button,
.citation-health-claim-actions button {
  min-height: var(--wizon-ai-action-target-sm);
  padding: var(--wds-space-2) var(--wds-space-3);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: var(--wds-font-weight-medium);
  cursor: pointer;
}
.citation-health-actions button:hover:not(:disabled),
.citation-health-claim-actions button:hover:not(:disabled) {
  background: var(--cg-hover);
}
.citation-health-actions button:focus-visible,
.citation-health-claim-actions button:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring);
  outline-offset: 2px;
}
.citation-health-actions button:disabled,
.citation-health-claim-actions button:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
.citation-health-actions .citation-health-primary {
  border-color: var(--cg-send-bg);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}
.citation-health-list {
  display: grid;
  gap: var(--wds-space-3);
}

.citation-recovery-steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--wds-space-2);
  margin: var(--wds-space-3) 0;
  padding: 0;
  list-style: none;
}

.citation-recovery-steps li {
  display: grid;
  justify-items: center;
  gap: var(--wds-space-1);
  min-width: 0;
  padding: var(--wds-space-3) var(--wds-space-2);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-md);
  background: var(--cg-surface);
  color: var(--cg-muted);
  font-size: var(--ds-text-micro-11);
  text-align: center;
}

.citation-recovery-steps li > span {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: var(--ds-radius-pill);
  background: var(--cg-hover-soft);
  font-weight: var(--wds-font-weight-semibold);
}

.citation-recovery-steps li[data-status="current"] {
  border-color: var(--ad-blue);
  color: var(--ad-blue);
  background: var(--ad-blue-tint);
}

.citation-recovery-steps li[data-status="complete"] {
  color: var(--cg-success);
  background: var(--ad-green-tint);
}

.citation-impact-review-list {
  display: grid;
  gap: 10px;
  margin-top: 12px;
}

.citation-impact-review-item {
  display: grid;
  gap: 12px;
  padding: 14px;
  border: 1px solid color-mix(in srgb, currentColor 12%, transparent);
  border-radius: var(--wizon-ai-menu-radius);
  background: color-mix(in srgb, var(--ad-amber) 5%, transparent);
}

.citation-impact-review-item__copy {
  min-width: 0;
  display: grid;
  gap: 4px;
}

.citation-impact-review-item__copy strong {
  overflow: hidden;
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  line-height: 20px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.citation-impact-review-item__copy small,
.citation-impact-review-item__copy p {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  line-height: 18px;
}

.citation-impact-review-item__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.citation-impact-review-item__actions button {
  min-height: 34px;
  padding: 6px 10px;
  border: 1px solid color-mix(in srgb, currentColor 16%, transparent);
  border-radius: var(--wizon-ai-radius-control);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}

.citation-impact-review-item__actions button:disabled {
  cursor: default;
  opacity: 0.5;
}
.citation-health-claim {
  display: grid;
  gap: var(--wds-space-3);
  padding: var(--wds-space-4);
  border: 1px solid var(--cg-menu-border);
  border-left-color: var(--cg-warning-fg);
  border-radius: var(--ds-radius-md);
  background: var(--cg-surface);
}
.citation-health-claim[data-status="missing"] {
  border-left-color: var(--cg-danger-fg);
}
.citation-health-claim[data-status="approval"] {
  border-left-color: var(--ad-blue);
}
.citation-health-claim.is-reviewed {
  border-left-color: var(--cg-success);
}
.citation-health-claim > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wds-space-2);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
.citation-health-claim > header strong {
  color: var(--cg-warning-fg);
  font-weight: var(--wds-font-weight-semibold);
}
.citation-health-claim[data-status="missing"] > header strong {
  color: var(--cg-danger-fg);
}
.citation-health-claim[data-status="approval"] > header strong {
  color: var(--ad-blue);
}
.citation-health-claim.is-reviewed > header strong {
  color: var(--cg-success);
}
.citation-health-claim > p {
  display: -webkit-box;
  margin: 0;
  overflow: hidden;
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  line-height: var(--wds-line-height-normal);
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
}
.citation-health-empty {
  display: grid;
  justify-items: center;
  gap: var(--wds-space-2);
  padding: var(--wds-space-8) var(--wds-space-4);
  color: var(--cg-muted);
  text-align: center;
}
.citation-health-empty > span,
.citation-health-empty > span svg {
  width: 28px;
  height: 28px;
}
.citation-health-empty > strong {
  color: var(--cg-text);
  font-size: var(--ds-text-body-15);
}
.citation-health-empty > p {
  margin: 0;
  font-size: var(--ds-text-caption-12);
}
.citation-revision-head {
  display: grid;
  gap: var(--wds-space-2);
  padding: var(--wds-space-4);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-md);
  background: var(--ad-blue-tint);
}
.citation-revision-head > span,
.citation-revision-head > p {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  line-height: var(--wds-line-height-normal);
}
.citation-revision-head > strong {
  color: var(--ad-blue);
  font-size: var(--ds-text-title-18);
  font-weight: var(--wds-font-weight-semibold);
}
.citation-revision-head[data-status="accepted"] {
  background: var(--ad-green-tint);
}
.citation-revision-head[data-status="accepted"] > strong {
  color: var(--cg-success);
}
.citation-revision-head[data-status="rejected"] {
  background: var(--cg-hover-soft);
}
.citation-revision-head[data-status="rejected"] > strong {
  color: var(--cg-muted);
}
.citation-revision-compare {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--wds-space-3);
  margin-top: var(--wds-space-4);
}
.citation-revision-compare > section {
  display: grid;
  align-content: start;
  gap: var(--wds-space-3);
  min-width: 0;
  padding: var(--wds-space-4);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-md);
  background: var(--cg-surface);
}
.citation-revision-compare > .citation-revision-candidate {
  border-color: var(--ad-blue);
  background: var(--ad-blue-tint);
}
.citation-revision-compare header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wds-space-2);
  color: var(--cg-text);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--wds-font-weight-semibold);
}
.citation-revision-compare header small {
  color: var(--cg-muted);
  font-size: var(--ds-text-micro-11);
  font-weight: var(--wds-font-weight-regular);
}
.citation-revision-compare p {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  line-height: var(--wds-line-height-normal);
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.citation-revision-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--wds-space-2);
  margin-top: var(--wds-space-4);
}
.citation-revision-actions button {
  min-height: var(--wizon-ai-action-target-sm);
  padding: var(--wds-space-2) var(--wds-space-4);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: var(--wds-font-weight-medium);
  cursor: pointer;
}
.citation-revision-actions button:hover:not(:disabled) {
  background: var(--cg-hover);
}
.citation-revision-actions button:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring);
  outline-offset: 2px;
}
.citation-revision-actions button:disabled {
  cursor: wait;
  opacity: 0.6;
}
.citation-revision-actions .citation-health-primary {
  border-color: var(--cg-send-bg);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}
.citation-revision-decision {
  margin: var(--wds-space-4) 0 0;
  padding: var(--wds-space-3) var(--wds-space-4);
  border-radius: var(--ds-radius-sm);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  line-height: var(--wds-line-height-normal);
}
@media (max-width: 520px) {
  .citation-drift-notice {
    margin: 6px 0 0;
  }
  .citation-health-counts {
    grid-template-columns: 1fr;
  }
  .citation-health-actions,
  .citation-health-claim-actions,
  .citation-revision-actions {
    display: grid;
  }
  .citation-health-actions button,
  .citation-health-claim-actions button,
  .citation-revision-actions button {
    width: 100%;
  }
  .citation-revision-compare {
    grid-template-columns: 1fr;
  }
  .citation-recovery-steps {
    grid-template-columns: 1fr;
  }
}
/* Citation hover preview (Phase 3, desktop) — a pointer-transparent floating card that peeks
   the source on hover/focus without opening the drawer. Positioned in JS; fixed to the
   viewport. Touch devices never trigger it (fine-pointer + hover gated in JS). */
.citation-preview {
  position: fixed;
  z-index: 210;
  width: min(320px, calc(100vw - 24px));
  padding: 8px;
  border: 1px solid var(--cg-menu-border, rgba(13, 13, 13, 0.1));
  border-radius: var(--ds-radius-md, 14px);
  background: var(--cg-surface);
  color: var(--cg-text);
  box-shadow: 0 14px 40px rgba(13, 13, 13, 0.18);
  /* doc 161 §7 B4: the preview is now interactive (← → pager) — clickable, kept open while
     hovered via a hide grace period. Touch never triggers it (fine-pointer gated in JS). */
  pointer-events: auto;
}
.citation-preview[hidden] {
  display: none;
}
/* Card body reuses .source-card-v2 (same anatomy as drawer/panel). Strip its own shadow here —
   the preview frame supplies the surround. */
.citation-preview .citation-preview-card {
  padding: 8px 6px 6px;
  box-shadow: none;
}
.citation-preview .citation-preview-card:hover {
  box-shadow: none;
}
/* ← → n/m pager: only when a tag covers multiple sources. */
.citation-preview-pager {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 6px 6px;
}
.citation-preview-nav {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--cg-text-soft, var(--muted));
  cursor: pointer;
}
.citation-preview-nav:hover {
  background: var(--cg-hover-soft, rgba(13, 13, 13, 0.06));
}
.citation-preview-nav:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 1px;
}
.citation-preview-nav .wds-inline-icon {
  width: 16px;
  height: 16px;
}
.citation-preview-count {
  margin-left: auto;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-micro-11);
  font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
  .source-pill {
    transition: none;
  }
}
/* 템플릿은 1512폭에 4열이다. 답변 안의 카드는 그만큼 넓을 수 없으므로 3 → 2 → 1열로 접는다.
   기준은 뷰포트가 아니라 컨테이너 쿼리, 즉 피드 «자신의» 폭이다 — 같은 화면에서도 사이드
   패널이 열리면 폭이 달라지고 카드가 따라 접혀야 한다. 그래서 정본 폭 분기점(520/521/900/901)
   래칫의 대상이 아니다. ★이 주석에 뷰포트 앳룰 이름을 글자 그대로 적지 말 것:
   test_design_system_contract의 스캐너는 주석을 걷어내지 않고 `@med` + `[^{]*` 로 훑기 때문에
   주석 안의 그 단어가 아래 컨테이너 쿼리까지 삼켜 620을 «비정본 분기점»으로 오검출한다. */
@container (max-width: 620px) {
  .news-cards {
    grid-auto-columns: calc((100% - 16px) / 2);
  }
  /* 행 흐름일 때의 열 수도 폭을 따라간다. ★줄 수는 건드리지 않는다. */
  .news-cards:not(:has(> .news-card:nth-child(5))) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
/* ★여기 있던 `.news-card:last-child:nth-child(odd) { grid-column: span 2 }`는 사라졌다.
   그 예외는 «3의 배수가 2열에서는 홀수일 수 있다»는, 세로 격자에서만 생기던 문제를 메우던
   규칙이다. 이제 줄 수가 폭과 무관하게 2로 고정이라 pickNewsHeroCount의 짝수 역산 하나로
   모든 폭에서 마지막 열이 채워지고, 메울 구멍 자체가 없다. */
@container (max-width: 380px) {
  .news-cards {
    grid-auto-columns: 100%;
    gap: 18px;
  }
  .news-cards:not(:has(> .news-card:nth-child(5))) {
    grid-template-columns: minmax(0, 1fr);
  }
  /* ★두 칸을 380px에 나란히 두면 칸당 ~180px이라 제목이 2줄 상한을 넘어 잘린다. 카드가
     1열로 접히는 것과 같은 폭에서 2단도 한 줄로 쌓는다. */
  .news-second {
    grid-template-columns: 1fr;
  }
  .news-lead {
    aspect-ratio: 4 / 3;
  }
  .news-lead-band {
    inset-inline: 10px;
    bottom: 10px;
    padding: 12px 13px;
  }
}
/* ★모션은 정보를 나르지 않는다 — 전부 꺼도 캐러셀은 «즉시» 그 슬라이드로 간다(위치는
   data-news-hero-index가 이미 정한다). 확대·부양은 장식이라 그대로 사라진다. */
@media (prefers-reduced-motion: reduce) {
  .news-hero-track,
  .news-hero-nav,
  .news-topic,
  .news-topics-slide,
  .news-arrow,
  .news-bookmark,
  .news-dot,
  .news-thumb {
    transition: none;
  }
  .news-topics-rail {
    scroll-behavior: auto;
  }
  .news-topic:hover {
    transform: none;
  }
  /* none이 아니라 기본값으로 되돌린다 — 이 버튼의 transform은 «수직 중앙»이라는 레이아웃이지
     모션이 아니다. none으로 지우면 화살표가 아래로 반쯤 내려간다. */
  .news-hero-nav:hover {
    transform: translateY(-50%);
  }
  .news-card:hover .news-card-img,
  .news-lead:hover .news-lead-img {
    transform: none;
  }
}

/* 20260709-mobile-keyboard-fix: validated on the iOS Simulator (real WebKit + soft
   keyboard). Pin the shell to the visual viewport so the topbar stays fixed and the
   composer rides just above the keyboard instead of the page scrolling up. The
   offsetTop compensation (--wizon-vv-offset-top, written by syncMobileViewportMetrics
   in app.js) is essential on iOS: when the keyboard opens iOS scrolls the layout
   viewport, so a fixed shell must translate down by offsetTop to stay aligned with the
   visible area — without it the composer flies to the top of the screen. Both vars fall
   back to 100dvh before the first visualViewport event. Scoped to <=900px so desktop
   keeps the global 100dvh chain (styles.css:8707) untouched. */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .app-shell {
    position: fixed;
    left: 0;
    top: var(--wizon-vv-offset-top, 0px);
    width: 100%;
    /* 20260803-shell-height-clamp — **믿지 말고 조인다.**
     *
     * ★실기기 실측(2026-08-03): 가로에서 컴포저 기본 액션이 잘리고 스크롤로도 닿지 않았다.
     *   셸이 뷰포트(292)보다 크게 잡히는데 overflow: hidden이라 넘친 아래가 사라진다.
     *   데스크톱 브라우저에서는 --wizon-vvh가 888로 낡은 것을 실제로 봤다.
     *
     * ★JS 값이 «언제 정착하는가»에 기대는 설계가 애초에 약했다. orientationchange 재동기화를
     *   먼저 넣어 봤지만 실기기에서 증상이 그대로였다 — 이벤트 타이밍을 맞추는 방향으로는
     *   닫히지 않는다. 그래서 «값을 못 믿어도 안전한» 모양으로 바꾼다.
     *
     * ★svh는 **브라우저 크롬을 뺀 보장 가시 높이**다(업계 권고: 레이아웃은 svh 기본, dvh는
     *   근거 있을 때만). min()으로 조이면 세 경우가 모두 옳다:
     *     · 값이 신선하고 더 작다(키보드 열림) → 그 값을 쓴다. 기존 키보드 대응 유지.
     *     · 값이 낡아 더 크다                  → svh로 잘려 화면 밖으로 못 나간다.
     *     · JS가 아직/전혀 안 돌았다           → 100svh 폴백.
     *
     * ★레퍼런스 대조: 같은 기기 가로에서 chatgpt.com은 컴포저가 온전히 보이고 그 아래까지
     *   스크롤로 닿는다 — 셸을 뷰포트 높이에 잠그지 않는다. 우리는 앱 셸 성격을 유지하되
     *   «잠그는 높이»를 보장 가시 높이로 바꾼다.
     */
    height: min(var(--wizon-vvh, 100svh), 100svh);
  }

  body.chatgpt-reference-v1 .main-pane,
  body.chatgpt-reference-v1 .workbench,
  body.chatgpt-reference-v1 .chat-pane {
    height: 100%;
  }
}

/* 20260803-dock-row-range — doc 143 항목 E. **이것이 가로 결함의 진짜 원인이었다.**
 *
 * 두 규칙의 미디어 범위가 어긋나 있었다:
 *
 *     styles.css L8256   .wds-ai-portal-v1 .composer-action-dock { flex-direction: column }
 *                        └ @media (max-width: 900px)        ← 가로(874px)에서 **적용됨**
 *     styles.css L17310  이를 되돌리는 flex-direction: row !important
 *                        └ @media (max-width: 520px)        ← 가로에서 **적용 안 됨**
 *
 * body에는 `wds-ai-portal-v1`과 `chatgpt-reference-v1`이 **둘 다** 붙는다. 그래서 폭이
 * 521~900px인 구간(= 폰 가로)에서만 dock이 세로로 쌓이고(실측 76×126), 기본 액션이 컴포저
 * 아래로 밀려 화면 밖으로 잘렸다. 세로(402px)는 두 규칙이 모두 적용돼 되돌리는 쪽이 이기므로
 * 멀쩡했다 — 결함이 «가로에서만» 나던 이유가 이것이다.
 *
 * ★높이는 처음부터 원인이 아니었다. 뷰포트 높이·안전영역·회전 재동기화를 차례로 의심했고
 *   전부 빗나갔다. 원인은 **폭 구간의 빈틈**이었다.
 *
 * ★고치는 방법은 «되돌리는 규칙을 자기가 되돌리려는 규칙과 같은 범위로» 맞추는 것이다.
 *   L17310의 520px 블록은 그대로 두고(세로에서 이미 옳다), 그 사이 구간만 여기서 덮는다.
 */
@media (min-width: 521px) and (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-composer-shell .composer-action-dock {
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    align-items: center;
    gap: 6px;
    justify-self: end;
  }
}

/* 20260803-short-viewport-scroll — doc 143 항목 E.
 *
 * ★증상(실기기 실측): iPhone 17을 가로로 돌리면 컴포저 기본 액션이 화면 아래로 잘리고
 *   **스크롤로도 닿을 수 없다.** 라이트·다크 양쪽에서 재현.
 *
 *       가로 뷰포트   874×292        (Safari 크롬이 110pt를 먹는다)
 *       셸            height: var(--wizon-vvh, 100dvh) + overflow: hidden
 *       문서 스크롤   없음            (scrollHeight == clientHeight)
 *
 * ★가설 셋을 세워 **셋 다 기각**했다(같은 길을 다시 파지 않도록 남긴다):
 *     · 컴포저 그리드가 2행으로 떨어진다 → 진짜 마크업 프로브가 292px에서 35px 여유.
 *     · 좌우 안전영역 미배선            → --wizon-safe-left/right는 이미 env()에 연결·7곳 사용.
 *     · 회전 시 뷰포트 값이 낡는다      → resize가 6ms 안에 2회, inner == visual == 292.
 *   남은 원인은 **홈 세로 스택이 292px에 안 들어가는 것**이다. 배관이 아니라 예산 문제다.
 *
 * ★고르지 않은 길: 인사말·칩을 접는 안. 그 둘은 이 표면의 정체성이라 지우면 잃는 게 크고,
 *   짧은 뷰포트는 예외 상황이다. 그래서 «그때만 스크롤을 연다».
 *
 * ★트리거가 max-height인 이유: 키보드를 올린 세로도 같은 부족이다. orientation으로 잡으면
 *   그 경우를 통째로 놓친다. 460px은 가로 실측(292) 위, 세로 실측(약 700) 아래다.
 *
 * ★여는 것은 **본문**이지 셸이 아니다. 셸까지 열면 주소창이 오르내려 «앱처럼 보이는»
 *   성질이 깨지고 상단바·컴포저 고정이 함께 무너진다. 셸 체인의 잠금은 그대로 둔다.
 */
@media (max-width: 900px) and (max-height: 460px) {
  body.chatgpt-reference-v1 .chat-pane {
    overflow-y: auto;
    /* 이 리포는 .message-list에서 같은 문제를 이미 이렇게 닫았다 — 스크롤 체이닝으로
       뒤 표면이 딸려 움직이는 것을 막는다. */
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
}

/* ---- User-message hover actions + inline editor (20260709-user-edit, ChatGPT parity) ---- */
/* Stack the bubble and its actions in a right-aligned column so copy/edit sit cleanly BELOW
   the bubble (not inside it). Overrides the grid layout for user turns. */
.chatgpt-reference-v1 .message.user {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}
.message.user .user-actions {
  display: flex;
  /* 20260911-message-actions-gutter-unify (C9): 2→4 로 답변 액션 행(4px)과 거터 1값. */
  gap: 4px;
  justify-content: flex-end;
  margin-top: 2px;
  padding-right: 6px;
  opacity: 0;
  transition: opacity 120ms ease;
}
.message.user:hover .user-actions,
.message.user:focus-within .user-actions {
  opacity: 1;
}
/* Touch: no hover, so the inline actions are hidden and a long-press context menu (below)
   provides 복사/편집 instead — the ChatGPT mobile pattern. Suppress the iOS text-selection
   callout so the long-press lifts the menu rather than the native magnifier. */
@media (hover: none) {
  .message.user .user-actions {
    display: none;
  }
  .chatgpt-reference-v1 .message.user .message-body {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
}
/* Long-press context menu: dimmed backdrop + a lifted clone of the pressed bubble (kept bright
   above the scrim) + a popover of actions. */
.user-msg-scrim {
  position: fixed;
  inset: 0;
  z-index: 190;
  background: rgba(0, 0, 0, 0.44);
}
.user-msg-scrim[hidden] {
  display: none;
}
.user-msg-lift {
  position: fixed;
  z-index: 195;
  margin: 0 !important;
  pointer-events: none;
}
.user-msg-lift .message-body {
  box-shadow: 0 16px 44px rgba(0, 0, 0, 0.3);
  transform: scale(1.02);
  transform-origin: top right;
}
.user-msg-lift .user-actions {
  display: none !important;
}
.user-msg-menu {
  position: fixed;
  z-index: 200;
  min-width: 184px;
  padding: 6px;
  border: 1px solid var(--cg-menu-border, rgba(13, 13, 13, 0.1));
  border-radius: 16px;
  background: var(--cg-menu-bg, #ffffff);
  box-shadow: 0 18px 50px rgba(13, 13, 13, 0.22);
}
.user-msg-menu[hidden] {
  display: none;
}
.user-msg-menu-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  border: none;
  border-radius: 11px;
  background: transparent;
  font: inherit;
  font-size: var(--ds-text-body-15);
  color: var(--cg-text, #0d0d0d);
  cursor: pointer;
}
.user-msg-menu-item:active {
  background: var(--cg-hover-soft, rgba(13, 13, 13, 0.06));
}
.user-msg-menu-item .wds-inline-icon {
  width: 19px;
  height: 19px;
}

/* 20260709-recent-longpress (doc 144 follow-up): touch has no hover, so a long-press on a
   recent conversation lifts a snippet preview above a dimmed backdrop and opens a bottom
   action sheet — the ChatGPT mobile pattern. All three live on <body> (not the fixed
   sidebar), so a real tap-capturing scrim is safe here. */
@media (hover: none) {
  .chatgpt-reference-v1 .recent-item {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
  .chatgpt-reference-v1 .recent-item input {
    -webkit-user-select: text;
    user-select: text;
  }
}
/* While the long-press sheet is open the finger is still down over the sheet, so lock the
   whole document unselectable (JS also drops any range iOS already made) — per-element
   user-select:none does not stop a selection the long-press gesture already armed. */
body.recent-longpress-open,
body.recent-longpress-open * {
  -webkit-touch-callout: none !important;
  -webkit-user-select: none !important;
  user-select: none !important;
}
.recent-longpress-scrim {
  position: fixed;
  inset: 0;
  z-index: 190;
  /* R-40 ②파도 build C(669 §M-11) — 스크림 값 1토큰(옛 .44 는 표면 고유 값이었다). */
  background: var(--wizon-scrim);
  /* R-41 B1(inventory S14) — 이전엔 즉시 나타남/사라짐. 다른 시트 스크림과 같은 188/300. */
  animation: wizon-scrim-in var(--wds-spring-effect-default-duration, 188ms)
    var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) both;
}
.recent-longpress-scrim[hidden] {
  display: none;
}
.recent-longpress-scrim[data-motion-state="closing"] {
  animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms)
    var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
}
.recent-longpress-lift {
  position: fixed;
  z-index: 195;
  left: 8px;
  right: 8px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 14px;
  border-radius: var(--ds-radius-md, 14px);
  background: var(--cg-menu-bg, #ffffff);
  box-shadow: 0 16px 44px rgba(0, 0, 0, 0.3);
  pointer-events: none;
}
.recent-longpress-title {
  overflow: hidden;
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.recent-longpress-preview {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--cg-muted-2);
  font-size: var(--ds-text-caption-13);
  line-height: 18px;
}
.recent-longpress-menu {
  position: fixed;
  z-index: 200;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 8px 8px calc(12px + var(--wizon-safe-bottom));
  /* fix(20260925-build-approval-sheet-dialog, inventory.md S14 행): raw 18px(값은 이미
     정본과 같다) → 토큰(M-4 — 반경은 1값·1토큰). */
  border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0;
  background: var(--cg-menu-bg, #ffffff);
  box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.22);
  /* R-41 B1(inventory §5 — 시간·곡선 정렬 S14: 240 → 372) — 한 벌 시간·곡선으로 정렬한다. */
  animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms)
    var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  /* The finger is still pressing when the sheet opens under it — suppress the native text
     selection / callout so the label ("공유하기" etc.) is not selected at the same time. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.recent-longpress-menu[hidden] {
  display: none;
}
.recent-longpress-menu[data-motion-state="closing"] {
  animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
    var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
}
.recent-longpress-item {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 13px 14px;
  border: none;
  border-radius: 12px;
  background: transparent;
  font: inherit;
  font-size: var(--ds-text-body-16);
  color: var(--cg-text, #0d0d0d);
  text-align: left;
  cursor: pointer;
}
.recent-longpress-item:active {
  background: var(--cg-hover-soft, rgba(13, 13, 13, 0.06));
}
.recent-longpress-item.is-danger {
  color: #e02e2a;
}
.recent-longpress-item .wds-inline-icon {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}
.recent-longpress-divider {
  height: 1px;
  margin: 6px 10px;
  background: var(--cg-menu-border, rgba(13, 13, 13, 0.1));
}
/* R-41 B1b(S14·M-15) — 기존 블록(즉시 animation:none)에 얹는다(primitives.md §① 규약 —
   새 prefers-reduced-motion 블록을 만들지 않는다). scrim 은 원래 이 블록에 없었다(닫힘
   결손 그대로 방치돼 있었다) — 같이 더한다. 로그인 창과 같은 문법. `.recent-longpress-*`
   원본 셀렉터(클래스 1개, 특이도 0,1,0)는 전역 하한(20260710-reduced-motion-floor, 이 셀렉터
   보다 파일 뒤에 있다 — 특이도가 같으면 소스 순서가 더 뒤인 하한이 이긴다)와 특이도가 같아
   !important 끼리는 소스 순서로 진다. `body.chatgpt-reference-v1` 접두사(이 요소는 실제로
   `document.body.appendChild` 로 body 바로 아래 붙는다, app.js ensureRecentLongPressMenu)를
   더해 특이도를 0,2,0 으로 올려 순서와 무관하게 이긴다 — 로그인 창 규칙들이 이미 쓰는
   접두사와 같다. */
@media (prefers-reduced-motion: reduce) {
  .recent-longpress-menu {
    animation: none;
  }
  body.chatgpt-reference-v1 .recent-longpress-menu {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both !important;
  }
  body.chatgpt-reference-v1 .recent-longpress-menu[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
  body.chatgpt-reference-v1 .recent-longpress-scrim {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both !important;
  }
  body.chatgpt-reference-v1 .recent-longpress-scrim[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
}

/* While editing, the bubble yields to a full-width inline editor. */
.chatgpt-reference-v1 .message.user .message-body:has(.user-edit) {
  max-width: 100%;
  width: 100%;
  padding: 0;
  background: transparent;
}
.user-edit {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(620px, 100%);
  margin-left: auto;
}
.user-edit-input {
  width: 100%;
  resize: none;
  border-radius: var(--ds-radius-lg, 18px);
  padding: 12px 16px;
  border: 1px solid var(--cg-menu-border, rgba(13, 13, 13, 0.14));
  background: var(--cg-surface, #ffffff);
  color: var(--cg-text, #0d0d0d);
  font: inherit;
  line-height: 1.5;
}
.user-edit-input:focus {
  outline: none;
  border-color: var(--cg-text, #0d0d0d);
  box-shadow: none;
}
.user-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.user-edit-cancel,
.user-edit-save {
  padding: 8px 16px;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  cursor: pointer;
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
  transition: background 120ms ease, opacity 120ms ease;
}
.user-edit-cancel {
  background: transparent;
  color: var(--cg-muted, #8f8f8f);
}
.user-edit-cancel:hover {
  background: var(--cg-hover-soft, rgba(13, 13, 13, 0.04));
  color: var(--cg-text, #0d0d0d);
}
.user-edit-save {
  background: var(--cg-send-bg, #0d0d0d);
  color: var(--cg-send-fg, #ffffff);
}
.user-edit-save:hover {
  opacity: 0.9;
}

/* 20260709-mobile-route-navbar: standard single top app bar (Material 3 / Apple HIG).
   On a route page (library/projects/…) the LEADING slot swaps hamburger→back and an
   inline route title appears; the TRAILING new-chat action stays put (like ChatGPT's
   Library). There is exactly ONE title — the route page's big H1 is hidden on mobile
   (see the route-page block) so the title isn't duplicated (NN/g: no redundant chrome). */
@media (max-width: 900px) {
  /* hide the hidden-attr controls even though .chatgpt-icon-button sets display */
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar [data-portal-route-back][hidden],
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar [data-mobile-route-title][hidden],
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar [data-mobile-workspace-trigger][hidden],
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar [data-mobile-library-new][hidden],
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar [data-drive-mobile-menu-trigger][hidden],
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar [data-mobile-trash-empty][hidden],
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar [data-rail-new-chat][hidden] {
    display: none !important;
  }

  /* route open: back replaces the hamburger; new-chat stays on the right. The title
     fills the middle so new-chat is pushed to the trailing edge. */
  body.portal-route-open.chatgpt-reference-v1 .chatgpt-mobile-topbar [data-sidebar-open] {
    display: none;
  }
  /* The title sits in the CENTER slot (20260725-mobile-topbar-workspace), so it no longer
     needs to grow to push the trailing action out — the equal-basis side slots already do
     that. `flex: 0 1 auto` lets it shrink-with-ellipsis instead of claiming the whole bar,
     which is what kept it pinned left before. */
  body.portal-route-open.chatgpt-reference-v1 .chatgpt-mobile-topbar .chatgpt-mobile-route-title {
    flex: 0 1 auto;
    text-align: center;
    margin-inline: 2px;
  }

  body.chatgpt-reference-v1 .chatgpt-mobile-route-title {
    font-size: 17px;
    font-weight: 600;
    color: var(--cg-text, #0d0d0d);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
  }
}

/* 20260709-mobile-safe-area (doc 143 item A): honor device safe areas now that
   viewport-fit=cover lets env() resolve. The app-shell is position:fixed at the
   very top (top:offsetTop), so the topbar pads its content below the notch, the
   composer clears the home indicator, and the drawer / content inset their edges
   (matters in landscape where the notch is on the side). Scoped <=900px. */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-mobile-topbar {
    height: calc(54px + var(--wizon-safe-top));
    padding-top: calc(4px + var(--wizon-safe-top));
    padding-left: calc(8px + var(--wizon-safe-left));
    padding-right: calc(8px + var(--wizon-safe-right));
    box-sizing: border-box;
  }

  /* composer clears the home indicator; collapse the inset while the keyboard is
     up (indicator hidden and the shell already sits on the keyboard). */
  body.chatgpt-reference-v1 .chat-pane {
    padding-bottom: var(--wizon-safe-bottom);
  }
  html.is-keyboard-open body.chatgpt-reference-v1 .chat-pane {
    padding-bottom: 0;
  }

  /* drawer clears the notch top and the side inset in landscape */
  body.chatgpt-reference-v1 .sidebar {
    padding-top: var(--wizon-safe-top);
    padding-left: var(--wizon-safe-left);
  }

  /* keep route/chat content clear of a side notch in landscape */
  body.chatgpt-reference-v1 .workbench {
    padding-left: var(--wizon-safe-left);
    padding-right: var(--wizon-safe-right);
  }
}

/* 20260709-mobile-bottom-sheet (doc 143 item C): on phones the composer "+" menu
   and the model/mode selector present as bottom sheets (slide up from the bottom,
   full-width, rounded top, dimming scrim + drag handle) instead of tiny dropdowns —
   the standard mobile menu affordance (Material bottom sheet / iOS action sheet).
   Overrides the JS dropdown positioning while the menu is open (:not([hidden])). */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 .composer-popover:not([hidden]),
  body.chatgpt-reference-v1 .reasoning-popover:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-more-menu:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-account-menu:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-conversation-menu:not([hidden]),
  /* doc 670 T1 — 더보기 임시 메뉴도 같은 목록에 얹는다(사본을 새로 만들지 않는다는 이 블록의
     관례를 그대로 따른다). */
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-recent-menu:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-library-popover,
  /* 20260818-settings-choice-sheet (doc 143 항목 C 잔여): 설정의 선택 팝오버만
     아직 220px 우측 드롭다운이었다. 규칙을 새로 만들지 않고 이 목록에 더한다 —
     사본을 만들면 두 시트가 서로 다르게 늙는다. 이 팝오버는 transform·will-change 를
     가진 설정 다이얼로그 «안»에 있지만, 그 다이얼로그가 [data-mobile-fullscreen] 로
     inset:0 이라 컨테이닝 블록이 곧 뷰포트다 — 하단이 화면 하단과 같다. */
  body.chatgpt-reference-v1 .settings-choice-popover {
    position: fixed !important;
    left: 0 !important;
    right: 0 !important;
    top: auto !important;
    bottom: 0 !important;
    width: 100% !important;
    max-width: none !important;
    /* Height fits the content — the sheet is only as tall as its rows, and scrolls
       (never stretches) once it would exceed the cap. `align-content: start` is critical:
       these menus are display:grid, and grid's default align-content (normal→stretch)
       was inflating each row (36px → 80px) to fill the capped box, spreading the items. */
    height: auto !important;
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px)) !important;
    align-content: start !important;
    overflow-y: auto !important;
    margin: 0 !important;
    padding: 8px 8px calc(12px + var(--wizon-safe-bottom)) !important;
    border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0 !important;
    /* R-40 ②파도 build C(669 §M-11) — 페인트 전용 100vmax 그림자 스크림을 걷었다(뒤 화면 탭·스크롤이
       그대로 통과했다). 스크림은 이제 진짜 요소다: 상단 레이어 승격 시트(계정·더보기·최근·작성 메뉴 등)는
       ::backdrop(아래 :popover-open 규칙), 라이브러리·드라이브 팝오버와 설정 선택은 시트 바로 앞
       형제 [data-sheet-scrim] 요소(ensureSheetScrim, 아래 §M-11 블록). 여기에는 들어올림 그림자만 남긴다.
       (지난 «본문 z-index 스크림이 사이드바 안 시트 위로 그려졌다» 는 이유로 되돌렸던 시도와 달리
       형제 요소라 같은 쌓임 맥락 안에서 시트 바로 밑(z 199 < 200)에 놓인다.) */
    box-shadow: var(--wizon-ai-sheet-shadow) !important;
    transform: none !important;
    /* 20260709-motion-m0 (doc 146 §1): motion rides the individual `translate` property, so the
       `transform: none !important` above (which blocks inherited desktop transforms) never touches
       it. `backwards` fill (not `both`): after the entrance settles, the base `translate: 0 0`
       governs — so the M1 exit transition takes over cleanly instead of fighting a forwards fill.
       Curve = WDS `effect-default` spring: an APPLE-style critically-damped `linear()` curve
       that decelerates to rest WITHOUT positional overshoot. The `spatial-default` spring was
       tried first but overshoots to 1.015 (translate -1.5%), which on a bottom-anchored sheet
       lifts it ~6px past rest and reveals a gap below before it settles (the reported
       "하단쪽 살짝 떴다가 다시 붙음"). effect-default never exceeds 1 → no lift, no gap. */
    translate: 0 0;
    z-index: 200 !important;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms) var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }

  /* R-40 ②파도 build C(669 §M-5) — 라이브러리·드라이브 팝오버 중 작업형(task: 검색 필터·권한 관리·
     인수인계 감사)은 긴 값. 선택형·안내형은 위 공용 짧은 값 그대로. */
  body.chatgpt-reference-v1 .chatgpt-library-popover[data-sheet-kind="task"] {
    max-height: calc(var(--wizon-vvh, 100dvh) - var(--wizon-safe-top, 0px) - 40px) !important;
  }

  /* R-41 B1b(S1b) — 닫힘: 재렌더 팝오버라 [data-mobile-bottom-sheet="popover"](§19, 26234행)와
     같은 모양의 closing 상태를 이 클래스 전용으로 반복한다(사본이 아니라 같은 wizon-sheet-down
     재사용, 새 키프레임 0개). */
  body.chatgpt-reference-v1 .settings-choice-popover[data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }

  /* 20260818-sheet-handle-dedupe: 여기 있던 `::after` 잡이를 걷었다.
     배치 4(`b20c576fa`)가 「이 블록은 핸들을 한 번도 그리지 않았다(hasHandle=false)」
     고 보고 `::before` 로 다시 그렸는데, 그 판단이 틀렸다 — `::after` 는 그리고
     있었고 결과적으로 **막대가 두 개**가 됐다. 2026-08-18 실측(375px 라이브):
     시트 8종 전부 ::before(+2px, 227 불투명)와 ::after(+8px, 138 50%)가 2px 간격을
     두고 «따로» 보인다. 정본은 배치 4 의 ::before 다(Mobbin 시트 6/6 근거).
     계약: apps/ai-portal/tests/test_mobile_sheet_handle_single.py */

  /* 20260710-conversation-menu-consolidation: in the full-width bottom sheet the
     conversation menu rows stretch edge-to-edge (the 174px desktop-popover width is a
     dropdown holdover) so labels like "그룹 채팅 시작" + the "준비 중" badge stay on one line. */
  body.chatgpt-reference-v1 .chatgpt-conversation-menu:not([hidden]) button {
    width: auto;
  }

  body.chatgpt-reference-v1 .chatgpt-conversation-menu:not([hidden]) button > span:not([class]) {
    white-space: nowrap;
  }

  /* doc 670 T1 다듬기(judge) — 폰 바텀 시트로 승격된 «이 메뉴만» 행 규격을 올린다.
     실측 RED(390): 공용 `.chatgpt-conversation-menu button` 의 min-height 36px 를 그대로
     상속해 다섯 행 전부 36px 였다 — 게이트 22(터치 타깃 44px) 위반이고 승인 목업의
     `.more-item` 은 52px 다. 공용 규칙을 고치면 계정/대화/모델 메뉴까지 함께 커지므로
     `.artifact-titlebar-more-menu` + `[data-artifact-more-item]` 로 특이도를 올려
     이 시트에서만 이긴다(파일 정보 `dl` 은 button 이 아니라 이 규칙 밖이다).
     polish2(§2.4) — 52 균일 → 48(부제 없음)/56(부제 있음). `:has()` 로 부제
     (.artifact-titlebar-more-hint) 유무를 가른다(이 리포가 이미 다른 곳에서 쓰는 관례,
     예: .artifact-ribbon-groupbox 4707행).
     polish2-fix2(정렬, spec §2.4 행 해부도) — ★width:100%/margin:0(신설). 공용
     `.chatgpt-conversation-menu button` 은 데스크톱 드롭다운 전제로 `width: auto;
     margin: 0 6px`(17948행)를 준다 — 버튼이 자기 내용(아이콘+라벨+어포던스) 폭까지만
     줄어들어, `.wizon-sheet-row-chevron`/`.wizon-sheet-format-chip`의 `margin-left:auto`
     가 밀 «남는 폭» 자체가 없었다(실측 RED: 「버전 기록」행 accRect.right 227.63~255.13,
     6행 산포 27.5px — 목업은 6행 전부 같은 x). 이 메뉴에서만 버튼을 그룹 카드
     (.wizon-sheet-group, 자체 패딩 없음) 폭 그대로 채워 «남는 폭»을 만든다 — margin은
     0으로 눌러 width:100%와 함께 그룹 경계 밖으로 새지 않게 한다(그룹 자체가
     overflow:hidden). */
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]) button[data-artifact-more-item] {
    width: 100%;
    margin: 0;
    min-height: 48px;
    align-items: center;
    gap: var(--ds-space-3);
    padding: var(--ds-space-2) var(--ds-space-3);
    font-size: var(--ds-text-body-15);
    font-weight: 500;
    color: var(--wizon-ai-text-primary);
    box-sizing: border-box;
  }
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]) button[data-artifact-more-item]:has(.artifact-titlebar-more-hint) {
    min-height: 56px;
  }
  /* polish2-fix(비활성 행 라벨 톤, spec §2.4) — 위 규칙의 특이도(body.class ×2 +
     :not([hidden]) + button[attr] = 0,4,2)가 기존 «비활성 색」 규칙
     (`.artifact-titlebar-more-menu [aria-disabled="true"]{color:...}`, 0,2,0, 4471행)을
     항상 이겨 「선택 범위로 질문」이 활성 행과 같은 잉크로 렌더됐다(빌드 캡처
     build-more-390-{light,dark}.png 실측 — 라벨 밝기 동일, 아이콘 타일은 opacity:.6이
     자기 규칙뿐이라 이미 정상 작동). 새 색 토큰 없이 같은 변수(--cg-muted-2)를 이 자리에서
     더 높은 특이도로 다시 선언해 되찾는다. */
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]) button[data-artifact-more-item][aria-disabled="true"] {
    color: var(--cg-muted-2, var(--cg-muted));
  }
  /* polish2(§2.4) — 눌림 면(신설). 기존 `:active{transform:scale(0.99)}`(공용 규칙)은
     362px 행에서 3.6px 라 지각 한계 아래였다(why-document-like.md §1 후보⑦) — 배경
     변화를 더해 «눌린 면»을 만든다. 비활성 행은 반응하지 않는다. */
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]) button[data-artifact-more-item]:active:not([aria-disabled="true"]) {
    background: var(--wizon-ai-surface-press);
  }
  /* ★행 사이 1px: 승인 목업은 그룹 안 행을 선으로 나눈다. polish2(§2.4) — 전폭
     border-top 대신 «인셋 헤어라인」으로 바꾼다(52 = padding 12 + 타일 28 + gap 12,
     타일 왼쪽 정렬과 시각적으로 이어지게). 인접 형제 선택자라 섹션 라벨/그룹 경계
     앞뒤에는 선이 생기지 않는다 — 그룹의 첫 행 위는 그룹 카드 자체가 경계다. */
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]) button[data-artifact-more-item] + button[data-artifact-more-item] {
    position: relative;
  }
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]) button[data-artifact-more-item] + button[data-artifact-more-item]::before {
    content: "";
    position: absolute;
    left: 52px;
    right: 0;
    top: 0;
    height: 1px;
    background: var(--cg-menu-border);
  }
  /* polish2(§2.4) — 시트 면 자체(#fff 한 장 → --wizon-ai-surface-sheet). `.chatgpt-conversation-menu`
     공용 베이스(17802행 `background:var(--cg-menu-bg)`)를 이 클래스로만 특이도를 올려
     이긴다 — 형제 11개(§4.1)는 무변화. */
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]) {
    background: var(--wizon-ai-surface-sheet);
  }
}

/* R-40 ②파도 build C(669 §M-11) — 라이브러리·드라이브 팝오버(S1b′ 17 템플릿)와 설정 선택 팝오버는
   상단 레이어가 아니라 재렌더 템플릿이라 ::backdrop 이 없다. 예전엔 시트의 100vmax 그림자로 «그리기만»
   했고 탭은 뒤 화면으로 통과했다. 이제 시트 바로 앞 형제 요소(app.js ensureSheetScrim 이 렌더마다 깐다)가
   화면 전체를 덮어 색(--wizon-scrim)도 내고 탭도 막는다 — 누르면 위임 핸들러(handleSheetScrimClick)가
   requestSheetDismiss(sheet,"scrim") 로 그 계열의 기존 닫기 세터를 부른다. 모바일 폭(≤520) 밖에서는
   숨김 — 데스크톱 팝오버는 스크림이 없다. z-index 199 = 시트(200) 바로 밑. 새 키프레임·새 토큰 0. */
body.chatgpt-reference-v1 [data-sheet-scrim] {
  display: none;
}
@media (max-width: 520px) {
  body.chatgpt-reference-v1 [data-sheet-scrim] {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 199;
    background: var(--wizon-scrim);
    /* iOS Safari 는 클릭 처리기가 없는 <div> 로는 문서 위임 click 을 안 보낸다 — cursor:pointer 로 «누를 수 있는 면» 표시. */
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    animation: wizon-scrim-in var(--wizon-motion-base) var(--wds-ease-out) backwards;
  }
  /* 시트가 제자리 갱신(필터 탭 등)이면 스크림도 다시 번쩍이지 않는다. */
  body.chatgpt-reference-v1 [data-sheet-scrim]:has(+ [data-library-sheet-static]),
  body.chatgpt-reference-v1 [data-sheet-scrim]:has(+ [data-sheet-settled]) {
    animation: none;
  }
  /* 닫힘 — 시트가 내려가는 동안 같이 옅어진다(라이브러리 계열은 유령 시트와 함께 스크림 유령도 뜬다). */
  body.chatgpt-reference-v1 [data-sheet-scrim]:has(+ [data-motion-state="closing"]),
  body.chatgpt-reference-v1 [data-sheet-scrim][data-library-sheet-ghost] {
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms) linear forwards;
    pointer-events: none;
  }
}

/* R-40 ②파도 이월 D(669 §M-11) — S4 팝오버 3종(채우기색·정렬·시트탭 목록)의 스크림. 이 시트는 z-index 300(§19
   공통 어댑터)이라 위 스크림(199)으로는 그 사이 300 미만 요소가 스크림 위에 남는다 — 시트 바로 밑(299)으로 올린다.
   새 색·키프레임 0(--wizon-scrim 과 위 옅어짐 규칙을 그대로 쓴다). 퇴장 유령은 형제가 없어 199 로 남는다(눌림 없음). */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 [data-sheet-scrim]:has(+ [data-mobile-bottom-sheet="popover"]) {
    z-index: 299;
  }
}

/* R-40 ②파도 build C(669 §M-2, 오너 09-25 «웹 우상단 × · 모바일 안내 시트 하단 확인») — 안내형(notice)
   시트의 하단 «확인» 줄(주버튼 1개, 48px). 모바일 경계에서만 보이고 그 경계에서 우상단 ×(44px 머리 버튼)는
   숨긴다(닫는 방법: 확인·끌어내림·스크림·뒤로 — 671 §4.13). 데스크톱은 × 그대로, 이 줄은 숨김. 경계는 표면이
   시트가 되는 폭과 같게: 폴더 활동 = ≤520, S16 패널·S6 미리보기/붙여넣기 시트 = ≤520 또는 굵은 포인터.
   새 키프레임·새 토큰 0(--cg-text/--cg-surface 는 기존 시트 버튼이 쓰는 값). */
body.chatgpt-reference-v1 .wizon-sheet-confirm-row {
  display: none;
}
body.chatgpt-reference-v1 .wizon-sheet-confirm-row .wizon-sheet-confirm-button {
  box-sizing: border-box;
  width: 100%;
  min-height: 48px;
  border: 0;
  border-radius: var(--wds-radius-md);
  background: var(--cg-text);
  color: var(--cg-surface);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
/* S16 패널은 어두운 «반전 면»(--wds-color-inverse-surface) 위에 뜬다 — 검정 버튼이 묻히므로 같은 반전 쌍을 쓴다. */
body.chatgpt-reference-v1 .composer-attachment-media-utility-panel .wizon-sheet-confirm-row .wizon-sheet-confirm-button {
  background: var(--wds-color-inverse-on-surface);
  color: var(--wds-color-inverse-surface);
}
body.chatgpt-reference-v1 .wizon-sheet-confirm-row .wizon-sheet-confirm-button:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 2px;
}
@media (max-width: 520px) {
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-dialog .wizon-sheet-confirm-row {
    display: block;
    flex: none;
    padding-bottom: var(--wizon-safe-bottom, 0px);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-head button[data-drive-action="mobile-activity-close"] {
    display: none;
  }
}
@media (max-width: 520px), (pointer: coarse) {
  body.chatgpt-reference-v1 .composer-attachment-media-utility-panel .wizon-sheet-confirm-row,
  body.chatgpt-reference-v1 .composer-attachment-sheet .wizon-sheet-confirm-row {
    display: block;
    flex: none;
    margin-top: 12px;
  }
  body.chatgpt-reference-v1 .composer-paste-sheet .wizon-sheet-confirm-row {
    padding: 0 16px calc(8px + var(--wizon-safe-bottom, 0px));
  }
  /* 안내형은 주버튼이 «확인» 하나다(671 §4.13) — 같은 시트의 «전체 화면에서 열기» 는 보조 버튼(윤곽선)으로 내린다. */
  body.chatgpt-reference-v1 .composer-attachment-sheet .composer-attachment-sheet-full {
    border: 1px solid var(--cg-menu-border);
    background: transparent;
    color: var(--cg-text);
  }
  /* 확인 줄이 바닥 안전영역을 이어받으므로 바로 위 동작 줄은 고정 8 만 둔다(안전영역 이중 계상 방지). */
  body.chatgpt-reference-v1 .composer-paste-sheet .composer-paste-surface-foot {
    padding-bottom: 8px;
  }
  body.chatgpt-reference-v1 .composer-attachment-media-utility-panel > header button[data-composer-attachment-panel-close],
  body.chatgpt-reference-v1 .composer-attachment-sheet [data-composer-attachment-sheet-close] {
    display: none;
  }
}

@keyframes wizon-sheet-up {
  from { translate: 0 100%; }
  to { translate: 0 0; }
}
@keyframes wizon-scrim-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
/* Exit as a keyframe animation (not a transition) — Safari paints animations, not transitions,
   on top-layer popovers. See the 20260709-motion-m1 block.
   R-41 B2(M-14 ③) — from 은 --wizon-sheet-drag-y(기본 0)를 읽는다: 손잡이로 끌다 놓아서
   닫히는 경우 놓인 자리에서 이어 내려간다. 이 변수를 쓰지 않는(대다수) 시트는 기본값 0 이라
   전과 같은 0 → 100% 그대로다. to 는 --wizon-kb-inset(기본 0px, 다른 곳에서 이미 쓰는 변수 —
   새 변수 0개)만큼 더 내려가 키보드가 띄운 만큼도 확실히 화면 밖으로 나간다 — kb-inset 을
   안 쓰는 시트는 +0px 라 값이 그대로다.
   R-40 ②파도 후속 E — 끌기 거리가 시트 높이보다 크면(짧은 시트를 멀리 끌어 놓음) to 가 from 보다 작아
   놓은 뒤 시트가 «위로» 움직였다(합성 시트탭 목록 898→896.7 실측). to 를 끌던 오프셋과의 max 로 보정해
   어느 쪽이든 아래로만(또는 제자리) 움직인다. 끌지 않은 시트는 오프셋 0 이라 max 가 전과 같은 값이다. */
@keyframes wizon-sheet-down {
  from { translate: 0 var(--wizon-sheet-drag-y, 0); }
  to { translate: 0 max(calc(100% + var(--wizon-kb-inset, 0px)), var(--wizon-sheet-drag-y, 0px)); }
}
/* R-41 B2(M-14 ④) — 임계 미달 복귀 전용(이 후보가 허용된 새 키프레임 1개). 현재
   --wizon-sheet-drag-y 오프셋에서 0 으로 188ms 스프링으로 되돌아간다. top-layer 에서는
   transition 이 페인트되지 않아(위 주석과 같은 이유) transition 이 아니라 키프레임을 쓴다. */
@keyframes wizon-sheet-snap {
  from { translate: 0 var(--wizon-sheet-drag-y, 0); }
  to { translate: 0 0; }
}
/* B2a 수리(judge 부수 발견 5, work-order build-b2a-scrim-fix) — from 이 고정 1이면 끌던
   중(스크림이 이미 calc(1 - var(--wizon-sheet-drag-progress))로 옅어져 있던 상태)에 놓아
   닫힐 때 첫 프레임이 그 옅은 값에서 1로 되튀었다가 다시 어두워지는 "번쩍"이 보인다(실측:
   Chromium·WebKit 모두, 닫힘 첫 3프레임 불투명도 표). --wizon-sheet-drag-progress(기본 0)를
   그대로 읽어 드래그하지 않은 채 닫히는(대다수) 시트는 1 - 0 = 1 로 전과 동일하다. */
@keyframes wizon-scrim-out {
  from { opacity: calc(1 - var(--wizon-sheet-drag-progress, 0)); }
  to { opacity: 0; }
}
/* Library sheet exit (20260710-library-sheet-motion): the new/filter menus re-render as fresh
   HTML (no persistent node for setAnimatedDisclosure's exit), so their close is animated via a
   cloned inert ghost that slides down AND fades — the fade also carries the paint-only dim
   (box-shadow inherits element opacity) out with it, matching the ::backdrop fade of the other
   sheets without needing the top layer. */
/* R-41 B2b(build 2 할 일 3, 설계 §3.6 위험표) — S1b′ 에 끌기를 붙이면(이 유령은
   spawnLibrarySheetGhost 의 cloneNode(true) 라 끌던 원본의 인라인 --wizon-sheet-drag-y 를
   그대로 물려받는다) from 이 0 으로 고정돼 있어 놓은 자리에서 0 으로 튀었다가 다시
   내려가는 결함이 생긴다. wizon-sheet-down 과 같은 처방 — 변수를 읽는다(기본 0, 끌지
   않고 닫힌 절대다수는 전과 동일). */
@keyframes wizon-library-sheet-out {
  from { translate: 0 var(--wizon-sheet-drag-y, 0); opacity: 1; }
  to { translate: 0 100%; opacity: 0; }
}
/* Mobile drawer offcanvas slide (M2, doc 146 §3). `translate` (not transform) so it coexists
   with the escape rule's `transform: none !important`; keyframe animation (not transition) with
   no forwards fill on entry so no residual translate re-traps the child sheets (doc 144). */
@keyframes wizon-drawer-in {
  from { translate: -100% 0; }
  to { translate: 0 0; }
}
@keyframes wizon-drawer-out {
  from { translate: 0 0; }
  to { translate: -100% 0; }
}

/* 20260710-motion-m3 (doc 146 §4): mobile route transition via the View Transitions API.
   The JS wraps the route render in document.startViewTransition (Safari 18+/Baseline; gated to
   mobile + feature-detected + reduced-motion). The new page cross-slides in from the right, the
   old slides slightly left and fades — a subtle iOS-style page transition. */
@keyframes wizon-route-in {
  from { opacity: 0; transform: translateX(16px); }
  to { opacity: 1; transform: translateX(0); }
}
@keyframes wizon-route-out {
  from { opacity: 1; transform: translateX(0); }
  to { opacity: 0; transform: translateX(-12px); }
}
@media (max-width: 900px) {
  ::view-transition-old(root) {
    animation: wizon-route-out 160ms var(--wizon-sheet-exit-ease, cubic-bezier(0.4, 0, 1, 1)) both;
  }
  ::view-transition-new(root) {
    animation: wizon-route-in 240ms var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }
}
@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}

/* 20260710-motion-m4 → 20260910-motion-m5 로 대체 이관(mobile-app-kit K2-S1, primitives.md §①):
   눌림 피드백 CSS(`.wds-pressed`/`.wds-pressed-fill` 표현·전환)는
   `apps/ai-portal/app/static/wizon-mobile-kit.css` 의 "① 눌림 피드백" 절로 옮겼다. 이유는 그
   절 주석에 적었다 — 요약하면 `body.chatgpt-reference-v1` 접두사가 로그인 없는 QA 하네스에서
   거짓이 되어 검수 화면에서 눌림이 사라져 보였다(primitives.md §① 회귀위험 1). */

/* 20260728-composer-attachment-preview-v3: keep ordered attachment context on its own compact shelf.
   Inline-size containment lets the rail scroll internally without widening the mobile document. */
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-attachment-lane:not([hidden])),
body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(.composer-attachment-lane:not([hidden])) {
  grid-template-rows: auto auto !important;
  min-height: 106px !important;
  height: auto !important;
  padding: 7px 8px 5px !important;
}

body.chatgpt-reference-v1 .composer-attachment-lane:not([hidden]) {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  box-sizing: border-box;
  contain: inline-size paint;
  gap: 7px;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  padding: 1px 2px 8px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}

body.chatgpt-reference-v1 .composer-attachment-lane::-webkit-scrollbar {
  display: none;
}

/* 20260802-composer-attachment-navigation: fine pointers get explicit horizontal
   navigation and grab-to-scroll. Touch keeps the native swipe lane with no chrome. */
body.chatgpt-reference-v1 .composer-attachment-rail:not([hidden]) {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  min-width: 0;
}

body.chatgpt-reference-v1 .composer-attachment-rail .composer-attachment-lane:not([hidden]) {
  flex: 1 1 auto;
  width: auto;
  cursor: grab;
  overscroll-behavior-inline: contain;
  scroll-snap-type: inline proximity;
}

body.chatgpt-reference-v1 .composer-attachment-rail .composer-attachment-lane.is-dragging {
  cursor: grabbing;
  scroll-snap-type: none;
  user-select: none;
}

body.chatgpt-reference-v1 .composer-attachment-card,
body.chatgpt-reference-v1 .composer-attachment-manage {
  scroll-snap-align: start;
}

body.chatgpt-reference-v1 .composer-attachment-nav {
  display: none;
  flex: 0 0 32px;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--cg-text) 10%, transparent);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--cg-surface);
  color: var(--cg-text);
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-attachment-nav:disabled {
  opacity: 0.32;
  cursor: default;
}

body.chatgpt-reference-v1 .composer-attachment-manage {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid color-mix(in srgb, var(--cg-text) 12%, transparent);
  border-radius: var(--wizon-ai-menu-radius);
  background: color-mix(in srgb, var(--cg-text) 3%, var(--cg-surface));
  color: var(--cg-text);
  font: inherit;
  font-size: var(--wizon-ai-font-size-label);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-attachment-card[data-question-active="false"] {
  opacity: 0.56;
}

@media (pointer: fine) {
  body.chatgpt-reference-v1 .composer-attachment-rail.has-overflow .composer-attachment-nav:not([hidden]) {
    display: grid;
  }

  body.chatgpt-reference-v1 .composer-attachment-nav:not(:disabled):hover,
  body.chatgpt-reference-v1 .composer-attachment-manage:hover {
    background: var(--cg-hover-soft);
  }
}

body.chatgpt-reference-v1 .composer-attachment-dialog {
  width: min(520px, calc(100vw - 32px));
  max-height: min(680px, calc(100dvh - 48px));
  padding: 0;
  border: 0;
  border-radius: var(--wizon-ai-radius-sheet);
  background: var(--cg-surface);
  color: var(--cg-text);
  box-shadow: var(--wizon-ai-dialog-shadow);
  overflow: hidden;
}

body.chatgpt-reference-v1 .composer-attachment-dialog::backdrop {
  background: var(--wds-modal-scrim);
  backdrop-filter: blur(2px);
}

/* 669 §M-11 — 첨부 목록은 모바일에서 모달 시트다: 스크림은 --wizon-scrim 하나(데스크톱 중앙 창은 위 값). */
@media (max-width: 520px), (pointer: coarse) {
  body.chatgpt-reference-v1 .composer-attachment-dialog::backdrop {
    background: var(--wizon-scrim);
  }
}

body.chatgpt-reference-v1 .composer-attachment-dialog-shell {
  display: flex;
  flex-direction: column;
  max-height: inherit;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 16px;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-head h2,
body.chatgpt-reference-v1 .composer-attachment-dialog-head p {
  margin: 0;
}

/* R29-P3a-2: 첨부 목록 머리 = 붙여넣기 미리보기 머리와 같은 타이포(제목 17/600 · 메타 13). */
body.chatgpt-reference-v1 .composer-attachment-dialog-head h2 {
  font-size: var(--cg-text-topbar-brand-size);
  font-weight: var(--ds-weight-semibold);
  line-height: 22px;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-head p {
  margin-top: 2px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  line-height: 18px;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-close,
body.chatgpt-reference-v1 .composer-attachment-dialog-remove,
body.chatgpt-reference-v1 .composer-attachment-dialog-toggle {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: var(--composer-attachment-control, 36px);
  height: var(--composer-attachment-control, 36px);
  padding: 0;
  border: 0;
  border-radius: var(--wizon-ai-radius-pill);
  background: transparent;
  color: inherit;
  cursor: pointer;
}

/* R29-P3a-2(초안 §1-5): 첨부 목록 = W-그리드 그룹 카드 2개. 세그먼트(전체/선택 파일)·파란 선택 면은
   없앴다 — 체크(잉크)가 곧 선택이다. 눈썹 12/600 muted(카드 밖, 아래 8 · 그룹 사이 20) · 카드 L1
   (1px 테두리, 반경 14 = --ds-radius-md) · 행 56 = 타일 28 | 이름 15/600 + 메타 13 | 체크 | × —
   파일·텍스트 두 문법 → 한 문법. 컨트롤 = 모바일 44 / 데스크톱 36. */
body.chatgpt-reference-v1 #composerAttachmentDialog {
  --composer-attachment-control: 36px;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-list {
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-height: 0;
  padding: 4px 16px 16px;
  overflow-y: auto;
}

body.chatgpt-reference-v1 .composer-attachment-group[hidden],
body.chatgpt-reference-v1 .composer-attachment-group-action[hidden],
body.chatgpt-reference-v1 .composer-attachment-group-warning[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .composer-attachment-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

body.chatgpt-reference-v1 .composer-attachment-group-label {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
  line-height: 16px;
}

/* «모두 선택»: 글자는 눈썹과 같은 기준선(align-items: baseline), 누르는 상자는 컨트롤 높이 —
   위아래 음수 여백으로 눈썹 줄 높이(16)를 키우지 않는다(09-23 게이트 소결함 «기준선»). */
body.chatgpt-reference-v1 .composer-attachment-group-action {
  min-height: var(--composer-attachment-control);
  margin: calc((16px - var(--composer-attachment-control)) / 2) -8px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--wizon-ai-radius-pill);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  font-weight: var(--ds-weight-semibold);
  line-height: 16px;
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-budget {
  display: flex;
  flex: none;
  line-height: 16px;
}

body.chatgpt-reference-v1 .composer-attachment-group-gauge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 16px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  line-height: 16px;
}

body.chatgpt-reference-v1 .composer-attachment-group-gauge[data-over-budget="true"] {
  color: var(--ds-feedback-warn-fg);
}

body.chatgpt-reference-v1 .composer-attachment-group-gauge-bar {
  display: block;
  width: 64px;
  height: 4px;
}

body.chatgpt-reference-v1 .composer-attachment-group-card {
  overflow: hidden;
  border: 1px solid var(--wizon-ai-composer-border);
  border-radius: var(--ds-radius-md);
  background: var(--cg-surface);
}

body.chatgpt-reference-v1 .composer-attachment-group-warning {
  margin: 8px 0 0;
  color: var(--ds-feedback-warn-fg);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .composer-attachment-dialog-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--composer-attachment-control) var(--composer-attachment-control);
  align-items: center;
  min-height: 56px;
  padding-right: 4px;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-item + .composer-attachment-dialog-item {
  border-top: 1px solid var(--wizon-ai-composer-border);
}

body.chatgpt-reference-v1 .composer-attachment-dialog-main {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  min-height: 56px;
  padding: 0 8px 0 12px;
  border: 0;
  background: transparent;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-main .composer-attachment-file-icon {
  width: 28px;
  height: 28px;
  border-radius: var(--wizon-ai-radius-control);
}

body.chatgpt-reference-v1 .composer-attachment-dialog-copy strong {
  font-size: var(--ds-text-body-15);
  font-weight: var(--ds-weight-semibold);
}

/* R-29 P2 후속: .composer-attachment-dialog-main 은 flex 컨테이너라 자식의 기본
   flex-shrink:1 이 적용된다. 제목이 긴 행에서 .composer-attachment-file-icon(고정
   34×34) 이 8~13px 까지 찌그러지던 결함 — 아이콘은 줄지 않고 제목만 말줄임으로
   줄어야 한다(.composer-attachment-dialog-copy 는 이미 min-width:0). */
body.chatgpt-reference-v1 .composer-attachment-dialog-main .composer-attachment-file-icon {
  flex: none;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-check {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border: 1.5px solid color-mix(in srgb, var(--cg-text) 32%, transparent);
  border-radius: var(--ds-radius-xs);
}

/* 체크 = 잉크(파란 면 제거 — 선택은 면이 아니라 체크 하나로 읽는다). 다크에서는 잉크가 밝은 면이
   되고 체크 글리프가 어두운 표면 색이 된다. */
body.chatgpt-reference-v1 .composer-attachment-dialog-toggle[aria-checked="true"] .composer-attachment-dialog-check {
  border-color: var(--cg-text);
  background: var(--cg-text);
  color: var(--cg-surface);
}

body.chatgpt-reference-v1 .composer-attachment-dialog-check svg {
  width: 16px;
  height: 16px;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-copy {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-copy strong,
body.chatgpt-reference-v1 .composer-attachment-dialog-copy small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-copy small {
  margin-top: 2px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
}

@media (pointer: fine) {
  body.chatgpt-reference-v1 .composer-attachment-dialog-main:hover,
  body.chatgpt-reference-v1 .composer-attachment-group-action:hover,
  body.chatgpt-reference-v1 .composer-attachment-dialog-close:hover,
  body.chatgpt-reference-v1 .composer-attachment-dialog-remove:hover {
    background: var(--cg-hover-soft);
  }
}

@media (max-width: 520px), (pointer: coarse) {
  body.chatgpt-reference-v1 .composer-attachment-rail {
    gap: 0;
  }

  body.chatgpt-reference-v1 .composer-attachment-nav {
    display: none !important;
  }

  body.chatgpt-reference-v1 .composer-attachment-rail .composer-attachment-lane:not([hidden]) {
    cursor: auto;
    padding-inline: 2px;
    -webkit-overflow-scrolling: touch;
  }

  body.chatgpt-reference-v1 .composer-attachment-dialog {
    width: 100%;
    max-width: none;
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px));
    margin: auto 0 0;
    border-radius: var(--wizon-ai-radius-sheet) var(--wizon-ai-radius-sheet) 0 0;
  }

  /* R-41 B1 — [data-mobile-sheet-motion](이 대화상자 전용 모션 옵트인, §11 [data-mobile-
     bottom-sheet] 위치 레시피는 얹지 않는다 — 78dvh/680px·드래그 오프셋을 그대로 둔다).
     닫힘은 키프레임이다(트랜지션 아님) — Safari 는 top-layer 위에서 transition 을 페인트하지
     않는다(20260709-motion-m1 과 같은 이유). animatedShowDialog/animatedCloseDialog 가 이미
     [data-motion-state] 를 opening→open, closing 순서로 매긴다(app.js). */
  body.chatgpt-reference-v1 .composer-attachment-dialog[data-mobile-sheet-motion][data-motion-state="opening"],
  body.chatgpt-reference-v1 .composer-attachment-dialog[data-mobile-sheet-motion][data-motion-state="open"] {
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }

  body.chatgpt-reference-v1 .composer-attachment-dialog[data-mobile-sheet-motion][data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }

  body.chatgpt-reference-v1 .composer-attachment-dialog-list {
    scrollbar-width: none;
    padding-bottom: calc(16px + env(safe-area-inset-bottom));
  }

  body.chatgpt-reference-v1 .composer-attachment-dialog-list::-webkit-scrollbar {
    display: none;
  }
}

/* R29-P2 결정 2ⓐ — 접힌 레일의 «요약줄»(첨부 N개). 칩과 같은 44px 행·같은 테두리·같은 반경을
   쓰고(레일 행 높이 1종 유지), 앞 3개 항목의 아이콘 타일을 겹쳐 «무엇이 들었는지»를 보인다.
   누르면 첨부 목록(데스크톱 = 가운데 다이얼로그, 모바일 = 바텀시트)을 연다. */
body.chatgpt-reference-v1 .composer-attachment-summary {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 8px 0 7px;
  border: 1px solid color-mix(in srgb, var(--cg-text) 11%, transparent);
  border-radius: var(--wizon-ai-menu-radius);
  background: color-mix(in srgb, var(--cg-text) 4%, var(--cg-surface));
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  scroll-snap-align: start;
}

/* R29-P2 중앙 게이트(09-23 16:0x): 요약줄을 탭하면 곧바로 열린 목록 시트의 같은 좌표 행에
   Android 기본 파란 tap-highlight 가 남았다(에뮬 실측). 가르는 값 = 같은 탭이 새 행에 click
   까지 전달되는가 — 10회 탭에서 목록 유지 10/10, 행 click 0·pointerup 0 → 유령 클릭이 아니라
   «탭에 딸린 일시 표시»다. 포털 선례(카드 리스트 행 tr[data-card-row], 위 md-table 절)와 같이
   누르는 상자에만 기본 하이라이트를 끈다 — 눌림 피드백은 역할 기반 .wds-pressed 가 이미 맡는다. */
body.chatgpt-reference-v1 .composer-attachment-summary,
body.chatgpt-reference-v1 #composerAttachmentDialog .composer-attachment-dialog-item,
body.chatgpt-reference-v1 #composerAttachmentDialog .composer-attachment-dialog-item button {
  -webkit-tap-highlight-color: transparent;
}

body.chatgpt-reference-v1 .composer-attachment-summary:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-ring);
  outline-offset: 1px;
}

body.chatgpt-reference-v1 .composer-attachment-summary-stack {
  display: inline-flex;
  align-items: center;
}

body.chatgpt-reference-v1 .composer-attachment-summary-tile {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 26px;
  height: 26px;
  margin-left: -8px;
  border-radius: var(--ds-radius-xs);
  overflow: hidden;
  background: var(--cg-surface);
  box-shadow: inset 0 0 0 1px var(--cg-menu-border), 0 0 0 2px var(--cg-surface);
}

body.chatgpt-reference-v1 .composer-attachment-summary-tile:first-child {
  margin-left: 0;
}

body.chatgpt-reference-v1 .composer-attachment-summary-tile > * {
  width: 22px;
  height: 22px;
  margin: 2px;
}

body.chatgpt-reference-v1 .composer-attachment-summary-note {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 400;
}

body.chatgpt-reference-v1 .composer-attachment-summary-note[data-state="failed"] {
  color: var(--wizon-ai-structure-warn);
}

body.chatgpt-reference-v1 .composer-attachment-summary-chevron {
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--cg-muted);
}

body.chatgpt-reference-v1 .composer-attachment-summary-chevron svg {
  width: 16px;
  height: 16px;
}

@media (pointer: fine) {
  body.chatgpt-reference-v1 .composer-attachment-summary:hover {
    background: var(--cg-hover-soft);
  }
}

/* R29-P2 결정 3ⓐ·D10′ — 첨부 목록의 텍스트 행과 합계 게이지. 텍스트 행은 체크박스 대신
   빈 머리 칸(아이콘 정렬선 유지) + ↻ 되돌리기 + × 제거. */
body.chatgpt-reference-v1 .composer-attachment-dialog-grabber {
  display: none;
}


/* 목록 행 아이콘도 칩 타일과 같은 2px 안전 여백을 준다 — 200×241 원화의 확장자 배지가
   타일 반경 9 에 잘리던 것(전|후 비교판 390 에서 실측: 「HTML」 배지 아래 1/3 잘림)을 닫는다.
   칩 쪽 규칙(.composer-attachment-card …--attachment)과 같은 이유·같은 값의 형제다. */
body.chatgpt-reference-v1 .composer-attachment-dialog-main
  .composer-attachment-file-icon > .wizon-file-type-icon--attachment {
  width: 24px;
  height: 24px;
  margin: 2px;
}

body.chatgpt-reference-v1 .composer-attachment-dialog-budget[hidden] {
  display: none;
}

/* R29-P2 결정 3ⓐ — 모바일(≤520 또는 coarse)에서 첨부 목록은 미리보기 시트와 «같은 옷»:
   핸들 36×4(그래버 히트 28) · 피크 min(48vh,406px) · 최대 min(78vh, vvh−32) · 드래그 오프셋
   변수 · 모서리 반경. 미리보기 시트(아래 .composer-attachment-sheet)와 달리 이 표면은
   <dialog> 자신이 시트라 top-layer 에서 바닥에 붙는다. overlays-content 뷰포트 메타
   (index.html)에서는 소프트키보드가 레이아웃 뷰포트를 줄이지 않는다 — 그래서 높이만
   --wizon-vvh 로 줄이는 것으로는 아래 버튼이 키보드 밑에 남는다. 바닥을 이미 살아 있는
   --wizon-kb-inset 만큼 띄워 목록 끝이 보이는 영역 안에 남게 한다(새 JS 0). */
@media (max-width: 520px), (pointer: coarse) {
  body.chatgpt-reference-v1 #composerAttachmentDialog {
    box-sizing: border-box;
    min-height: min(48vh, 406px);
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px));
    margin: auto 0 var(--wizon-kb-inset, 0px);
    border: 1px solid var(--ds-modal-border);
    border-bottom: 0;
    box-shadow: var(--ds-modal-shadow);
    /* R-41 B2(M-14 ②) — 위 .composer-attachment-sheet 와 같은 이유로 transform 대신
       개별 속성 translate 를 쓴다(공유 변수 --wizon-sheet-drag-y). */
    translate: 0 var(--wizon-sheet-drag-y, 0);
    transition: translate var(--wizon-motion-fast, 140ms) var(--wizon-motion-spatial-ease);
  }

  body.chatgpt-reference-v1 #composerAttachmentDialog.is-dragging {
    transition: none;
    animation: none;
  }

  body.chatgpt-reference-v1 #composerAttachmentDialog.is-sheet-snapping {
    transition: none;
    animation: wizon-sheet-snap var(--wds-spring-effect-default-duration, 188ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) forwards;
  }

  body.chatgpt-reference-v1 #composerAttachmentDialog .composer-attachment-dialog-grabber {
    display: grid;
    flex: none;
    place-items: center;
    width: 100%;
    /* R-40(M-3 ③) — 손잡이 히트 ≥44 로 통일(이전 28 미달, 위 .composer-attachment-sheet-grabber
       와 같은 처방). */
    min-height: 44px;
    padding-top: 4px;
    cursor: grab;
    touch-action: none;
  }

  body.chatgpt-reference-v1 #composerAttachmentDialog.is-dragging .composer-attachment-dialog-grabber {
    cursor: grabbing;
  }

  /* 미리보기 시트 핸들(.composer-attachment-sheet-handle)과 같은 36×4·같은 색. 반경은 이
     목록 스코프가 이미 쓰는 pill 토큰 하나로(반경 종류 수를 늘리지 않는다, doc 669 §A-3). */
  /* R-41 B2b(build 2 할 일 6, 설계 §M-3 ②) — 막대 색 1토큰: 다른 계열이 이미 쓰는
     --cg-menu-border 로 맞춘다(불투명도 낮춘 회색 1종 대신 시트 계열 공통 색 1종). */
  body.chatgpt-reference-v1 #composerAttachmentDialog .composer-attachment-dialog-handle {
    width: 36px;
    height: 4px;
    border-radius: var(--wizon-ai-radius-pill);
    background: var(--cg-menu-border);
  }

  body.chatgpt-reference-v1 #composerAttachmentDialog .composer-attachment-dialog-head {
    align-items: center;
    padding-top: 0;
  }

  body.chatgpt-reference-v1 #composerAttachmentDialog {
    --composer-attachment-control: 44px;
  }
}

@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 #composerAttachmentDialog {
    transition: none;
  }
}

body.chatgpt-reference-v1 .chatgpt-composer-shell.has-attachment .composer-prompt-body {
  padding-top: 4px;
  border-top: 1px solid color-mix(in srgb, var(--cg-text, #0d0d0d) 8%, transparent);
}

body.chatgpt-reference-v1 .composer-chip-removable.composer-attachment-card {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 0;
  width: auto;
  max-width: min(240px, calc(100vw - 80px));
  min-height: 44px;
  padding: 0 4px 0 6px;
  border: 1px solid color-mix(in srgb, var(--cg-text, #0d0d0d) 11%, transparent);
  border-radius: 13px;
  background: color-mix(in srgb, var(--cg-text, #0d0d0d) 4%, #fff);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.035);
}

body.chatgpt-reference-v1 .composer-attachment-open {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: 8px;
  min-width: 0;
  height: 42px;
  padding: 0 7px 0 0;
  border: 0;
  border-radius: 11px 0 0 11px;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-attachment-open:focus-visible {
  outline: 2px solid var(--wds-brand-blue, #155eef);
  outline-offset: 1px;
}

body.chatgpt-reference-v1 .composer-chip-removable.composer-attachment-policy-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 1px 31px;
  grid-template-rows: auto auto;
  width: min(430px, calc(100vw - 80px));
  max-width: min(430px, calc(100vw - 80px));
  padding: 5px 4px 5px 6px;
}

body.chatgpt-reference-v1 .composer-attachment-policy-copy {
  grid-row: 1;
  grid-column: 1;
  height: auto;
  min-height: 42px;
  cursor: default;
}

body.chatgpt-reference-v1 .composer-attachment-policy-card .composer-attachment-index-state {
  max-width: 330px;
  white-space: normal;
}

body.chatgpt-reference-v1 .composer-attachment-policy-actions {
  display: flex;
  grid-row: 2;
  grid-column: 1 / -1;
  gap: 6px;
  padding: 3px 2px 0 38px;
}

body.chatgpt-reference-v1 .composer-attachment-policy-actions button {
  min-height: 30px;
  padding: 0 9px;
  border: 1px solid color-mix(in srgb, var(--cg-text) 13%, transparent);
  border-radius: var(--ds-radius-sm, 8px);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--cg-text-meta-size);
  font-weight: var(--cg-text-label-active-weight);
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-attachment-policy-actions button:hover,
body.chatgpt-reference-v1 .composer-attachment-policy-actions button:focus-visible {
  border-color: color-mix(in srgb, var(--ad-blue) 38%, transparent);
  background: color-mix(in srgb, var(--ad-blue) 9%, transparent);
  outline: 2px solid var(--ad-blue);
  outline-offset: 1px;
}

body.chatgpt-reference-v1 .composer-attachment-policy-card .composer-attachment-separator {
  grid-row: 1;
  grid-column: 2;
}

body.chatgpt-reference-v1 .composer-attachment-policy-card .composer-chip-remove {
  grid-row: 1;
  grid-column: 3;
}

body.chatgpt-reference-v1 .composer-range-context-copy {
  cursor: default;
}

body.chatgpt-reference-v1 .composer-range-quick-action {
  display: grid;
  flex: 0 0 auto;
  min-width: var(--wds-control-height-md);
  height: var(--wds-control-height-md);
  padding: 0 var(--ds-space-2);
  place-items: center;
  border: 0;
  border-radius: var(--ds-radius-sm);
  background: transparent;
  color: var(--wds-color-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  font-weight: var(--ds-weight-medium);
  cursor: pointer;
}

@media (hover: hover) {
  body.chatgpt-reference-v1 .composer-range-quick-action:hover {
    background: color-mix(in srgb, var(--wds-color-text) 8%, transparent);
  }
}

body.chatgpt-reference-v1 .composer-range-quick-action:focus-visible {
  outline: 2px solid var(--wds-color-focus-ring);
  outline-offset: -2px;
}

body.chatgpt-reference-v1 .composer-range-quick-action:disabled {
  color: var(--wds-color-text-muted);
  cursor: default;
  opacity: 0.55;
}

@media (pointer: coarse) {
  .artifact-titlebar-button[data-artifact-range-ask] {
    width: 44px;
    height: 44px;
  }
}

body.chatgpt-reference-v1 .composer-attachment-card .composer-attachment-file-icon {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--cg-menu-border);
}

/* The canonical 200x241 artwork paints its extension badge to y=240.  Filling the
   30x30 host therefore puts that ink under the host's radius-9 clip even though
   object-fit:contain reports a healthy image.  Keep the shared host mask, but give
   attachment artwork a real two-pixel safe area on every side. */
body.chatgpt-reference-v1 .composer-attachment-card
  .composer-attachment-file-icon > .wizon-file-type-icon--attachment {
  width: 26px;
  height: 26px;
  margin: 2px;
}

body.chatgpt-reference-v1 .composer-attachment-card .composer-attachment-file-icon svg {
  width: 18px;
  height: 18px;
}

body.chatgpt-reference-v1 .composer-attachment-card .composer-attachment-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
}

/* R29-P1(결정5ⓐ): 칩을 2줄로 — 윗줄 «타입 · 크기/줄수» + 아랫줄 «파일명/본문 첫 줄».
   예전엔 이 줄이 sr-only(1px clip)였고 .composer-chip-label 하나만 보였다.
   .composer-attachment-open 의 고정 높이(42px, 위 34733행)와 .composer-attachment-copy
   의 flex-column;justify-content:center(위 34890행) 는 그대로 두고 — 두 줄의
   합계 라인하이트(13+16=29px)가 그 42px 안에서 세로 중앙정렬되게 폰트만 줄인다.
   칩 자신의 min-height(44px, 위 34722행)는 건드리지 않는다. */
body.chatgpt-reference-v1 .composer-attachment-card .composer-attachment-type {
  --composer-attachment-type-text-size: 10px;
  display: block;
  overflow: hidden;
  color: var(--wds-color-text-muted);
  font-size: var(--composer-attachment-type-text-size);
  font-weight: 600;
  line-height: 13px;
  letter-spacing: 0.01em;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-attachment-card .composer-chip-label {
  display: block;
  overflow: hidden;
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-caption-13, 13px);
  font-weight: 500;
  line-height: 16px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-attachment-index-state {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--cg-muted);
  font-size: smaller;
  line-height: 14px;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-attachment-index-state::before {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ad-blue);
  content: "";
}

body.chatgpt-reference-v1 .composer-attachment-index-state[data-state="failed"] {
  color: var(--cg-danger-fg);
}

body.chatgpt-reference-v1 .composer-attachment-index-state[data-state="failed"]::before {
  background: currentColor;
}

body.chatgpt-reference-v1 .composer-attachment-index-state[data-state="background"] {
  color: var(--ad-blue);
}

body.chatgpt-reference-v1 .composer-attachment-index-state[data-state="degraded"] {
  color: var(--cg-warning-fg);
}

body.chatgpt-reference-v1 .composer-attachment-index-state[data-state="degraded"]::before {
  background: currentColor;
}

body.chatgpt-reference-v1 .composer-attachment-progress {
  position: relative;
  display: block;
  appearance: none;
  width: 100%;
  height: 2px;
  margin-top: 2px;
  overflow: hidden;
  border: 0;
  border-radius: var(--ds-radius-pill);
  background: var(--cg-menu-border);
}

body.chatgpt-reference-v1 .composer-attachment-progress::-webkit-progress-bar {
  background: var(--cg-menu-border);
}

body.chatgpt-reference-v1 .composer-attachment-progress::-webkit-progress-value,
body.chatgpt-reference-v1 .composer-attachment-progress::-moz-progress-bar {
  border-radius: var(--ds-radius-pill);
  background: var(--ad-blue);
}

body.chatgpt-reference-v1 .composer-attachment-separator {
  flex: 0 0 1px;
  width: 1px;
  height: 22px;
  background: color-mix(in srgb, var(--cg-text, #0d0d0d) 11%, transparent);
}

body.chatgpt-reference-v1 .composer-attachment-retry {
  display: grid;
  flex: 0 0 auto;
  width: 30px;
  height: 38px;
  padding: 0;
  place-items: center;
  border: 0;
  border-radius: var(--ds-radius-sm);
  background: transparent;
  color: var(--cg-warning-fg);
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-attachment-retry:hover {
  background: color-mix(in srgb, var(--cg-warning-fg) 10%, transparent);
}
/* focus-reset-shell-ring-10: 전역 P0-3 폴백보다 특정성을 올린다. */
body.chatgpt-reference-v1.chatgpt-reference-v1 .composer-attachment-retry:focus-visible {
  background: color-mix(in srgb, var(--cg-warning-fg) 10%, transparent);
  outline: none;
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

body.chatgpt-reference-v1 .composer-attachment-retry svg {
  width: 16px;
  height: 16px;
}

body.chatgpt-reference-v1 .composer-attachment-retry:disabled {
  color: var(--cg-muted);
  cursor: progress;
  opacity: 0.72;
}

body.chatgpt-reference-v1 .composer-attachment-card .composer-chip-remove {
  width: 31px;
  height: 38px;
  margin-left: 2px;
  color: var(--cg-muted, #6f6f6f);
}

/* R29-P3a-2 Q4(671 §9.10-1 C10, 09-24 QA 실측 390: 칩 열기 138.5×42 · × 31×38): 터치에서는 칩
   안의 두 조작이 칩 높이 44 전부를 누르는 상자로 쓴다. 칩 테두리(1px) 때문에 안쪽 높이가 42 라
   -1px 위아래 여백으로 테두리까지 덮는다(칩 바깥 치수 44 는 그대로 — 시각 변화 0). × 는 폭도
   44 로 넓히고 칩의 오른쪽 안쪽 여백(4)을 음수 여백으로 먹어 제목 폭 손실을 9px 로 줄인다. */
@media (max-width: 520px), (pointer: coarse) {
  body.chatgpt-reference-v1 .composer-attachment-card .composer-attachment-open {
    height: 44px;
    margin-block: -1px;
  }

  body.chatgpt-reference-v1 .composer-attachment-card .composer-chip-remove {
    width: 44px;
    height: 44px;
    margin: -1px -4px -1px 0;
  }
}

/* composer-edit-enter-unify-and-chip-restore ⓑ2: «본문으로 되돌리기»는 칩의 세 번째
   조작(펼치기·되돌리기·제거)이고 실수하면 되돌릴 수 없는 ×와 나란히 있어 히트 영역이
   좁으면 오조작 위험이 크다 — WCAG 2.2 목표 크기(44×44)를 폭에도 명시로 준다(높이는
   위 규칙의 38px 가 이미 적용되지만 폭은 31px 라 부족했다). 아이콘 자체는 14px 로
   유지(위 .composer-chip-remove svg 상속) — 히트 영역만 넓히고 시각 크기는 그대로.
   position:relative + ::after 확장(위 .composer-icon-button::after 와 같은 정본
   패턴, 09-11 library-toolbar-siblings 채택 C)으로 이웃(제거 버튼)과의 간격은 유지. */
body.chatgpt-reference-v1 .composer-attachment-card .composer-chip-restore {
  position: relative;
  width: var(--wizon-ai-action-target-touch, 44px);
}

body.chatgpt-reference-v1 .composer-attachment-card .composer-chip-restore::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: var(--wizon-ai-action-target-touch, 44px);
  transform: translate(-50%, -50%);
}

body.chatgpt-reference-v1 .composer-attachment-card:hover,
body.chatgpt-reference-v1 .composer-attachment-card:focus-within {
  border-color: color-mix(in srgb, var(--cg-text, #0d0d0d) 17%, transparent);
  background: color-mix(in srgb, var(--cg-text, #0d0d0d) 7%, #fff);
  color: var(--cg-text, #0d0d0d);
}

/* Success is announced once through the existing polite live region, but does not become a
   persistent green instruction above the prompt. Progress and error remain visible in place. */
body.chatgpt-reference-v1 .composer-attachment-status[data-tone="success"] {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

body.chatgpt-reference-v1 .composer-attachment-preview {
  position: fixed;
  z-index: 1250;
  width: min(352px, calc(100vw - 24px));
  padding: 14px;
  border: 1px solid color-mix(in srgb, var(--cg-text, #0d0d0d) 12%, transparent);
  border-radius: var(--ds-radius-lg, 18px);
  background: var(--cg-surface, #fff);
  color: var(--cg-text, #0d0d0d);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.16), 0 2px 8px rgba(0, 0, 0, 0.08);
}

body.chatgpt-reference-v1 .composer-attachment-preview[hidden],
body.chatgpt-reference-v1 .composer-attachment-preview-host[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .composer-attachment-preview-host[open] {
  position: fixed;
  inset: 0;
  width: 100vw;
  max-width: none;
  height: 100dvh;
  max-height: none;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border: 0;
  background: transparent;
}

body.chatgpt-reference-v1 .composer-attachment-preview-host::backdrop {
  background: transparent;
}

body.chatgpt-reference-v1 .composer-attachment-preview-header {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

body.chatgpt-reference-v1 .composer-attachment-preview-icon {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 42px;
  height: 42px;
  overflow: hidden;
  border-radius: 11px;
  background: var(--cg-surface);
  color: var(--ad-blue, var(--wds-brand-blue, #155eef));
  box-shadow: inset 0 0 0 1px var(--cg-menu-border);
}

/* 결함수리(mixed-rail-card-layout §6, 09-22, 오너 신고 R-29 (다)): 파일 칩 hover
   미리보기 팝오버 헤더의 파일 타입 타일이 잘려 보였다. 가르는 값(1440×900 실측) —
   .composer-attachment-preview-icon(42×42, overflow:hidden, 위 규칙)의 실제 자식은
   composerAttachmentIconHtml→renderFileTypeIcon(app.js:90476)이 항상 만드는
   `<img class="wizon-file-type-icon">`이지 `<svg>`가 아니다 — 이 아래 셀렉터가
   `svg` 만 겨눠 실제로는 한 번도 매치되지 않았다(죽은 셀렉터). 매치가 없으니
   img 는 기반 규칙(45027행 `.wizon-file-type-icon{width:100%;height:100%}`)만
   받는데, 부모가 `inline-grid; place-items:center`(= 자식을 늘려 채우지 않는
   정렬)라 퍼센트 높이가 안 풀려 img 가 원본 고유 크기로 렌더된다(실측: 42×50,
   42×42 타일보다 8px 커서 타일 자신의 overflow:hidden 에 아래쪽이 잘림 — 팝오버
   자신의 overflow/모서리가 아니라 타일 내부 클리핑, ⓐ에 가장 가까움). 수리는
   이미 선언돼 있던 19×19 의도(위 규칙, 한 번도 적용된 적 없는 죽은 값)를 실제
   렌더 요소(img)에도 매치시키는 것 — 새 크기를 발명하지 않는다. */
body.chatgpt-reference-v1 .composer-attachment-preview-icon img,
body.chatgpt-reference-v1 .composer-attachment-preview-icon svg {
  width: 19px;
  height: 19px;
}

body.chatgpt-reference-v1 .composer-attachment-preview-heading {
  display: grid;
  min-width: 0;
  gap: 2px;
}

body.chatgpt-reference-v1 .composer-attachment-preview-heading strong {
  overflow: hidden;
  font-size: var(--ds-text-body-14, 14px);
  font-weight: 600;
  line-height: 20px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-attachment-preview-heading > span {
  display: flex;
  align-items: center;
  gap: 6px;
  overflow: hidden;
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-caption-12, 12px);
  line-height: 16px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-attachment-preview-heading i {
  color: color-mix(in srgb, var(--cg-muted, #6f6f6f) 55%, transparent);
  font-style: normal;
}

body.chatgpt-reference-v1 .composer-attachment-preview-content {
  margin-top: 12px;
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--cg-text, #0d0d0d) 9%, transparent);
  border-radius: 13px;
  background: color-mix(in srgb, var(--cg-text, #0d0d0d) 2.5%, #fff);
}

body.chatgpt-reference-v1 .composer-attachment-preview-kicker {
  display: block;
  padding: 10px 12px 0;
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-caption-12, 12px);
  font-weight: 500;
  line-height: 16px;
}

body.chatgpt-reference-v1 .composer-attachment-preview-image {
  display: block;
  width: 100%;
  height: 172px;
  margin-top: 8px;
  object-fit: cover;
  background: color-mix(in srgb, var(--cg-text, #0d0d0d) 5%, #fff);
}

body.chatgpt-reference-v1 .composer-attachment-preview-text,
body.chatgpt-reference-v1 .composer-attachment-preview-empty {
  display: -webkit-box;
  overflow: hidden;
  margin: 7px 12px 12px;
  color: var(--cg-text, #0d0d0d);
  font-size: var(--ds-text-caption-13, 13px);
  line-height: 20px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 7;
}

body.chatgpt-reference-v1 .composer-attachment-preview-empty {
  color: var(--cg-muted, #6f6f6f);
}

body.chatgpt-reference-v1 .composer-attachment-preview-hint {
  display: block;
  margin-top: 10px;
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-caption-12, 12px);
  line-height: 16px;
}

body.chatgpt-reference-v1 .composer-document-preview-surface {
  box-sizing: border-box;
  min-width: 0;
  color: var(--cg-text, #0d0d0d);
}

body.chatgpt-reference-v1 .composer-document-preview-quality {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-height: 18px;
  width: fit-content;
  margin-top: 3px;
  padding: 1px 6px;
  border: 1px solid color-mix(in srgb, var(--wds-brand-blue, #155eef) 20%, transparent);
  border-radius: var(--ds-radius-pill, 999px);
  background: color-mix(in srgb, var(--wds-brand-blue, #155eef) 8%, transparent);
  color: color-mix(in srgb, var(--wds-brand-blue, #155eef) 82%, var(--cg-text, #0d0d0d));
  font-size: 10px;
  font-weight: 600;
  line-height: 14px;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-document-preview-quality.is-pending {
  visibility: hidden;
}

body.chatgpt-reference-v1 .composer-document-preview-quality[data-preview-quality="converted"],
body.chatgpt-reference-v1 .composer-document-preview-quality[data-preview-quality="limited"] {
  border-color: color-mix(in srgb, #f79009 25%, transparent);
  background: color-mix(in srgb, #f79009 10%, transparent);
  color: #9a4e00;
}

body.chatgpt-reference-v1 .composer-document-preview-quality[data-preview-quality="unavailable"] {
  border-color: color-mix(in srgb, var(--cg-muted, #6f6f6f) 24%, transparent);
  background: color-mix(in srgb, var(--cg-muted, #6f6f6f) 10%, transparent);
  color: var(--cg-muted, #6f6f6f);
}

body.chatgpt-reference-v1 .composer-attachment-preview .composer-document-preview-surface {
  height: 226px;
  max-height: 226px;
  margin-top: 7px;
  overflow: auto;
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-loading,
body.chatgpt-reference-v1 .composer-document-preview-surface.is-state {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 9px;
  min-height: 92px;
  padding: 14px 12px;
  color: var(--cg-muted, #6f6f6f);
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-loading p,
body.chatgpt-reference-v1 .composer-document-preview-surface.is-state p {
  margin: 0;
  font-size: var(--ds-text-caption-13, 13px);
  line-height: 20px;
}

body.chatgpt-reference-v1 .composer-document-preview-state-copy {
  display: grid;
  grid-column: 1 / -1;
  gap: 12px;
}

body.chatgpt-reference-v1 .composer-document-preview-state-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

body.chatgpt-reference-v1 .composer-document-preview-state-actions button {
  min-height: 34px;
  padding: 6px 10px;
  border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
  border-radius: var(--ds-radius-sm, 8px);
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--ds-text-caption-13, 13px);
  font-weight: 600;
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-document-preview-state-actions button:hover,
body.chatgpt-reference-v1 .composer-document-preview-state-actions button:focus-visible {
  background: color-mix(in srgb, currentColor 10%, transparent);
}

body.chatgpt-reference-v1 .composer-document-preview-spinner {
  width: 16px;
  height: 16px;
  border: 2px solid color-mix(in srgb, var(--cg-muted, #6f6f6f) 22%, transparent);
  border-top-color: var(--wds-brand-blue, #155eef);
  border-radius: 50%;
  animation: composer-document-preview-spin 700ms linear infinite;
}

@keyframes composer-document-preview-spin {
  to { transform: rotate(360deg); }
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-visual {
  display: grid;
  place-items: center;
  overflow: hidden;
  background: color-mix(in srgb, var(--cg-text, #0d0d0d) 4%, #fff);
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-visual img {
  display: block;
  width: 100%;
  max-height: 226px;
  object-fit: contain;
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-text pre {
  box-sizing: border-box;
  min-height: 92px;
  margin: 0;
  padding: 12px;
  overflow: auto;
  color: var(--cg-text, #0d0d0d);
  font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  tab-size: 2;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

body.chatgpt-reference-v1 .composer-document-preview-note {
  margin: 0;
  padding: 8px 12px;
  border-top: 1px solid color-mix(in srgb, var(--cg-text, #0d0d0d) 8%, transparent);
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-caption-12, 12px);
  line-height: 16px;
}

body.chatgpt-reference-v1 .composer-document-preview-sheet-tabs {
  display: flex;
  gap: 4px;
  padding: 8px 8px 0;
  overflow-x: auto;
}

body.chatgpt-reference-v1 .composer-document-preview-sheet-tabs button {
  flex: 0 0 auto;
  min-height: 30px;
  padding: 4px 9px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--cg-muted, #6f6f6f);
  font: inherit;
  font-size: var(--ds-text-caption-12, 12px);
}

body.chatgpt-reference-v1 .composer-document-preview-sheet-tabs button[aria-selected="true"] {
  border-color: color-mix(in srgb, var(--cg-text, #0d0d0d) 10%, transparent);
  background: var(--cg-surface, #fff);
  color: var(--cg-text, #0d0d0d);
}

body.chatgpt-reference-v1 .composer-document-preview-table-wrap {
  overflow: auto;
}

body.chatgpt-reference-v1 .composer-document-preview-table-wrap table {
  width: max-content;
  min-width: 100%;
  border-spacing: 0;
  border-collapse: separate;
  font-size: var(--ds-text-caption-12, 12px);
  line-height: 18px;
}

body.chatgpt-reference-v1 .composer-document-preview-table-wrap th,
body.chatgpt-reference-v1 .composer-document-preview-table-wrap td {
  max-width: 240px;
  padding: 6px 9px;
  overflow: hidden;
  border-right: 1px solid color-mix(in srgb, var(--cg-text, #0d0d0d) 7%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--cg-text, #0d0d0d) 7%, transparent);
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-document-preview-table-wrap th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: color-mix(in srgb, var(--cg-text, #0d0d0d) 5%, var(--cg-surface, #fff));
  font-weight: 600;
}

body.chatgpt-reference-v1 .composer-document-preview-table-wrap .is-search-target {
  position: relative;
  z-index: 2;
  background: color-mix(in srgb, var(--ds-color-accent) 14%, var(--cg-surface));
  box-shadow: inset 0 0 0 2px var(--ds-color-accent);
}

body.chatgpt-reference-v1 .composer-document-preview-surface mark[data-document-preview-search-target] {
  padding: 1px 2px;
  border-radius: var(--ds-radius-xs);
  background: color-mix(in srgb, var(--ds-color-accent) 18%, var(--cg-surface));
  color: inherit;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-document-pane {
  width: min(1120px, calc(100% - var(--chatgpt-route-gap-lg) * 2));
  max-width: min(1120px, calc(100% - var(--chatgpt-route-gap-lg) * 2));
  max-height: min(72svh, 920px);
}

body.chatgpt-reference-v1 .chatgpt-library-preview-document-pane > .composer-document-preview-surface {
  min-height: min(58svh, 720px);
  margin-top: var(--chatgpt-route-gap-md);
  overflow: auto;
  border-top: 1px solid var(--cg-menu-border);
  background: color-mix(in srgb, var(--cg-text, #0d0d0d) 3%, var(--cg-surface, #fff));
}

body.chatgpt-reference-v1 .chatgpt-library-preview-document-pane > .composer-document-preview-surface.is-loading,
body.chatgpt-reference-v1 .chatgpt-library-preview-document-pane > .composer-document-preview-surface.is-state {
  place-content: center;
  grid-template-columns: auto minmax(0, 340px);
}

body.chatgpt-reference-v1 .chatgpt-library-preview-document-pane > .composer-document-preview-surface.is-visual img {
  width: auto;
  max-width: 100%;
  max-height: min(62svh, 800px);
}

body.chatgpt-reference-v1 .chatgpt-library-preview-document-pane > .composer-document-preview-surface.is-text pre {
  min-height: min(58svh, 720px);
  padding: 24px 28px;
  background: var(--cg-surface, #fff);
  font-size: 14px;
  line-height: 1.65;
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-pdf {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  overflow: hidden !important;
}

/* doc 630 §25.11 -- 사전 결함(153 배치 이전부터) 실측: `.document-viewer-find` 는
   기본값이 `hidden` 속성(=`display:none`)이라 처음 마운트될 때 그리드 아이템 목록에서
   아예 빠진다. `has-external-toolbar` 축(라이브러리 미리보기 다이얼로그)은 두 자식 모두
   `grid-row` 를 명시해서 자동 배치를 안 쓰므로 무사했지만, 드라이브 편집기 오버레이는
   `[data-library-preview-dialog]` 조상이 없어 이 축을 타지 못하고 기본 3행 자동 배치에
   맡겨진다 -- find 가 빠지면 브라우저 그리드 자동 배치가 `.document-viewer-layout` 을
   3행(`minmax(0, 1fr)`, 스테이지 몫)이 아니라 2행(`auto`, find 몫)에 앉히고, 3행은 아무
   것도 없이 비어 그 공간만 통째로 버려진다. layout 은 2행의 "auto" 트랙 크기 =
   자신의 내용 기반 높이로 눌린다 -- 데스크톱은 사이드바 썸네일 실제 높이가 우연히
   뷰포트와 비슷해 육안으로 안 보였을 뿐(3페이지 고정 픽스처에서만 우연히 맞음), 390 처럼
   사이드바가 126px 로 눌리는 모바일 폭에서는 stage 가 그대로 0 이 된다(실측,
   `document-viewer-stage` clientHeight 0). find 의 표시 여부와 무관하게 항상 옳은 행에
   앉도록 기본 축에도 명시 배치를 준다 -- has-external-toolbar 축과 같은 처방이다. */
body.chatgpt-reference-v1 .composer-document-preview-surface.is-pdf > .document-viewer-find {
  grid-row: 2;
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-pdf > .document-viewer-layout {
  grid-row: 3;
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-pdf.has-external-toolbar {
  grid-template-rows: auto minmax(0, 1fr);
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-pdf.has-external-toolbar > .document-viewer-find {
  grid-row: 1;
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-pdf.has-external-toolbar > .document-viewer-layout {
  grid-row: 2;
}

/* doc 171 S2c — the live workbook. Same three-band grid as the PDF viewer (toolbar, notice,
   stage) so both full-viewer surfaces share one chrome rhythm. The stage must get a real
   height: Univer measures its container to lay out the canvas, and an auto-height parent
   collapses it to nothing. */
body.chatgpt-reference-v1 .composer-document-preview-surface.is-workbook {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  overflow: hidden !important;
}

body.chatgpt-reference-v1 .composer-document-preview-workbook {
  min-height: 320px;
  height: 100%;
}

/* Deliberately NOT .composer-document-preview-toolbar: that one sizes every button to a
   34x34 icon box, which turned this text label into wrapped characters spilling across the
   sheet (2026-08-10 live defect). */
body.chatgpt-reference-v1 .composer-document-preview-workbook-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--cg-text) 8%, transparent);
  background: var(--cg-surface);
}

body.chatgpt-reference-v1 .composer-document-preview-workbook-save {
  flex: 0 0 auto;
  min-height: 32px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--ds-radius-pill);
  background: var(--cg-text);
  color: var(--cg-surface);
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-document-preview-workbook-save:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* P3 sheet -> CPQ proposal bridge. A distinct outline treatment (not a solid fill like
   Save) on purpose: this action creates a §13.4 PROPOSAL, not a saved version -- the
   weight of the button should not imply it commits anything by itself. */
body.chatgpt-reference-v1 .composer-document-preview-workbook-cpq-proposal {
  flex: 0 0 auto;
  min-height: 32px;
  padding: 0 14px;
  border: 1px solid color-mix(in srgb, var(--cg-text) 24%, transparent);
  border-radius: var(--ds-radius-pill);
  background: transparent;
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

body.chatgpt-reference-v1 .composer-document-preview-workbook-cpq-proposal:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

body.chatgpt-reference-v1 .workbook-cpq-proposal-preview {
  display: grid;
  gap: 8px;
  margin-top: 8px;
  padding: 8px;
  border: 1px solid color-mix(in srgb, var(--cg-text) 12%, transparent);
  border-radius: var(--ds-radius-md);
}

body.chatgpt-reference-v1 .workbook-cpq-proposal-preview p {
  margin: 0;
}

body.chatgpt-reference-v1 .composer-document-preview-workbook-status {
  min-width: 0;
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12, 12px);
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-document-preview-note.is-risk {
  display: grid;
  gap: 4px;
  border-top: 1px solid color-mix(in srgb, var(--cg-warning-fg) 32%, transparent);
  background: color-mix(in srgb, var(--cg-warning-fg) 8%, transparent);
  color: var(--cg-text);
}
/* 20260904-titlebar-file-collapse (doc 635) — 같은 결함군, 다른 자리(위
   .artifact-titlebar-button[hidden] 주석과 같음): 이 규칙의 `display: grid` 는 저자
   규칙이라 캐스케이드 출처 우선순위로 UA `[hidden] { display: none }` 를 특이도와 무관하게
   이긴다. 실측(390×844, 채팅 표를 시트로 열면 [data-workbook-risk] 는 hidden 이고 자식
   3개(content/recovery/history)도 전부 hidden 이라 안내 텍스트가 없는데도, 리본 탭 줄
   바로 아래에 경고색(color-mix(var(--cg-warning-fg) 8%)) 가로 띠(17px, 전체 폭)가 홈·삽입
   탭 무관하게 상시 렌더된다 — 실위험 배너가 없을 때도 늘 보이는 줄이었다. */
body.chatgpt-reference-v1 .composer-document-preview-note.is-risk[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .composer-document-preview-note.is-risk label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

body.chatgpt-reference-v1 .workbook-version-comparison {
  display: grid;
  gap: 8px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid color-mix(in srgb, var(--cg-text) 12%, transparent);
}

body.chatgpt-reference-v1 .workbook-version-comparison p,
body.chatgpt-reference-v1 .workbook-version-comparison ul {
  margin: 0;
}

body.chatgpt-reference-v1 .workbook-version-comparison-sheet {
  display: grid;
  gap: 6px;
  min-width: 0;
  margin: 0;
  padding: 8px;
  border: 1px solid color-mix(in srgb, var(--cg-text) 12%, transparent);
  border-radius: var(--ds-radius-md);
}

body.chatgpt-reference-v1 .workbook-version-comparison-sheet ul {
  display: grid;
  gap: 4px;
  padding-left: 18px;
}

body.chatgpt-reference-v1 .workbook-version-comparison-sheet li label {
  align-items: flex-start;
}

body.chatgpt-reference-v1 .workbook-version-comparison-sheet li span {
  min-width: 0;
  overflow-wrap: anywhere;
}

body.chatgpt-reference-v1 .workbook-version-comparison-sheet small {
  display: block;
  margin-left: 24px;
  color: var(--cg-muted);
}

body.chatgpt-reference-v1 .workbook-version-structure-warning {
  display: grid;
  gap: 4px;
  padding: 8px;
  border-radius: var(--ds-radius-md);
  background: color-mix(in srgb, var(--cg-warning-fg) 10%, transparent);
}

body.chatgpt-reference-v1 .workbook-version-comparison-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

body.chatgpt-reference-v1 .workbook-ai-delta-preview {
  display: grid;
  gap: 8px;
  margin-top: 8px;
  padding: 8px;
  border: 1px solid color-mix(in srgb, var(--cg-text) 12%, transparent);
  border-radius: var(--ds-radius-md);
}

body.chatgpt-reference-v1 .workbook-ai-delta-preview p {
  margin: 0;
}

body.chatgpt-reference-v1 .workbook-ai-delta-preview ul {
  display: grid;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Same emphasis token .workbook-version-structure-warning already uses for
   "review before you commit" -- an AI-proposed cell change is exactly that. */
body.chatgpt-reference-v1 .workbook-ai-delta-preview-cell {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 8px;
  border-radius: var(--ds-radius-md);
  background: color-mix(in srgb, var(--cg-warning-fg) 10%, transparent);
}

body.chatgpt-reference-v1 .workbook-ai-delta-preview-cell span {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--cg-muted);
}

body.chatgpt-reference-v1 .workbook-ai-delta-preview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

body.chatgpt-reference-v1 [data-workbook-history] button {
  min-height: 28px;
  padding: 3px 9px;
  border: 1px solid color-mix(in srgb, var(--cg-text) 14%, transparent);
  border-radius: var(--ds-radius-pill);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  cursor: pointer;
}

body.chatgpt-reference-v1 [data-workbook-history] button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--cg-menu-border);
  background: color-mix(in srgb, var(--cg-surface, #fff) 94%, transparent);
  backdrop-filter: blur(12px);
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar button {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--cg-text, #0d0d0d);
  font: inherit;
  font-size: 18px;
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar button:hover,
body.chatgpt-reference-v1 .composer-document-preview-toolbar button:focus-visible {
  background: var(--cg-hover-soft);
}

/* doc 630 P0-a — 주석 도구도 «같은» 눌림 상태를 쓴다. 새 팔레트·새 밴드를 만들지 않는
   것이 doc 544 §28 R8(상태 3종 동일)의 요지다: 이 툴바의 밴드는 34px 버튼 / 18px 아이콘이고
   주석 도구는 그 밴드 «안»에 들어간다. 리본(.artifact-ribbon-*)의 28px 밴드를 여기 끌어오면
   한 줄에 두 밴드가 섞이는데, 그것이 §28 이 고친 바로 그 결함이다. */
body.chatgpt-reference-v1 .composer-document-preview-toolbar [data-pdf-sidebar][aria-pressed="true"],
body.chatgpt-reference-v1 .composer-document-preview-toolbar [data-pdf-annotation-tool][aria-pressed="true"] {
  /* 눌림은 «상태»이므로 파랑을 유지한다(doc 345 §6 — 토글 ON 과 같은 근거).
     줄이는 것은 «종»뿐: --wds-color-primary-text 는 WDS 기본 #1252b2 라 포털의 정보
     파랑 --ad-blue(#155eef)와 다른 파랑이 하나 더 생긴다. A 로 흡수한다. */
  background: var(--wds-color-primary-container);
  color: var(--ad-blue);
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar button svg {
  width: 18px;
  height: 18px;
}

/* §28 R8 의 셋째 상태. 저장 전제(개정 잠금)를 못 잡았을 때 도구와 저장 버튼은 열리지
   않는데, 그 «안 열림»이 눈에 보이지 않으면 사용자는 버튼이 고장 났다고 읽는다. */
body.chatgpt-reference-v1 .composer-document-preview-toolbar button:disabled {
  opacity: 0.45;
  cursor: default;
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar [data-pdf-save-status] {
  min-width: 0;
  padding-inline: 4px;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar > span:not(.composer-document-preview-toolbar-spacer) {
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-caption-13, 13px);
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar output {
  min-width: 42px;
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-caption-12, 12px);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar [data-pdf-viewer-status] {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  min-width: 1px !important;
  padding: 0 !important;
  overflow: hidden !important;
  clip-path: inset(50%) !important;
  white-space: nowrap !important;
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar-divider {
  width: 1px;
  height: 22px;
  background: color-mix(in srgb, currentColor 16%, transparent);
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar select,
body.chatgpt-reference-v1 .document-viewer-page-form input,
body.chatgpt-reference-v1 .document-viewer-find input {
  min-height: 32px;
  border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
  border-radius: 8px;
  background: color-mix(in srgb, var(--cg-surface, #fff) 92%, transparent);
  color: inherit;
  font: inherit;
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar select {
  max-width: 112px;
  padding: 0 28px 0 9px;
  font-size: var(--ds-text-caption-12, 12px);
}

body.chatgpt-reference-v1 .document-viewer-scale-control {
  display: inline-flex;
  min-width: 0;
}

body.chatgpt-reference-v1 .document-viewer-page-form {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--cg-muted, #6f6f6f);
}

body.chatgpt-reference-v1 .document-viewer-page-form input {
  width: 48px;
  padding: 0 6px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

body.chatgpt-reference-v1 .document-viewer-find {
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--cg-menu-border);
  background: color-mix(in srgb, var(--cg-surface, #fff) 96%, transparent);
}

body.chatgpt-reference-v1 .document-viewer-find[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .document-viewer-find input {
  flex: 0 1 320px;
  min-width: 140px;
  padding: 0 10px;
}

body.chatgpt-reference-v1 .document-viewer-find button {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: inherit;
}

body.chatgpt-reference-v1 .document-viewer-find button:hover,
body.chatgpt-reference-v1 .document-viewer-find button:focus-visible {
  background: color-mix(in srgb, currentColor 10%, transparent);
}

body.chatgpt-reference-v1 .document-viewer-find button svg {
  width: 17px;
  height: 17px;
}

body.chatgpt-reference-v1 .document-viewer-find output {
  flex: 1 1 auto;
  min-width: 96px;
  color: var(--cg-muted, #6f6f6f);
  font-size: var(--ds-text-caption-12, 12px);
}

body.chatgpt-reference-v1 .composer-document-preview-toolbar-spacer {
  flex: 1 1 auto;
}

/* doc 630 §25.10 -- 주석·페이지 연산 두 저장 버튼을 한 그룹으로 묶는 래퍼(app.js
   `composerAttachmentPdfAnnotationToolsHtml`). 기본(넓은 뷰포트) 값은 이 그룹을
   감싸기 전과 «시각적으로 동일»하게 만드는 것이 전부다 -- 래퍼가 생기면서 두 버튼이
   toolbar 의 직속 flex 항목이 아니라 이 span 하나의 자식이 되므로, toolbar 의
   `gap: 8px` 가 더는 그 내부에 적용되지 않는다. 여기서 같은 gap 을 다시 준다.
   좁은 뷰포트 전용 규칙(max-width: 900px, 아래)이 이 그룹을 스크롤 오른쪽에 고정한다. */
body.chatgpt-reference-v1 .composer-document-preview-toolbar-save-group {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

body.chatgpt-reference-v1 .document-viewer-layout {
  display: grid;
  grid-template-columns: 196px minmax(0, 1fr);
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-sidebar-collapsed .document-viewer-layout {
  grid-template-columns: 0 minmax(0, 1fr);
}

body.chatgpt-reference-v1 .document-viewer-sidebar {
  min-width: 0;
  overflow: auto;
  border-right: 1px solid color-mix(in srgb, currentColor 13%, transparent);
  background: color-mix(in srgb, var(--cg-text, #0d0d0d) 5%, var(--cg-surface, #fff));
  transition: opacity 140ms ease;
}

body.chatgpt-reference-v1 .is-sidebar-collapsed .document-viewer-sidebar {
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}

body.chatgpt-reference-v1 .document-viewer-thumbnails {
  display: grid;
  gap: 12px;
  justify-items: center;
  padding: 14px 10px 24px;
}

body.chatgpt-reference-v1 .document-viewer-thumbnail {
  display: grid;
  justify-items: center;
  gap: 6px;
  width: 164px;
  padding: 8px;
  border: 2px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--ds-text-caption-12, 12px);
}

body.chatgpt-reference-v1 .document-viewer-thumbnail:hover,
body.chatgpt-reference-v1 .document-viewer-thumbnail:focus-visible {
  background: color-mix(in srgb, currentColor 8%, transparent);
}

body.chatgpt-reference-v1 .document-viewer-thumbnail.is-active {
  border-color: var(--wds-brand-blue, #155eef);
  background: color-mix(in srgb, var(--wds-brand-blue, #155eef) 10%, transparent);
}

body.chatgpt-reference-v1 .document-viewer-thumbnail canvas {
  display: block;
  max-width: 144px;
  min-height: 90px;
  background: #fff;
  box-shadow: 0 3px 14px rgba(15, 23, 42, 0.18);
  transition: transform 120ms ease;
}

/* doc 630 P1-b -- 관리 모드에서만 회전·삭제 오버레이가 보인다. 기본은 숨김이라 읽기
   전용/보기 상태에서는 썸네일이 P0 이전과 픽셀 단위로 같다. */
body.chatgpt-reference-v1 .document-viewer-thumbnail-item {
  position: relative;
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-item.is-dragging {
  opacity: 0.5;
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-item.is-deleted .document-viewer-thumbnail canvas {
  opacity: 0.35;
  filter: grayscale(1);
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-rotate,
body.chatgpt-reference-v1 .document-viewer-thumbnail-delete {
  position: absolute;
  top: 6px;
  display: none;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: var(--ds-radius-pill);
  background: color-mix(in srgb, var(--wds-color-inverse-surface) 72%, transparent);
  color: var(--wds-color-inverse-on-surface);
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-rotate {
  right: 38px;
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-delete {
  right: 6px;
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-rotate:hover,
body.chatgpt-reference-v1 .document-viewer-thumbnail-rotate:focus-visible {
  background: var(--wds-brand-blue);
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-delete:hover,
body.chatgpt-reference-v1 .document-viewer-thumbnail-delete:focus-visible {
  background: var(--wds-color-error);
}

body.chatgpt-reference-v1 [data-pdf-sidebar-panel][data-manage-pages="true"] .document-viewer-thumbnail-rotate,
body.chatgpt-reference-v1 [data-pdf-sidebar-panel][data-manage-pages="true"] .document-viewer-thumbnail-delete {
  display: flex;
}

/* R-41 3ⓐ(WCAG 2.5.7) -- press alternative to whole-item drag reorder above. Same overlay
   treatment as rotate/delete (only visible in manage mode) but anchored to the bottom
   corners so it never overlaps them. */
body.chatgpt-reference-v1 .document-viewer-thumbnail-move {
  position: absolute;
  bottom: 6px;
  display: none;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: var(--ds-radius-pill);
  background: color-mix(in srgb, var(--wds-color-inverse-surface) 72%, transparent);
  color: var(--wds-color-inverse-on-surface);
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-move-back {
  left: 6px;
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-move-forward {
  right: 6px;
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-move:hover,
body.chatgpt-reference-v1 .document-viewer-thumbnail-move:focus-visible {
  background: var(--wds-brand-blue);
}

body.chatgpt-reference-v1 .document-viewer-thumbnail-move:disabled {
  opacity: 0.35;
  pointer-events: none;
}

body.chatgpt-reference-v1 [data-pdf-sidebar-panel][data-manage-pages="true"] .document-viewer-thumbnail-move {
  display: flex;
}

/* Move motion = 188ms effect ease (doc669 §M), same tokens as the rest of the surface
   motion system. `is-moved` is a transient class the JS adds to the moved thumbnail item
   and removes shortly after. */
body.chatgpt-reference-v1 .document-viewer-thumbnail-item.is-moved {
  animation: wizon-pdf-thumbnail-move var(--wds-spring-effect-default-duration, 188ms)
    var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) both;
}

@keyframes wizon-pdf-thumbnail-move {
  from {
    opacity: 0.45;
  }
  to {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .document-viewer-thumbnail-item.is-moved {
    animation: none;
  }
}

body.chatgpt-reference-v1 .document-viewer-main {
  position: relative;
  min-width: 0;
  min-height: 0;
}

body.chatgpt-reference-v1 .document-viewer-stage {
  position: absolute;
  inset: 0;
  overflow: auto;
  background: #25282e;
  outline: none;
}

body.chatgpt-reference-v1 .document-viewer-stage:focus-visible {
  box-shadow: inset 0 0 0 2px var(--wds-brand-blue, #155eef);
}

body.chatgpt-reference-v1 .document-viewer-stage > .pdfViewer {
  position: relative;
  min-height: 100%;
  padding: 18px 0 44px;
  --page-border: 1px solid rgba(0, 0, 0, 0.14);
}

body.chatgpt-reference-v1 .document-viewer-stage .pdfViewer .page {
  margin: 0 auto 14px;
  box-shadow: 0 10px 32px rgba(0, 0, 0, 0.28);
}

body.chatgpt-reference-v1 .composer-document-preview-canvas-wrap {
  min-height: 0;
  padding: 20px 12px 36px;
  overflow: auto;
  text-align: center;
}

body.chatgpt-reference-v1 .composer-document-preview-canvas-wrap canvas {
  display: inline-block;
  max-width: none;
  background: #fff;
  box-shadow: 0 10px 32px rgba(15, 23, 42, 0.16);
}

body.chatgpt-reference-v1 .composer-document-preview-surface.is-hwp {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  overflow: hidden !important;
}

body.chatgpt-reference-v1 .composer-document-preview-hwp-wrap {
  display: grid;
  grid-template-columns: auto minmax(0, auto);
  place-content: center;
  align-items: center;
  gap: 9px;
  min-height: 0;
  padding: 16px;
  overflow: auto;
  color: var(--cg-muted, #6f6f6f);
  text-align: center;
}

body.chatgpt-reference-v1 .composer-document-preview-hwp-wrap > p {
  margin: 0;
  font-size: var(--ds-text-caption-13, 13px);
  line-height: 20px;
}

body.chatgpt-reference-v1 .composer-document-preview-hwp-wrap > img {
  display: block;
  grid-column: 1 / -1;
  max-width: none;
  height: auto;
  margin: auto;
  background: #fff;
  box-shadow: 0 10px 32px rgba(15, 23, 42, 0.16);
}

body.chatgpt-reference-v1 .composer-attachment-preview .composer-document-preview-surface.is-hwp {
  display: block;
}

body.chatgpt-reference-v1 .composer-attachment-preview .composer-document-preview-hwp-wrap {
  width: 100%;
  height: 226px;
  padding: 0;
  overflow: hidden;
}

body.chatgpt-reference-v1 .composer-attachment-preview .composer-document-preview-hwp-wrap > img {
  width: 100% !important;
  max-width: 100%;
  max-height: 226px;
  object-fit: contain;
  box-shadow: none;
}

/* Composer attachment full preview reuses the common media viewer's shell/head/stage/icon
   primitives and dark canvas. Its content state stays separate because the image viewer's
   carousel/pan state machine cannot represent PDF pages, sheets, or selectable text. */
body.chatgpt-reference-v1 .composer-attachment-media-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1250;
  display: grid;
  background: rgb(8 9 11 / 88%);
  color: #fff;
}

body.chatgpt-reference-v1 .composer-attachment-media-dialog {
  position: relative;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  width: 100vw;
  height: 100dvh;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: transparent;
  outline: none;
}

body.chatgpt-reference-v1 .composer-attachment-media-head {
  position: relative;
  z-index: 3;
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(520px, auto) auto;
  align-items: center;
  gap: 18px;
  min-height: 68px;
  padding: 8px 18px 8px 24px;
  border-bottom: 1px solid color-mix(in srgb, var(--wds-color-inverse-on-surface) 10%, transparent);
  background: color-mix(in srgb, var(--wds-neutral-tonal-t6) 96%, transparent);
  box-shadow: var(--cg-dialog-shadow);
}

body.chatgpt-reference-v1 .composer-attachment-media-file,
body.chatgpt-reference-v1 .composer-attachment-media-actions {
  display: flex;
  align-items: center;
  min-width: 0;
  gap: 10px;
}

body.chatgpt-reference-v1 .composer-attachment-media-actions.image-preview-head-actions {
  grid-column: 3;
}

body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools {
  display: flex;
  min-width: 0;
  justify-content: center;
}

body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools:empty {
  display: none;
}

body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar {
  position: static;
  min-width: 0;
  min-height: 44px;
  padding: 0;
  overflow: visible;
  border: 0;
  background: transparent;
  color: var(--wds-color-inverse-on-surface);
  backdrop-filter: none;
}

body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar button {
  color: var(--wds-color-inverse-on-surface);
}

body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar button:hover,
body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar button:focus-visible {
  background: color-mix(in srgb, var(--wds-color-inverse-on-surface) 10%, transparent);
}

body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar [data-pdf-sidebar][aria-pressed="true"],
body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar [data-pdf-annotation-tool][aria-pressed="true"] {
  background: color-mix(in srgb, var(--wds-color-inverse-primary) 22%, transparent);
  color: var(--wds-color-inverse-primary);
}

body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar > span:not(.composer-document-preview-toolbar-spacer),
body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar output {
  color: color-mix(in srgb, var(--wds-color-inverse-on-surface) 68%, transparent);
}

body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar select,
body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .document-viewer-page-form input {
  border-color: color-mix(in srgb, var(--wds-color-inverse-on-surface) 18%, transparent);
  background: color-mix(in srgb, var(--wds-color-inverse-on-surface) 8%, transparent);
  color: var(--wds-color-inverse-on-surface);
}

body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar-spacer {
  display: none;
}

body.chatgpt-reference-v1 .composer-attachment-media-dialog .composer-attachment-preview-heading strong {
  color: #fff;
}

body.chatgpt-reference-v1 .composer-attachment-media-dialog .composer-attachment-preview-heading > span {
  color: rgba(255, 255, 255, 0.62);
}

body.chatgpt-reference-v1 .composer-attachment-media-icon-button {
  width: 44px;
  height: 44px;
  color: #fff;
}

body.chatgpt-reference-v1 .composer-attachment-media-icon-button svg {
  width: 20px;
  height: 20px;
}

body.chatgpt-reference-v1 .composer-attachment-media-icon-button:disabled {
  cursor: wait;
  opacity: 0.38;
}

body.chatgpt-reference-v1 .composer-attachment-media-more-menu {
  position: absolute;
  z-index: 8;
  top: calc(100% + 8px);
  right: 58px;
  display: grid;
  width: min(248px, calc(100vw - 24px));
  padding: 6px;
  border: 1px solid color-mix(in srgb, var(--wds-color-inverse-on-surface) 14%, transparent);
  border-radius: var(--ds-radius-sm);
  background: var(--wds-color-inverse-surface);
  color: var(--wds-color-inverse-on-surface);
  box-shadow: var(--cg-dialog-shadow);
  backdrop-filter: blur(18px);
  /* focus-reset-shell-ring-10: 이 패널은 항상 배경과 반대 표면이다 — 전역 잉크
     링 토큰을 같은 반전 쌍(inverse-on-surface)으로 뒤집는다. */
  --wizon-ai-focus-visible-ring: var(--wds-color-inverse-on-surface);
}

body.chatgpt-reference-v1 .composer-attachment-media-more-menu[hidden],
body.chatgpt-reference-v1 .composer-attachment-media-utility-panel[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .composer-attachment-media-more-menu > button {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  min-height: 42px;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--ds-radius-xs);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
}

body.chatgpt-reference-v1 .composer-attachment-media-more-menu > button:hover {
  background: color-mix(in srgb, var(--wds-color-inverse-on-surface) 10%, transparent);
}
/* focus-reset-shell-ring-10: 전역 P0-3 폴백보다 특정성을 올린다. */
body.chatgpt-reference-v1.chatgpt-reference-v1 .composer-attachment-media-more-menu > button:focus-visible {
  background: color-mix(in srgb, var(--wds-color-inverse-on-surface) 10%, transparent);
  outline: none;
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

body.chatgpt-reference-v1 .composer-attachment-media-more-menu > button.is-danger {
  color: var(--wds-color-error-text);
}

body.chatgpt-reference-v1 .composer-attachment-media-more-menu > button.is-danger:hover,
body.chatgpt-reference-v1 .composer-attachment-media-more-menu > button.is-danger:focus-visible {
  background: color-mix(in srgb, var(--wds-color-error) 14%, transparent);
}

body.chatgpt-reference-v1 .composer-attachment-media-more-menu > button svg {
  width: 18px;
  height: 18px;
}

body.chatgpt-reference-v1 .composer-attachment-media-more-menu > button kbd {
  color: color-mix(in srgb, var(--wds-color-inverse-on-surface) 58%, transparent);
  font: inherit;
  font-size: var(--ds-text-caption-12, 12px);
}

body.chatgpt-reference-v1 .composer-attachment-media-more-menu .is-mobile-only {
  display: none;
}

body.chatgpt-reference-v1 .composer-attachment-media-utility-panel {
  position: absolute;
  z-index: 7;
  top: 76px;
  right: 24px;
  width: min(360px, calc(100vw - 48px));
  padding: 14px;
  border: 1px solid color-mix(in srgb, var(--wds-color-inverse-on-surface) 14%, transparent);
  border-radius: var(--ds-radius-md);
  background: var(--wds-color-inverse-surface);
  color: var(--wds-color-inverse-on-surface);
  box-shadow: var(--cg-dialog-shadow);
  backdrop-filter: blur(18px);
  /* focus-reset-shell-ring-10: 이 패널은 항상 배경과 반대 표면이다 — 전역 잉크
     링 토큰을 같은 반전 쌍(inverse-on-surface)으로 뒤집는다. */
  --wizon-ai-focus-visible-ring: var(--wds-color-inverse-on-surface);
}

body.chatgpt-reference-v1 .composer-attachment-media-utility-panel > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 36px;
  padding-bottom: 10px;
  border-bottom: 1px solid color-mix(in srgb, var(--wds-color-inverse-on-surface) 10%, transparent);
}

body.chatgpt-reference-v1 .composer-attachment-media-utility-panel > header > div {
  display: flex;
  align-items: center;
  gap: 9px;
}

body.chatgpt-reference-v1 .composer-attachment-media-utility-panel > header svg {
  width: 18px;
  height: 18px;
}

body.chatgpt-reference-v1 .composer-attachment-media-utility-panel > header button {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: var(--ds-radius-xs);
  background: transparent;
  color: var(--wds-color-inverse-on-surface);
}

body.chatgpt-reference-v1 .composer-attachment-media-utility-panel > header button:hover {
  background: color-mix(in srgb, var(--wds-color-inverse-on-surface) 10%, transparent);
}
/* focus-reset-shell-ring-10: 전역 P0-3 폴백보다 특정성을 올린다. */
body.chatgpt-reference-v1.chatgpt-reference-v1 .composer-attachment-media-utility-panel > header button:focus-visible {
  background: color-mix(in srgb, var(--wds-color-inverse-on-surface) 10%, transparent);
  outline: none;
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

body.chatgpt-reference-v1 :is(.composer-attachment-shortcut-list, .composer-attachment-file-info) {
  display: grid;
  gap: 0;
  margin: 8px 0 0;
}

body.chatgpt-reference-v1 :is(.composer-attachment-shortcut-list, .composer-attachment-file-info) > div {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
  min-height: 38px;
}

body.chatgpt-reference-v1 :is(.composer-attachment-shortcut-list, .composer-attachment-file-info) :is(dt, dd) {
  min-width: 0;
  margin: 0;
}

body.chatgpt-reference-v1 :is(.composer-attachment-shortcut-list, .composer-attachment-file-info) dt {
  color: color-mix(in srgb, var(--wds-color-inverse-on-surface) 68%, transparent);
  font-size: var(--ds-text-caption-13, 13px);
}

body.chatgpt-reference-v1 .composer-attachment-shortcut-list kbd {
  display: inline-flex;
  min-width: 58px;
  justify-content: center;
  padding: 4px 8px;
  border: 1px solid color-mix(in srgb, var(--wds-color-inverse-on-surface) 18%, transparent);
  border-radius: var(--ds-radius-xs);
  background: color-mix(in srgb, var(--wds-color-inverse-on-surface) 8%, transparent);
  color: var(--wds-color-inverse-on-surface);
  font: inherit;
  font-size: var(--ds-text-caption-12, 12px);
}

body.chatgpt-reference-v1 .composer-attachment-file-info > div {
  grid-template-columns: 92px minmax(0, 1fr);
  align-items: start;
  padding-block: 8px;
}

body.chatgpt-reference-v1 .composer-attachment-file-info dd {
  overflow-wrap: anywhere;
  font-size: var(--ds-text-caption-13, 13px);
  line-height: 1.45;
}

.composer-document-print-root {
  position: fixed;
  top: 0;
  left: -100000px;
  width: max-content;
  background: var(--ds-color-surface-solid);
  pointer-events: none;
}

.composer-document-print-page {
  width: var(--composer-print-page-width);
  height: var(--composer-print-page-height);
  overflow: hidden;
  background: var(--ds-color-surface-solid);
  break-after: page;
  page-break-after: always;
}

.composer-document-print-page:last-child {
  break-after: auto;
  page-break-after: auto;
}

.composer-document-print-page canvas {
  display: block;
  width: 100%;
  height: 100%;
}

@media print {
  @page {
    margin: 0;
  }

  body > *:not(.composer-document-print-root) {
    display: none !important;
  }

  .composer-document-print-root {
    position: static;
    display: block !important;
    width: auto;
    background: var(--ds-color-surface-solid);
  }

  .composer-document-print-page {
    margin: 0 auto;
    box-shadow: none;
  }
}

body.chatgpt-reference-v1 .composer-attachment-media-stage {
  position: relative;
  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 0;
  padding: 16px 24px 24px;
  overflow: hidden;
  background: transparent;
  cursor: default;
  touch-action: pan-x pan-y;
  user-select: auto;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target {
  position: absolute;
  z-index: 8;
  top: 28px;
  left: 50%;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  max-width: min(520px, calc(100% - 64px));
  min-height: 34px;
  padding: 7px 12px;
  border: 1px solid color-mix(in srgb, var(--ds-color-accent) 22%, transparent);
  border-radius: var(--ds-radius-pill);
  background: color-mix(in srgb, var(--cg-surface) 94%, transparent);
  color: var(--cg-text);
  box-shadow: var(--ds-shadow-md);
  font-size: var(--ds-text-caption-12, 12px);
  line-height: 18px;
  transform: translateX(-50%);
  backdrop-filter: blur(12px);
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target svg {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target > button {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: var(--ds-radius-pill);
  background: transparent;
  color: inherit;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target > button:hover:not(:disabled) {
  background: var(--cg-hover-soft, var(--ds-color-accent-soft));
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target > button:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 1px;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target > .chatgpt-library-preview-target-ask {
  width: auto;
  padding-inline: 9px;
  background: var(--cg-text);
  color: var(--cg-surface);
  font: inherit;
  font-weight: 600;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target > .chatgpt-library-preview-version-toggle {
  width: auto;
  padding-inline: 9px;
  border: 1px solid color-mix(in srgb, var(--ds-color-accent) 30%, transparent);
  color: var(--ds-color-accent);
  font: inherit;
  font-weight: 600;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target > .chatgpt-library-preview-target-ask:hover:not(:disabled) {
  background: color-mix(in srgb, var(--cg-text) 84%, transparent);
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-copy {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-copy > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-copy small {
  flex: 0 0 auto;
  color: var(--cg-muted);
  font: inherit;
  font-variant-numeric: tabular-nums;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  display: grid;
  gap: 4px;
  width: min(460px, calc(100vw - 48px));
  max-height: min(420px, 56vh);
  padding: 6px;
  overflow-y: auto;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--wizon-ai-radius-popover, 16px);
  background: var(--cg-menu-bg);
  box-shadow: var(--cg-menu-shadow, var(--ds-shadow-lg));
  color: var(--cg-text, #0d0d0d);
  transform: translateX(-50%);
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list > button {
  display: grid;
  gap: 5px;
  width: 100%;
  min-height: 54px;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--wizon-ai-radius-menu-row, 10px);
  background: transparent;
  color: inherit;
  text-align: left;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list > button:hover,
body.chatgpt-reference-v1 .chatgpt-library-preview-target-list > button[aria-selected="true"] {
  background: var(--cg-hover-soft, var(--ds-color-accent-soft));
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list > button:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: -2px;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list-head strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list-head small {
  flex: 0 0 auto;
  color: var(--cg-muted);
  font: inherit;
  font-variant-numeric: tabular-nums;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list-snippet {
  display: -webkit-box;
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12, 12px);
  font-weight: 400;
  line-height: 1.45;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

body.chatgpt-reference-v1 .chatgpt-library-preview-target-list-snippet mark {
  border-radius: var(--chatgpt-route-gap-2xs);
  background: color-mix(in srgb, var(--wds-brand-blue) 12%, transparent);
  color: inherit;
}

@media (pointer: coarse) {
  body.chatgpt-reference-v1 .chatgpt-library-preview-target {
    min-height: 44px;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-target > button {
    width: 44px;
    height: 44px;
    margin-block: -7px;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-target > .chatgpt-library-preview-target-ask {
    width: auto;
    padding-inline: 12px;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-target > .chatgpt-library-preview-version-toggle {
    width: auto;
    padding-inline: 10px;
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-target-list {
    width: min(460px, calc(100vw - 24px));
  }

  body.chatgpt-reference-v1 .chatgpt-library-preview-target-list > button {
    min-height: 58px;
  }
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface {
  width: min(1600px, 100%);
  height: 100%;
  max-height: 100%;
  overflow: auto;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--ds-radius-sm);
  background: #14161a;
  color: #f4f4f4;
  box-shadow: 0 20px 64px rgba(0, 0, 0, 0.42);
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-loading,
body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-state {
  place-content: center;
  grid-template-columns: auto minmax(0, 360px);
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-visual {
  border: 0;
  background: transparent;
  box-shadow: none;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-visual img {
  width: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-text {
  width: min(920px, 100%);
  background: #f8f8f6;
  color: #171717;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-text pre {
  min-height: 100%;
  padding: 28px 32px;
  color: #171717;
  font-size: 14px;
  line-height: 1.65;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-spreadsheet {
  background: #f8f8f6;
  color: #171717;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage .composer-document-preview-toolbar {
  color: #f4f4f4;
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(20, 22, 26, 0.94);
}

body.chatgpt-reference-v1 .composer-attachment-media-stage .composer-document-preview-toolbar button {
  color: #fff;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage .composer-document-preview-toolbar > span:not(.composer-document-preview-toolbar-spacer) {
  color: rgba(255, 255, 255, 0.68);
}

body.chatgpt-reference-v1 .composer-attachment-media-stage .composer-document-preview-toolbar output {
  color: rgba(255, 255, 255, 0.68);
}

body.chatgpt-reference-v1 .composer-attachment-media-stage .document-viewer-find {
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(20, 22, 26, 0.96);
}

body.chatgpt-reference-v1 .composer-attachment-media-stage .document-viewer-find input,
body.chatgpt-reference-v1 .composer-attachment-media-stage .composer-document-preview-toolbar select,
body.chatgpt-reference-v1 .composer-attachment-media-stage .document-viewer-page-form input {
  border-color: rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage .document-viewer-sidebar {
  border-color: rgba(255, 255, 255, 0.12);
  background: #1b1e23;
}

@media (max-width: 1200px) {
  body.chatgpt-reference-v1 .composer-attachment-media-head {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "file actions"
      "tools tools";
    gap: 4px 12px;
    padding-block: 8px;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-file {
    grid-area: file;
    overflow: hidden;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-actions.image-preview-head-actions {
    grid-area: actions;
    grid-column: auto;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools {
    grid-area: tools;
    justify-content: flex-start;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    /* 375px 뷰어 툴바가 실측 scrollWidth 622 / clientWidth 355 로 넘치는데 스크롤바를
       숨겨 놔서(scrollbar-width: none) "더 있음" 단서가 전혀 없었다. .artifact-ribbon-tabs
       와 같은 알파 전용 마스크 페이드로 좌우 모두 「더 있음」을 알린다(문서 147). */
    mask-image: linear-gradient(90deg, transparent 0, currentColor 12px, currentColor calc(100% - 12px), transparent 100%);
  }

  body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools::-webkit-scrollbar {
    display: none;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-viewer-tools .composer-document-preview-toolbar {
    flex: 0 0 max-content;
    width: max-content;
  }
}

@media (max-width: 900px) {
  /* 20260927-pdf-thumb-rotate-fit (대장 ③6, 669 K-2) -- 이 행이 고정 126px 이었는데
     썸네일 칸 실제 높이(캔버스 + gap 6px + 라벨 + 상하 padding 10px, A4 비율 기준
     ≈149px)보다 작아 `.document-viewer-sidebar` 의 `overflow-y: hidden` 이 라벨과
     이동 버튼 줄(하단 절대배치)까지 통째로 잘랐다(실측 android-pdf-manage.png --
     페이지 번호 라벨·앞/뒤 이동 화살표가 전혀 안 보인다). `max-content` 로 바꾸면
     실제 썸네일 칸 높이(회전 fit 스케일 적용 후에도 칸 자체 크기는 그대로)만큼
     행이 늘어나 스크롤 없이 전부 보인다 -- 페이지 종횡비가 달라도 항상 맞는다. */
  body.chatgpt-reference-v1 .document-viewer-layout {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: max-content minmax(0, 1fr);
  }

  body.chatgpt-reference-v1 .document-viewer-sidebar {
    overflow-x: auto;
    overflow-y: hidden;
    border-right: 0;
    border-bottom: 1px solid color-mix(in srgb, currentColor 13%, transparent);
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }

  body.chatgpt-reference-v1 .document-viewer-sidebar::-webkit-scrollbar,
  body.chatgpt-reference-v1 .document-viewer-stage::-webkit-scrollbar {
    display: none;
    width: 0;
    height: 0;
  }

  body.chatgpt-reference-v1 .document-viewer-stage {
    scrollbar-width: none;
    -ms-overflow-style: none;
  }

  body.chatgpt-reference-v1 .document-viewer-thumbnails {
    display: flex;
    width: max-content;
    height: 100%;
    gap: 8px;
    padding: 8px 12px;
  }

  body.chatgpt-reference-v1 .document-viewer-thumbnail {
    flex: 0 0 98px;
    width: 98px;
    padding: 5px;
    scroll-snap-align: center;
  }

  body.chatgpt-reference-v1 .document-viewer-thumbnail canvas {
    max-width: 84px;
    min-height: 64px;
  }

  /* doc 630 §25.10 -- 실 브라우저 QA 실측: 이 툴바의 실제 폭이 912px 인데 부모
     `.composer-document-preview-surface.is-pdf` 가 `overflow: hidden !important` 라
     x >= 498 의 버튼 9개(회전·주석 도구·두 저장 버튼·페이지 관리·검색)에 아예 닿지
     못했다. `.composer-attachment-media-viewer-tools .composer-document-preview-toolbar`
     (위 max-width: 1200px 규칙, 문서 147)가 이미 같은 문제를 이 방식으로 풀었다 --
     숨기는 대신 가로 스크롤로 모든 버튼에 닿게 하고, 알파 마스크로 "더 있음"을
     알린다. 오른쪽은 저장 그룹이 항상 고정돼 있으므로 페이드를 넣지 않는다(고정된
     것을 흐리게 보이면 "안 눌린다"는 오독을 만든다).
  */
  body.chatgpt-reference-v1 .composer-document-preview-toolbar {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    mask-image: linear-gradient(90deg, transparent 0, currentColor 12px, currentColor 100%);
  }

  body.chatgpt-reference-v1 .composer-document-preview-toolbar::-webkit-scrollbar {
    display: none;
  }

  /* 터치 타깃 44px(WCAG 2.5.8) -- 34px 아이콘 박스는 이 폭에서 옆 버튼을 잘못 누르기
     쉽다. `min-width`/`min-height` 는 고정된 `width: 34px`/`height: 34px` 보다 바닥값이
     세므로(computed size = max(min, 지정값)) 별도로 `width`/`height` 를 지울 필요가
     없다. */
  body.chatgpt-reference-v1 .composer-document-preview-toolbar button {
    min-width: 44px;
    min-height: 44px;
  }

  body.chatgpt-reference-v1 .composer-document-preview-toolbar [data-pdf-sidebar] {
    display: none;
  }

  body.chatgpt-reference-v1 .composer-document-preview-toolbar-spacer {
    display: none;
  }

  /* `order` 로 시각적 순서만 바꾼다(DOM·querySelector 는 그대로) -- 저장 그룹을 실제
     마지막 항목으로 만들어야 `position: sticky; right: 0` 뒤에 아무것도 안 남는다.
     검색 버튼이 저장 그룹 «뒤»에 그대로 있으면, 끝까지 스크롤했을 때 두 항목이 같은
     오른쪽 자리를 다투다 검색 버튼이 고정된 저장 그룹 밑에 가려진다. */
  body.chatgpt-reference-v1 .composer-document-preview-toolbar [data-pdf-find-toggle] {
    order: 1;
  }

  /* 저장은 이 툴바에서 가장 되돌릴 수 없는 조작이다 -- 스크롤 위치와 무관하게 항상
     보이게 컨테이너 오른쪽에 고정한다(발주서 "저장 버튼은 항상 보이게(우선 고정)").
     ★실측(실 브라우저 캡처, 390 폭) -- toolbar 자신의 반투명 배경(94% + blur) 을
     그대로 쓰면 스크롤되어 밑에 깔린 페이지 수 표시(`/3`)가 얇게 비쳐 보인다 --
     고정된 것이 흐리게 보이면 "안 눌린다"는 오독을 만든다. 이 그룹만 불투명한
     실제 표면색을 쓰고, 세로로도 꽉 채워 위·아래 어느 각도로도 안 비치게 한다. */
  body.chatgpt-reference-v1 .composer-document-preview-toolbar-save-group {
    order: 2;
    position: sticky;
    right: 0;
    align-self: stretch;
    padding-left: 8px;
    background: var(--cg-surface, #fff);
    box-shadow: -10px 0 10px -6px color-mix(in srgb, currentColor 22%, transparent);
  }

  /* 20260926-pdf-toolbar-page-count-overlap (669 K-2, qa pdf-mobile-layout 판정 ③6) --
     위 주석(38773행)이 이미 «스크롤되어 밑에 깔린 페이지 수 표시(/3)가 비쳐 보인다»를
     실측해 놓고도 불투명 배경만 고쳤다 -- 그 결과 쪽수(«1 / 3»)가 안 비치는 대신
     저장 그룹 밑에 완전히 «가려진다»(실측: scrollLeft=0 에서 겹침 11~23px, 라벨이
     두 줄로 꺾이면 최대 51px). 저장 그룹과 같은 관례(position: sticky, 불투명 배경)를
     반대편(왼쪽)에도 적용한다 -- `order: -1` 로 툴바의 첫 항목이 되게 해 자연 위치를
     왼쪽 여백(12px)까지 당기면(자연 x 12~127) 저장 그룹의 고정 시작점(390 폭 기준
     x=168, 222px 폭)에 절대 닿지 않는다(폭 ≤430px 까지 여유 ≥40px). `sticky`는
     스크롤해도 이 조합이 다시 겹치지 않도록 왼쪽 경계에 고정해 둔다. */
  body.chatgpt-reference-v1 .document-viewer-page-form {
    order: -1;
    position: sticky;
    left: 0;
    z-index: 1;
    padding-right: 8px;
    background: var(--cg-surface);
  }

  body.chatgpt-reference-v1 .composer-attachment-media-stage {
    padding-inline: 10px;
  }
}

/* 20260926-pdf-thumbnail-touch-target (669 K-2, qa pdf-mobile-layout 판정 ③): 회전·삭제·이동
   오버레이 버튼은 26px 고정이라 터치 하한 44px(WCAG 2.5.8 내부 정책선)에 못 미친다 — 같은
   화면 위 툴바는 이미 `@media (max-width: 900px) .composer-document-preview-toolbar
   button { min-width/height: 44px }`(위 38728행)로 44 정책선을 쓰는데 썸네일 쪽만 26px로
   남아 같은 화면 안에서 불일치했다. 시각 크기는 그대로 두고(밀도 붕괴 방지) 기존 히트슬롭
   관례(styles.css 아트디렉션 P1-5, 투명 ::after · min-width/min-height 44px ·
   translate(-50%,-50%))를 그대로 재사용한다 — 새 기법을 만들지 않는다.
   회전·삭제는 원래 둘 다 우상단(right:38px/right:6px, 중심 간 거리 32px)이라 44px 히트를
   그대로 겹치면 12px 겹친다. 이동 버튼(move-back/move-forward)은 이미 카드 좌우 반대편
   모서리에 있어(중심 간 거리 = 카드폭-38px ≥44px) 겹치지 않으므로, 회전을 좌상단으로 옮겨
   네 모서리 배치로 맞춘다(새 클래스 없이 기존 rotate 선택자의 left/right만 덮어씀).
   데스크톱(마우스, 이 블록 밖)은 원래 위치·26px 그대로다. */
@media (max-width: 900px), (pointer: coarse) {
  body.chatgpt-reference-v1 .document-viewer-thumbnail-rotate::after,
  body.chatgpt-reference-v1 .document-viewer-thumbnail-delete::after,
  body.chatgpt-reference-v1 .document-viewer-thumbnail-move::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }

  body.chatgpt-reference-v1 .document-viewer-thumbnail-rotate {
    right: auto;
    left: 6px;
  }
}

body.chatgpt-reference-v1 .composer-attachment-media-ask-button {
  min-height: 44px;
  padding: 10px 16px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--ds-radius-xl, 22px);
  background: rgba(24, 27, 32, 0.9);
  color: #fff;
  font: inherit;
  font-weight: 600;
  backdrop-filter: blur(12px);
}

body.chatgpt-reference-v1 .composer-attachment-media-ask-button.is-header-action {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 8px 14px;
  border-color: color-mix(in srgb, var(--wds-color-inverse-on-surface) 18%, transparent);
  border-radius: var(--ds-radius-sm);
  background: color-mix(in srgb, var(--wds-color-inverse-on-surface) 8%, transparent);
  white-space: nowrap;
}

body.chatgpt-reference-v1 .composer-attachment-media-ask-label-short {
  display: none;
}

body.chatgpt-reference-v1 .composer-attachment-media-ask-drawer {
  position: absolute;
  z-index: 5;
  top: 76px;
  right: 24px;
  bottom: auto;
  width: min(520px, calc(100vw - 48px));
  padding: 10px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  /* fix(20260925-build-approval-sheet-dialog, inventory.md S16 행): raw 20px → 토큰(M-4).
     18 이 정본(설정 v2)이라 20 에서 18 로 값이 바뀐다 — 이 팝오버는 폭 520 밖에서만 쓰이고
     반경 2px 차이는 시각 회귀가 아니다(같은 판단, S14 위 fix 주석 참고). */
  border-radius: var(--wizon-ai-radius-sheet, 18px);
  background: rgba(24, 27, 32, 0.96);
  box-shadow: 0 20px 56px rgba(0, 0, 0, 0.46);
  backdrop-filter: blur(18px);
}

body.chatgpt-reference-v1 .composer-attachment-media-ask-drawer[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .composer-attachment-media-ask-drawer .chatgpt-library-preview-composer.chatgpt-composer-shell {
  width: 100%;
  margin: 0;
}

body.chatgpt-reference-v1 .composer-attachment-media-ask-drawer .composer-prompt-body {
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "input actions";
}

/* Composer library document-version lineage. */
body.chatgpt-reference-v1 .chatgpt-library-version-badge {
  display: inline-flex;
  align-items: center;
  margin-inline-start: 8px;
  padding: 1px 6px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-pill);
  color: var(--cg-muted);
  font-size: var(--ds-text-micro-11);
  font-weight: 600;
  line-height: 18px;
  vertical-align: middle;
}

body.chatgpt-reference-v1 .chatgpt-library-version-backdrop {
  z-index: 24;
}

body.chatgpt-reference-v1 .chatgpt-library-version-dialog {
  width: min(640px, calc(100vw - 32px));
  max-height: min(720px, calc(100dvh - 48px));
  overflow: hidden;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--wizon-ai-radius-sheet);
  background: var(--cg-surface);
  box-shadow: var(--cg-dialog-shadow);
}

body.chatgpt-reference-v1 .chatgpt-library-version-header,
body.chatgpt-reference-v1 .chatgpt-library-version-toolbar,
body.chatgpt-reference-v1 .chatgpt-library-version-row {
  display: flex;
  align-items: center;
  gap: 16px;
}

body.chatgpt-reference-v1 .chatgpt-library-version-header {
  justify-content: space-between;
  padding: 20px 22px 14px;
}

body.chatgpt-reference-v1 .chatgpt-library-version-header h2,
body.chatgpt-reference-v1 .chatgpt-library-version-header p,
body.chatgpt-reference-v1 .chatgpt-library-version-toolbar p {
  margin: 0;
}

body.chatgpt-reference-v1 .chatgpt-library-version-header p,
body.chatgpt-reference-v1 .chatgpt-library-version-toolbar p,
body.chatgpt-reference-v1 .chatgpt-library-version-copy span {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
}

body.chatgpt-reference-v1 .chatgpt-library-version-toolbar {
  justify-content: space-between;
  padding: 0 22px 16px;
  border-bottom: 1px solid var(--cg-menu-border);
}

body.chatgpt-reference-v1 .chatgpt-library-version-list {
  min-height: 120px;
  overflow-y: auto;
  padding: 8px 22px 20px;
}

body.chatgpt-reference-v1 .chatgpt-library-version-row {
  justify-content: space-between;
  padding: 14px 0;
  border-bottom: 1px solid var(--cg-menu-border);
}

body.chatgpt-reference-v1 .chatgpt-library-version-row.is-current strong {
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-library-version-copy {
  display: grid;
  gap: 3px;
  min-width: 0;
}

body.chatgpt-reference-v1 .chatgpt-library-version-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
}

body.chatgpt-reference-v1 .chatgpt-library-version-actions button,
body.chatgpt-reference-v1 .chatgpt-library-version-actions a {
  border: 0;
  border-radius: var(--wizon-ai-radius-control);
  padding: 7px 9px;
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  text-decoration: none;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-library-version-actions button:hover,
body.chatgpt-reference-v1 .chatgpt-library-version-actions button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-library-version-actions a:hover,
body.chatgpt-reference-v1 .chatgpt-library-version-actions a:focus-visible {
  background: var(--cg-hover-soft);
}

body.chatgpt-reference-v1 .chatgpt-library-version-actions .is-primary {
  background: var(--cg-text);
  color: var(--cg-surface);
}

body.chatgpt-reference-v1 .chatgpt-library-version-actions button:disabled,
body.chatgpt-reference-v1 .chatgpt-library-version-actions a[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
  cursor: default;
}

body.chatgpt-reference-v1 .chatgpt-library-version-empty {
  padding: 32px 0;
  color: var(--cg-muted);
  text-align: center;
}

@media (max-width: 520px) {
  /* fix(20260925-build-approval-sheet-dialog, inventory.md S8 행): dvh 단독은 소프트
     키보드에 안 반응한다(M-5) — 이미 살아 있는 --wizon-vvh(폴백 100dvh, 35591행 같은
     선택형 시트가 쓰는 것과 같은 공식 min(70vh, vvh-96px))로 바꾼다. 바닥 안전영역도
     0건이었다(M-6) — 다른 시트의 calc(기존 + env(safe-area-inset-bottom,0px)) 관례를
     그대로 따른다. */
  body.chatgpt-reference-v1 .chatgpt-library-version-dialog {
    width: 100%;
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px));
    margin-top: auto;
    border-radius: var(--wizon-ai-radius-sheet) var(--wizon-ai-radius-sheet) 0 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    /* R-41 B1(inventory S8 — 이전엔 모션 0). 폰에서만 시트가 되므로 모션도 여기서만. */
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }

  body.chatgpt-reference-v1 .chatgpt-library-version-dialog[data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-version-backdrop {
    animation: wizon-scrim-in var(--wds-spring-effect-default-duration, 188ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }

  body.chatgpt-reference-v1 .chatgpt-library-version-backdrop[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }

  body.chatgpt-reference-v1 .chatgpt-library-version-toolbar,
  body.chatgpt-reference-v1 .chatgpt-library-version-row {
    align-items: flex-start;
    flex-direction: column;
  }

  body.chatgpt-reference-v1 .chatgpt-library-version-actions {
    justify-content: flex-start;
  }
}

/* R-41 B1b(S8·M-15) — 위 §S8 블록엔 reduce 대응이 아예 없었다(하한이 즉시로 눌렀다). 로그인 창과
   같은 문법으로 124ms 흐려짐, 이동 0. */
@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .chatgpt-library-version-dialog {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }
  body.chatgpt-reference-v1 .chatgpt-library-version-dialog[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
  body.chatgpt-reference-v1 .chatgpt-library-version-backdrop {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }
  body.chatgpt-reference-v1 .chatgpt-library-version-backdrop[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
}

body.chatgpt-reference-v1 .composer-attachment-media-dialog :is(
  .composer-attachment-media-icon-button,
  .composer-attachment-media-ask-button
):focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

@media (hover: hover) {
  body.chatgpt-reference-v1 .composer-attachment-media-icon-button:hover {
    background: rgba(255, 255, 255, 0.1);
    opacity: 1;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-ask-button:hover {
    background: color-mix(in srgb, var(--wds-color-inverse-on-surface) 14%, transparent);
  }
}

[data-theme="dark"] body.chatgpt-reference-v1 .composer-chip-removable.composer-attachment-card,
body[data-theme="dark"].chatgpt-reference-v1 .composer-chip-removable.composer-attachment-card,
[data-theme="dark"] body.chatgpt-reference-v1 .composer-attachment-preview-content,
[data-theme="dark"] body.chatgpt-reference-v1 .composer-document-preview-surface.is-visual,
[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-library-preview-document-pane > .composer-document-preview-surface {
  background: color-mix(in srgb, var(--cg-text, #f4f4f4) 8%, var(--cg-surface, #212121));
}

@media (hover: none), (pointer: coarse) {
  body.chatgpt-reference-v1 .composer-attachment-preview {
    display: none !important;
  }
}

@media (max-width: 520px) {
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-attachment-lane:not([hidden])),
  body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(.composer-attachment-lane:not([hidden])) {
    min-height: 112px !important;
  }

  body.chatgpt-reference-v1 .composer-chip-removable.composer-attachment-card {
    max-width: min(220px, calc(100vw - 64px));
  }

  body.chatgpt-reference-v1 .composer-chip-removable.composer-attachment-policy-card {
    width: calc(100vw - 64px);
    max-width: calc(100vw - 64px);
  }
}

@media (max-width: 520px), (pointer: coarse) {
  body.chatgpt-reference-v1 .composer-attachment-lane:not([hidden]) {
    display: flex;
    flex-wrap: nowrap;
    max-width: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: 2px;
    overscroll-behavior-inline: contain;
    /* Native horizontal swipe stays available without trapping a vertical page gesture
       that happened to start on the attachment shelf. */
    touch-action: pan-x pan-y;
    -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;
  }

  body.chatgpt-reference-v1 .composer-chip-removable.composer-attachment-card {
    flex: none;
    scroll-snap-align: start;
    scroll-snap-stop: normal;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--wizon-layer-scrim, 190);
    display: flex;
    align-items: flex-end;
    justify-content: center;
    background: var(--wizon-scrim);
    animation: wizon-scrim-in var(--wds-spring-effect-default-duration, 188ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-backdrop[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }

  /* R-41 B2(M-14 ⑥) — 스크림 추종. 이 시트는 진짜 <dialog>::backdrop 이 아니라 조상 div 라
     CSS 변수가 자손 .composer-attachment-sheet 에서 위로 안 번진다 — 컨트롤러가
     scrimTarget 으로 이 배경에도 같은 변수를 직접 얹는다(app.js bindComposerAttachmentSheetDrag).
     .is-dragging 로 진입 애니메이션을 끄고 진행도만큼 불투명도를 뺀다. */
  body.chatgpt-reference-v1 .composer-attachment-sheet-backdrop.is-dragging {
    animation: none;
    opacity: calc(1 - var(--wizon-sheet-drag-progress, 0));
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet {
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    width: 100%;
    min-height: min(48vh, 406px);
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px));
    padding: 8px 16px calc(16px + var(--wizon-safe-bottom));
    overflow: hidden;
    border: 1px solid var(--ds-modal-border, rgba(15, 23, 42, 0.12));
    border-bottom: 0;
    border-radius: var(--wizon-ai-radius-sheet, var(--ds-modal-radius)) var(--wizon-ai-radius-sheet, var(--ds-modal-radius)) 0 0;
    background: var(--ds-modal-surface, #fff);
    color: var(--cg-text);
    box-shadow: var(--ds-modal-shadow, 0 -12px 36px rgba(15, 23, 42, 0.12));
    /* R-41 B2(M-14 ②) — 개별 속성 translate 로 옮긴다(공유 CSS 변수 --wizon-sheet-drag-y,
       wizon-sheet-down/-snap 키프레임과 같은 이름). transform: none !important 를 쓰는
       메뉴 시트도 같은 컨트롤러를 받을 수 있게 하려는 것 — transform 은 더 안 쓴다. */
    translate: 0 var(--wizon-sheet-drag-y, 0);
    transition: translate var(--wizon-motion-fast, 140ms) var(--wizon-motion-spatial-ease);
    will-change: translate;
    /* R-41 B1(inventory §0 함정 — --wizon-motion-spatial-ease 는 이름과 달리 스프링이 아니라
       ease-in-out 로 풀린다). 한 벌 곡선으로 정렬한다(motion-spec.md §5 시간·곡선 정렬). */
    animation: wizon-sheet-up var(--wizon-motion-panel-duration) var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet[data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards !important;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet.is-dragging {
    transition: none;
    animation: none;
  }

  /* R-41 B2(M-14 ④) — 임계 미달 복귀. is-dragging 이 빠지고 이 클래스가 붙는 사이 한 프레임도
     transition 이 끼어들지 않게 위 .is-dragging 규칙과 같은 특이도로 나란히 둔다. */
  body.chatgpt-reference-v1 .composer-attachment-sheet.is-sheet-snapping {
    transition: none;
    animation: wizon-sheet-snap var(--wds-spring-effect-default-duration, 188ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) forwards;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-grabber {
    display: grid;
    flex: none;
    place-items: center;
    width: 100%;
    /* R-40(M-3 ③) — 손잡이 히트 높이는 ≥44 한 값으로 통일한다(이전 28 은 미달). 시각 크기
       (36×4, 아래 -handle)는 그대로 두고 히트 영역만 키운다. */
    min-height: 44px;
    margin-top: -4px;
    cursor: grab;
    touch-action: none;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet.is-dragging .composer-attachment-sheet-grabber {
    cursor: grabbing;
  }

  /* R-41 B2b(build 2 할 일 6, 설계 §M-3 ②) — 막대 색 1토큰(위 .composer-attachment-dialog-handle
     과 같은 처방). */
  body.chatgpt-reference-v1 .composer-attachment-sheet-handle {
    flex: none;
    align-self: center;
    width: 36px;
    height: 4px;
    border-radius: var(--wizon-ai-radius-pill, var(--ds-radius-pill));
    background: var(--cg-menu-border);
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-header {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-close {
    display: inline-grid;
    place-items: center;
    min-width: 44px;
    min-height: 44px;
    padding: 0;
    border: 0;
    border-radius: var(--ds-radius-md);
    background: transparent;
    color: var(--cg-text);
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-close:focus-visible {
    outline: 2px solid var(--wizon-ai-focus-ring, #155eef);
    outline-offset: 1px;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-close svg {
    width: 20px;
    height: 20px;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-content {
    flex: 1 1 auto;
    min-height: 0;
    margin-top: 12px;
    overflow-x: hidden;
    overflow-y: auto;
    border: 1px solid var(--ds-modal-border, rgba(15, 23, 42, 0.1));
    border-radius: var(--ds-radius-md, 12px);
    background: var(--cg-surface, #fff);
    overscroll-behavior: contain;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-image {
    display: block;
    width: 100%;
    max-height: 48vh;
    margin-top: 8px;
    object-fit: contain;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-preview {
    margin: 8px 12px 14px;
    color: var(--cg-text);
    font-size: var(--ds-text-body-14);
    line-height: 1.55;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-preview.is-empty {
    color: var(--cg-muted);
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-content .composer-document-preview-surface {
    min-height: 220px;
    max-height: none;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-content .composer-document-preview-surface.is-visual img {
    max-height: 48vh;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet-full {
    flex: none;
    min-height: 44px;
    margin-top: 12px;
    border: 0;
    border-radius: var(--wds-radius-md);
    background: var(--cg-text, #0d0d0d);
    color: var(--cg-surface, #fff);
    font: inherit;
    font-weight: 600;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-head {
    min-height: calc(112px + env(safe-area-inset-top, 0px));
    padding: calc(6px + env(safe-area-inset-top, 0px)) 8px 6px 12px;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-file {
    gap: 8px;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-file .composer-attachment-preview-icon {
    width: 36px;
    height: 36px;
    border-radius: 9px;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-actions {
    gap: 0;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-ask-button.is-header-action {
    min-height: 40px;
    padding-inline: 10px;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-ask-label {
    display: none;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-ask-label-short {
    display: inline;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-icon-button {
    width: 44px;
    height: 44px;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-print-button {
    display: none;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-more-menu {
    top: calc(100% + 4px);
    right: 44px;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-more-menu .is-mobile-only {
    display: grid;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-utility-panel {
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    width: auto;
    padding: 14px 16px calc(16px + env(safe-area-inset-bottom, 0px));
    border-width: 1px 0 0;
    border-radius: var(--ds-radius-lg) var(--ds-radius-lg) 0 0;
  }

  /* R-41 B1b(S16) — 닫힘만 추가(발주 범위: «닫힘 모션 추가»). 열림은 고정 위치 앵커 패널이라
     이 후보 밖(B1 계열 여닫힘 키프레임 대상 아님, S8/S14 와 같은 관례 — 닫힘만). 새 키프레임
     0개, wizon-sheet-down 재사용. */
  body.chatgpt-reference-v1 .composer-attachment-media-utility-panel[data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-stage {
    padding: 8px 0 calc(8px + env(safe-area-inset-bottom, 0px));
  }

  body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface {
    width: 100%;
    height: 100%;
    border-inline: 0;
    border-radius: 0;
  }

  body.chatgpt-reference-v1 .composer-attachment-media-ask-drawer {
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    width: auto;
    padding: 12px 12px calc(12px + env(safe-area-inset-bottom, 0px));
    border-width: 1px 0 0;
    border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .composer-document-preview-spinner {
    animation: none;
  }
  /* R-41 B1b(M-15) — .composer-attachment-sheet(S6 컴팩트)는 이전엔 여기서 animation:none 으로
     즉시였다. 이동(translateY drag)·전환은 여전히 0 이지만 열림·닫힘 키프레임은 로그인 창
     (19547행)과 같은 문법으로 124ms 흐려짐으로 바꾼다 — 새 키프레임 0개(wizon-scrim-in/-out
     재사용), !important 로 전역 하한을 특이도 아닌 !important 대결로 이긴다. */
  body.chatgpt-reference-v1 .composer-attachment-sheet {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
    transition: none !important;
  }
  body.chatgpt-reference-v1 .composer-attachment-sheet[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
  body.chatgpt-reference-v1 .composer-attachment-sheet-backdrop {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }
  body.chatgpt-reference-v1 .composer-attachment-sheet-backdrop[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
  /* R-41 B1b(M-15) — [data-mobile-sheet-motion] 옵트인(S6 첨부 목록/붙여넣기,
     composerAttachmentDialog)은 §11 [data-mobile-bottom-sheet] 와 다른 셀렉터라 위 26166행
     블록의 reduce 대응을 못 받았다(선별자가 정확히 일치해야 한다) — 실측(Playwright reduce
     에뮬레이션)으로 확인한 결손. 같은 문법으로 흐려짐만 얹는다. */
  body.chatgpt-reference-v1 .composer-attachment-dialog[data-mobile-sheet-motion][data-motion-state="opening"],
  body.chatgpt-reference-v1 .composer-attachment-dialog[data-mobile-sheet-motion][data-motion-state="open"] {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
  }
  body.chatgpt-reference-v1 .composer-attachment-dialog[data-mobile-sheet-motion][data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
  /* R-41 B1b(S16·M-15) — 닫힘만(발주 범위, 위 §S16 블록과 같은 이유로 열림 0). */
  body.chatgpt-reference-v1 .composer-attachment-media-utility-panel[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
  }
}

/* Server-backed archive and trash routes. Rows remain table-like on desktop and
   turn their actions into a wrapped second line on narrow touch screens. */
body.chatgpt-reference-v1 .chatgpt-route-action,
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 34px;
  padding: 6px 10px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-xs);
  background: var(--cg-surface, #fff);
  color: var(--cg-text, #0d0d0d);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-route-action svg,
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button svg { width: 16px; height: 16px; }

body.chatgpt-reference-v1 .chatgpt-route-action:hover,
body.chatgpt-reference-v1 .chatgpt-route-action:focus-visible,
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button:hover,
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button:focus-visible { background: var(--cg-hover, #f5f5f5); outline: none; }
/* focus-reset-shell-ring-10: 전역 P0-3 폴백((0,3,3), 39890행)에 특정성으로 지지 않도록
   .chatgpt-reference-v1 을 두 번 겹쳐 (0,4,1)로 올린다(같은 클래스 반복 — 항상 참, DOM 변경 없음). */
body.chatgpt-reference-v1.chatgpt-reference-v1 .chatgpt-route-action:focus-visible,
body.chatgpt-reference-v1.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

body.chatgpt-reference-v1 .chatgpt-route-action.is-danger,
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button.is-danger,
body.chatgpt-reference-v1 .chatgpt-lifecycle-empty.is-error { color: #b42318; }
/* F13(D2-fix, 09-14 judge 재검토): 이 라이트 전용 리터럴(#b42318)에 다크 대응 규칙이 아예
   없었다 — 다크 배경 위에서 실측 3.19:1(C9 ≥4.5 위반, 인스펙터 삭제 버튼에서 처음 드러났지만
   같은 클래스를 쓰는 다른 화면도 전부 같은 결함). --wizon-ai-danger-text 다크 값(#ff6b5f,
   settings-v2.css/wizon-mobile-kit.css 가 이미 참조하는 같은 토큰)으로 다크에서만 덮는다. */
html[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-route-action.is-danger,
html[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button.is-danger,
html[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-lifecycle-empty.is-error {
  /* anti-drift(raw-color 재수정): 리터럴 폴백을 빼고 다른 소비처(settings-v2.css,
     wizon-mobile-kit.css)와 같은 무폴백 형태로 — 그 토큰은 전역에 이미 정의돼 있다.
     의미색 통합 S2: WDS error-text alias. canvas 실측 다크 7.52→7.592:1(AA 통과). */
  color: var(--wds-color-error-text);
}

body.chatgpt-reference-v1 .chatgpt-lifecycle-list { border-top: 1px solid var(--ds-color-line); }

body.chatgpt-reference-v1 .chatgpt-lifecycle-row {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  min-height: 76px;
  padding: 12px 2px;
  border-bottom: 1px solid var(--ds-color-line);
}

body.chatgpt-reference-v1 .chatgpt-lifecycle-row-icon { display: inline-grid; place-items: center; color: var(--cg-muted, #737373); }
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-icon svg { width: 19px; height: 19px; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-copy { min-width: 0; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-title { display: flex; align-items: center; min-width: 0; gap: 7px; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-title strong { overflow: hidden; min-width: 0; color: var(--cg-text, #0d0d0d); font-size: var(--ds-text-body-14); font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-copy p { overflow: hidden; margin: 4px 0 0; color: var(--cg-muted, #737373); font-size: var(--ds-text-caption-12); line-height: 18px; text-overflow: ellipsis; white-space: nowrap; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-retention { flex: 0 0 auto; color: var(--cg-muted, #737373); font-size: var(--ds-text-caption-12); }
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-meta { display: flex; flex-wrap: wrap; gap: 5px 8px; margin-top: 4px; font-size: var(--ds-text-caption-12); line-height: 18px; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-purge { color: var(--cg-muted, #737373); font-weight: 500; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-purge.is-warning { color: #b54708; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions { display: flex; align-items: center; gap: 7px; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-empty { display: grid; justify-items: center; gap: 8px; padding: 72px 16px; color: var(--cg-muted, #737373); text-align: center; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-empty strong { color: var(--cg-text, #0d0d0d); font-size: var(--ds-text-body-15); font-weight: 500; }
body.chatgpt-reference-v1 .chatgpt-lifecycle-empty span { font-size: var(--ds-text-caption-13); }

@media (max-width: 520px) {
  body.chatgpt-reference-v1 .chatgpt-lifecycle-row { grid-template-columns: 24px minmax(0, 1fr); align-items: start; }
  body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions { grid-column: 2; flex-wrap: wrap; }
  body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button { flex: 1 1 auto; }
  body.chatgpt-reference-v1 .chatgpt-lifecycle-row-title { flex-wrap: wrap; }
}

/* 20260802-lifecycle-row-mobile-title (북극성 ③ 배치 3): 폰에서 휴지통/아카이브 행 제목이
   한 줄로 강제돼 잘렸다. 라이브 /trash 390px 실측: 제목 열이 316px 인데 내용은 401~766px 라
   최대 450px(대략 2/3)가 말줄임으로 사라졌고, 사라지는 쪽이 하필 두 대화를 구별해 주는
   뒷부분이었다(「2026년 7월 18일부터 제주 2일 가족 자동차 여행 사진 …」). 데스크톱은 폭이
   충분해 한 줄 그대로 두고(공유 규칙의 nowrap 은 계약이 함께 핀한다), 폰에서만 2줄까지 감싼다.
   미리보기 <p> 도 같은 이유로 함께 푼다 — 제목과 같은 문장이면 렌더러가 아예 안 그린다. */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-lifecycle-row-title strong,
  body.chatgpt-reference-v1 .chatgpt-lifecycle-row-copy > p {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    line-height: 1.35;
    white-space: normal;
  }
}

/* 20260726-trash-mobile-topbar (T3b): mobile decluttering for the archive/trash route.
   (1) the subtitle under the H1 explains server behaviour that doesn't need a whole line on a
   small screen — the fixed top app bar already shows the route title, so the subtitle is pure
   restatement there. Scoped to .chatgpt-lifecycle-page only (NOT the shared .chatgpt-route-page
   -head selector) because /scheduled renders its own unrelated <p> subtitle inside the very same
   header shape and must stay untouched.
   (2) the desktop "대화 휴지통 비우기" text button now has a same-purpose icon twin in the fixed
   topbar (see .chatgpt-mobile-topbar [data-mobile-trash-empty] wiring in
   syncPortalRouteSurfaceVisibility) — mirrors the established library precedent just above
   (20260710-library-new-in-topbar hides .chatgpt-route-primary in the route head once its own
   topbar "+" twin exists), so the redundant full-text copy is hidden here rather than duplicated
   under one sticky header. Desktop is untouched — this whole block is <=900px only. */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-lifecycle-page .chatgpt-route-page-head p {
    display: none;
  }
  body.chatgpt-reference-v1 .chatgpt-lifecycle-page .chatgpt-route-page-actions {
    display: none;
  }
}

/* 20260729-touch-target-a2 (WCAG 2.5.5 / Apple HIG 44pt): 휴지통·아카이브 행 액션(복원/영구
   삭제)과 워크스페이스 칩(전체/채팅/업무/코딩)이 390px 실측에서 각각 142x36 / 167x36 / 73x40 —
   너비는 이미 44 이상이라 세로만 44로 올린다. 가로 padding은 그대로 둔다: 복원↔영구 삭제
   사이 가로 간격이 7px뿐이라(파괴적 액션이 바로 옆) 가로로 넓히면 오탭 위험이 커진다. 세로는
   행 높이 168px 중 132px가 여유라 겹침 없이 들어간다(실측). 44px는 이 파일 전역에 이미 40곳
   넘게 리터럴로 쓰인 기존 관례(예: .btn--lg, line 7291)를 따른 것 — 이 값에 맞는 --ds-* 스케일
   토큰이 없어(스페이싱 스케일 최댓값 --ds-space-10=64px) 새 토큰을 만들지 않고 그 관례를 그대로
   따른다. 칩은 .chatgpt-trash-category-tabs로만 스코프해 다른 라우트의 .chatgpt-route-tabs
   (예: /projects)는 건드리지 않는다. 데스크톱은 마우스라 44px가 불필요하고 지금 밀도가 의도된
   디자인이므로 <=900px only. */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button {
    min-height: 44px;
  }
  body.chatgpt-reference-v1 .chatgpt-trash-category-tabs button {
    min-height: 44px;
  }
}

/* Title row + workspace chips pin below the fixed topbar, only the row list scrolls below them —
   mirrors .chatgpt-drive-mobile-stickyhead above verbatim. `display: contents` dissolves the
   wrapper on desktop so `.chatgpt-route-page-inner`'s existing layout is byte-for-byte unchanged
   there; the <=900px override is the only visible change. */
body.chatgpt-reference-v1 .chatgpt-lifecycle-mobile-stickyhead {
  display: contents;
}
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-lifecycle-mobile-stickyhead {
    display: flex;
    flex-direction: column;
    gap: var(--chatgpt-route-gap-xs);
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--cg-bg);
    padding-bottom: var(--chatgpt-route-gap-xs);
  }
}

/* T2 unified trash (docs/product/215): 전체/대화/드라이브 category chips reuse
   .chatgpt-route-tabs verbatim (same markup/behaviour as the projects page's own filter tabs) —
   only the count badge and the row-level category badge/original-path are new here. No new
   color tokens: --cg-muted, --cg-hover-soft, --ds-badge-info-bg, --ds-badge-info-fg,
   --ds-radius-pill, --ds-radius-md and --ds-text-caption-12 all already exist and are referenced
   WITHOUT a literal hex/rgb fallback. (No glob-star before a slash inside a CSS comment — that
   sequence closes the comment early and silently drops the next rule; this file has been burned
   by it three times in this track.)
   The anti-drift ratchet has zero headroom on raw-color/raw-rgb (measured: 2608/2608 and
   658/658), but that only bars new HEX/RGB LITERALS — `background`/`color` are not scanned
   properties at all, so a token-expressed distinction is free. See
   tests/test_design_system_contract.py:70-73 for the scanned pattern set. */
body.chatgpt-reference-v1 .chatgpt-route-tabs-count {
  margin-left: 4px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-trash-category-badge {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: var(--ds-radius-pill);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 500;
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-trash-category-badge svg { width: 13px; height: 13px; }

/* The two badges were byte-identical grey (measured: both rgb(244,244,244)/rgb(111,111,111)),
   which left a mixed list with no colour signal at all — the badge WORD was the only cue, at
   caption size. "Zero ratchet headroom" only forbids new raw hex/rgb: `background`/`color` are
   not scanned properties, and --ds-badge-info-* is an existing Tier-3 token pair defined for
   both themes (shared/design-tokens/component.css), so this distinction costs the ratchet
   nothing. Drive = the foreign source being merged in, so it takes the accent; conversation
   stays neutral as the page's native content. */
body.chatgpt-reference-v1 .chatgpt-trash-category-badge--drive {
  background: var(--ds-badge-info-bg);
  color: var(--ds-badge-info-fg);
}

/* doc 228 P3 — conversation topic tag pill (trash/archive rows). Same pill geometry as
   .chatgpt-trash-category-badge just above (radius/padding/caption size) so the two read as one
   family, but deliberately NOT filled and NOT iconed: the category badge answers "what KIND of
   row is this" (대화/드라이브) — an axis already visible one level up in the workspace filter chips
   — while this pill answers "what is this conversation ABOUT", brand-new information the reader
   has never seen elsewhere on this screen. Giving it the same filled weight as the category badge
   would make two different kinds of fact compete for the same attention; a hairline outline with
   no icon keeps the family resemblance (still a pill, still caption-sized) while reading as
   lighter, secondary, descriptive rather than classificatory.
   Border token: NOT --cg-border — that name looks like this file's other generic tokens but is
   never actually defined at :root (verified: only ever consumed with a literal fallback wherever
   it appears elsewhere in this file, so it silently renders as no border at all wherever no
   fallback is given — confirmed live, this rule rendered with zero border before the fix, using
   that name). --ds-color-line is the real, already-defined Tier-2 semantic border token
   (shared/design-tokens/semantic.css, light+dark) already used without a fallback dozens of times
   elsewhere in this exact file (e.g. line 1471), so this adds no new color and needs none. */
body.chatgpt-reference-v1 .chatgpt-topic-tag-badge {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  padding: 2px 8px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-pill);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 500;
  white-space: nowrap;
}

/* Partial-failure banner: one source is missing, the rest of the list below is real. Deliberately
   NOT .is-error styling — an alert-red block over a working list would read as "this list is
   broken" when it is merely incomplete. */
body.chatgpt-reference-v1 .chatgpt-lifecycle-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 12px;
  padding: 10px 14px;
  border-radius: var(--ds-radius-md, 12px);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-trash-original-path {
  flex: 0 0 auto;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

/* Restore is disabled (not hidden) for a viewer-role drive row — the row itself already carries
   a title= explaining why, this is only the visual half. */
body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

body.chatgpt-reference-v1 .chatgpt-trash-load-more {
  margin-top: 12px;
}

/* docs/product/215-wizon-drive-design.md DR-1b explorer page. Rows/empty/toolbar
   all reuse .chatgpt-route-page-* and .chatgpt-lifecycle-* above as-is (no new rules
   needed for those) — only the breadcrumb, the folder-name-as-button reset, the
   inline feedback line, and the new-folder dialog are new here. */
body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-size: var(--ds-text-caption-13);
}
body.chatgpt-reference-v1 .chatgpt-drive-history-controls,
body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-trail {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
body.chatgpt-reference-v1 .chatgpt-drive-history-controls {
  flex: 0 0 auto;
  padding-right: 4px;
  border-right: 1px solid var(--cg-menu-border, var(--line));
}
body.chatgpt-reference-v1 .chatgpt-drive-history-button {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: var(--ds-radius-xs);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-history-button svg { width: 16px; height: 16px; }
body.chatgpt-reference-v1 .chatgpt-drive-history-button:hover { background: var(--cg-hover); }
body.chatgpt-reference-v1 .chatgpt-drive-history-button:disabled { color: var(--cg-muted); opacity: 0.45; cursor: default; }
body.chatgpt-reference-v1 .chatgpt-drive-history-button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 2px;
}
body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 7px;
  border: none;
  border-radius: var(--ds-radius-xs);
  background: transparent;
  color: var(--cg-muted);
  font: inherit;
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item svg { width: 14px; height: 14px; }
body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item:hover,
body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item:focus-visible { background: var(--cg-hover); }
body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item.is-current { color: var(--cg-text); font-weight: 500; }
body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-sep { color: var(--cg-muted); }

body.chatgpt-reference-v1 .chatgpt-drive-row-open {
  display: block;
  min-width: 0;
  border: none;
  background: transparent;
  padding: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-row-open:hover strong,
body.chatgpt-reference-v1 .chatgpt-drive-row-open:focus-visible strong { text-decoration: underline; }

/* drive-mobile-folder-chevron (2026-09-14, judge ⓓ): 폴더 행 disclosure(chevron) — 비대화형
   장식, 데스크톱은 항상 숨김. 기본값을 미디어쿼리 밖에 명시적으로 둔다 — M2에서 겪은 함정
   (스타일을 미디어쿼리 안에만 둬서 그 밖 화면에 기본 규칙이 없어 스타일 없는 요소가 새는 결함,
   package/DECISIONS.md 참고)을 반복하지 않기 위해서다. 노출 규칙은 아래
   @media (max-width: 900px) 안에서만 켠다(app.js renderDriveColumnRow 참고). */
body.chatgpt-reference-v1 .chatgpt-drive-columns-disclosure {
  display: none;
}

body.chatgpt-reference-v1 .chatgpt-drive-feedback,
body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt {
  margin: 0;
  padding: 8px 12px;
  border-radius: var(--ds-radius-xs);
  background: var(--cg-hover-soft, var(--cg-hover));
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
body.chatgpt-reference-v1 .chatgpt-drive-feedback button,
body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt button {
  min-width: 44px;
  min-height: 44px;
  border: none;
  border-radius: var(--ds-radius-xs);
  padding: 4px 10px;
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-feedback button:hover,
body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt button:hover {
  background: var(--cg-hover);
}
body.chatgpt-reference-v1 .chatgpt-drive-feedback button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt button:focus-visible {
  background: var(--cg-hover);
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 2px;
}
/* drive-mobile-open-action-visible-failure-fix-c: syncDriveModalAccessibility 가 실패
   사유 뒤 이 버튼(예: 412/etag 의 새로고침)을 코드로 focus() 한다. 코스 포인터(터치)
   컨텍스트 직후의 프로그램적 focus() 는 Chromium 의 :focus-visible 휴리스틱이 링을
   숨긴다(실측: outlineStyle=none) — 사유가 방금 나타났다는 신호를 보여 줘야 하는
   자리라 여기서는 :focus 로도 같은 링을 켠다(임의의 마우스 클릭에는 안 켜진다 —
   이 버튼은 스크립트가 의도적으로 포커스를 옮긴 경우만 해당). */
body.chatgpt-reference-v1 .chatgpt-drive-feedback button:focus {
  background: var(--cg-hover);
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 2px;
}
body.chatgpt-reference-v1 .chatgpt-drive-feedback button:disabled,
body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt button:disabled {
  color: var(--cg-muted);
  cursor: wait;
}
/* drive-mobile-open-action-visible-failure-fix-c: 사유 카드에 트레일링 버튼이 없을 때
   (예: 428/500/blocked) syncDriveModalAccessibility 가 카드 컨테이너 자체(tabindex="-1")
   로 포커스를 옮긴다. 버튼과 같은 포커스 링 토큰을 컨테이너에도 그대로 준다 — 스크린
   리더/키보드 사용자가 «사유가 여기 있다」를 시각으로도 확인할 수 있어야 한다. */
body.chatgpt-reference-v1 .chatgpt-drive-feedback:focus,
body.chatgpt-reference-v1 .chatgpt-drive-feedback:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 2px;
}

/* A destructive operation remains a local, readable receipt while it is pending and after an
   error; this is intentionally a compact Drive surface rather than a global toast. */
body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt {
  display: flex;
  align-items: center;
  min-height: 40px;
  margin: 0;
  padding: 8px 12px;
  border-left: 3px solid var(--wizon-ai-focus-ring);
  background: var(--cg-hover-soft, var(--cg-hover));
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
}
body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt > span {
  min-width: 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt.is-pending {
  color: var(--cg-muted);
}

body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt.is-error,
body.chatgpt-reference-v1 .chatgpt-drive-operation-receipt.is-partial {
  border-left-color: var(--cg-danger-fg);
  color: var(--cg-danger-fg);
}

body.chatgpt-reference-v1 .chatgpt-drive-new-folder-dialog form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
}
body.chatgpt-reference-v1 .chatgpt-drive-new-folder-dialog label {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
body.chatgpt-reference-v1 .chatgpt-drive-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
body.chatgpt-reference-v1 .chatgpt-drive-dialog-actions button {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 6px 12px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-xs);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-dialog-actions button[type="submit"] {
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
  border-color: transparent;
}
@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-dialog-actions button:hover { outline: none; }
}
body.chatgpt-reference-v1 .chatgpt-drive-dialog-actions button:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 2px;
}
body.chatgpt-reference-v1 .chatgpt-drive-dialog-actions button[data-drive-action="permissions-save"] {
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
  border-color: transparent;
}

/* DR-2 permission-grant dialog (docs/product/215-wizon-drive-design.md §9). Reuses
   .chatgpt-library-popover/.chatgpt-drive-dialog-actions/.ds-input/.ds-chip as-is —
   only the list/row/add-row layout below is new (no raw color/radius/shadow/font-size,
   anti-drift net-zero: every declaration here is spacing/layout, or points at a
   --cg-* and --ds-* token already used by the sibling DR-1b dialog above). */
body.chatgpt-reference-v1 .chatgpt-drive-permissions-dialog {
  position: fixed;
  z-index: var(--wds-z-modal);
  top: 50%;
  left: 50%;
  right: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px;
  width: min(560px, calc(100vw - (var(--wizon-ai-thread-gutter) * 2)));
  min-width: 520px;
  max-width: 560px;
  max-height: min(80vh, 760px);
  transform: translate(-50%, -50%);
  box-shadow: var(--cg-shadow-float-lg), 0 0 0 100vmax var(--wizon-scrim);
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-head h2 {
  margin: 0;
  font-size: var(--ds-text-body-15);
  font-weight: 600;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 220px;
  overflow-y: auto;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-scroll {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-md);
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-section {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-xs);
  padding-bottom: var(--chatgpt-route-gap-md);
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-section:last-child {
  border-bottom: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-sm);
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-section-head > span:first-child {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-2xs);
  min-width: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-section-head strong {
  padding: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-section-head small,
body.chatgpt-reference-v1 .chatgpt-drive-permissions-row-copy span,
body.chatgpt-reference-v1 .chatgpt-drive-share-list small {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: var(--ds-radius-xs);
  background: var(--cg-hover-soft, var(--cg-hover));
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-row.is-inherited {
  border: 1px dashed var(--cg-menu-border, var(--line));
  background: transparent;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-row-copy {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  gap: 2px;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-row-copy strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-row select.ds-input {
  flex: 0 0 auto;
  width: auto;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-owner-note {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-empty {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  padding: 8px 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-add-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-add-row select.ds-input {
  flex: 1 1 140px;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-empty-hint {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  margin: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-inheritance-chain {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--chatgpt-route-gap-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-inheritance-chain li {
  display: inline-flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-2xs);
  min-height: var(--chatgpt-route-control-height);
  padding: 0 var(--chatgpt-route-gap-xs);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-pill);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
body.chatgpt-reference-v1 .chatgpt-drive-inheritance-chain li.is-boundary {
  border-color: var(--wizon-ai-focus-ring);
  color: var(--cg-text);
}
body.chatgpt-reference-v1 .chatgpt-drive-inheritance-chain svg {
  width: 14px;
  height: 14px;
}
body.chatgpt-reference-v1 .chatgpt-drive-share-create,
body.chatgpt-reference-v1 .chatgpt-drive-share-created {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--chatgpt-route-gap-xs);
}
body.chatgpt-reference-v1 .chatgpt-drive-share-create select.ds-input {
  flex: 1 1 120px;
}
body.chatgpt-reference-v1 .chatgpt-drive-share-created label {
  display: flex;
  flex: 1 1 280px;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-2xs);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
body.chatgpt-reference-v1 .chatgpt-drive-share-created input {
  width: 100%;
}
body.chatgpt-reference-v1 .chatgpt-drive-share-list {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-share-list li {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  min-height: var(--chatgpt-route-control-height);
  padding: var(--chatgpt-route-gap-xs);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
}
body.chatgpt-reference-v1 .chatgpt-drive-share-list li > span:first-child {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-share-list button,
body.chatgpt-reference-v1 .chatgpt-drive-permissions-row > button {
  min-height: var(--chatgpt-route-control-height);
  padding: 0 var(--chatgpt-route-gap-xs);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-permissions-error {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-caption-12);
}

/* DR-3 administrator review queue and audit log. */
body.chatgpt-reference-v1 .chatgpt-drive-governance-dialog {
  position: fixed;
  z-index: var(--wds-z-modal);
  top: 50%;
  left: 50%;
  right: auto;
  display: flex;
  flex-direction: column;
  width: min(720px, calc(100vw - (var(--wizon-ai-thread-gutter) * 2)));
  min-width: 640px;
  max-width: 720px;
  max-height: min(84vh, 820px);
  gap: var(--chatgpt-route-gap-sm);
  padding: var(--chatgpt-route-gap-md);
  transform: translate(-50%, -50%);
  box-shadow: var(--cg-shadow-float-lg), 0 0 0 100vmax var(--wizon-scrim);
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-head,
body.chatgpt-reference-v1 .chatgpt-drive-handover-case > header,
body.chatgpt-reference-v1 .chatgpt-drive-handover-case li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-sm);
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-head > span,
body.chatgpt-reference-v1 .chatgpt-drive-handover-case header > span:first-child,
body.chatgpt-reference-v1 .chatgpt-drive-handover-case li > span:first-child,
body.chatgpt-reference-v1 .chatgpt-drive-handover-create > span:first-child {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: var(--chatgpt-route-gap-2xs);
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-head h2 {
  margin: 0;
  font-size: var(--ds-text-title-20);
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-head small,
body.chatgpt-reference-v1 .chatgpt-drive-handover-case small,
body.chatgpt-reference-v1 .chatgpt-drive-handover-create small {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-head button,
body.chatgpt-reference-v1 .chatgpt-drive-governance-tabs button,
body.chatgpt-reference-v1 .chatgpt-drive-handover-case button,
body.chatgpt-reference-v1 .chatgpt-drive-governance-error button {
  min-height: var(--chatgpt-route-control-height);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-tabs,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-tabs {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-2xs);
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-tabs button,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-tabs button {
  min-height: var(--chatgpt-route-control-height);
  padding: 0 var(--chatgpt-route-gap-sm);
  border: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--cg-muted);
  font: inherit;
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-tabs button[aria-selected="true"],
body.chatgpt-reference-v1 .chatgpt-drive-inspector-tabs button[aria-selected="true"] {
  border-bottom-color: var(--wizon-ai-focus-ring);
  color: var(--cg-text);
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-dialog button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-permissions-dialog button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-tabs button:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 2px;
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-dialog button:disabled,
body.chatgpt-reference-v1 .chatgpt-drive-permissions-dialog button:disabled {
  opacity: 0.45;
  cursor: default;
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-body {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-sm);
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
body.chatgpt-reference-v1 .chatgpt-drive-handover-create {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(180px, 0.8fr) auto;
  align-items: end;
  gap: var(--chatgpt-route-gap-xs);
  padding: var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-lg);
}
body.chatgpt-reference-v1 .chatgpt-drive-handover-case {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-xs);
  padding: var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-lg);
}
body.chatgpt-reference-v1 .chatgpt-drive-handover-case ul {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-handover-case li {
  padding: var(--chatgpt-route-gap-xs);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
}
body.chatgpt-reference-v1 .chatgpt-drive-handover-case select {
  min-width: 160px;
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-error {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-xs);
  padding: var(--chatgpt-route-gap-xs);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
  font-size: var(--ds-text-caption-13);
}
body.chatgpt-reference-v1 .chatgpt-drive-governance-loading {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-drive-rag-governance,
body.chatgpt-reference-v1 .chatgpt-drive-rag-index-copy,
body.chatgpt-reference-v1 .chatgpt-drive-rag-governance > header > span {
  display: flex;
  min-width: 0;
  flex-direction: column;
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-governance {
  gap: var(--chatgpt-route-gap-sm);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-governance > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-sm);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-governance > header > span,
body.chatgpt-reference-v1 .chatgpt-drive-rag-index-copy {
  gap: var(--chatgpt-route-gap-2xs);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-governance small,
body.chatgpt-reference-v1 .chatgpt-drive-rag-index-copy small {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-filters {
  display: flex;
  gap: var(--chatgpt-route-gap-2xs);
  overflow-x: auto;
  scrollbar-width: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-filter {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--chatgpt-route-gap-2xs);
  min-height: var(--chatgpt-route-control-height);
  padding: 0 var(--chatgpt-route-gap-xs);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-pill);
  background: transparent;
  color: var(--cg-muted);
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-filter.is-selected {
  border-color: var(--cg-text);
  background: var(--cg-text);
  color: var(--cg-surface);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-index-list {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-index-row {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  padding: var(--chatgpt-route-gap-xs);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-control);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-index-icon .wizon-file-type-icon {
  width: 32px;
  height: 32px;
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-index-copy strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-status {
  display: inline-flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-2xs);
  color: var(--cg-text);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-status i,
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-status[data-status="ready"] i,
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-state[data-status="ready"] .chatgpt-drive-ai-index-dot {
  background: var(--ad-green);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-status[data-status="failed"] i,
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-state.is-error .chatgpt-drive-ai-index-dot {
  background: var(--cg-danger-fg);
}
body.chatgpt-reference-v1 .chatgpt-drive-rag-status[data-status="processing"] i,
body.chatgpt-reference-v1 .chatgpt-drive-rag-status[data-status="queued"] i {
  background: var(--ad-amber);
}

body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-dialog {
  position: fixed;
  z-index: var(--wds-z-modal);
  top: 50%;
  left: 50%;
  right: auto;
  display: flex;
  flex-direction: column;
  width: min(560px, calc(100vw - (var(--wizon-ai-thread-gutter) * 2)));
  min-width: 440px;
  max-width: 560px;
  max-height: min(80vh, 760px);
  gap: var(--chatgpt-route-gap-sm);
  padding: var(--chatgpt-route-gap-md);
  overflow: hidden;
  transform: translate(-50%, -50%);
  box-shadow: var(--cg-shadow-float-lg), 0 0 0 100vmax var(--wizon-scrim);
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-sm);
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-head > span {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-2xs);
  min-width: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-head h2 {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-title-20);
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-head small {
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-head button {
  display: inline-grid;
  place-items: center;
  min-width: 44px;
  min-height: 44px;
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-head button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-body button:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 2px;
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-body {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* 20260812-drive-permissions-mobile-targets: production 390x844 QA measured
   the native selects/actions at 31–36px. Keep the compact desktop dialog,
   while making every direct permission action a non-overlapping 44px target
   on touch layouts. */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-drive-permissions-dialog,
  body.chatgpt-reference-v1 .chatgpt-drive-governance-dialog,
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-dialog {
    min-width: 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-permissions-dialog select.ds-input,
  body.chatgpt-reference-v1 .chatgpt-drive-permissions-dialog button,
  body.chatgpt-reference-v1 .chatgpt-drive-governance-dialog select.ds-input,
  body.chatgpt-reference-v1 .chatgpt-drive-governance-dialog button,
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-activity-dialog button {
    min-width: 44px;
    min-height: 44px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-permissions-row {
    align-items: stretch;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-handover-create {
    grid-template-columns: 1fr;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-handover-case li {
    align-items: stretch;
    flex-direction: column;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-rag-index-row {
    grid-template-columns: 36px minmax(0, 1fr);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-rag-index-row > .chatgpt-route-action {
    grid-column: 1 / -1;
    justify-content: center;
  }
}

/* ============================================================================
 * E1 2패널 탐색기(docs/product/215-wizon-drive-design.md) — 브레드크럼은 하단 경로 바로
 * 이동. 라우트 자체는 중앙정렬 document 컨테이너를 풀고 전체폭으로 전환한다(아래 첫 블록,
 * #artifactPanel의 ID+attribute 조합으로 베이스 .chatgpt-route-page보다 특이도를 높이는
 * 패턴을 그대로 미러). 색/반경은 전부 기존 --cg-* 와 --chatgpt-route-* 토큰 재사용 — 이 블록의
 * raw 리터럴은 없다(anti-drift 순증 0).
 *
 * 20260724-drive-shellfit (docs/product/215, 셸 정합 재구성): 위즈온 앱 셸이 이미 왼쪽에
 * 260px 앱 사이드바(.app-shell)를 갖고 있어, E1이 그 옆에 또 240px 폴더 트리 레일을 두면
 * "이중 사이드바"로 보였다 — 그 좌측 트리 패널(옛 .chatgpt-drive-tree*)을 데스크톱에서도
 * 완전히 없애고, 목록은 전체폭 브라우즈로, 그 오른쪽을 선택 상태 기반 인스펙터 패널로
 * 대체했다. 트리 렌더 CSS는 지금 실제 DOM 어디에도 쓰이지 않아 통째로 제거한다(JS의
 * renderDriveTreePane/renderDriveTreeNode 자체는 남아 있지만 renderDrivePage가 더 이상
 * 호출하지 않는다 — 트리 캐시(state.drive.tree.childrenByParentId)는 Miller 뷰 조상 컬럼이
 * 계속 쓰므로 그대로 둔다).
 * ========================================================================== */
html[data-portal-route="/drive"] #portalPage.chatgpt-route-page {
  /* 20260725-drive-redesign R1: 800px 중앙문서 캡을 풀되 전체폭이 아니라 파인더식
     넓은 중앙 콘텐츠(1000px)로 재중앙정렬한다 — 죽은 우측 여백 제거. 인스펙터는 더 이상
     이 흐름 안 320px 컬럼이 아니라 아티팩트 패널과 동일한 position:fixed 오버레이(아래). */
  --chatgpt-drive-content-max-width: 1000px;
  width: 100%;
  max-width: var(--chatgpt-drive-content-max-width);
  margin: 0 auto;
  padding: var(--chatgpt-route-page-top) var(--wizon-ai-thread-gutter) var(--chatgpt-route-page-bottom);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
html[data-portal-route="/drive"] #portalPage .chatgpt-route-page-inner {
  flex: 1;
  min-height: 0;
}

/* 20260724-drive-shellfit: [전체폭 브라우즈][320px 인스펙터] — 옛 [240px 트리][1fr 목록]을
   대체. 인스펙터가 닫히면 .chatgpt-drive-explorer--inspector-closed가 붙어 1열로 접힌다
   (renderDriveInspectorPane이 닫힘 상태에서 마크업 자체를 만들지 않으므로, 빈 grid 트랙이
   생기지 않도록 트랙 정의도 같이 접어야 한다). */
/* 20260725-drive-redesign R1: 인스펙터가 흐름 밖 position:fixed 오버레이로 빠졌으므로
   explorer는 인스펙터 열림/닫힘과 무관하게 항상 단일 컬럼(중앙 콘텐츠 전체폭). 옛 [1fr 320px]
   2컬럼 트랙은 제거 — 인스펙터가 목록을 밀지 않고 그 위에 얹힌다(아티팩트 패널과 동일 계열). */
body.chatgpt-reference-v1 .chatgpt-drive-explorer {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--chatgpt-route-gap-md);
  min-height: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-explorer.chatgpt-drive-explorer--inspector-closed {
  grid-template-columns: minmax(0, 1fr);
}

/* 우측 컬럼 목록(선택/이름/크기/수정일). 헤더와 행이 같은 컬럼 폭을 공유해 정렬을
   맞추고, 행에만 마지막에 컨텍스트 메뉴("⋮") 트랙을 더한다(헤더는 그 트랙을 비워 둔다 —
   두 selector가 동일 폭을 쓰므로 정렬은 깨지지 않는다).
   20260724-drive-explorer-e2b: 맨 앞에 다중 선택 체크박스 컬럼을 추가하고, 기존에 각 행마다
   따로 있던 다운로드/삭제/권한 관리 버튼은 컨텍스트 메뉴로 통합되어 마지막 트랙이 "⋮" 트리거
   하나로 줄었다(그만큼 폭도 줄여 min-width로 보정).
   20260725-drive-row-r2b: 유형 트랙(96px)을 통째로 제거 — 유형/권한 정보는 이제 이름 셀
   안의 subline(둘째 줄)으로 옮겨 갔다(renderDriveColumnRow 참고). 남는 폭은 이름의
   minmax(0, 1fr) 트랙이 그대로 흡수한다. min-width도 지워진 트랙+그 간격만큼(96px + 8px
   gap) 그대로 줄여, 이름 트랙이 실제로 확보하던 최소 폭(옛 600px 기준 약 172px)을 그대로
   보존한다. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-pane {
  min-width: 0;
  overflow-x: auto;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns-head,
body.chatgpt-reference-v1 .chatgpt-drive-columns-row {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) 96px 128px 40px;
  align-items: center;
  gap: 8px;
  min-width: 496px;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns-cell-select {
  display: flex;
  align-items: center;
  justify-content: center;
}
body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox {
  width: 16px;
  height: 16px;
  margin: 0;
  cursor: pointer;
  /* 20260725-drive-redesign R2: 체크 표시를 네이티브 파랑이 아니라 잉크로 — 파랑은 계정 아바타
     전용(모노 규칙). 토큰 구동이라 다크에서는 밝은 잉크로 flip. */
  accent-color: var(--cg-text);
}
/* D1-delta Δ2 (독립 검토 D-5): 행 체크는 이제 <button role="checkbox">(COMPONENT-RULES.md
   "28×36 히트, 20×20 시각 상자"). D-5(절반) 수리로 헤더 전체선택(`data-drive-select-all`,
   목록·검색 결과 둘 다)도 같은 <button role="checkbox"> 마크업이 되어 여기 포함한다 —
   같은 역할(체크)은 1값이어야 한다(C3). 시각 상자는 안쪽 `.chatgpt-drive-select-checkbox-box`
   span이 20×20 테두리로 그린다. */
body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox[data-drive-select-checkbox],
body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox[data-drive-select-all] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 36px;
  padding: 0;
  border: 0;
  background: transparent;
}
body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox-box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--ds-radius-2xs, 3px);
  background: var(--cg-surface);
  color: var(--cg-surface);
}
body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox-box svg {
  width: 14px;
  height: 14px;
}
/* 체크 = 모노 잉크(위 accent-color와 같은 규칙) — 파랑 없음. */
body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox[aria-checked="true"] .chatgpt-drive-select-checkbox-box {
  background: var(--cg-text);
  border-color: var(--cg-text);
  color: var(--cg-surface);
}
body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: 2px;
}
/* 20260725-drive-redesign R2: 미세 포인터(데스크톱)에서는 체크박스를 호버/선택/포커스에서만
   드러내 평소 행을 조용하게 유지 — 터치(호버 없음)에서는 항상 보여 선택 가능성을 지킨다(doc147). */
@media (hover: hover) and (pointer: fine) {
  body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox {
    opacity: 0;
    transition: opacity 0.12s ease;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row:hover .chatgpt-drive-select-checkbox,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row.is-selected .chatgpt-drive-select-checkbox,
  body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox:focus-visible,
  body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox:checked,
  /* D1-delta Δ2: button has no native :checked — same reveal-when-checked rule via aria-checked. */
  body.chatgpt-reference-v1 .chatgpt-drive-select-checkbox[aria-checked="true"] {
    opacity: 1;
  }
}
body.chatgpt-reference-v1 .chatgpt-drive-columns-row.is-selected {
  background: var(--cg-hover-soft);
}
/* [data-drive-row-id] 를 덧붙여 특정성을 (0,3,1)→(0,4,1)로 올린다 — 전역 기본 링
   `.chatgpt-reference-v1 :focus-visible:not(input):not(textarea):not(select):not([contenteditable="true"])`
   이 (0,3,3)라 이 규칙(덧붙이기 전) 자체보다 세서 outline-offset 이 2px(바깥)로 새고 있었다
   (색은 둘 다 --cg-focus-visible-ring 계열이라 우연히 같아 안 보였다 — 실측: getComputedStyle
   로 확인해야 드러나는 함정, drive-row-focus-ring-and-autofocus). 행은 촘촘히 붙어 있어 -2px
   (안쪽)이어야 아래 행과 겹치지 않는다.
   ★fixup(QA 09-13): --cg-focus-visible-ring 을 거치면 후보 72(focus-visible-ring-token,
   948672c12) 가 착지하기 전까지 라이트 1.42:1·다크 1.73:1 — SC 1.4.11(3:1) FAIL 로 실측됐다
   (QA). ★var(--wizon-ai-focus-visible-ring, <폴백>) 형태로 폴백만 갈아 끼우는 시도는
   실측으로 기각했다 — shared/wds/wizon-ai-shell-tokens.css:99 가 지금(72 착지 전)
   `--wizon-ai-focus-visible-ring: var(--wizon-ai-focus-ring)`(호버 헤일로)로 **이미
   정의**돼 있어, CSS var() 폴백은 변수가 "정의 안 됨"일 때만 걸리지 "정의는 됐는데
   값이 나쁨"일 때는 안 걸린다(Playwright 실측으로 확인: 폴백을 바꿔도 여전히
   rgba(13,13,13,.16)/rgba(255,255,255,.18) 그대로). 그래서 --wizon-ai-focus-visible-ring
   자체를 참조하지 않고, 후보 72가 그 토큰에 **최종적으로** 줄 값과 동일한
   --wizon-ai-text-primary(텍스트 원색, 라이트 #0d0d0d·다크 #ececec, 실측 대비
   19.44:1/17.78:1) 를 직접 쓴다 — 새 raw 리터럴이 아니라 이미 있는 토큰이라 anti-drift
   래칫에 안 걸리고, 72가 착지해 그 토큰의 정의 자체를 --wizon-ai-text-primary 로 고치면
   값이 정확히 같아 이음매 없이 이어받는다(참조 지점만 --wizon-ai-focus-visible-ring 으로
   되돌리는 후속 정리는 72 착지 뒤 별도 후보 몫). */
body.chatgpt-reference-v1 .chatgpt-drive-columns-row[data-drive-row-id]:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-search-result[data-drive-row-id]:focus-visible {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: -2px;
}
/* 20260725-drive-redesign R2: 선택 = 표면 상승 + 좌측 잉크 바(색 하이라이트 없이 명도/위치로만). */
body.chatgpt-reference-v1 .chatgpt-drive-columns-row.is-selected::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--cg-text);
}
/* 좌측 잉크 바는 리스트/밀러 같은 각진 행의 스파인 어포던스다. 라운드 카드(그리드·갤러리
   뷰, border-radius control)에서는 직각 바가 모서리를 삐져나오므로 끄고, 카드 선택은 이미
   있는 표면 상승(is-selected 배경)으로만 표시한다 — 4뷰 공유 행 마크업의 뷰별 분기. */
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row.is-selected::before,
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row.is-selected::before {
  display: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns-head {
  padding: 8px 12px;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  text-transform: uppercase;
  /* 20260725-drive-redesign R2: 헤더 라벨 트래킹을 넓혀 "설계된" 캡션 톤(정렬은 행과 동일 패딩). */
  letter-spacing: 0.06em;
}
/* 20260725-drive-redesign R2: 30px 아이콘 칩과 함께 행 높이가 ~46px로 올라가 리듬이 생긴다.
   position:relative는 선택 좌측 잉크 바(::before)의 앵커. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-row {
  position: relative;
  padding: 8px 12px;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}
@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row:hover {
    background: var(--cg-hover-soft);
  }
}
body.chatgpt-reference-v1 .chatgpt-drive-columns-cell {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  /* 20260725-drive-redesign R2: 크기/수정일 숫자를 tabular-nums로 우측 정렬 시 자리수 맞춤(질서). */
  font-variant-numeric: tabular-nums;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns-cell-name {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
  color: var(--cg-text);
}
body.chatgpt-reference-v1 .chatgpt-drive-columns-cell-name strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
  min-width: 0;
  color: var(--cg-text);
  font-weight: 500;
  /* 20260725-drive-redesign R2: 파일명에 살짝의 마이너스 자간 — 프리미엄 리스트의 "설계된" 톤. */
  letter-spacing: -0.01em;
}
/* 20260725-drive-row-r2b: 이름(strong) + subline(둘째 줄)을 세로로 쌓는 래퍼 — 파일 행은
   .chatgpt-drive-columns-cell-name의 바로 아래 자식, 폴더 행은 .chatgpt-drive-row-open
   버튼 안쪽 자식이라 depth가 다르지만 이 규칙은 depth와 무관하게 둘 다 잡는다. min-width:0은
   부모(cell-name, 폴더는 그 위에 .chatgpt-drive-row-open까지)가 flex 아이템일 때 이 래퍼가
   내용 크기로 안 늘어나고 정상적으로 줄어들도록(그래야 strong/subline의 ellipsis가 먹는다). */
body.chatgpt-reference-v1 .chatgpt-drive-columns-name-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* D1-delta 독립 검토 D-7: 값 2px는 «한 줄↔다음 줄» 사다리(§2, 0/4/…) 밖에 있던 값이다 —
     행이 1줄이던 D1 이전엔 안 보이던 자리였는데, Δ1이 웹 행을 2줄로 만들면서 화면에
     드러났다(v8.2 잔여 처분). 4로 올려 사다리에 맞춘다. */
  gap: 4px;
}
/* subline = 파일 유형(driveFileTypeMeta 라벨) 또는 폴더 내 권한(driveRoleLabel), 실제
   컬럼에서 옮겨온 값 — 톤은 헤더 캡션과 같은 --ds-text-caption-12 + --cg-muted. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-subline {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
/* List glyphs are a dedicated 24px role. Grid, gallery, and preview still use the
   approved portrait file artwork through .wizon-file-type-icon. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  overflow: visible;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  color: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-columns-icon .wizon-file-type-icon {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 1 / 1;
  object-fit: contain;
}
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  color: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph .wds-inline-icon {
  width: 24px;
  height: 24px;
}
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph--pdf { color: var(--wds-color-error-text); }
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph--document,
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph--markdown { color: var(--wds-color-info); }
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph--spreadsheet,
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph--image { color: var(--wds-color-success); }
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph--presentation,
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph--hangul,
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph--archive { color: var(--wds-color-warning); }
body.chatgpt-reference-v1 .chatgpt-drive-compact-file-glyph--folder { color: var(--cg-text); }
/* 폴더 칩은 잉크 글리프로 위계 강조(색상 구분이 아니라 명도 — 모노 규칙 준수). */
body.chatgpt-reference-v1 .chatgpt-drive-columns-row[data-drive-folder-row-id] .chatgpt-drive-columns-icon {
  color: var(--cg-text);
}
body.chatgpt-reference-v1 .chatgpt-drive-columns-icon svg { width: 16px; height: 16px; }
/* 모노 잉크(색상 구분 금지) — 파일 유형 아이콘은 색이 아니라 glyph로만 구분한다. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  flex-wrap: wrap;
}

/* M3 스와이프 삭제(우→좌) — 모바일 전용 기능이지만, 래퍼 자체(overflow:hidden 앵커 + 행의
   touch-action)는 뷰포트와 무관하게 항상 필요하다(행이 그 안에서만 좌우로 움직여야 페이지가
   안 밀린다) — 그래서 이 블록은 미디어쿼리 밖에 둔다. 실제 액션 패널(.chatgpt-drive-swipe-actions)의
   기본값은 display:none이고, 화면에 드러내는 규칙은 아래 <=900px 블록 "안에서만" 켠다 — M2에서
   겪은 함정(칩 스타일을 미디어쿼리 안에만 둬서 그 밖의 기본 규칙이 없어 1280px 데스크톱에
   스타일 없는 요소가 그대로 샌 결함)을 반복하지 않기 위해 기본값을 항상 명시적으로 밖에 둔다.
   1280px에서 렌더되는지 실측 결과는 작업 보고에 기록한다. */
body.chatgpt-reference-v1 .chatgpt-drive-swipe-row {
  position: relative;
  overflow: hidden;
}

@media (min-width: 901px) {
  /* drive-desktop-shell-sidebar-restore (2026-09-13): the global WIZON sidebar is the only
     escape route on desktop (오너 09-11 "사이드바는 항상 왼쪽") — Drive no longer removes it
     or collapses .app-shell to a single column. These two row/select-cell rules are kept
     (moved out of the now-deleted sidebar-hiding block): dropping them along with the hidden
     rules would grow row height (46px → larger), which is not part of this restore. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell-select {
    min-height: 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-select-checkbox {
    min-height: 16px;
  }
}
body.chatgpt-reference-v1 .chatgpt-drive-swipe-row .chatgpt-drive-columns-row {
  /* 컴포지터 전용 속성(transform)만 애니메이션한다 — left/margin 금지. transform 값 자체는
     bindDriveRowSwipe가 인라인 스타일로 쓰고, 여기서는 그 값이 바뀔 때의 전환 채널만 연다. */
  transition: transform var(--wizon-motion-micro-duration, 150ms) var(--wizon-motion-effect-fast, ease);
  touch-action: pan-y; /* 세로 스크롤은 브라우저에 맡기고 가로만 JS가 가져간다(preventDefault) */
}
@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .chatgpt-drive-swipe-row .chatgpt-drive-columns-row {
    transition: none;
  }
}
body.chatgpt-reference-v1 .chatgpt-drive-swipe-actions {
  display: none;
}
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-drive-swipe-actions {
    display: flex;
    position: absolute;
    inset: 0 0 0 auto;
    align-items: stretch;
    /* 닫혀 있을 때는 시각적으로도, 포커스/탭 순서에서도 존재하지 않는다 — visibility:hidden은
       display:none과 달리 레이아웃 폭 계산(actionWidth 측정)은 유지하면서 상호작용만 끈다. */
    visibility: hidden;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-swipe-row.is-drive-swipe-active .chatgpt-drive-swipe-actions {
    visibility: visible;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-swipe-delete-btn {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--ds-space-2);
    padding: 0 var(--ds-space-6);
    min-width: var(--ds-space-9);
    border: none;
    background: var(--ds-button-danger-bg);
    color: var(--ds-button-danger-fg);
    font-size: var(--ds-text-body-14);
    font-weight: 500;
    cursor: pointer;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-swipe-delete-btn:hover,
  body.chatgpt-reference-v1 .chatgpt-drive-swipe-delete-btn:focus-visible {
    background: var(--ds-button-danger-bg-hover);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-swipe-delete-btn svg {
    width: 16px;
    height: 16px;
  }
}

/* R3a Finder-style inline folder creation (docs/product/215): the draft row (renderDriveDraftRow)
   reuses the SAME row/cell classes as a real row (select/name/size/modified/actions) so list and
   grid stay visually consistent — only the name cell differs (a live input instead of the
   open-folder button). The select/size/modified/actions cells render empty; the row itself is a
   <form> (data-drive-draft-form) so its geometry needs no override beyond the folder-ink icon
   color a real folder row already gets via the data-drive-folder-row-id selector above (the
   draft has no such attribute since it isn't a real row yet).
   R3b Finder-style inline rename reuses this SAME .chatgpt-drive-draft-input/-columns-subline
   pair verbatim (renderDriveRenameNameCell) so create and rename look identical — a folder being
   renamed IS a real row, so it already carries data-drive-folder-row-id and needs no icon-color
   override of its own. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-row--draft .chatgpt-drive-columns-icon {
  color: var(--cg-text);
}
body.chatgpt-reference-v1 .chatgpt-drive-draft-input {
  width: 100%;
  min-width: 0;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-14);
  padding: 4px 8px;
  outline: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-draft-input:focus-visible {
  border-color: var(--cg-focus-ring);
  box-shadow: 0 0 0 2px var(--cg-focus-ring);
}
body.chatgpt-reference-v1 .chatgpt-drive-draft-input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
/* Error subline reuses the SAME danger token every other drive error surface already uses
   (move dialog, delete confirm) — never a raw color. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-subline.is-error {
  color: var(--cg-danger-fg);
}

/* 20260724-drive-explorer-e2b: 행 "⋮" 트리거 — 컨텍스트 메뉴의 주경로(항상 보임, 터치/키보드
   접근 가능). border-radius는 토큰만 사용, 신규 :hover는 hover 가능 기기에서만. */
body.chatgpt-reference-v1 .chatgpt-drive-context-trigger {
  display: inline-grid;
  place-items: center;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-muted);
  cursor: pointer;
}
@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-context-trigger:hover {
    background: var(--cg-hover-soft);
    color: var(--cg-text);
  }
}
body.chatgpt-reference-v1 .chatgpt-drive-context-trigger:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-context-trigger[aria-expanded="true"] {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}
/* 20260729-touch-target-a2: ⋮ 트리거(32x32) 탭 타깃을 44px로 — 시각 크기는 그대로 두고 투명
   ::after로 히트영역만 6px/변 확장. 실측(390px, 3행 스택): 행 높이 62px, 트리거는 행 안에서
   top 14.49~bottom 46.49로 위아래 여백이 각각 14~15px — 6px 확장 후에도 다음 행 트리거까지
   ~18px 여유가 남아 겹치지 않는다. 가로도 트리거가 행 우측 끝(뷰포트 기준 -12px)에 있어 6px
   확장이 뷰포트를 벗어나지 않는다. 이 확장은 행 자체 탭(모바일 선택모드에서 행 전체가 선택
   토글, handlePortalRouteClick 상단부)과 아이콘 겹침이 생기지만 — ::after는 버튼의 생성 콘텐츠라
   클릭 시 event.target은 여전히 버튼 자신이고, 선택모드 핸들러는 event.target.closest(
   "[data-drive-context-menu-trigger]")로 이 버튼을 먼저 걸러 제외하므로(app.js) 동작은
   변하지 않는다 — 다만 트리거 주변 6px 띠에서는 "행 열기/선택" 대신 "메뉴 열기"가 되므로 의도된
   변화다(실브라우저로 행 탭이 여전히 항목을 여는지 확인 완료, 아래 보고 참고). */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-drive-context-trigger {
    position: relative;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-context-trigger::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }
}
/* R3b: the ⋮ trigger goes inert (native `disabled`) on the one row currently being renamed —
   same dimmed/not-allowed treatment .chatgpt-drive-draft-input:disabled already uses (토큰만,
   raw 리터럴 없음) so both "off while editing" cues read as one visual language. */
body.chatgpt-reference-v1 .chatgpt-drive-context-trigger:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* 컨텍스트 메뉴 — openUserMsgMenu의 스크림+플로팅 메뉴 구조를 재사용하되(같은 190/200
   z-index 계열, 다른 fixed 최상위 오버레이와 공유), 리프트할 요소가 없는 단순 메뉴이므로
   스크림은 어둡게 칠하지 않고 바깥 클릭만 잡는 투명 캐처로 둔다. 항목 마크업은
   .chatgpt-library-menu-item/.chatgpt-library-popover 그대로 재사용. 좌표는 JS
   (positionDriveContextMenu)가 뷰포트 안으로 clamp한다. */
.chatgpt-drive-context-scrim {
  position: fixed;
  inset: 0;
  z-index: 190;
  background: transparent;
}
body.chatgpt-reference-v1 .chatgpt-drive-context-menu {
  position: fixed;
  z-index: 200;
  box-sizing: border-box;
  inline-size: min(288px, calc(100vw - 16px));
  max-inline-size: calc(100vw - 16px);
  min-inline-size: min(calc(var(--wizon-ai-space-unit) * 50), calc(100vw - 16px));
  padding: var(--chatgpt-route-gap-2xs);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  box-shadow: var(--cg-shadow-float-lg);
  color: var(--cg-text);
}
/* R3c: separator above 삭제 — mirrors .recent-longpress-divider's own shape (non-interactive
   div, role="separator", a 1px --cg-menu-border line inset from the popover edge) rather than
   inventing a new visual language for "danger item below a line". */
body.chatgpt-reference-v1 .chatgpt-drive-context-menu-divider {
  height: 1px;
  margin: var(--chatgpt-route-gap-2xs) var(--chatgpt-route-gap-xs);
  background: var(--cg-menu-border);
}

/* drive-toolbar-unify: 행1 "새로 만들기 ▾" 팝오버 — 트리거(.chatgpt-route-primary)와 팝오버
   (.chatgpt-library-popover)/항목(.chatgpt-library-menu-item)을 라이브러리 새로 만들기 메뉴와
   100% 동일하게 재사용하므로 여기 신규 규칙은 없다(진짜 통일 — 룩얼라이크가 아니다). */

/* drive-toolbar-unify 행2(.chatgpt-drive-filter-row) — 뼈대는 .chatgpt-library-filter-row를
   그대로 재사용(flex space-between, 반응형 규칙 포함)한다. 여기서는 LEFT 슬롯 두 콘텐츠
   (컨텍스트 라벨/일괄 작업 바)가 그 슬롯에 맞물리도록 하는 배치만 추가 — 색/반경/그림자는
   전부 기존 --cg-* 와 --ds-* 토큰 재사용, raw 리터럴 없음(anti-drift 순증 0). */
body.chatgpt-reference-v1 .chatgpt-drive-filter-context {
  margin: 0;
  min-width: 0;
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  line-height: var(--chatgpt-route-control-height);
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 행2 트랙의 버튼 라벨은 절대 줄바꿈되지 않는다 — .chatgpt-route-action 베이스는 white-space를
   지정하지 않아(normal) 슬롯이 좁아지면 "다운로 드"/"새로고 침"처럼 라벨이 두 줄로 깨지고 행
   높이가 들쭉날쭉해진다(실렌더 계측으로 확인). 드라이브 행2 스코프로만 한정해 다른 라우트의
   .chatgpt-route-action 동작은 그대로 둔다. */
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-route-action,
body.chatgpt-reference-v1 .chatgpt-drive-bulk-bar .chatgpt-route-action {
  white-space: nowrap;
}

/* D4 (drive-desktop-shell-sidebar-restore, 2026-09-13): 09-11 judge measured 4 heights
   {32,36.3,36.4,38}, 3 radii {6,8,999}, 3 fonts {13,14,15} across row-2 siblings ("새로 만들기"
   pill, "업로드"/"필터"/"정렬"/"새로고침"/"관리" outline buttons, the view-toggle segment group,
   the tree/inspector toggle buttons). Scoped to .chatgpt-drive-filter-row only — the shared
   .chatgpt-route-primary/.chatgpt-route-action base classes are reused by library/projects/apps/
   shared-links toolbars, which this candidate does not touch. Primary keeps its pill radius
   (999px) — the one accepted "주/보조 2종" exception; everything else becomes 1 radius (8px).
   Real-render measured after this block (Playwright, not hand-typed): height 1 value (36px,
   inner 32px glyphs untouched), radius {999(primary)/8}, font 1 value (13px) — see report.md. */
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-route-primary,
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-route-action,
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-drive-view-toggle,
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-drive-inspector-toggle {
  height: 36px;
  min-height: 36px;
}
/* .chatgpt-route-action already resolves font-size to 13px (--ds-text-caption-13) and svg to
   16x16 — measured, not redeclared. .chatgpt-route-primary resolves to 14px
   (--chatgpt-route-body-font-size) and the icon-only buttons (view-toggle group/buttons,
   tree/inspector toggles) to 15px — those two are unified to the SAME existing token
   (--ds-text-caption-13) rather than a new raw literal, so no new anti-drift raw-font-size
   count. Radius is unified the same way, reusing the token the view-toggle/inspector-toggle
   buttons already carry (--chatgpt-route-radius-control, 8px) instead of a raw literal. Primary
   keeps its pill radius (999px) — the accepted "주/보조 2종" exception. */
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-route-primary,
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-drive-view-toggle,
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-drive-view-toggle-btn,
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-drive-inspector-toggle {
  font-size: var(--ds-text-caption-13);
}
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-route-action,
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-drive-view-toggle-btn,
body.chatgpt-reference-v1 .chatgpt-drive-filter-row .chatgpt-drive-inspector-toggle {
  border-radius: var(--chatgpt-route-radius-control);
}

body.chatgpt-reference-v1 .chatgpt-drive-filter-trigger {
  position: relative;
}

/* 정렬 메뉴 — 방향 표시자만 신규(트리거/팝오버 자체는 .chatgpt-library-menu-anchor/-popover 재사용). */
body.chatgpt-reference-v1 .chatgpt-drive-sort-direction svg {
  width: 13px;
  height: 13px;
  transition: transform var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast);
}
body.chatgpt-reference-v1 .chatgpt-drive-sort-direction.is-asc svg {
  transform: rotate(180deg);
}

/* 일괄 작업 바 — 선택 시에만 노출. 버튼은 기존 .chatgpt-route-action 그대로 재사용.
   drive-toolbar-unify: 이제 헤더 아래 독립 블록이 아니라 행2 LEFT 슬롯에 인라인으로 얹히므로
   margin-bottom은 더 이상 필요 없고(형제가 flex 갭으로 이미 분리) min-width:0으로 좁은 화면에서
   버튼 트랙이 슬롯 폭을 넘지 않게 한다. */
body.chatgpt-reference-v1 .chatgpt-drive-bulk-bar {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-sm);
  min-width: 0;
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
}
body.chatgpt-reference-v1 .chatgpt-drive-bulk-bar-count {
  font-weight: var(--wds-font-weight-medium);
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-drive-bulk-bar-spacer {
  flex: 1 1 auto;
}

/* doc147 규칙4 — 컨텍스트/롱프레스 시트가 열려 있는 동안 문서 전체 선택 잠금(요소별
   user-select:none만으로는 이미 armed된 네이티브 선택을 못 막는다). M2의 선택모드 롱프레스도
   같은 클래스를 재사용한다(enterDriveMobileSelectMode/exitDriveMobileSelectMode) — 켜져 있는
   "시트"가 팝업 메뉴 대신 선택모드 세션 그 자체일 뿐, 손가락이 눌린 채로 480ms를 넘긴 시점에
   반응한다는 타이밍은 동일하다. */
body.drive-longpress-open,
body.drive-longpress-open * {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* 위 body 클래스는 480ms 타이머가 "발동한 뒤"에야 붙는다 — 손 누르고 있는 480ms 동안은 아직
   아무 잠금도 없다는 뜻. .recent-item(20260709-recent-longpress)이 이미 쓰는 좁은 상시 가드를
   그대로 옮겨 온다: 터치 기기에서 드라이브 행 자체는 항상 콜아웃/선택이 꺼져 있어, 타이머가
   돌아가는 동안에도(선택모드든 ⋮ 컨텍스트 메뉴든) iOS의 선택 핸들·돋보기 콜아웃이 뜨지 않는다.
   문서 전체가 아니라 행 하나로 좁힌 가드라 다른 곳(검색창 등)의 텍스트 선택은 그대로 된다. */
@media (hover: none) {
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
}

@media (max-width: 900px) {
  /* 20260724-drive-shellfit: 모바일은 인스펙터도 데스크톱 전용 — state.drive.inspectorOpen이
     true여도(트리처럼) 항상 숨기고 브라우즈 목록만 전체폭 1컬럼으로. 오버플로는 목록 자체의
     overflow-x(위)가 흡수하므로 페이지 가로 스크롤은 생기지 않는다. */
  body.chatgpt-reference-v1 .chatgpt-drive-explorer {
    grid-template-columns: minmax(0, 1fr);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-inspector {
    display: none;
  }
}

/* 하단 경로 바(브레드크럼은 renderDriveBreadcrumb 그대로, 배치만 하단으로 이동). */
body.chatgpt-reference-v1 .chatgpt-drive-pathbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: var(--chatgpt-route-gap-xs);
  border-top: 1px solid var(--cg-menu-border, var(--line));
}
body.chatgpt-reference-v1 .chatgpt-drive-pathbar-label {
  flex: 0 0 auto;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
body.chatgpt-reference-v1 .chatgpt-drive-pathbar .chatgpt-drive-breadcrumb {
  margin-top: 0;
}

/* ============================================================================
   20260725-drive-mobile-shell (M1, docs handoff): mobile-only drive route shell.
   Desktop (901px+) is untouched by everything in this block — the base (non-media) rules only
   neutralize the EXTRA breadcrumb copy renderDrivePage now emits (display:contents dissolves the
   wrapper so it doesn't disturb `.chatgpt-route-page-inner`'s grid on desktop; see the same
   pattern already used for the library-scope-docked actions wrapper above), and every visible
   change lives inside `@media (max-width: 900px)`.
   ============================================================================ */
/* renderDrivePage wraps the breadcrumb + row-1 header together so mobile can pin both below the
   fixed topbar as one sticky unit — see the <=900px override below. `display: contents` makes the
   wrapper invisible to `.chatgpt-route-page-inner`'s grid on desktop, so `<header>` still lays out
   exactly as it did before (a direct grid item); the extra breadcrumb copy is simply hidden
   there — desktop keeps its one existing bottom breadcrumb (`.chatgpt-drive-pathbar` above),
   unchanged. */
body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead {
  display: contents;
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead > .chatgpt-drive-breadcrumb {
  display: none;
}
/* 크기·수정일은 데스크톱에서 전용 컬럼(-cell--size / -cell--modified)이 담당하므로 이름 아래
   모바일 사본은 기본적으로 접혀 있다. 아래 <=900px 블록이 유일한 노출 지점이다 — 어느 폭에서든
   정확히 한쪽만 보인다. 이 규칙은 반드시 그 블록보다 '앞'에 있어야 한다(같은 특이도라 뒤에 두면
   미디어쿼리 안의 display:block을 소스 순서로 덮어버린다). */
body.chatgpt-reference-v1 .chatgpt-drive-columns-mobilemeta {
  display: none;
}
/* D1-delta Δ1 (2026-09-14): webmeta is the >=901px counterpart of mobilemeta above — same
   "hidden by default, revealed by exactly one width-scoped block" shape, so it never doubles up
   with mobilemeta/subline at any width. Its own reveal rule lives inside the D1 901px+ media
   block below (§3b) — must stay AFTER this base rule in source order for the same reason the
   comment above gives. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-webmeta {
  display: none;
}

/* ============================================================================
   drive-web-row-anatomy-and-column-ladder (D1, 2026-09-13)
   웹(>900px) 목록 행을 W-그리드 4슬롯(타일 28 | 이름 1fr | AI 색인 상태 텍스트 | ⋯ 36)으로
   재편한다 — 정본 실측: `settings-v2.css` 행 grid `28px 1fr 20px`·gap 12·padding-inline 10·
   타일 28×28 r8 (drive-redesign-design-spec-judge report.md §16-5). ≤900px(D3)·grid-view·
   gallery-filmstrip은 이 블록 밖(:not() 가드·아래 max-width:900px 블록)에서 그대로 유지된다.
   두 개의 기존 6열 정의(38308 부근 구 기준값, 42423 부근 현재 유효값 — 같은 셀렉터, 나중 정의가
   이긴다: 42423 쪽이 1440에서 실제로 이기는 값이었다)를 여기서 한 번에 4슬롯으로 대체한다.

   ★색·반경 값은 anti-drift 래칫(design-system-quality-gate.sh, raw-color/raw-border-radius/
   raw-font-size 0 증가)을 넘지 않는 방식으로만 넣는다 — 이 파일(apps/ai-portal/app/static)의
   raw_hex 검사는 `var(--x, #hex)` 폴백 안의 리터럴도 그대로 센다(font-size/border-radius와
   달리 색은 var() 로 면책되지 않는다). 그래서:
     · 반경·글자크기는 기존 --ds-radius-* / --ds-text-* 토큰(shared/design-tokens/primitive.css,
       DTCG 생성분, 손 편집 금지) 또는 이 블록 전용 로컬 커스텀 프로퍼티(비색상)로 var() 래핑.
     · 선택 행 틴트는 새 hex를 쓰지 않고, grid-view의 기존 `.is-selected` 배경이 이미 쓰는
       `color-mix(in srgb, var(--cg-focus-ring) 12%, var(--cg-surface))`(§L 17.6 "≤12%"와
       숫자까지 같은 관용구, line ~39948 기존 코드)를 리스트 행에도 그대로 재사용한다.
     · 유형 팔레트 7종(§16-3, folder/doc/sheet/deck/pdf/audio/image 고유 hue) — D1-delta ①
       (package/orders/D1-delta.md 「B. ①」)이 DTCG 소스에 정식 추가했다:
       design-system/dtcg/tokens/wizon-ai.tokens.json / .dark.tokens.json 에 `wizon.ai.filetype-*`
       8토큰(row-selected-bg 포함) 추가 → `scripts/dtcg_emit_css.py --write` 재생성 →
       shared/wds/wizon-ai-shell-tokens.css 의 `--wizon-ai-filetype-*`. 이 파일(apps/ai-portal)은
       그 var()만 참조하므로 raw-color 증가 0. ★D15ⓒ(DECISIONS.md 7부, 오너 확정)를 지킨다 —
       유형색은 **28px 타일 안 글리프에만**(아래 §2b, 이 미디어쿼리 블록으로 스코프) 적용하고,
       모바일 24px/그리드/갤러리/Miller 등 다른 문맥의 4-톤(info/success/warning/error) 매핑은
       건드리지 않는다(같은 색상이 두 채널에 나와도 크기·자리가 겹치지 않아 혼동이 없다는 게
       D15ⓒ의 근거이므로, 채널을 벗어나 전역으로 넓히면 그 근거가 깨진다).
   ============================================================================ */
@media (min-width: 901px) {
  /* ── 0. 이 블록 전용 로컬 상수(비색상 — anti-drift raw-color 대상 아님) ─────────────────── */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row {
    --d1-tile-radius: 8px;
  }

  /* ── 1. 행 = 4슬롯 1종 (list 뷰만 — grid-view/gallery-filmstrip은 별도 레이아웃) ────────── */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-head,
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row {
    grid-template-columns: 28px minmax(0, 1fr) 132px 36px;
    gap: 12px;
    min-width: 0;
    align-items: center;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row {
    height: 56px;
    min-height: 56px;
    padding-inline: 10px;
    padding-block: 0;
  }
  /* 체크박스는 기존 hover-reveal(opacity 0/1, 위 규칙 그대로) 덕에 평소엔 안 보인다 — 그래서
     행당 «보이는» 요소는 타일·이름·AI색인·⋯ 4개로 유지된다. 유형/크기/수정일 3개 컬럼은
     여기서 완전히 접는다(값 자체가 없어지는 게 아니라 AI 색인 열로 자리를 넘긴다 — §16-7 ①). */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--type,
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--size,
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--modified {
    display: none;
  }
  /* ── 1b. 이름 중간 말줄임(머리+마지막 토큰·확장자) — driveNameHeadTail(app.js) 이 stem이
     10자 이상일 때만 strong 안을 .chatgpt-drive-columns-name-head + -name-tail 2-span으로
     쪼갠다(짧은 이름은 여전히 strong 안에 평문 텍스트 1개, 이 3규칙의 영향을 안 받는다).
     span 사이 공백은 app.js가 이미 0으로 낸다 — flex 컨테이너 gap도 0(선언 안 함, 기본값)
     이라야 head/tail 사이에 틈이 생기지 않는다(드라이브 긴 이름 셀 판정문 D-1~D-5). */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-name-text > strong {
    display: flex;
    min-width: 0;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-name-head {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-name-tail {
    flex: 0 0 auto;
    white-space: nowrap;
  }
  /* ★실측으로 잡은 버그(측정 스크립트, 09-13): 헤드 행의 유형·크기·수정일 정렬 버튼은
     이 후보 이전엔 CLASS가 없는 맨 `<span role="columnheader">${sortHeader(...)}</span>`
     (app.js renderDriveColumnList)라 위 `.chatgpt-drive-columns-cell--type/--size/--modified`
     셀렉터가 헤드에는 닿지 않았다 — 그래서 헤드만 그리드 아이템 7개가 그대로 남아 4열
     템플릿을 넘쳐 암시적 2번째 그리드 행으로 줄바꿈했고(측정: AI색인 헤드 라벨이 132px
     아니라 392px(1fr) 폭으로, y=240에 나타나 첫 데이터 행(y=230~286)과 겹침 — 가림 6건).
     test_drive_row_two_line.py 의 위치-선택자 금지 계약 시험(R2b 이력이 이유를 적어 뒀다)이
     "N번째 자식" 식 셀렉터를 스타일시트 전체에서 금지하므로, 위치 대신 app.js 의 헤드
     마크업에 행과 같은 클래스(-cell-name/--type/--size/--modified)를 붙이고 여기서는
     그 클래스로 숨긴다 — 아래 셀렉터가 위 규칙과 합쳐 헤드도 행과 똑같이 4개만 남긴다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-head .chatgpt-drive-columns-cell--type,
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-head .chatgpt-drive-columns-cell--size,
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-head .chatgpt-drive-columns-cell--modified {
    display: none;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-head .chatgpt-drive-columns-cell--aiindex-head {
    font-size: var(--ds-text-caption-12, 12px);
    font-weight: 600;
    color: var(--cg-muted);
    text-transform: none;
    letter-spacing: 0;
  }

  /* ── 2. 타일 28×28 · r8 · 고스트 면(무채) — 폴더도 파일도 같은 규격 ─────────────────────── */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-icon {
    /* max-width/max-height 도 함께 올린다 — 기존 24px 규칙(줄 ~38460)이 max-width:24px 를
       걸어 둬서 width:28px 만으로는 그 상한에 잘려 24×24 로 그려졌다(실측, 09-13). */
    width: 28px;
    height: 28px;
    max-width: 28px;
    max-height: 28px;
    flex: 0 0 28px;
    border-radius: var(--d1-tile-radius, 8px);
    background: var(--cg-hover-soft);
    border: 1px solid var(--cg-sidebar-border);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-icon svg,
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-compact-file-glyph .wds-inline-icon {
    width: 22px;
    height: 22px;
  }
  /* 글리프 stroke 1.8 — lucide 기본 stroke-width 2를 살짝 낮춰 22px 안에서 §16-1 ② 값을 맞춘다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-compact-file-glyph svg {
    stroke-width: 1.8;
  }
  /* ── 2b. 유형 팔레트 7종 — 28px 타일 안 글리프 채널에만(D15ⓒ, D1-delta ①) ───────────────────
     스코프를 이 미디어쿼리(웹 리스트 행)로 좁혀, 모바일/그리드/갤러리/Miller의 기존 4-톤
     의미 매핑(위 §38467~38475, info/success/warning/error)은 그대로 둔다. doc·markdown은
     같은 hue(doc·md, DECISIONS A8)를 공유한다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--folder {
    color: var(--wizon-ai-filetype-folder);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--document,
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--markdown {
    color: var(--wizon-ai-filetype-doc);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--spreadsheet {
    color: var(--wizon-ai-filetype-sheet);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--presentation {
    color: var(--wizon-ai-filetype-deck);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--pdf {
    color: var(--wizon-ai-filetype-pdf);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--audio {
    color: var(--wizon-ai-filetype-audio);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--image {
    color: var(--wizon-ai-filetype-image);
  }
  /* D1-delta 독립 검토 D-4: hwp(hangul)·zip(archive)는 7색 팔레트 밖이라 기존 --wds-color-warning
     (#f59e0b, 양 테마 동일)을 그대로 상속했는데 라이트 L2(#f4f4f4) 위에서 1.95:1 — 팔레트 7색은
     그대로 두고(anti-drift 스코프 밖 확장은 D1-delta ① 몫), --wds-color-warning-text
     (=--wizon-ai-warning-text, 라이트 #b45309 / 다크 #f59e0b)로 이 두 종류만 바꾼다. 다크는
     토큰 값이 기존과 같아(#f59e0b) 9.65:1 유지, 라이트만 ~4.6:1로 오른다. unknown(json)은
     이미 --cg-muted로 4.57:1 통과라 손대지 않는다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--hangul,
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-compact-file-glyph--archive {
    color: var(--wds-color-warning-text);
  }
  /* unknown은 7색 팔레트 밖 — 기존 muted 매핑을 그대로 상속(위 §38467 기본 규칙, 여기서
     오버라이드하지 않음). */

  /* ── 3. 이름 15/600 + 메타 13/400, 2줄(D1-delta Δ1) ──────────────────────────────────────── */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-cell-name strong {
    font-size: var(--ds-text-body-15, 15px);
    font-weight: 600;
  }
  /* 데스크톱 목록은 부제(유형 텍스트만의 짧은 캡션)는 그대로 접는다 — 같은 정보(유형)가 이제
     아래 webmeta 2번째 줄 맨 앞에 포함된다(중복 노출 방지, subline/webmeta 둘 다 보이면 3줄이
     된다). 상태는 여전히 AI 색인 열이 맡는다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-subline {
    display: none;
  }
  /* D1-delta Δ1: 웹 행도 2줄(이름 + 메타) — D6/A2 «메타 0줄이면 크기·수정일이 화면에서
     사라진다»를 닫는다. mobilemeta와 값 출처는 완전히 같다(driveSizeLabel/driveModifiedLabel,
     app.js 참고) — 폭에 따라 정확히 한쪽 span만 보인다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-columns-webmeta {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--cg-muted);
    font-size: var(--ds-text-caption-13, 13px);
    font-weight: 400;
  }

  /* ── 4. AI 색인 상태 = 텍스트(채움 알약 금지), 실패만 6px 점 ────────────────────────────── */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-cell--aiindex {
    display: flex;
    align-items: center;
    gap: 6px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--ds-text-caption-13, 13px);
    font-weight: 400;
    color: var(--cg-muted);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-row-aiindex-dot {
    width: 6px;
    height: 6px;
    border-radius: var(--ds-radius-pill, 999px);
    flex: 0 0 auto;
    background: var(--cg-danger-fg);
  }

  /* ── 5. ⋯ 트리거 36 (기존 40 → 36) ───────────────────────────────────────────────────────── */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-context-trigger {
    width: 36px;
    height: 36px;
    position: relative;
  }
  /* 20260915-drive-context-trigger-44-desktop: 데스크톱(>=901px) 목록 행의 ⋯ 트리거는 위
     36×36 시각 크기만 있고 히트 영역 확장이 없었다(실측: getBoundingClientRect·elementFromPoint
     격자 탐침 둘 다 36×36 — §K-5 44px 정책선 미달). 이미 <=900px에 있는
     20260729-touch-target-a2 관례(투명 ::after + min-width/min-height:44px + 중앙 정렬)를
     그대로 이 폭에도 복제한다 — 새 규약이 아니라 같은 파일이 이미 쓰는 것과 같은 규약 1종.
     시각 36px·행 높이 56px·아이콘은 무변경(::after는 흐름 밖 position:absolute라 레이아웃에
     영향 없음). 겹침 실측(1440, 폴더 행): ::after 좌측 끝(트리거 중심 -22px)이 12px gap
     안에 머물러 132px AI색인 컬럼 텍스트(우측 정렬)와 교차하지 않고, 우측 끝(+22px)도 행의
     10px inline padding 안에 들어가 행 경계를 넘지 않는다(실측표는 작업 보고 참고). */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row .chatgpt-drive-context-trigger::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }

  /* ── 6. 선택 행 = 유채 틴트 1종(오너 확정 2ⓐ, §L 17.6 ≤12% 안쪽) — 좌측 잉크 바는 이 문법
     에서 걷는다(단일 표현 규칙). D1-delta ②: `--cg-focus-ring` 기반 color-mix는 무채(S=0%)라
     §16-5 미달(라이트 6.1%·다크 9.8% 미달)이었다 — DTCG로 신설한 `--wizon-ai-row-selected-bg`
     (라이트 #e8eef7 채도 6.1% · 다크 #252729 채도 9.8%)로 교체한다. 호버는 무채 그대로. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row.is-selected {
    background: var(--wizon-ai-row-selected-bg);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row.is-selected::before {
    display: none;
  }
  /* D1-delta 독립 검토 D-3: 선택 행의 새 유채 틴트(#e8eef7) 위에서 --cg-muted(라이트
     #6f6f6f)가 4.31:1로 AA 미달(비선택 5.02:1과 대비) — COMPONENT-RULES.md §7은 「muted는
     #666666」을 지목하지만 그 값은 이 스타일시트에 raw hex로 없다(anti-drift raw-color
     래칫이 새 hex 도입을 막는다). 이미 존재하는 더 어두운 중립 토큰
     --cg-text-soft(=--wizon-ai-text-secondary, 라이트 #3f3f3f)로 대신한다 — 12:1대라
     ≥4.5 여유 있게 통과. **라이트 전용** — 다크 선택 틴트(#252729)는 이미 --cg-muted
     (#b4b4b4)로 7.23:1 통과라 손대지 않는다. */
  html:not([data-theme="dark"]) body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row.is-selected .chatgpt-drive-columns-webmeta,
  html:not([data-theme="dark"]) body.chatgpt-reference-v1 .chatgpt-drive-columns:not(.is-grid-view) .chatgpt-drive-columns-row.is-selected .chatgpt-drive-columns-cell--aiindex {
    color: var(--cg-text-soft);
  }

  /* ── 7. 목록 = 카드 1장(1px·overflow는 스크롤 유지 위해 auto 그대로) — 정본 그룹 카드 문법 */
  body.chatgpt-reference-v1 .chatgpt-drive-columns-pane {
    border: 1px solid var(--cg-sidebar-border);
    border-radius: var(--ds-radius-md, 14px);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns-head {
    border-radius: var(--ds-radius-md, 14px) var(--ds-radius-md, 14px) 0 0;
  }

  /* ── 8. 레일(폴더 열) — 글리프 타일 24 (§16-5/D1 할 일 6, 부분. 행 40 은 이 컴포넌트가
     ≤900px 에서 통째로 display:none 이라 여기서 다시 감쌀 필요가 없어 base 규칙
     `.chatgpt-drive-tree-root, .chatgpt-drive-tree-row`(min-height, line ~42120)을 직접
     38→40으로 고쳤다 — 두 자리로 나뉜 카스케이드가 소스 순서 때문에 서로를 덮어써
     40이 적용 안 되던 실측 버그(측정 스크립트로 잡음)를 그렇게 수리했다.
     ★정직한 값 한계(남은 위험, report.md): 오너 발주서의 «개수 12 muted 우측»은 넣지
     않는다 — drive-service `FolderSummary`(engines/drive-service/app/models.py:15)에
     하위 항목 개수 필드가 전혀 없다("size_bytes"도 폴더는 항상 None, 주석이 스스로
     "aggregate size is not computed"라고 적는다). 없는 값을 날조하지 않는다는
     raw_data_isolation 원칙(위 renderDriveRowAiIndexCell 주석과 같은 근거)에 따라
     타일만 이 후보에서 맞추고, 개수는 백엔드 집계 API가 생기는 별도 후보로 미룬다. */
  body.chatgpt-reference-v1 .chatgpt-drive-tree-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    flex: 0 0 24px;
    border-radius: var(--d1-tile-radius, 8px);
    background: var(--cg-hover-soft);
    border: 1px solid var(--cg-sidebar-border);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-tree-icon svg {
    width: 16px;
    height: 16px;
  }
}

@media (max-width: 900px) {
  /* R4 고정 헤더: 경로(breadcrumb) + 검색바까지 상단 고정, 목록만 스크롤. `.chat-pane`가 실제
     스크롤 컨테이너(`.portal-route-open .chat-pane { overflow-y:auto }`, 위쪽 참고)이므로
     `position: sticky; top: 0` 은 고정 상단바 바로 아래에서 정확히 붙는다. 큰 타이틀(h1)은 이미
     시스템 규칙으로 숨겨져 있어 여기서 다시 숨기지 않는다. */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead {
    display: flex;
    flex-direction: column;
    gap: var(--chatgpt-route-gap-xs);
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--cg-bg);
    padding-bottom: var(--chatgpt-route-gap-xs);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead > .chatgpt-drive-breadcrumb {
    display: flex;
  }
  /* 마지막 조각을 굵게 — 데스크톱 하단 경로 바의 기존 .is-current(500)는 그대로 두고, 이 모바일
     사본에만 적용(선택자가 .chatgpt-drive-mobile-stickyhead로 스코프돼 있어 데스크톱 무회귀). */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead .chatgpt-drive-breadcrumb-item.is-current {
    font-weight: 700;
  }
  /* 상단에 고정 사본이 생겼으니 목록 맨 아래 원래 경로 바는 중복 — 모바일에서만 숨긴다. */
  body.chatgpt-reference-v1 .chatgpt-drive-pathbar {
    display: none;
  }

  /* 20260725-drive-mobile-topgap (M1 audit fix): 고정 헤더가 상단바 '바로 아래'에 붙어야
     목업 화면 1이 되는데, 실측 390px에서 경로 바가 166px에서 시작했다 — 상단바 아래로 112px가
     빈 채였다(844px 화면의 13%). 원인은 위 `20260724-drive-shellfit` 블록의
     `padding: var(--chatgpt-route-page-top) …`(=112px)가 모바일 전용 토큰
     `--chatgpt-route-page-mobile-top`(=16px)을 적용하는 <=900px 규칙보다 뒤에 와서 폭에
     상관없이 덮어버리는 것. 드라이브 라우트에만, <=900px에서만 되돌린다(큰 H1은 이미 숨겨져
     있어 이 여백이 받쳐줄 대상도 없다). 회귀 핀: DriveMobileTopGapTests. */
  html[data-portal-route="/drive"] #portalPage.chatgpt-route-page {
    padding-top: var(--chatgpt-route-page-mobile-top);
  }

  /* ── 20260725-drive-mobile-consolidation (M1 audit fix) ──────────────────────────────
     M1은 ⋯ 시트를 "새로 만들기/정렬/보기전환/새로고침의 모바일 통합"으로 추가했지만, 정작
     통합 대상인 데스크톱 툴바를 숨기지 않아 같은 액션이 두 곳에 남았다(승인 목업 화면 1에는
     이 컨트롤들이 없다). 실측 390px: 「새로 만들기」 필(131.6x39.8)이 고정 헤더 안에 박히고,
     그 아래 정렬+뷰토글(컬럼 포함)+새로고침 행이 38px를 더 먹어 목록 시작이 388px로 밀렸다.
     시트가 일부러 제외한 컬럼(Miller) 뷰도 이 툴바로는 그대로 열 수 있었다.
     회귀 핀: DriveMobileToolbarConsolidationTests. */
  body.chatgpt-reference-v1 .chatgpt-drive-page .chatgpt-route-page-actions [data-drive-new-trigger],
  body.chatgpt-reference-v1 .chatgpt-drive-page .chatgpt-library-view-controls {
    display: none;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-command-bar > .chatgpt-library-menu-anchor,
  body.chatgpt-reference-v1 .chatgpt-drive-command-bar > [data-drive-action="upload"] {
    display: none;
  }

  /* 목록 헤더 행(선택 전체 + 이름/크기/수정일 정렬 헤더)도 모바일에는 없다 — 정렬은 시트가
     갖고, 선택 모드는 M2 범위다. 목업 화면 1·5 모두 헤더 없는 순수 행 목록이다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns-head {
    display: none;
  }

  /* M2: renderDriveBulkBar(행2 왼쪽 슬롯, 다운로드/삭제/선택 해제 필)는 M1 당시 모바일에서
     선택할 방법 자체가 없어(체크박스 숨김) driveSelectedCount() > 0이 될 수 없는 죽은 코드
     경로였다 — 그래서 이 행을 따로 숨길 필요가 없었다. M2가 롱프레스로 진짜 선택을 도입하면서
     이 조건이 처음으로 참이 될 수 있게 됐고, 그 순간 데스크톱용 이 필이 하단 플로팅 그룹칩과
     동시에 화면에 뜬다(같은 화면에 "선택 작업" UI가 두 벌 — 실측: 390px에서 "1개 선택됨" 필이
     고정 헤더 바로 아래에 뜨고, 그 아래 플로팅 칩까지 이중으로 보였다). 모바일의 유일한 선택
     작업 표면은 이제 아래 .chatgpt-drive-mobile-select-chips뿐이다. */
  body.chatgpt-reference-v1 .chatgpt-drive-bulk-bar {
    display: none;
  }

  /* ── 행을 화면 폭에 맞춘다 ────────────────────────────────────────────────────────────
     `.chatgpt-drive-columns-row`의 `min-width: 496px`(28+1fr+96+128+40 트랙) 때문에 390px
     화면에서 행이 496px로 넘쳐 `.chat-pane`이 가로 스크롤(scrollWidth 421 > clientWidth 390)
     되고, 행 ⋯가 화면 밖 오른쪽 110px에 놓여 elementFromPoint가 아무것도 잡지 못했다 —
     즉 목업이 요구하는 "행마다 ⋯"가 실제로는 누를 수 없었다. 모바일에서는 크기/수정일 컬럼과
     선택 셀을 접고 [이름][⋯] 2트랙으로 만든다. 접힌 두 값은 이름 아래
     `.chatgpt-drive-columns-mobilemeta`로 그대로 보인다(renderDriveColumnRow 참고).
     회귀 핀: DriveMobileRowFitsViewportTests. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row {
    grid-template-columns: minmax(0, 1fr) 40px;
    min-width: 0;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell-select,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--type,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--size,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--modified,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--aiindex {
    display: none;
  }
  /* 아이콘 칩 + 이름 셀은 남은 1fr을 전부 쓰고, 긴 이름은 말줄임으로 접힌다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell-name {
    min-width: 0;
  }
  /* drive-mobile-folder-chevron (2026-09-14, judge ⓓ): 폴더 행 disclosure(chevron) — judge
     report §4 실측 그대로: 슬롯 22 · 글리프 20, .chatgpt-drive-row-open 안 맨 끝(app.js). 그
     버튼 자체는 display:block인데 ⋯ 처럼 우측 끝에 고정하려면 여기서만 flex로 바꿔야 한다
     (C1 — 밖에 두면 탭이 "선택"이 되므로 반드시 버튼 안, 이 override는 ≤900px 폴더 행에만
     스코프). 이름 셀은 그 결과로 자동 −34px(judge report §4 실측: 390에서 262→228) — 새 값을
     손으로 계산하지 않고 flex가 남는 폭을 계산하게 둔다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row[data-drive-folder-row-id] .chatgpt-drive-row-open {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row[data-drive-folder-row-id] .chatgpt-drive-row-open > .chatgpt-drive-columns-name-text {
    flex: 1 1 auto;
    min-width: 0;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns-disclosure {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 22px;
    width: 22px;
    height: 22px;
    color: var(--cg-muted);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns-disclosure svg {
    width: 20px;
    height: 20px;
  }
  /* 목업 화면 1의 행은 정확히 2줄이다. 파일 행에서는 유형 라벨까지 합친 이 한 줄이 원래
     subline을 '대체'한다 — 둘을 함께 보이면 이름/유형/크기·날짜 3줄이 되어 행 높이가 62->86px로
     불어난다(실측). 폴더 행에는 mobilemeta가 없으므로 subline("편집 가능")이 그대로 남는다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns-mobilemeta {
    display: block;
    color: var(--cg-muted);
    font-size: var(--ds-text-caption-12);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns-name-text:has(> .chatgpt-drive-columns-mobilemeta) > .chatgpt-drive-columns-subline {
    display: none;
  }

  /* M2 (모바일 선택모드): 길게 눌러 선택모드에 들어가면 위 [이름][⋮] 2트랙 위에 체크박스 트랙을
     다시 얹는다 — renderDriveColumnList가 붙이는 .chatgpt-drive-columns--select-mode 컨테이너
     한정이라, 평소(선택모드 아님)의 2트랙/숨김 체크박스 규칙은 그대로 살아있다(더 구체적인
     선택자라 순서와 무관하게 이 규칙이 이긴다). 체크박스 자체 크기·색은 기존
     .chatgpt-drive-select-checkbox를 그대로 쓴다 — 새 시각 언어를 만들지 않는다. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns--select-mode .chatgpt-drive-columns-row {
    grid-template-columns: 28px minmax(0, 1fr) 40px;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns--select-mode .chatgpt-drive-columns-cell-select {
    display: flex;
  }
  /* 위 desktop 전용 규칙 `@media (hover: hover) and (pointer: fine) { .chatgpt-drive-select-
     checkbox { opacity: 0 } }`은 미세 포인터 장치에서 체크박스를 평소엔 숨긴다 — 실기기라면
     터치는 hover:none이라 상관없지만, 데스크톱 창을 그냥 900px 이하로 좁히기만 한 채(터치
     에뮬레이션 없이) 확인하면 마우스/트랙패드는 여전히 pointer:fine이라 이 규칙이 그대로
     적용돼 선택 중이 아닌 행의 체크박스가 전부 투명해진다(실측: Electron 데스크톱 브라우저를
     375px로 리사이즈만 했을 때 unchecked 체크박스 opacity 0, rect 16x42 — DOM엔 있지만 안
     보임). 선택모드 자체가 "체크박스가 보여야 하는" 유일한 조건이므로, 포인터 종류와 무관하게
     여기서 명시적으로 1로 고정한다(선택자가 한 클래스 더 많아 hover 규칙보다 항상 이긴다). */
  body.chatgpt-reference-v1 .chatgpt-drive-columns--select-mode .chatgpt-drive-select-checkbox {
    opacity: 1;
  }
}

/* 20260725-drive-mobile-menu (M1): the fixed topbar's drive-only "..." trigger opens this as a
   bottom sheet. Reuses `.chatgpt-library-popover`'s card look (border/background/menu-item
   spacing) as-is — only POSITION is overridden below. Unlike the composer/library/etc popovers
   gated at max-width:520px (they still fall back to an anchored desktop dropdown above that),
   this trigger only ever exists <=900px (`.chatgpt-mobile-topbar` itself is display:none above
   that) and its popover renders inside the SCROLLING route body while its trigger lives in the
   separate FIXED topbar — the two are not DOM-adjacent, so the 520px list's anchored-dropdown
   fallback would float wrong if it ever applied here. One always-fixed rule across the full
   900px shelf avoids that instead of sharing the 520px-gated list.
   20260725-drive-sheet-desktop-guard (audit fix): "the trigger only exists <=900px" makes the
   sheet unreachable at desktop widths, but it does NOT make it un-RENDERED. Nothing closes
   state.drive.mobileMenuOpen on resize (handleViewportReflow does not, and renderDrivePage keeps
   emitting the markup), so widening the window with the sheet open left an orphan popover styled
   by `.chatgpt-library-popover`'s desktop base rules — measured at 1280px: position absolute,
   bottom -276px, width 200px, i.e. a stray 268px-tall dropdown hanging off the route page. The
   base `display: none` below is the same shape of safety net the tab bar uses, for the same
   reason. Regression pin: DriveMobileSheetDesktopGuardTests. */
body.chatgpt-reference-v1 .chatgpt-drive-mobile-menu,
body.chatgpt-reference-v1 .chatgpt-drive-mobile-submenu {
  display: none;
}

@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-menu,
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-submenu {
    display: grid;
    position: fixed;
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    width: 100%;
    max-width: none;
    height: auto;
    max-height: min(70vh, calc(var(--wizon-vvh, 100dvh) - 96px));
    align-content: start;
    overflow-y: auto;
    margin: 0;
    padding: 8px 8px calc(12px + var(--wizon-safe-bottom));
    border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0;
    box-shadow: var(--cg-shadow-float-lg);
    z-index: 200;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms) var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }

  /* R-41 B2b(할 일 4, 설계 §3.3·§6-①) — ≤520 에서 이 ::after 가 .chatgpt-library-popover
     클래스도 함께 가진 같은 엘리먼트에 옛 ::before(당시 44147행)와 겹쳐 막대 2개를 그리던
     결함(설계 §6-①)의 원인이었다. 이 클래스가 쓰이는 두 템플릿(드라이브 모바일 메뉴·검색
     필터/정렬의 -submenu 변형) 모두 이제 data-sheet-kind 를 선언해 핸들이 항상 있다 —
     S1(44090행 주석)이 이미 세운 선례와 같이, 조건부로 끄는 대신 완전히 지운다(막대는
     DOM 핸들 하나로 통합, test_mobile_sheet_handle_single.py 가 옛 규칙 부재를 고정). */
}

/* 20260725-drive-mobile-tabbar (M1): drive-route-only bottom tab bar (프로젝트/드라이브/산출물/
   일정 — index.html의 기존 목적지 재사용, 사이드바는 건드리지 않음). position:fixed라 DOM
   위치와 무관하게 뷰포트 바닥에 붙는다(바텀시트와 동일 기법). `.chat-pane`에 같은 높이만큼
   하단 패딩을 더해, 스크롤되는 목록 마지막 행이 탭바 밑에 가려지지 않게 한다.
   Unlike the topbar's drive-only trigger (safely nested inside `.chatgpt-mobile-topbar`, which is
   itself `display: none` on desktop — an ancestor's display:none hides descendants regardless of
   their own JS `hidden` state), this tab bar is a standalone <nav> with no such ancestor. Its
   visibility is JS-driven (`hidden` attribute, see syncPortalRouteSurfaceVisibility) but resizing
   across the 900px breakpoint does NOT re-run that sync — so a bare `<nav>` UA default of
   `display: block` would show it, unstyled, at desktop widths if `hidden` was ever cleared while
   narrow. This explicit `display: none` base rule is the safety net (mirrors
   `.chatgpt-mobile-topbar`'s own base rule for the identical reason) — the <=900px block below is
   the only place it becomes visible, independent of the `hidden` attribute's timing.
   20260725-drive-tabbar-hidden-gate (audit fix): the <=900px rule below MUST carry
   `:not([hidden])`. An author-sheet `display: grid` outranks the UA sheet's `[hidden] {
   display: none }`, so without the guard the `hidden` attribute syncPortalRouteSurfaceVisibility
   sets is silently defeated and this drive-only bar renders on EVERY mobile route (library /
   projects / scheduled) and over the chat home's composer. Measured before the fix at 390px:
   `hidden === true` yet `getComputedStyle().display === "grid"`, height 67.4px, on all four
   surfaces. Regression pin: DriveTabbarHiddenAttributeGateTests. */
body.chatgpt-reference-v1 .chatgpt-mobile-drive-tabbar {
  display: none;
}

/* 20260728-select-chips-desktop-leak (audit fix): 칩 행도 탭바와 똑같이 기본값을 숨김으로 둔다.
   M2의 나머지 칩 스타일은 전부 아래 <=900px 블록 안에 있었지만 이 기본 규칙이 없어서, 데스크톱에서
   선택모드 상태가 되면 스타일 없는 칩 행이 그대로 렌더됐다 — 1280px 실측: `display: block`,
   너비 968px, 높이 32px로 보이고 데스크톱 자체 벌크바와 겹쳤다.

   진입 자체는 isMobileSheetViewport()(max-width: 900px)로 막혀 있으므로 데스크톱에서 새로
   선택모드에 들어갈 수는 없다. 문제는 그 다음이다: 폭 검사는 롱프레스 '시점'에 한 번만 하고
   state.drive.mobileSelectMode는 그 뒤 창 크기와 무관하게 남는다. 그래서 모바일 폭에서
   선택모드에 들어간 뒤 창을 넓히거나 기기를 가로로 돌리면 이 상태가 그대로 데스크톱 레이아웃에
   들어온다. 회귀 핀: DesktopDoesNotShowTheMobileSelectChipsTests. */
body.chatgpt-reference-v1 .chatgpt-drive-mobile-select-chips {
  display: none;
}

@media (max-width: 900px) {
  /* One authoritative height for the bar, consumed BOTH by the bar itself and by the scroll
     reserve below, so the two can never drift apart. */
  body.chatgpt-reference-v1 .chatgpt-mobile-drive-tabbar:not([hidden]) {
    --wizon-drive-tabbar-block: 68px;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    min-height: var(--wizon-drive-tabbar-block);
    background: var(--cg-bg);
    border-top: 1px solid var(--cg-menu-border);
    padding: 6px calc(var(--wizon-safe-right, 0px)) calc(6px + var(--wizon-safe-bottom)) calc(var(--wizon-safe-left, 0px));
  }
  body.chatgpt-reference-v1 .chatgpt-mobile-drive-tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 4px 2px;
    border: 0;
    background: transparent;
    color: var(--cg-muted);
    font-size: var(--wds-font-size-caption);
    text-decoration: none;
    cursor: pointer;
    position: relative;
  }
  body.chatgpt-reference-v1 .chatgpt-mobile-drive-tab svg {
    width: 22px;
    height: 22px;
  }
  body.chatgpt-reference-v1 .chatgpt-mobile-drive-tab.is-active {
    color: var(--cg-text);
  }
  /* 승인 목업 화면 1·6의 `.tab.on::before` — 활성 탭 상단 2px 인디케이터(24px 너비). 색만
     바꾸면 활성 탭이 "조금 진한 회색"으로만 읽혀 목업의 신호 강도에 못 미친다. */
  body.chatgpt-reference-v1 .chatgpt-mobile-drive-tab.is-active::before {
    content: "";
    position: absolute;
    top: -6px;
    width: 24px;
    height: 2px;
    border-radius: var(--wizon-ai-radius-pill, 999px);
    background: var(--cg-text);
  }
  /* 키보드가 올라오면 하단 네비게이션은 숨긴다(iOS·Android 공통 관행). position:fixed bottom:0은
     Android처럼 레이아웃 뷰포트가 줄어드는 환경에서 키보드 위에 떠 검색 입력과 목록을 동시에
     좁히고, iOS에서는 키보드 아래에 깔려 아무 값도 못 낸다. 함께 스크롤 예약도 회수한다. */
  html.is-keyboard-open body.chatgpt-reference-v1 .chatgpt-mobile-drive-tabbar {
    display: none;
  }
  /* 스크롤 예약은 `.chat-pane`이 아니라 드라이브 라우트 페이지에 얹는다. `.chat-pane`에는
     `@media (max-width: 520px)`의 `html[data-chat-state="empty"] … { padding-bottom: 10px
     !important }`(위쪽 참고)가 있어, 대화가 없는 상태(로그인 직후 드라이브로 바로 들어오는
     기본 경로)에서는 예약이 조용히 무효화된다 — 실측 결과 390px/empty에서 예약이 10px로
     덮여 홈 인디케이터가 있는 실기기에서는 마지막 행이 탭바에 가려진다. 라우트 페이지에는
     경쟁하는 !important가 없다. 회귀 핀: DriveTabbarScrollReserveTests. */
  html[data-portal-route="/drive"] body.chatgpt-reference-v1.portal-route-open .chatgpt-drive-page {
    /* 68px tab bar + 56px FAB + 20px air + safe area. This keeps the final
       Finder row fully visible instead of letting the fixed FAB cover it. */
    padding-bottom: calc(68px + 76px + var(--wizon-safe-bottom) + 8px);
  }
  /* 키보드가 올라오면 탭바를 숨기므로 예약도 회수한다. 선택자는 위 예약 규칙보다 반드시 더
     구체적이어야 한다 — `html.is-keyboard-open body… .chatgpt-drive-page`(클래스 3)로는 예약
     규칙(속성 1 + 클래스 3)에 밀려 조용히 무시된다(실측: 76px 그대로 유지). */
  html.is-keyboard-open[data-portal-route="/drive"] body.chatgpt-reference-v1.portal-route-open .chatgpt-drive-page {
    padding-bottom: 0;
  }

  /* M2: 선택모드 그룹칩이 탭바 위에 position:fixed로 떠 있는 동안에는 목록 마지막 행이 그
     칩에 가려질 수 있다 — 위 탭바 전용 예약(68px)과 같은 계열로, 칩 높이(44px 버튼 + 위아래
     여백)만큼 더 얹는다. `:has()`로 렌더된 칩 유무를 직접 읽으므로 selectMode용 body 클래스를
     따로 만들 필요가 없다(선택자 자체가 이미 .chatgpt-drive-page보다 더 구체적이라 순서 무관하게
     이 규칙이 이긴다). 키보드가 뜨면 위 리셋 규칙(html.is-keyboard-open… 접두사가 하나 더 붙어
     더 구체적이다)이 그대로 이겨 0으로 되돌아간다 — 칩은 선택모드 중 검색창을 누르는 극히 드문
     경우를 빼면 키보드와 동시에 뜨지 않는다. */
  html[data-portal-route="/drive"] body.chatgpt-reference-v1.portal-route-open .chatgpt-drive-page:has(.chatgpt-drive-mobile-select-chips) {
    padding-bottom: calc(68px + 76px + 56px + var(--wizon-safe-bottom) + 8px);
  }
}

/* M2 (모바일 선택모드) 그룹칩 — 탭바 바로 위에 뜨는 4개의 독립된 floating 버튼. 제품 결정:
   컨테이너 자체는 경계선/막대/배경을 두지 않는다("리스트 상위 레이어" 개념 — 나중에 리스트
   위에 버튼만 떠 있어야 한다) — 그래서 아래 .chatgpt-drive-mobile-select-chips 규칙은 순수하게
   레이아웃(위치·간격·가로 스크롤)만 맡고 background/border/box-shadow가 전혀 없다. 개별 버튼
   (.chatgpt-drive-mobile-select-chip)만 자기 몫의 표면(배경+그림자)을 갖는다 — 안 그러면 목록
   위에 텍스트만 둥둥 떠 가독성이 없다. .chatgpt-drive-view-toggle-btn.is-active가 이미 쓰는
   "border:0 + background:var(--cg-surface) + box-shadow:var(--cg-shadow-float-xs)" 조합과 같은
   무경계 표면 패턴을 재사용한다(새 시각 언어 아님). */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-select-chips {
    position: fixed;
    left: 0;
    right: 0;
    /* 68px = 탭바 자체 높이(--wizon-drive-tabbar-block과 같은 값 — 커스텀 프로퍼티는 형제
       요소로 상속되지 않아 여기서 재선언할 수 없으므로, 탭바 쪽 값이 바뀌면 이 숫자도 같이
       바꿔야 한다. 탭바의 스크롤 예약(위 DriveTabbarScrollReserveTests 규칙)도 같은 이유로
       이미 68을 하드코딩하고 있다). */
    bottom: calc(68px + var(--wizon-safe-bottom) + 10px);
    z-index: 21;
    display: flex;
    gap: 8px;
    padding: 0 calc(12px + var(--wizon-safe-right)) 0 calc(12px + var(--wizon-safe-left));
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-select-chips::-webkit-scrollbar {
    display: none;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-select-chip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 44px;
    padding: 0 16px;
    border: 0;
    border-radius: var(--wizon-ai-radius-pill, 999px);
    background: var(--cg-surface);
    color: var(--cg-text);
    font: inherit;
    font-size: var(--ds-text-caption-13);
    font-weight: var(--wds-font-weight-medium);
    box-shadow: var(--cg-shadow-float-lg);
    cursor: pointer;
    white-space: nowrap;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-select-chip svg {
    width: 16px;
    height: 16px;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-select-chip.is-danger {
    color: var(--cg-danger-fg);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-select-chip:disabled {
    opacity: 0.4;
    cursor: default;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-select-chip:active {
    background: var(--cg-hover-soft);
  }
}

/* ============================================================================
   20260724-drive-shellfit: 우측 인스펙터 패널(docs/product/215, 셸 정합 재구성). 선택 없음
   (현재 폴더 요약)/단일 선택(세부 정보+작업 버튼)/다중 선택(합계+일괄 작업) 3가지 콘텐츠를
   전부 같은 레이아웃 클래스로 담는다 — 콘텐츠는 renderDriveInspectorPane이 고르고, 여기서는
   배치만. 버튼은 새 클래스가 아니라 기존 .chatgpt-route-action을 재사용(패널 폭에 맞춰 전체
   너비로만 재배치). 전부 기존 --cg-*·--chatgpt-route-*·--ds-*·--wizon-ai-* 토큰, raw 색상·
   반경/!important 신규 없음(anti-drift 순증 0).
   ============================================================================ */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: 0 0 auto;
  gap: var(--chatgpt-route-gap-xs);
  padding: var(--chatgpt-route-gap-sm) var(--chatgpt-route-gap-md);
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-head h2 {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  font-weight: var(--wds-font-weight-medium);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-toggle {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-muted);
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-toggle svg {
  width: 16px;
  height: 16px;
}
@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-inspector-toggle:hover {
    background: var(--cg-hover-soft);
    color: var(--cg-text);
  }
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-toggle:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-toggle[aria-pressed="true"] {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--chatgpt-route-gap-md);
}
/* B2(D2-fix5, 09-14 재검토 §2-B): 마지막 세부 행이 스크롤되지 않은 상태에서 발 경계를
   16.5px 넘겨 잘려 보이고(scrollTop=0 에서 측정) 어떤 스크롤 단서도 없었다(review.md 실측:
   본문 바닥 781.3 vs 행 바닥 797.8). 실제로는 스크롤이 가능하고 끝까지 내리면 행 전체가
   보인다(over=45, 스크롤 후 overflowBelow -28.5) — 없는 건 «스크롤 가능» 그 자체가 아니라
   «스크롤해야 한다는 신호». 이 파일 5701행 .artifact-ribbon-row(가로) 와 같은
   scroll(self) 타임라인 패턴을 세로로 재사용한다: 스크롤 가능(over>0)할 때만 바닥 20px 를
   서서히 지우는 페이드를 걸고, 끝까지 스크롤하면 사라진다. 넘치지 않는 선택(폴더·다중,
   over=0 실측)은 애니메이션 타임라인이 비활성이라 이 규칙 자체가 적용되지 않고 mask-image
   가 초기값(none)으로 남는다 — ribbon-row 처럼 base 셀렉터에 정적 mask-image 를 중복
   선언하지 않은 이유(그 정적 폴백을 넣으면 over=0 에서도 항상 페이드가 걸려 실제 콘텐츠
   위에 그려진다 — 실측 확인: over=0 인 두 선택 모두 clientHeight==scrollHeight, 여유
   마진이 0이라 정적 폴백은 즉시 회귀였다). animation-timeline 미지원 브라우저는 이 규칙이
   전부 무효라 기존 동작(신호 없음) 그대로 — 새 결함을 얹지 않는다. */
@supports (animation-timeline: scroll(self block)) {
  body.chatgpt-reference-v1 .chatgpt-drive-inspector-body {
    animation: 1ms linear forwards chatgpt-drive-inspector-body-fade-scroll;
    animation-timeline: scroll(self block);
  }
}
@keyframes chatgpt-drive-inspector-body-fade-scroll {
  from {
    mask-image: linear-gradient(180deg, currentColor 0, currentColor calc(100% - 20px), transparent 100%);
  }
  to {
    mask-image: linear-gradient(180deg, currentColor 0, currentColor 100%);
  }
}
body.chatgpt-reference-v1 .chatgpt-drive-activity-list {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-activity-item {
  display: grid;
  grid-template-columns: var(--wizon-ai-action-target-sm) minmax(0, 1fr);
  align-items: start;
  gap: var(--chatgpt-route-gap-xs);
}
body.chatgpt-reference-v1 .chatgpt-drive-activity-marker {
  display: inline-grid;
  place-items: center;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-activity-marker svg {
  width: 16px;
  height: 16px;
}
body.chatgpt-reference-v1 .chatgpt-drive-activity-copy {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-2xs);
  min-width: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-activity-copy strong {
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  font-weight: var(--wds-font-weight-medium);
}
body.chatgpt-reference-v1 .chatgpt-drive-activity-copy span,
body.chatgpt-reference-v1 .chatgpt-drive-activity-copy small {
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-drive-activity-more {
  width: 100%;
  margin-top: var(--chatgpt-route-gap-md);
  justify-content: center;
}
/* D2 (drive-inspector-panel-hero-and-rows, v8 §1 히어로 카드): one card — full-width 180px
   thumbnail box, title, one-line meta, an optional plain 3-column metrics row (NO well
   background/border, C1 정정), an optional source line. padding 16 · radius 14(--wds-radius-lg)
   · 1px border · shadow none — same L1 surface token every other card on this screen uses, no
   new raw color. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-sm);
  /* F20(inspector-width-cleanup, 09-14 중앙 재검토): v8 D2 정본 padding 은 16 인데 이 규칙은
     --chatgpt-route-gap-md(24)를 쓰고 있었다 — 실측 드리프트(패널 280·padding 24×2 로는
     정본 썸네일 220 은커녕 197 만 나온다). --ds-space-4(16, F9 리듬 표의 정본 카드 padding과
     동일 스케일)로 복귀. */
  padding: var(--ds-space-4, 16px);
  /* F9/C2: 짝인 .chatgpt-drive-inspector-section 패딩(위 참조, 12)과 더해 정본 사다리를
     만든다 — 리듬은 {8,12,24}(padding 16 은 자체 사다리가 아니라 카드 내부 값). */
  margin-bottom: 12px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-lg);
  background: var(--cg-surface);
  box-shadow: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  height: 180px;
  overflow: hidden;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
}
/* D3 — files only: a same-size overlay button (sibling, not a wrapper — see the app.js comment
   at renderDriveInspectorHeroCard for why) opens the fullscreen preview modal. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb-expand {
  position: absolute;
  inset: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb-expand:focus-visible {
  outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
  outline-offset: -2px;
}
/* Tier ① — a real live-preview surface (iframe/canvas/img/audio/video) owns the box; it must
   fill it and never expose its own scroll/controls chrome at this compact size (the same
   content reopens interactive and full-size in the fullscreen modal). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview {
  width: 100%;
  height: 100%;
  /* F3(D2-fix4, 09-14 재검토): the shared (non-scoped) `.chatgpt-drive-live-preview` rule sets
     `min-height: 250px` for the fullscreen modal/mobile-sheet context this class is also used
     in — `min-height` always wins over a shorter `height` (100% = this box's own 180px), so
     the is-error card was measured 250px tall inside a 180px thumbnail (70px overflow, "다시
     시도" clipped at the bottom) regardless of how much the error card's own padding/gap were
     already trimmed above. Zeroing it here is scoped to the compact thumbnail only — the
     fullscreen modal's own (unscoped) 250px is untouched. */
  min-height: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  pointer-events: none;
}
/* The idle/error live-preview states carry their own retry/open buttons (e.g.
   .chatgpt-drive-preview-open "미리보기 열기") — redundant here (the thumb-expand overlay
   already opens the same fullscreen modal) and, being unreachable under pointer-events:none,
   they must not stay in the tab order or count as a second "control height" (C10/§1 gate). */
/* F3/F16(D2-fix, 09-14 judge 재검토): is-error·is-sheet·is-unsupported 는 카드 자신의 버튼
   (다시 시도/문서 열기/다운로드)이 유일한 진입점이라 여기서는 숨기지 않는다 — 그 3상태가
   아닐 때만(정상 렌더 중인 iframe/img/video 등) 버튼을 지운다(그 상태들은 확장 오버레이가
   같은 동작을 대신한다). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview:not(.is-error):not(.is-sheet):not(.is-unsupported) button,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-preview-open {
  display: none;
}
/* F16: is-error·is-sheet·is-unsupported 카드 자신의 버튼이 살아있는 동안은 같은 자리에 겹친
   «확장» 오버레이(hero-thumb-expand)를 꺼서 진입점이 2개로 겹치지 않게 한다. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb:has(.chatgpt-drive-live-preview.is-error) .chatgpt-drive-inspector-hero-thumb-expand,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb:has(.chatgpt-drive-live-preview.is-sheet) .chatgpt-drive-inspector-hero-thumb-expand,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb:has(.chatgpt-drive-live-preview.is-unsupported) .chatgpt-drive-inspector-hero-thumb-expand {
  display: none;
}
/* F3: is-error·is-sheet·is-unsupported 카드는 원래 전체화면 모달/모바일 시트용 밀도(패딩 24 ·
   아이콘 28 · 무제한 줄바꿈)라 180px 썸네일 박스 안에서 71px 초과했다. 박스 안에서만 밀도를
   낮추고 문장을 2줄로 자른다. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-error,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-sheet,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-unsupported {
  gap: 4px;
  padding: 12px;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-error > span svg,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-sheet > span svg,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-unsupported > span svg {
  width: 18px;
  height: 18px;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-error strong,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-sheet strong,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-unsupported strong {
  font-size: var(--ds-text-caption-12);
  line-height: 1.3;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-error p,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-sheet p,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-unsupported p {
  display: -webkit-box;
  overflow: hidden;
  font-size: var(--ds-text-caption-12);
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-error .chatgpt-drive-preview-note {
  display: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview-error-actions {
  gap: 4px;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-error .chatgpt-route-action,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-sheet .chatgpt-route-action,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview.is-unsupported .chatgpt-route-action {
  min-height: 28px;
  padding: 2px 10px;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview iframe,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview img,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-live-preview video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-inspector-preview {
  width: 100%;
  height: 100%;
  min-height: 0;
  max-height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  justify-content: center;
}
/* F2(D2-fix, 09-14 judge 재검토): height:auto 는 이 카드 내부 요소들의 자연 높이(폴더 글리프
   img 의 원본 픽셀 크기 등)를 그대로 반영해 180px 박스를 82.6px 초과시켰다(실측
   262.6px·글리프 195×234). 박스(thumb) 높이 100% 를 물려받게 고정하고 min-height:0 으로
   flex/grid 강제-최소높이를 끈다 — 아래 stage/glyph 규칙과 함께 내부를 박스 안으로 접는다. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-inspector-preview-stage {
  height: 100%;
  min-height: 0;
  max-height: 100%;
}
/* 글리프(폴더/미확인 유형)는 정적 <img> 원본 크기를 그대로 쓰던 것이 넘침의 실제 원인이다
   (renderFileTypeIcon 이 --inspector variant 에는 CSS 크기 규칙이 아예 없어 img 가 원본
   픽셀로 렌더됐다 — folder.svg 195×234). 짧은 변 기준 박스의 40%(=72px)로 상한하고
   object-fit:contain 으로 비율을 지킨다(D5 §2 규격과 동일 사다리). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-inspector-preview-glyph {
  width: 100%;
  height: 100%;
  max-height: 100%;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-thumb .chatgpt-drive-inspector-preview-glyph img.wizon-file-type-icon {
  width: auto;
  height: auto;
  max-width: 72px;
  max-height: 72px;
  object-fit: contain;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
/* D-5(드라이브 긴 이름 셀 판정문): 히어로 이름도 행과 같은 중간 말줄임 규칙 — driveNameHeadTail
   이 head/tail 2-span으로 쪼개면 이 규칙이 flex 컨테이너가 되어 꼬리(마지막 구분자 토큰+확장자)
   를 항상 보존한다. 짧은 이름은 여전히 span 없는 평문 텍스트라 영향 없음. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-name {
  display: flex;
  overflow: hidden;
  min-width: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-body-15);
  font-weight: var(--wds-font-weight-medium);
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-name .chatgpt-drive-columns-name-head {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-name .chatgpt-drive-columns-name-tail {
  flex: 0 0 auto;
  white-space: nowrap;
}
/* C1 (v8 중앙 정정): 상태·유형·크기는 여기 «메타 1줄» — 제목과 같은 급의 두 번째 제목이
   아니다. 화면에서 가장 큰 글자는 위 hero-name(제목) 하나뿐이다. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-meta {
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* F6(D2-fix, 09-14 judge 재검토): data-n(실제 채운 칸 수, app.js renderDriveInspectorHeroCard)
   이 없으면 3열로 폴백 — 폴더 요약처럼 항상 3개를 채우는 호출은 그대로 3열. 가능한 칸 수는
   1(다중 선택 용량 합계)·2(파일: 크기+수정일)·3(폴더 요약/역할 포함 파일) 뿐이라 인라인
   style= 대신 어트리뷰트 셀렉터로 나열한다(anti-drift inline-style 카테고리 회피). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--chatgpt-route-gap-sm);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-metrics[data-n="1"] {
  grid-template-columns: repeat(1, minmax(0, 1fr));
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-metrics[data-n="2"] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-metrics > div {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-metrics strong {
  overflow: hidden;
  color: var(--cg-text);
  font-size: var(--ds-text-body-15);
  font-weight: var(--wds-font-weight-medium);
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-metrics small {
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-hero-src {
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* R3d-1 (docs/product/215 승인 목업, 2026-07-25): 단일 선택 전용 미리보기 카드 + 제목/부제.
   폴더 요약·다중 선택은 위 .chatgpt-drive-inspector-hero를 그대로 쓰고(콘텐츠 불변), 여기 제목
   (--title)·부제(--subtitle)는 hero-name와 hero-sub가 쓰는 것과 같은 --ds-text-body-15 그리고
   --ds-text-caption-12 토큰을 그대로 재사용해 세 상태의 타이포 리듬이 갈라지지 않게 맞춘다.
   미리보기 자체는 실제 썸네일 백엔드가 없어 확장자로 고른 순수 CSS 자리표시자 — 신규 raw 색상
   없음, 전부 기존 --cg-*, --wizon-ai-*, --chatgpt-route-* 토큰.
   AI "이 파일에 대해 질문하기" 슬롯은 드라이브→문서 브리지가 없어 이 자리에 넣지 않는다
   (app.js의 renderDriveInspectorPreview 위 주석 참고). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  padding: var(--chatgpt-route-gap-md);
  margin-bottom: var(--chatgpt-route-gap-md);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-lg);
  background: var(--cg-hover-soft);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: calc(var(--wizon-ai-space-unit) * 22);
  overflow: hidden;
}
/* File-type SVGs are portrait-shaped source art. Give the inspector a bounded
   portrait owner instead of stretching the asset to the full preview stage. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-stage .wizon-file-type-icon--preview {
  width: min(68px, 100%);
  height: min(82px, 100%);
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-type {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* document — 문서, PDF, 프레젠테이션: 폭이 들쭉날쭉한 텍스트 줄 4개. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-lines {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 7px;
  width: 76%;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-line {
  height: 7px;
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-hover);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-line:nth-child(1) { width: 92%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-line:nth-child(2) { width: 100%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-line:nth-child(3) { width: 70%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-line:nth-child(4) { width: 55%; }
/* spreadsheet — 스프레드시트: 3x3 셀 그리드. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
  width: 62%;
  aspect-ratio: 1 / 1;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-cell {
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-radius-micro);
  background: var(--cg-hover);
}
/* image — 이미지: 톤온톤 그라디언트 타일 위에 옅은 워터마크 아이콘(실제 사진 아님). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-tone {
  display: inline-grid;
  place-items: center;
  width: 78%;
  aspect-ratio: 4 / 3;
  border-radius: var(--chatgpt-route-radius-control);
  background: linear-gradient(135deg, var(--cg-hover-soft), var(--cg-hover));
  color: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-tone svg {
  width: calc(var(--wizon-ai-space-unit) * 7);
  height: calc(var(--wizon-ai-space-unit) * 7);
  opacity: 0.55;
}
/* audio/video — 오디오, 동영상: 막대 파형 모티프. 동영상만 재생 배지를 덧얹어 구분한다. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview--video .chatgpt-drive-inspector-preview-stage {
  position: relative;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-bars {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: calc(var(--wizon-ai-space-unit) * 10);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-bar {
  width: 5px;
  height: 30%;
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-bar:nth-child(1) { height: 42%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-bar:nth-child(2) { height: 70%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-bar:nth-child(3) { height: 100%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-bar:nth-child(4) { height: 58%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-bar:nth-child(5) { height: 85%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-bar:nth-child(6) { height: 46%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-bar:nth-child(7) { height: 66%; }
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-play {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(var(--wizon-ai-space-unit) * 7);
  height: calc(var(--wizon-ai-space-unit) * 7);
  /* 정사각이므로 pill 토큰이 50%와 동일한 원을 만든다 — raw 리터럴 대신 토큰 사용. */
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-send-bg);
  opacity: 0.85;
  transform: translate(-50%, -50%);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-play::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 55%;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 6px 0 6px 9px;
  border-color: transparent transparent transparent var(--cg-send-fg);
  transform: translate(-50%, -50%);
}
/* folder/unknown — 큰 유형 글리프. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-glyph {
  display: inline-grid;
  place-items: center;
  color: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview-glyph svg {
  width: calc(var(--wizon-ai-space-unit) * 11);
  height: calc(var(--wizon-ai-space-unit) * 11);
}
/* .chatgpt-drive-inspector-heading/-title/-subtitle removed (D2, drive-inspector-panel-hero-and
   -rows): folded into the hero card above (title/meta live there now) — 0 remaining JS refs. */
/* F9/C2(D2-fix4, 09-14 재검토): 히어로→첫 섹션 간격(48) 과 섹션→섹션 간격(49) 이 1px 차이로
   갈라져 있었다 — 둘 다 "24(margin/padding) + 24(padding)"를 겹쳤는데, 섹션 쪽만 border-bottom
   1px 이 더해졌기 때문(rhythm.py 실측 그대로). border 를 없애고(구분은 meta-row 가 이미 쓰는
   배경 1px 하프라인 기법으로 옮긴다 — 레이아웃 높이에 더해지지 않는다), 히어로 마진과 섹션
   패딩을 각 12로 절반씩 낮춰 두 간격을 정본 사다리 값 24 하나로 합친다(12+12=24,
   12+0+12=24). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-section {
  padding: 12px 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-section + .chatgpt-drive-inspector-section {
  background-image: linear-gradient(var(--cg-menu-border, var(--line)), var(--cg-menu-border, var(--line)));
  background-repeat: no-repeat;
  background-position: 0 0;
  background-size: 100% 1px;
}
/* D2 §3 (v8): 섹션 제목 12/600 muted — 대문자·자간 변형 없음(한글에는 no-op이었고, 영문
   라벨이 섞여도 캡션 단은 대소문자 변형 없이 12/600 하나로 통일한다). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-section h3 {
  margin: 0 0 var(--chatgpt-route-gap-xs);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
}
/* D2 §3 (v8): 섹션 행 36 — 제목(13/600) + 설명(12 muted) / 우측 값(13 muted). 구분선은
   border 가 아니라 배경 1px(border 는 min-height 위에 더해져 36/37 두 값을 만든다 — 규칙표
   §3 실측 주석 그대로 적용). */
/* F9/C2: align-items 를 center 에서 flex-start 로 바꾼 이유 — h3 margin-bottom(8, 사다리 값)
   에 이 행의 padding-top(4)만 더하면 정확히 12(사다리 값)가 된다. center 였을 때는 min-height
   36 안에서 폰트 라인박스가 수직 중앙 정렬되며 생기는 추가 슬랙(약 2.8px, 폰트 지표 의존값 —
   사다리 밖의 소수점 값)이 h3 마진에 더해져 14.8 이 됐다(review.md R2 F9). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-meta-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-xs);
  min-height: 36px;
  padding: 4px 0;
  font-size: var(--ds-text-caption-13);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-meta-row + .chatgpt-drive-inspector-meta-row {
  background-image: linear-gradient(var(--cg-menu-border, var(--line)), var(--cg-menu-border, var(--line)));
  background-repeat: no-repeat;
  background-position: 0 0;
  background-size: 100% 1px;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-meta-k {
  flex: 0 0 auto;
  color: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-meta-v {
  overflow: hidden;
  min-width: 0;
  color: var(--cg-muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* F5(D2-fix2, 09-14 v8 정본): the 09-14 sticky-in-body patch (`position: sticky` on
   `.chatgpt-drive-inspector-actions` itself) still left the actions as the scrolling body's
   last child — sticky can only pin a box AT the edge of its own scroll container, so at
   1440×900 it still measured 147px of body overflow and needed a scroll gesture to reach
   (review.md row 12: «D-4 회귀»). The v8 정본 wants the actions in their OWN sibling outside
   the scroll region — `.chatgpt-drive-inspector-foot` — so overflow in the body can never
   cover them. `.chatgpt-drive-inspector-actions` here now sits inside that foot (see
   app.js renderDriveInspectorPane/renderDriveInspectorFoot), not at the bottom of
   `.chatgpt-drive-inspector-body` — a plain flex child needs no sticky/negative-margin trick
   at all. Canonical spec: padding 12, border-top 1px, buttons 36, 2-column grid; only the
   FIRST 4 buttons render here — the rest move into `.chatgpt-drive-inspector-more-menu`
   («더보기»), so the foot's own height never grows past 2 rows regardless of how many
   actions a selection has. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-foot {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 12px var(--chatgpt-route-gap-md);
  border-top: 1px solid var(--cg-menu-border, var(--line));
  background: var(--cg-menu-bg, var(--bg));
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-actions {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  min-width: 0;
}
/* The "더보기" toggle sits to the right of the always-visible 4, same row height. Its popover
   opens ABOVE (bottom: 100%) since the foot itself is pinned to the panel's own bottom edge —
   opening downward would push it off-panel. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-more {
  position: relative;
  flex: 0 0 auto;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-more > .chatgpt-route-action {
  width: 100%;
  min-height: 36px;
  padding-inline: var(--chatgpt-route-gap-sm);
  border-radius: var(--chatgpt-route-radius-control);
}
/* N2(D2-fix4, 09-14 judge 재검토): the icon+label "더보기" button competed with the visible
   2-col action grid for the foot's fixed width (82.72px measured), squeezing each grid cell
   down to 66.14px — too narrow for "복사본 만들기"/"이름 변경", which then wrapped to 2 lines
   and inflated every foot button to a non-ladder height (58.72/81.08 instead of one 36 value).
   Icon-only 36×36 (aria-label carries the name — no visible text lost) frees that width back
   to the grid instead. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-more {
  width: 36px;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-more-btn {
  width: 36px;
  min-width: 36px;
  padding: 0;
  gap: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-more-menu {
  /* .chatgpt-library-popover's own base rule (top: calc(100% + gap)) opens the popover
     DOWNWARD from its relative parent — left unset here, `top` and `bottom` fighting over
     the same ~36px-tall `.chatgpt-drive-inspector-more` box squeezed this menu's rendered
     height down to ~14px (measured). `top: auto` lets `bottom` alone drive placement, so it
     opens upward from the foot instead of downward off the panel's own bottom edge. */
  position: absolute;
  top: auto;
  right: 0;
  bottom: calc(100% + 8px);
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 200px;
  padding: 6px;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-more-menu .chatgpt-route-action {
  width: 100%;
  min-height: 36px;
  justify-content: flex-start;
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
}
@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-inspector-more-menu .chatgpt-route-action:hover {
    background: var(--cg-hover-soft);
  }
}
/* N1(D2-fix4, 09-14 재검토): the base `.chatgpt-library-popover` rule
   (`.chatgpt-reference-v1 .chatgpt-library-popover`, specificity 0,2,0) sets `display: grid`,
   and this file's own `body.chatgpt-reference-v1 .chatgpt-drive-inspector-more-menu` override
   two rules above (specificity 0,2,1) sets `display: flex` — both are author-origin, so the
   UA stylesheet's `[hidden]{display:none}` (lower-origin, always loses to any author rule
   regardless of specificity) never gets a chance to hide the popover on first render, even
   though `hidden` IS on the element (measured: `hiddenAttr:true` yet `display:"flex"`,
   `covers:true` — it sat open over "AI 인덱싱"/"세부" on every render). Adding `[hidden]` here
   raises this selector's own specificity to 0,3,1, which beats the plain `-more-menu` rule
   within the SAME (author) origin — so the closed state finally wins instead of losing to a
   same-origin display:flex it can never outrank on specificity alone. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-more-menu[hidden] {
  display: none;
}
/* F9(D2-fix, 09-14 judge 재검토): 이 패널 스코프 반경 종류를 {6,8,16}(+999 알약)로 좁힌다 —
   기본 .chatgpt-route-action 은 --ds-radius-xs(6px, 아카이브·휴지통 등 다른 화면과 공유하는
   전역 클래스라 여기서 바꾸지 않는다)를 쓰지만, 이 패널 안에서는 --chatgpt-route-radius-control
   (8px, 같은 패널의 다른 컨트롤과 이미 같은 값)로 맞춘다. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-actions .chatgpt-route-action {
  width: 100%;
  min-height: 36px;
  min-width: 0;
  justify-content: center;
  border-radius: var(--chatgpt-route-radius-control);
  /* F19(D2-fix6, 09-14 중앙 재검토): 실측(가르는 값) — 이 패널의 실폭은 `.chatgpt-drive-explorer`의
     grid-template-columns 세 번째 트랙 `--drive-inspector-width: 280px`(고정값, 42168행)로
     결정된다(901~1920px 뷰포트 전 구간 실측 panelW=280 고정). 이 패널 자신의 clamp(320px,32%,400px)
     규칙은 죽은 코드였고 후보 inspector-width-cleanup 에서 삭제됨 — 42922행대의 살아 있는
     `width:auto` 재정의만 트랙 폭을 채운다. 2열 그리드 셀 89.5px에서 기본 padding-inline 10px+아이콘16+갭7을
     빼면 라벨 가용폭 ≈45px 뿐 — "이름 변경"(scrollWidth 48)조차 3px 넘쳐 말줄임됐다.
     padding-inline을 6px로 좁혀 라벨 가용폭을 키운다(버튼 자체 너비는 grid 100%라 터치 타깃엔
     영향 없음). 안전판(overflow/ellipsis)은 남기되 라벨 사다리(app.js entries, ≤4음절)와
     맞물려 320~400px 어느 폭에서도 실제로 트리거되지 않아야 한다(계약 시험 참조). */
  padding-inline: 6px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* B1(D2-fix5, 09-14 재검토 §2-N2b): the three declarations above never took effect on the
   label — this button is `display:inline-flex` (base `.chatgpt-route-action` rule), and
   `text-overflow` has no effect on an anonymous flex item (a bare text node between the
   icon <svg> and the closing tag is exactly that). Measured: rendered label width 96.4px ==
   full label width 96.4px > content width 88px — the browser laid the text out at full
   width and `overflow:hidden` on the BUTTON then hard-clipped 7px off the right edge with no
   "…" ever drawn. Wrapping the label text in its own <span class="chatgpt-route-action-label">
   (app.js renderDriveInspectorSingleActions) makes it a real flex item that CAN take
   text-overflow; `min-width:0` lets it actually shrink instead of forcing the button wider.
   `flex:0 0 auto` on the icon keeps it at intrinsic 16px (it too was collapsing to width 0
   as a sibling anonymous item competing for the same shrunk space) — acceptance = rendered
   label width <= content width at 1440/1920/1100, icon width > 0, button height unchanged. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-actions .chatgpt-route-action-label {
  overflow: hidden;
  min-width: 0;
  white-space: nowrap;
  text-overflow: ellipsis;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-actions .chatgpt-route-action svg,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-more-menu .chatgpt-route-action svg {
  flex: 0 0 auto;
}
/* R3d-1: 순서상 첫 작업(폴더=열기, 파일=미리보기)만 잉크 필 1차 버튼 — 나머지는 기존 2차
   스타일 그대로. .chatgpt-route-action의 border/background를 --cg-send-bg·--cg-send-fg로
   덮어써 컴포저 전송 버튼과 같은 잉크 필 토큰을 재사용한다(신규 색상 없음). */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-actions .chatgpt-route-action.is-primary {
  justify-content: center;
  border-color: var(--cg-send-bg);
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}
@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-inspector-actions .chatgpt-route-action.is-primary:hover {
    background: var(--cg-send-bg);
    opacity: 0.92;
  }
}

/* R3d-2a: 파일 전용 "이 파일을 AI가 참고하도록 허용" 토글 행 — 매니저 이상만 렌더된다(app.js
   driveRoleAtLeast 게이트, 서버가 authoritative). 새 색상을 만들지 않는다: on 상태는 위
   1차 버튼과 같은 잉크 필 토큰(--cg-send-bg 와 --cg-send-fg)을, off 상태는 --cg-hover를
   재사용한다. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-ai-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-xs);
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-state {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  padding: var(--chatgpt-route-gap-xs);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
  color: var(--cg-text);
  font-size: var(--ds-text-caption-12);
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-state > span:last-child {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-state small {
  color: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-dot {
  flex: 0 0 auto;
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-dot.is-loading {
  animation: cgPulse 1s ease-in-out infinite;
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-summary {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--chatgpt-route-gap-2xs);
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-summary > span {
  /* D2 §1 v7→v8 변경 #3 (규칙표): 지표 3칸 = 회색 «웰» 제거 — 배경·테두리 없는 숫자열. */
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-summary strong {
  color: var(--cg-text);
  font-size: var(--ds-text-body-15);
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-summary small {
  color: var(--cg-muted);
  font-size: var(--ds-text-micro-11);
}
body.chatgpt-reference-v1 .chatgpt-drive-ai-index-summary .has-failure strong {
  color: var(--cg-danger-fg);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-ai-label {
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-ai-toggle {
  position: relative;
  flex: 0 0 auto;
  width: 32px;
  height: 20px;
  padding: 2px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-hover);
  cursor: pointer;
}
/* F14(D2-fix, 09-14 judge 재검토): 시각 크기 32×20 은 그대로 두고(스위치 관용 모양, C10 은
   히트 영역을 잰다) ::before(이미 노브에 쓰는 ::after 와 겹치지 않는다)로 36×36 히트 영역만
   투명하게 넓힌다 — 의사 요소도 소유 요소의 클릭 영역에 포함된다. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector-ai-toggle::before {
  content: "";
  position: absolute;
  inset: 50%;
  width: 36px;
  height: 36px;
  transform: translate(-50%, -50%);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-ai-toggle[aria-checked="true"] {
  border-color: var(--cg-send-bg);
  background: var(--cg-send-bg);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-ai-toggle::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-surface);
  transition: transform 0.12s ease;
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-ai-toggle[aria-checked="true"]::after {
  transform: translateX(12px);
}
body.chatgpt-reference-v1 .chatgpt-drive-inspector-ai-toggle.is-pending {
  cursor: default;
  opacity: 0.6;
}

/* 20260724-drive-shellfit: 트리 레일 제거로 브라우즈 목록이 전체폭을 쓰게 되어, 진짜 빈 폴더
   (권한 0인 계정에서 자주 보임)가 더는 좁은 박스로 보이지 않는다 — 폭을 한 번 더 제한해
   중앙에 정돈하고 아이콘 칩을 키운다. 다른 라우트가 쓰는 공유 .chatgpt-lifecycle-empty
   자체는 건드리지 않는다(두 클래스 조합 선택자라 특이도가 자연히 더 높다, !important 없음). */
body.chatgpt-reference-v1 .chatgpt-drive-empty {
  max-width: 420px;
  margin: 0 auto;
  padding: 96px 20px;
}
body.chatgpt-reference-v1 .chatgpt-drive-empty .chatgpt-route-empty-icon {
  width: calc(var(--wizon-ai-space-unit) * 16);
  height: calc(var(--wizon-ai-space-unit) * 16);
}
body.chatgpt-reference-v1 .chatgpt-drive-empty .chatgpt-route-empty-icon svg {
  width: calc(var(--wizon-ai-space-unit) * 8);
  height: calc(var(--wizon-ai-space-unit) * 8);
}
/* D14 (drive-desktop-shell-sidebar-restore, 2026-09-13): CTA row for the empty-folder state —
   margin-top matches the parent grid's own 8px rhythm (.chatgpt-lifecycle-empty gap:8px) instead
   of a new value; the row itself reuses the 8px toolbar gutter (D4). */
body.chatgpt-reference-v1 .chatgpt-drive-empty-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}

/* ============================================================================
   20260724-drive-explorer-e2b: 뷰 토글(목록/아이콘/컬럼/갤러리) + 컬럼(Miller)/갤러리 뷰 +
   드래그로 이동. 정렬·다중선택·컨텍스트 메뉴·행 마크업(renderDriveColumnRow)은 4뷰가 전부
   그대로 재사용하므로 여기서는 렌더러별 배치만 다르다. 전부 기존 --cg-*·--chatgpt-route-*·
   --ds-*·--wizon-ai-* 토큰 재사용, raw 색상·반경·!important 신규 없음(anti-drift 순증 0).
   ============================================================================ */

/* 뷰 토글 — .chatgpt-route-action과 같은 툴바 트랙에 얹는 세그먼트 컨트롤. */
body.chatgpt-reference-v1 .chatgpt-drive-view-toggle {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-hover-soft);
}
body.chatgpt-reference-v1 .chatgpt-drive-view-toggle-btn {
  display: inline-grid;
  place-items: center;
  width: var(--wizon-ai-action-target-sm);
  height: var(--wizon-ai-action-target-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-muted);
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-view-toggle-btn svg {
  width: 15px;
  height: 15px;
}
body.chatgpt-reference-v1 .chatgpt-drive-view-toggle-btn.is-active {
  background: var(--cg-surface);
  color: var(--cg-text);
  box-shadow: var(--cg-shadow-float-xs);
}
@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-view-toggle-btn:hover {
    color: var(--cg-text);
  }
}
/* focus-reset-shell-ring-10: 전역 P0-3 폴백보다 특정성을 올린다. */
body.chatgpt-reference-v1.chatgpt-reference-v1 .chatgpt-drive-view-toggle-btn:focus-visible {
  color: var(--cg-text);
  outline: none;
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
}

/* 아이콘 뷰는 Finder처럼 고정 리듬의 타일을 왼쪽부터 채운다. 목록의 5개 셀을 단순히
   늘려 카드로 보이게 하지 않고, body가 타일 그리드를 소유하며 각 행은 아이콘과 이름을
   중심으로 다시 배치한다. 남는 폭으로 타일을 억지로 늘리지 않는 것이 핵심이다. */
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view {
  display: block;
  min-width: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-head {
  display: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-body {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(calc(var(--wizon-ai-space-unit) * 34), calc(var(--wizon-ai-space-unit) * 38)));
  grid-auto-rows: calc(var(--wizon-ai-space-unit) * 36);
  align-content: start;
  justify-content: start;
  gap: var(--chatgpt-route-gap-xs);
  min-width: 0;
  padding: var(--chatgpt-route-gap-sm);
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  align-items: stretch;
  align-content: stretch;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  padding: 10px 8px;
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell-select {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row > .chatgpt-drive-columns-actions {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 2;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell-name {
  grid-column: 1;
  grid-row: 1;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  gap: 8px;
  min-width: 0;
  padding: 2px 0 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-cell-name .chatgpt-drive-columns-icon {
  width: calc(var(--wizon-ai-space-unit) * 11);
  height: calc(var(--wizon-ai-space-unit) * 11);
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-cell-name .chatgpt-drive-columns-icon svg {
  width: 24px;
  height: 24px;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-row-open,
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-name-text {
  width: 100%;
  align-items: center;
  text-align: center;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-cell-name strong {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  max-width: 100%;
  line-height: 1.3;
  white-space: normal;
  overflow-wrap: anywhere;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-subline {
  max-width: 100%;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell--size {
  display: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell--modified {
  display: none;
}
/* drive-web-row-anatomy-and-column-ladder (D1): the new "AI 색인" text cell (list-view only,
   ≥901px) has no place in the card grid — same treatment as the size/modified cells above. */
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell--aiindex {
  display: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row.is-selected {
  background: color-mix(in srgb, var(--wizon-ai-focus-ring) 12%, var(--cg-surface));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wizon-ai-focus-ring) 40%, var(--cg-menu-border));
}
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row--draft .chatgpt-drive-draft-input {
  width: 100%;
  text-align: center;
}
@media (hover: hover) and (pointer: fine) {
  body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-actions {
    opacity: 0;
    transition: opacity var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row:hover .chatgpt-drive-columns-actions,
  body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row:focus-within .chatgpt-drive-columns-actions {
    opacity: 1;
  }
}

@media (min-width: 901px) {
  /* 20260915-drive-grid-miller-context-trigger-44-desktop: 20260915-drive-context-trigger-44-
     desktop 가 데스크톱(>=901px) list view의 ⋯ 트리거에 44px 히트슬롭을 복제한 뒤, 그 레인
     최종 보고가 grid-view/Miller(컬럼) 뷰는 스코프 밖이라고 명시했다 — 같은
     renderDriveContextMenuTrigger 버튼(class="chatgpt-drive-context-trigger")을
     grid-view(.chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row)와 Miller 현재
     컬럼(.chatgpt-drive-miller-column--current 안의 .chatgpt-drive-columns-row,
     renderDriveColumnRow 그대로 재사용 — 탐색 전용 컬럼의 .chatgpt-drive-miller-row 버튼에는
     이 트리거가 없다)이 이어받는다. 둘 다 기존 desktop list-view 수리와 같은 규약 1종(투명
     ::after + min-width/min-height:44px + 중앙 정렬)을 그대로 복제한다 — 시각 크기(기존 base
     규칙의 32px, var(--wizon-ai-action-target-sm))·행/타일 크기는 무변경(::after는 흐름 밖
     position:absolute라 레이아웃에 영향 없음). 겹침 실측(1440, grid-view 타일·Miller 현재
     컬럼 행)은 작업 보고 참고. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row .chatgpt-drive-context-trigger,
  body.chatgpt-reference-v1 .chatgpt-drive-miller-column--current .chatgpt-drive-columns-row .chatgpt-drive-context-trigger {
    position: relative;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-row .chatgpt-drive-context-trigger::after,
  body.chatgpt-reference-v1 .chatgpt-drive-miller-column--current .chatgpt-drive-columns-row .chatgpt-drive-context-trigger::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }
}

/* 컬럼(Miller) 뷰 — 좌→우 컬럼 체인. 마지막 컬럼(--current)만 renderDriveColumnRow 그대로라
   격자(6트랙)를 유지하고, 그 앞의 탐색 전용 컬럼들은 단순 버튼 목록이다. */
body.chatgpt-reference-v1 .chatgpt-drive-miller {
  display: flex;
  align-items: stretch;
  gap: 0;
  min-width: 0;
  height: 100%;
  overflow-x: auto;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-control);
}
body.chatgpt-reference-v1 .chatgpt-drive-miller-column {
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  width: calc(var(--wizon-ai-space-unit) * 56);
  min-width: calc(var(--wizon-ai-space-unit) * 56);
  padding: 4px;
  overflow-y: auto;
  border-right: 1px solid var(--cg-menu-border, var(--line));
}
body.chatgpt-reference-v1 .chatgpt-drive-miller-column:last-child {
  border-right: 0;
}
/* 마지막 컬럼의 각 행(.chatgpt-drive-columns-row)은 이미 자기 자신의 6트랙 grid-template을
   기본 규칙에서 가져오므로(위 "우측 컬럼 목록" 블록), 여기서는 그 폭(min-width:600px)을 담을
   수 있도록 컬럼 컨테이너 자체 폭만 넉넉히 넓힌다 — 행 내부 레이아웃을 다시 정의하지 않는다. */
body.chatgpt-reference-v1 .chatgpt-drive-miller-column--current {
  width: auto;
  min-width: max(600px, calc(var(--wizon-ai-space-unit) * 90));
  gap: 1px;
}
body.chatgpt-reference-v1 .chatgpt-drive-miller-row {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 5px 6px;
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
body.chatgpt-reference-v1 .chatgpt-drive-miller-row.is-active {
  background: var(--cg-hover-soft);
}
@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-miller-row:hover {
    background: var(--cg-hover-soft);
  }
}
body.chatgpt-reference-v1 .chatgpt-drive-miller-row-label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  font-size: var(--ds-text-caption-13);
}
body.chatgpt-reference-v1 .chatgpt-drive-miller-row-chevron {
  flex: 0 0 auto;
  color: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-miller-row-chevron svg {
  width: 13px;
  height: 13px;
}
body.chatgpt-reference-v1 .chatgpt-drive-miller-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  padding: var(--chatgpt-route-gap-md);
  text-align: center;
}
body.chatgpt-reference-v1 .chatgpt-drive-miller-preview-meta {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

/* 갤러리 뷰 — 큰 미리보기(스테이지) + 하단 필름스트립. 백엔드 썸네일이 없으므로 실제 이미지가
   아니라 유형 아이콘을 "미리보기"로 쓴다(정직성 — 날조된 사진 미리보기 없음). */
body.chatgpt-reference-v1 .chatgpt-drive-gallery {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-md);
  min-width: 0;
  min-height: 0;
  height: 100%;
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  flex: 1 1 auto;
  min-height: calc(var(--wizon-ai-space-unit) * 70);
  padding: var(--chatgpt-route-gap-lg);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-lg);
  background: var(--cg-surface);
  text-align: center;
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-stage.is-empty {
  color: var(--cg-muted);
  justify-content: center;
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-stage-icon {
  display: inline-grid;
  place-items: center;
  color: var(--cg-muted);
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-stage-icon svg {
  width: calc(var(--wizon-ai-space-unit) * 14);
  height: calc(var(--wizon-ai-space-unit) * 14);
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-stage-name {
  color: var(--cg-text);
  font-size: var(--ds-text-body-16);
  font-weight: var(--wds-font-weight-medium);
  word-break: break-word;
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-stage-meta {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-stage-actions {
  display: flex;
  gap: var(--chatgpt-route-gap-xs);
  margin-top: var(--chatgpt-route-gap-xs);
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip.chatgpt-drive-columns.is-gallery-view {
  flex: 0 0 auto;
  display: block;
  overflow-x: auto;
  overflow-y: hidden;
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-body {
  display: flex;
  gap: var(--chatgpt-route-gap-xs);
  min-width: 0;
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-content: start;
  gap: 4px;
  flex: 0 0 auto;
  width: calc(var(--wizon-ai-space-unit) * 30);
  min-height: calc(var(--wizon-ai-space-unit) * 26);
  padding: var(--chatgpt-route-gap-xs);
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row.is-gallery-active {
  border-color: var(--cg-text);
  background: var(--cg-hover-soft);
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell-select {
  grid-column: 1;
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row > .chatgpt-drive-columns-actions {
  grid-column: 3;
  justify-self: end;
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell-name {
  grid-column: 1 / -1;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
/* 20260725-drive-row-r2b: 필름스트립은 30 space-unit(~240px) 폭이라 subline까지 넣으면
   빽빽해진다 — 예전에도 이 자리(수정일+유형 칸)를 통째로 숨겼으니(구 nth-child(4),(5)
   display:none) subline도 같은 이유로 계속 숨긴다. 필름스트립은 "지금 보는 파일 고르기"
   용도라 이름만으로 충분하다. */
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-cell-name .chatgpt-drive-columns-subline {
  display: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell--size,
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell--modified,
body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row > .chatgpt-drive-columns-cell--aiindex {
  display: none;
}

/* 드래그로 이동 — 데스크톱 전용(driveDragEnabled). 소스/드롭 타깃 시각 효과는 renderPortalPage를
   거치지 않고 classList로 직접 토글되므로(handleDriveRowDragStart/Over/Leave/End 자체 주석 참고)
   여기 선택자들은 애니메이션 없이 즉시 상태만 반영한다. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-row.is-drive-dragging,
body.chatgpt-reference-v1 .chatgpt-drive-miller-row.is-drive-dragging {
  opacity: 0.5;
}
body.chatgpt-reference-v1 .is-drive-drop-target,
body.chatgpt-reference-v1 .is-drive-file-drop-target {
  outline: 2px solid var(--cg-text);
  outline-offset: -2px;
  background: var(--cg-hover-soft);
}

/* 바깥에서 파일을 끌고 들어오면 목록 전체가 목적지임을 보인다 — 폴더 행이 아니라 빈 곳에
   떨궈도 지금 보고 있는 폴더로 들어가기 때문에, 강조가 행에만 붙으면 「여기 놔도 되나」를
   알 수 없다. */
body.chatgpt-reference-v1.is-drive-file-dragging .chatgpt-drive-page {
  outline: 2px dashed var(--cg-text);
  outline-offset: -6px;
  border-radius: var(--wizon-ai-radius-lg, 12px);
}

@media (max-width: 900px) {
  /* 컬럼/갤러리도 900px 이하에서는 오버플로 없이 세로로 눌러 담는다(가로 스크롤은 각 트랙
     자체의 overflow-x가 흡수). */
  body.chatgpt-reference-v1 .chatgpt-drive-miller-column {
    width: calc(var(--wizon-ai-space-unit) * 44);
    min-width: calc(var(--wizon-ai-space-unit) * 44);
  }
  /* --current는 위 기본 규칙의 min-width: max(600px, ...)를 그대로 유지한다 — 마지막 컬럼의
     행(.chatgpt-drive-columns-row) 자체가 이미 min-width:600px를 요구하므로, 900px 이하에서
     좁히려 해도 행 쪽이 바닥을 정한다. .chatgpt-drive-miller의 overflow-x:auto가 그 폭을
     가로 스크롤로 흡수해 페이지 레벨 오버플로 없이 담아낸다(붕괴 금지 요구사항 충족). */
  body.chatgpt-reference-v1 .chatgpt-drive-gallery-stage {
    min-height: calc(var(--wizon-ai-space-unit) * 50);
    padding: var(--chatgpt-route-gap-md);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-gallery-filmstrip .chatgpt-drive-columns-row {
    width: calc(var(--wizon-ai-space-unit) * 24);
  }
}

/* .wds-pressed 의 reduced-motion 예외(scale: 1)는 wizon-mobile-kit.css 의 reduced-motion 블록으로
   이관(20260910-motion-m5, primitives.md §① — 새 prefers-reduced-motion 블록을 만들지 않는다). */

@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .composer-popover:not([hidden]),
  body.chatgpt-reference-v1 .reasoning-popover:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-more-menu:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-account-menu:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-conversation-menu:not([hidden]),
  /* doc 670 T1 — 더보기 임시 메뉴도 같은 목록에 얹는다(사본을 새로 만들지 않는다는 이 블록의
     관례를 그대로 따른다). */
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-recent-menu:not([hidden]),
  body.chatgpt-reference-v1 .chatgpt-library-popover {
    animation: none !important;
  }
}

/* R-41 B1b(S1b·M-15) — .settings-choice-popover 만 이 목록에서 빼서 흐려짐으로 세분화한다(위
   블록의 나머지 메뉴는 B1a/B1b 가 손대지 않은 경로라 그대로 둔다 — «데스크톱/미손댄 경로 모션
   변경 금지»). 전역 하한(20260710-reduced-motion-floor, animation-duration:0.01ms !important)의
   «세부 조정» — 로그인 창(19547행)과 같은 문법: 열림 상태는 wizon-scrim-in, 닫힘은
   wizon-scrim-out 124ms 로 흐려진다(새 키프레임 0개). animation-duration 을 다시 !important 로
   박아야 특이도가 아니라 !important 끼리 겨루는 하한을 이긴다. */
@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .settings-choice-popover {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration, 124ms) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration, 124ms) !important;
    translate: 0 0 !important;
  }
  body.chatgpt-reference-v1 .settings-choice-popover[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration, 124ms) linear forwards !important;
    translate: 0 0 !important;
  }
}

/* 20260709-mobile-sheet-top-layer (doc 144 §5, §7 rollout): these mobile bottom-sheet
   menus are promoted to the native top layer via the Popover API on touch viewports so
   they escape the fixed sidebar's stacking context (the root cause — doc 144 §1). The
   layout still comes from the shared bottom-sheet block above (matched via :not([hidden]),
   which holds while the popover is open). Here we swap the interim paint-only scrim for a
   real ::backdrop dim. NOTE: the ::backdrop is VISUAL ONLY — every UA stylesheet forces
   pointer-events:none on it, so it cannot block taps; pass-through is stopped by the JS
   capture guard (handleTopLayerOutsidePointerDown in app.js). The library popover is NOT
   here — it re-renders outside setAnimatedDisclosure and keeps the interim box-shadow. */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 .chatgpt-account-menu:popover-open,
  body.chatgpt-reference-v1 .chatgpt-more-menu:popover-open,
  body.chatgpt-reference-v1 .chatgpt-recent-menu:popover-open,
  body.chatgpt-reference-v1 .composer-popover:popover-open,
  body.chatgpt-reference-v1 .chatgpt-conversation-menu:popover-open,
  body.chatgpt-reference-v1 .reasoning-popover:popover-open {
    /* Drop the 0 0 0 100vmax scrim (it let taps fall through); keep only the lift shadow. */
    box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.22) !important;
  }

  body.chatgpt-reference-v1 .chatgpt-account-menu:popover-open::backdrop,
  body.chatgpt-reference-v1 .chatgpt-more-menu:popover-open::backdrop,
  body.chatgpt-reference-v1 .chatgpt-recent-menu:popover-open::backdrop,
  body.chatgpt-reference-v1 .composer-popover:popover-open::backdrop,
  body.chatgpt-reference-v1 .chatgpt-conversation-menu:popover-open::backdrop,
  body.chatgpt-reference-v1 .reasoning-popover:popover-open::backdrop {
    background: var(--wizon-scrim, rgba(0, 0, 0, 0.24));
    /* `backwards` (not `both`) so the resting opacity:1 governs post-entrance and the exit
       transition below can fade it out (mirrors the sheet translate approach). */
    opacity: 1;
    animation: wizon-scrim-in var(--wizon-motion-base) var(--wds-ease-out) backwards;
  }
}

/* 20260709-motion-m1 (doc 146 §2, revised after VIDEO verification): EXIT — sheet slides back
   down + scrim fades out while closing.
   ★ CRITICAL: the exit MUST be a keyframe `animation`, NOT a `transition`. Safari does not paint
   a CSS `transition` on a top-layer popover/::backdrop — the layout box moves (getBoundingClientRect
   sees it) but the rendered pixels just vanish, so the sheet appeared to "just close". A keyframe
   `animation` IS composited in the top layer (same as the working entrance). Verified frame-by-frame
   on the iOS simulator. Only the six stateful sheets reach this (library re-renders outside
   setAnimatedDisclosure → no state). */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 .chatgpt-account-menu[data-motion-state="closing"],
  body.chatgpt-reference-v1 .chatgpt-more-menu[data-motion-state="closing"],
  body.chatgpt-reference-v1 .chatgpt-recent-menu[data-motion-state="closing"],
  body.chatgpt-reference-v1 .composer-popover[data-motion-state="closing"],
  body.chatgpt-reference-v1 .chatgpt-conversation-menu[data-motion-state="closing"],
  body.chatgpt-reference-v1 .reasoning-popover[data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms) var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
  }

  body.chatgpt-reference-v1 .chatgpt-account-menu[data-motion-state="closing"]:popover-open::backdrop,
  body.chatgpt-reference-v1 .chatgpt-more-menu[data-motion-state="closing"]:popover-open::backdrop,
  body.chatgpt-reference-v1 .chatgpt-recent-menu[data-motion-state="closing"]:popover-open::backdrop,
  body.chatgpt-reference-v1 .composer-popover[data-motion-state="closing"]:popover-open::backdrop,
  body.chatgpt-reference-v1 .chatgpt-conversation-menu[data-motion-state="closing"]:popover-open::backdrop,
  body.chatgpt-reference-v1 .reasoning-popover[data-motion-state="closing"]:popover-open::backdrop {
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms) linear forwards;
  }
}

/* 20260712-question-card-bottom-sheet (doc 166; fallback symmetry doc 167 §4.4): on touch the
   question card rides the same top-layer bottom-sheet system as the menus — a popup separate from
   the thread, so it slides up over everything with a ::backdrop dim instead of an inline dock that
   stacked over the message actions/meta (the reported overlap). Gated on (pointer: coarse), NOT on
   [popover]: a coarse device whose browser lacks the Popover API still gets the fixed bottom-sheet
   layout + the paint-only 100vmax scrim (same degraded mode as the sibling menus), while a NARROW
   DESKTOP window (fine pointer) keeps the inline dock — [popover] gating gave that browser no sheet
   treatment at all. The card's own surface is flattened — the dock provides the sheet chrome.
   Sizing differs from the sibling menu sheets ON PURPOSE (85vh/-40px vs 70vh/-96px, 20/14 vs 8/8
   padding): this is a form (text field + keyboard + submit), not a row menu — it needs the extra
   height headroom and breathing room around inputs (doc 167 §4.4). */
@media (max-width: 520px) and (pointer: coarse) {
  body.chatgpt-reference-v1 .orchestration-dock:not([hidden]) {
    position: fixed !important;
    left: 0 !important;
    right: 0 !important;
    top: auto !important;
    /* 669 §M-8(wave1) — 이 도크는 질문 카드 입력칸(.orchestration-other-input)을 담는다.
       overlays-content 뷰포트에서 소프트키보드는 레이아웃 뷰포트를 줄이지 않으므로 위 max-height
       의 --wizon-vvh 축소만으로는 아래 입력·제출 버튼이 키보드 밑에 남는다(인벤토리 실측:
       「bottom:0, kb-inset 미사용(입력 칸 있음)」) — 같은 선례(workspacePicker·첨부 목록·
       재확인·로그인)를 따라 바닥을 --wizon-kb-inset 만큼 띄운다. */
    bottom: var(--wizon-kb-inset, 0px) !important;
    width: 100% !important;
    max-width: none !important;
    height: auto !important;
    /* R-40 ②파도 build C(669 §M-5) — 질문 카드 도크는 작업형(task): 긴 값. */
    max-height: calc(var(--wizon-vvh, 100dvh) - var(--wizon-safe-top, 0px) - 40px) !important;
    overflow-y: auto !important;
    margin: 0 !important;
    padding: 20px 14px calc(16px + var(--wizon-safe-bottom)) !important;
    background: var(--cg-menu-bg, var(--surface, #fff)) !important;
    border-radius: var(--wizon-ai-radius-sheet, 18px) var(--wizon-ai-radius-sheet, 18px) 0 0 !important;
    /* R-40 ②파도 build C(669 §M-11) — 페인트 전용 스크림 제거: 이 시트는 Popover API 상단 레이어로
       승격돼 아래 :popover-open::backdrop 이 스크림이고(탭은 JS 캡처 가드가 막는다), 승격을 못 하는
       엔진의 폴백만 파일 끝 @supports not selector(:popover-open) 블록에 남겼다. */
    box-shadow: var(--wizon-ai-sheet-shadow) !important;
    transform: none !important;
    translate: 0 0;
    z-index: 200 !important;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration, 372ms)
      var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
  }

  /* R-41 B2b(할 일 1, 설계 §3.3) — grab handle 은 이제 DOM 손잡이(ensureSheetHandle 이
     data-sheet-kind="task" 를 보고 깐 [data-sheet-handle], 기본 sticky 레시피는 44105행)가
     그린다. 도크는 정적으로 항상 종류를 선언하므로(index.html) 옛 ::after 는 되살릴 형제가
     없다 — 지운다. 손잡이가 첫 행(min-height 44)을 차지하므로 옛 top 여백(20px)은 중복이라
     0 으로 지운다(S1 이 이미 쓰는 같은 :has(> .wizon-sheet-handle) 패턴, 44128행). */
  body.chatgpt-reference-v1 .orchestration-dock:not([hidden]):has(> .wizon-sheet-handle) {
    padding-top: 0 !important;
  }

  /* Flatten the card — the dock now IS the sheet surface (bg, rounding, handle). */
  body.chatgpt-reference-v1 .orchestration-dock:not([hidden]) .orchestration-card {
    background: transparent;
    box-shadow: none;
    border-radius: 0;
    padding: 0;
  }

  /* Top layer active: swap the paint-only scrim for the real ::backdrop (mirrors the sibling
     sheets' :popover-open shadow swap). */
  body.chatgpt-reference-v1 .orchestration-dock:popover-open {
    box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.22) !important;
  }

  /* Real ::backdrop dim (VISUAL only — the JS capture guard blocks the tap-through). */
  body.chatgpt-reference-v1 .orchestration-dock:popover-open::backdrop {
    background: var(--wizon-scrim, rgba(0, 0, 0, 0.24));
    opacity: 1;
    animation: wizon-scrim-in var(--wizon-motion-base)
      var(--wds-ease-out) backwards;
  }

  /* Exit — slide down + scrim fade (keyframe animation, not transition; Safari top-layer paints
     only animations — see the 20260709-motion-m1 note above). */
  body.chatgpt-reference-v1 .orchestration-dock[data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
  }
  body.chatgpt-reference-v1 .orchestration-dock[data-motion-state="closing"]:popover-open::backdrop {
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms) linear forwards;
  }
}

/* R-40 ②파도 build C(669 §M-11) — Popover API 를 못 쓰는 엔진(:popover-open 미지원)의 폴백. 이 엔진에서는
   상단 레이어 승격이 없어 ::backdrop 이 없으므로 예전 페인트 전용 스크림을 «여기에만» 남긴다(지원 엔진은
   ::backdrop 이 스크림 — 위 규칙). 시트 블록 안 페인트 전용 스크림 0 계약의 유일한 예외. */
@supports not selector(:popover-open) {
  @media (max-width: 520px) {
    body.chatgpt-reference-v1 .composer-popover:not([hidden]),
    body.chatgpt-reference-v1 .reasoning-popover:not([hidden]),
    body.chatgpt-reference-v1 .chatgpt-more-menu:not([hidden]),
    body.chatgpt-reference-v1 .chatgpt-account-menu:not([hidden]),
    body.chatgpt-reference-v1 .chatgpt-conversation-menu:not([hidden]),
    body.chatgpt-reference-v1 .chatgpt-recent-menu:not([hidden]),
    body.chatgpt-reference-v1 .orchestration-dock:not([hidden]) {
      box-shadow: var(--wizon-ai-sheet-shadow), 0 0 0 100vmax var(--wizon-scrim) !important;
    }
  }
}

/* 20260710-library-sheet-motion: the library new/filter menus re-render as fresh HTML inside
   renderPortalPage, so the setAnimatedDisclosure exit above can never reach them. EXIT is animated
   by cloning the closing sheet to <body> as an inert ghost (JS spawnLibrarySheetGhost) — the ghost
   suppresses the entrance and slides+fades out. An in-place update while the menu stays open (e.g.
   a filter tap re-rendering the popover) would otherwise replay the entrance slide every tap; the
   [data-library-sheet-static] attribute suppresses that so only a genuine open animates. */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 .chatgpt-library-popover[data-library-sheet-static] {
    animation: none !important;
  }
  body.chatgpt-reference-v1 .chatgpt-library-popover[data-library-sheet-ghost] {
    pointer-events: none;
    animation: none;
  }
  body.chatgpt-reference-v1 .chatgpt-library-popover[data-library-sheet-ghost][data-motion-state="closing"] {
    animation: wizon-library-sheet-out var(--wizon-sheet-exit-ms, 300ms)
      var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
  }
}

@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 [data-motion-state="closing"],
  body.chatgpt-reference-v1 [data-motion-state="closing"]:popover-open::backdrop {
    transition: none !important;
    translate: none !important;
  }
}

@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .chatgpt-account-menu:popover-open::backdrop,
  body.chatgpt-reference-v1 .chatgpt-more-menu:popover-open::backdrop,
  body.chatgpt-reference-v1 .chatgpt-recent-menu:popover-open::backdrop,
  body.chatgpt-reference-v1 .composer-popover:popover-open::backdrop,
  body.chatgpt-reference-v1 .chatgpt-conversation-menu:popover-open::backdrop,
  body.chatgpt-reference-v1 .reasoning-popover:popover-open::backdrop {
    animation: none !important;
  }
}

/* 20260709-mobile-sheet-escape (doc 143 item C fix): the sidebar carries
   transform: translate3d(0,0,0) + will-change: transform, each of which makes it the
   containing block for its position:fixed descendants — so a bottom-sheet menu inside
   the drawer (account / more / recent) was clipped to the drawer width instead of
   spanning the viewport. While such a sheet is open, drop those properties so the
   fixed sheet escapes to the full screen. :has() is supported on the target Safari. */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .sidebar:not([data-motion-state="opening"]):not([data-motion-state="closing"]) {
    transform: none !important;
    will-change: auto !important;
  }
}

/* 20260710-motion-m2 (doc 146 §3): mobile drawer offcanvas slide. The sidebar is a plain
   <aside> (NOT top-layer), animated by keyframe `animation` on the individual `translate`
   property. `opening` parks it off-canvas for one frame; `open` slides it in; `closing` slides
   it out. The escape rule above owns `transform` at rest — we only touch `translate`/`opacity`,
   so the child bottom-sheets still escape. Verified frame-by-frame on the iOS simulator (R2). */
@media (max-width: 900px) {
  /* Pin the mobile drawer's box while animating. The DESKTOP collapse-transition rule
     `body[data-sidebar-transition] .sidebar` (styles.css:10139) sets width:260px + absolute +
     pointer-events:none during the transition — which our M2 change now triggers on mobile too,
     making the drawer stretch 260→300px at the end (the reported "왼쪽이 튕기며 늘어남"). Same
     specificity, later source order → this wins and keeps the drawer at its open box throughout. */
  body.chatgpt-reference-v1 .sidebar[data-motion-state] {
    position: fixed;
    width: min(300px, calc(100vw - 48px));
    z-index: 70;
    pointer-events: auto;
  }
  body.chatgpt-reference-v1 .sidebar[data-motion-state="opening"] {
    translate: -100% 0;
    opacity: 1;
    transform: none;
  }
  body.chatgpt-reference-v1 .sidebar[data-motion-state="open"] {
    opacity: 1;
    transform: none;
    animation: wizon-drawer-in var(--wds-spring-spatial-default-duration, 372ms) var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1));
  }
  body.chatgpt-reference-v1 .sidebar[data-motion-state="closing"] {
    opacity: 1;
    transform: none;
    animation: wizon-drawer-out var(--wizon-sheet-exit-ms, 300ms) var(--wizon-sheet-exit-ease, cubic-bezier(0.33, 0, 0.2, 1)) forwards;
  }

  /* Scrim present during open + closing so it fades OUT with the drawer (the base
     `:not(.sidebar-collapsed)` rule hides it the instant `closing` adds the collapsed class). */
  body.chatgpt-reference-v1:has(.sidebar[data-motion-state="opening"]) .chatgpt-sidebar-scrim,
  body.chatgpt-reference-v1:has(.sidebar[data-motion-state="open"]) .chatgpt-sidebar-scrim,
  body.chatgpt-reference-v1:has(.sidebar[data-motion-state="closing"]) .chatgpt-sidebar-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 65;
    background: var(--wizon-scrim, rgba(0, 0, 0, 0.24));
  }
  body.chatgpt-reference-v1:has(.sidebar[data-motion-state="opening"]) .chatgpt-sidebar-scrim {
    opacity: 0;
  }
  body.chatgpt-reference-v1:has(.sidebar[data-motion-state="open"]) .chatgpt-sidebar-scrim {
    animation: wizon-scrim-in var(--wds-spring-spatial-default-duration, 372ms) linear backwards;
  }
  body.chatgpt-reference-v1:has(.sidebar[data-motion-state="closing"]) .chatgpt-sidebar-scrim {
    animation: wizon-scrim-out var(--wizon-sheet-exit-ms, 300ms) linear forwards;
  }
}
@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .sidebar[data-motion-state],
  body.chatgpt-reference-v1:has(.sidebar[data-motion-state]) .chatgpt-sidebar-scrim {
    animation: none !important;
    translate: none !important;
  }
}

/* 20260709-mobile-input-zoom (doc 143): iOS Safari zooms the page IN when a text field with
   computed font-size < 16px receives focus, and keeps that zoom afterwards — so logging in
   (14px fields) left the whole app zoomed-in on first entry, and focusing the 15px composer
   did the same. Forcing >= 16px on text fields for coarse-pointer devices stops the auto-zoom
   entirely (16px is also the iOS-recommended readable minimum). !important is needed to beat
   the component rules that set the smaller token sizes. (ref: WebKit text-field zoom rule.) */
@media (pointer: coarse) {
  input:where([type="text"], [type="email"], [type="password"], [type="search"], [type="url"], [type="tel"], [type="number"]),
  input:not([type]),
  textarea {
    font-size: 16px !important;
  }
}

/* 20260709-mobile-focus-rings (doc 143): on touch devices a focus ring only ever appears
   from a tap (no keyboard in use) and reads as a stuck highlight. Per-component
   :focus-visible guards are not enough because iOS Safari mis-reports :focus-visible after a
   tap (the ring still paints). So on coarse-pointer devices suppress the ring globally for
   interactive controls. Text fields are intentionally excluded — they keep their focus
   styling and caret. Desktop / fine-pointer users are unaffected and keep keyboard focus
   rings. (ref: MDN :focus-visible; user-reported sidebar/settings focus-ring cleanup.) */
@media (pointer: coarse) {
  .chatgpt-reference-v1 button:focus,
  .chatgpt-reference-v1 a:focus,
  .chatgpt-reference-v1 [role="button"]:focus,
  .chatgpt-reference-v1 [role="switch"]:focus,
  .chatgpt-reference-v1 [role="menuitem"]:focus,
  .chatgpt-reference-v1 [role="menuitemradio"]:focus,
  .chatgpt-reference-v1 [role="option"]:focus,
  .chatgpt-reference-v1 [role="tab"]:focus,
  .chatgpt-reference-v1 summary:focus {
    outline: none;
  }
}

/* 20260710-composer-mode-slot-expansion (doc 150 P0): a visible mode pill (이미지 만들기 등)
   lives in the same .composer-textarea-scroll as the input, so the empty-state rules squeezed
   the pill and placeholder onto one line (overlap). The empty-state clamps are now guarded
   against .composer-mode-slot (so they skip the mode case); this block supplies the matching
   two-row expansion — mirroring the attachment-lane expansion — so the pill stacks ABOVE the
   input. Placed last so source order beats the @media(520) mobile composer block. */
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])),
body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])) {
  grid-template-rows: auto !important;
  min-height: 84px !important;
  height: auto !important;
  max-height: min(360px, calc(100dvh - 24px)) !important;
  padding: 5px 8px !important;
}
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])) .composer-prompt-body {
  grid-template-rows: auto !important;
  align-items: start !important;
  min-height: 0 !important;
  height: auto !important;
  max-height: none !important;
}
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])) .composer-textarea-scroll {
  display: block !important;
  align-self: start !important;
  min-height: 0 !important;
  height: auto !important;
  max-height: max(30svh, 5rem) !important;
  overflow-y: auto !important;
}
body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])) textarea {
  align-self: start !important;
  min-height: 26px !important;
  height: auto !important;
  max-height: none !important;
}


/* 20260710-starter-chip-hidden-guard (docs/product/150 §6 결정1): [hidden]으로 표시한
   스타터 칩/컴포저 메뉴 아이템(이미지 만들기 = P2까지 비노출)이 실제로 숨도록 강제.
   `.prompt-chip.chatgpt-action-chip { display: flex }`의 명시적 display가 UA
   `[hidden] { display: none }`(낮은 특정성)을 이겨 hidden이 무력화되던 문제를 차단. */
.chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip[hidden],
.chatgpt-reference-v1 .composer-menu-item[hidden] {
  display: none !important;
}


/* 20260710-starter-shelf-chipgroup (실기기 피드백): 스타터 칩 셸프를 WDS ChipGroup 패턴으로 —
   고정 크기 칩 + 좌우 가로 스크롤. 칩이 트랙에 맞으면 중앙, 넘치면 시작 정렬+스크롤(P2 이미지 등
   추가 대비). safe center = 맞을 때 중앙 / 넘칠 때 start(클리핑 방지).
   20260912-chip-320-overflow-fade (chat-home-chip-320-overflow): 320/375 폭에서 3번째 칩
   («제안서 AI»)이 스크롤 가능하지만 아무 신호 없이 글자 한가운데(«제»)서 뚝 잘려 보였다
   (documentElement.scrollWidth==clientWidth 라 페이지 리플로 위반은 아니지만 §10.5 밀도·잘림
   위반). library-toolbar-siblings §15.4 가 이미 같은 문제(390 «파일» 칩 잘림)를 오른쪽 페이드
   마스크로 풀었다(styles.css:23061, 신규 토큰 없이 --wizon-ai-action-target-xs 재사용) — 같은
   메커니즘을 그대로 적용해 «잘린 글자» 대신 «다음이 이어진다»는 스크롤 신호로 바꾼다. 트랙이
   다 맞을 때(넓은 뷰포트)는 페이드가 콘텐츠 없는 여백만 지워 육안 차이가 없다. */
html[data-chat-state="empty"] .chatgpt-reference-v1 .chatgpt-prompt-shelf {
  flex-wrap: nowrap !important;
  justify-content: safe center;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding-inline: 4px;
  -webkit-mask-image: linear-gradient(to right, black calc(100% - var(--wizon-ai-action-target-xs)), transparent);
  mask-image: linear-gradient(to right, black calc(100% - var(--wizon-ai-action-target-xs)), transparent);
  /* 20260912-chip-320-overflow-focus-scroll (fix-b): 실 app.js 렌더로 재현 — 320px 에서
     Tab 으로 3번째 칩에 포커스가 가도 셸프 scrollLeft 가 그대로였다(doc 669 K-4 «포커스
     가려짐»). 가르는 값은 mask-image/scroll-snap 이 아니라(모두 제거해도 재현) 칩이 이미
     절반 가까이 clientWidth 안에 걸쳐 있으면 크로미움 네이티브 focus-scroll 이 «이미 보임»
     으로 판단해 끝까지 스크롤하지 않는 것(고립 재현: rect_check.py/rect_check2.py) —
     scroll-padding-inline-end 로 마스크가 가리는 폭만큼 스크롤 목표 여백을 넓혀 강제한다. */
  scroll-padding-inline-end: var(--wizon-ai-action-target-xs);
}
html[data-chat-state="empty"] .chatgpt-reference-v1 .chatgpt-prompt-shelf::-webkit-scrollbar {
  display: none;
}
.chatgpt-reference-v1 .chatgpt-prompt-shelf .prompt-chip.chatgpt-action-chip {
  flex: 0 0 auto;
  scroll-snap-align: start;
  /* 20260912-chip-320-overflow-focus-scroll (fix-b): 셸프의 scroll-padding-inline-end 와
     짝 — 칩 자체도 스크롤 목표 계산에서 같은 폭만큼 여유를 두어야 마지막 칩이 마스크 밖으로
     완전히 나온다. */
  scroll-margin-inline-end: var(--wizon-ai-action-target-xs);
}
.chatgpt-reference-v1 .chatgpt-prompt-shelf .prompt-chip.chatgpt-action-chip:focus-visible {
  /* 20260912-chip-320-overflow-focus-scroll (fix-b, K-5 포커스 표시 저대비): 이 칩은 전역
     저대비 --cg-focus-ring(rgba(13,13,13,.16), 대비 ≈1.4:1) 을 상속받고 있었다. 새 색·두께를
     만들지 않고 candidate 41(bc52c9f4b)이 #chatInput 등에 넣은 기존 규약 1종
     (composer-surface textarea:focus-visible, 19549행 — outline: 2px solid var(--cg-text)
     솔리드, 대비 ≈19:1, raw 폴백 없이)을 그대로 옮긴다. */
  outline: 2px solid var(--cg-text);
  outline-offset: 1px;
}


/* 20260710-composer-focus-wake (실기기 인터랙션 요청): 칩/모드 선택으로 키보드가 올라올 때
   컴포저에 1회 글로우 펄스를 줘 "여기 입력하세요"를 명확히 신호. 캐럿도 진한 텍스트색으로 강조.
   모노크롬 브랜드에 맞춰 텍스트색 링, compositor-친화(opacity/transform), reduced-motion 시 펄스 생략. */
body.chatgpt-reference-v1 .composer-surface textarea,
body.chatgpt-reference-v1 .chatgpt-composer-shell textarea {
  caret-color: var(--cg-text, var(--wizon-ai-text-primary, #0d0d0d));
}
body.chatgpt-reference-v1 .chatgpt-composer-shell.composer-focus-wake::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 0 0 2px var(--cg-text, #0d0d0d);
  opacity: 0;
  pointer-events: none;
  z-index: 1;
  animation: composer-focus-wake 520ms cubic-bezier(0.16, 1, 0.3, 1) 1;
}
@keyframes composer-focus-wake {
  0% { opacity: 0; transform: scale(0.992); }
  28% { opacity: 0.34; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.016); }
}
@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .chatgpt-composer-shell.composer-focus-wake::after { animation: none; opacity: 0; }
}


/* 20260710-composer-mode-slot-web-inline (실기기 피드백): 웹(≥521px)은 모바일처럼 pill을 윗줄에
   쌓을 필요 없음 — ChatGPT처럼 태그+입력을 한 줄로. P0 2행 확장(20260710-composer-mode-slot-expansion)을
   넓은 화면에서만 인라인 flex 행으로 되돌린다. 모바일(≤520px)은 2행 유지. */
@media (min-width: 521px) {
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])),
  body.chatgpt-reference-v1 .composer-surface.chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])) {
    min-height: 52px !important;
  }
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])) .composer-prompt-body {
    align-items: center !important;
  }
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])) .composer-textarea-scroll {
    display: flex !important;
    align-items: center !important;
    align-self: center !important;
    gap: 8px;
  }
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])) .composer-mode-slot {
    margin: 0 !important;
    flex: 0 0 auto;
    align-self: center !important;
  }
  body.chatgpt-reference-v1 .chatgpt-composer-shell:has(.composer-mode-slot:not([hidden])) textarea {
    flex: 1 1 auto !important;
    min-width: 0 !important;
    align-self: center !important;
  }
}


/* 20260710-branch-switcher (docs/product/156 §5): ‹ n/m › active-branch switcher in a message
   action row. Hidden entirely when a turn has a single branch (renderBranchSwitcher returns ""). */
.chatgpt-reference-v1 .branch-switcher {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.chatgpt-reference-v1 .branch-switcher-count {
  font-size: var(--ds-text-caption-12);
  color: #8f8f8f;
  min-width: 2.4em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.chatgpt-reference-v1 .branch-switch[disabled] {
  opacity: 0.35;
  pointer-events: none;
}


/* 20260710-icon-vertical-centering (아트디렉션 규칙): 아이콘을 어디서나 수직 중앙정렬로 기본값화.
   그동안 정렬은 컴포넌트마다 개별 처리(예: sidebar/menu는 [data-icon]에 inline-grid+place-items)라,
   정렬을 명시 안 한 아이콘 래퍼(라이브러리 검색바·"이 채팅의 파일" 칩 등)는 SVG가 inline baseline에
   걸려 아래로 처졌다 = 간헐적 어긋남의 원인. 아래 두 규칙이 그 기본값을 전역으로 세운다:
   ① [data-icon] 래퍼는 grid로 자식 SVG를 중앙에 둔다(기존 정상 컴포넌트와 동일 값 → tie여도 무회귀,
      :not([hidden])로 숨김 아이콘 미노출). ② 래퍼 없이 텍스트와 인라인으로 놓인 아이콘은
      vertical-align:middle로 텍스트 중앙에 맞춘다(grid/flex 자식에선 무시되어 무해). */
.chatgpt-reference-v1 [data-icon]:not([hidden]) {
  display: inline-grid;
  place-items: center;
}
.chatgpt-reference-v1 .wds-inline-icon {
  vertical-align: middle;
}


/* 20260710-reduced-motion-floor (아트디렉션 P0-2): 모션 감소 전역 하한. 그동안 reduced-motion은
   블록별 opt-in(17곳)이라 thinking-dot·streaming-cursor 같은 무한루프가 미커버로 새고 있었다.
   이 블랭킷이 기본값(모든 애니·전환 즉시 종료)이고, 기존 17개 블록은 세부 조정으로 유지.
   0.01ms(0 아님)라 animationend/transitionend는 정상 발화 = JS 대기 로직 무해. doc 143 하한선의 전역판. */
@media (prefers-reduced-motion: reduce) {
  .chatgpt-reference-v1 *,
  .chatgpt-reference-v1 *::before,
  .chatgpt-reference-v1 *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* 20260710-focus-visible-base (아트디렉션 P0-3): 키보드 포커스 링 단일 기본값. 그동안 outline:none
   리셋(32곳)+제각각 재구현(링 8종 혼재)이라 빠뜨린 컨트롤은 키보드 포커스가 안 보였다. 이 기본이
   구멍을 메우고, 컴포넌트의 의도적 오버라이드(더 높은 특정성)는 그대로 이긴다.
   input/textarea/select/contenteditable 제외 — 텍스트 입력은 UA가 클릭 포커스에도 :focus-visible을
   매칭해 컴포저/검색창에 원치 않는 링이 생기므로 컴포넌트 스타일(웨이크 링 등)에 맡긴다. */
.chatgpt-reference-v1 :focus-visible:not(input):not(textarea):not(select):not([contenteditable="true"]) {
  outline: 2px solid var(--cg-focus-visible-ring, #0d0d0d);
  outline-offset: 2px;
}
/* 20260710-focus-visible-base-coarse-guard-removed (09-12 실측 철회): 터치 탭은 :focus-visible을 매칭하지 않는다(iOS 26.5 Safari 탭 시 포커스 없음·Android Chrome 124 false, 09-12 실측) — 맨 :focus 억제(20260709-mobile-focus-rings)만으로 충분. */

/* 20260710-icon-size-tokens: 기본 md 외 크기는 래퍼 모디파이어로 (개별 px 하드코딩 대신). */
.chatgpt-reference-v1 [data-icon-size="sm"] .wds-inline-icon {
  width: var(--wds-icon-size-sm, 16px);
  height: var(--wds-icon-size-sm, 16px);
}
.chatgpt-reference-v1 [data-icon-size="lg"] .wds-inline-icon {
  width: var(--wds-icon-size-lg, 24px);
  height: var(--wds-icon-size-lg, 24px);
}

/* 20260710-truncate-util (아트디렉션 P1-7): 한 줄 말줄임 표준 유틸. 그동안 overflow/nowrap/ellipsis를
   사이트마다 손으로 복붙(32곳)했는데, flex/grid 자식에서 필수인 min-width:0을 빠뜨리면 말줄임이
   "간헐적으로 안 먹는" 버그(아이콘 정렬과 동류의 기본값 누락)가 났다. 신규 말줄임은 이 클래스로 통일. */
.chatgpt-reference-v1 .wds-truncate {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  min-width: 0;
}

/* 20260711-typography-contract (doc 160, 사용자 승인 D1+D2): 타이포그래피 시맨틱 토큰 계약.
   실측(GPT/Claude/WIZON) × 코드감사 × 벤치마킹(Apple HIG·KRDS·WCAG) 교차 결과:
   본문 15px+strong 700(GPT는 16px+600)이 "굵고 답답"의 체감 원인, 모바일은 업스케일 부재.
   4종 시맨틱(body/label/meta/title)로 계약하고, 기존 파운데이션 토큰(--wds-font-size-body-2 70+곳,
   --wds-font-size-caption 12곳)을 body 스코프에서 시맨틱으로 승계해 소비처 일괄 편입.
   웨이트 팔레트 400/500/600 — 본문 강조 700 금지(strong→600). letter-spacing은 본문·UI 0 고정.
   font-synthesis-weight:none = 미로드 웨이트의 faux-bold 합성 차단(상속). */
body.chatgpt-reference-v1 {
  --cg-text-body-size: 16px;
  --cg-text-body-weight: 400;
  --cg-text-body-leading: 1.7;
  --cg-text-strong-weight: 600;
  --cg-text-label-size: 14px;
  --cg-text-label-weight: 400;
  --cg-text-label-leading: 1.45;
  --cg-text-label-active-weight: 500;
  --cg-text-meta-size: 12px;
  --cg-text-meta-weight: 500;
  --cg-text-title-size: 18px;
  --cg-text-title-weight: 600;
  --cg-text-title-leading: 1.35;
  /* anti-drift reconcile: dedicated token for the mobile top app bar's primary label.
     Introduced for .chatgpt-mobile-topbar-brand; that wordmark was replaced by the
     workspace switcher in 20260725-mobile-topbar-workspace, which inherits the token so
     the bar's headline size stays one value. NOT aliased to --cg-text-label-size — that
     variable is only repointed to 17px inside the narrower max-width:520px block, so
     aliasing here would silently shrink the label to 14px across the 521-900px range.
     Value unchanged (17px), just named. */
  --cg-text-topbar-brand-size: 17px;
  /* 파운데이션 승계: 기존 소비처(사이드바 route/recent/account/menu 별칭 체인)가 계약을 따라오게 */
  --wds-font-size-body-2: var(--cg-text-label-size);
  --wds-font-size-caption: var(--cg-text-meta-size);
  /* 공유 토큰은 불가침이므로 포털 스코프에서만 bold를 계약 상한(600)으로 승계 */
  --ds-weight-bold: var(--cg-text-strong-weight);
  font-synthesis-weight: none;
}

/* 본문(답변·유저버블): 15→16px, 행간 1.7. pre/code는 자체 규칙(12px mono) 유지. */
.chatgpt-reference-v1 .message-body,
.chatgpt-reference-v1 .message-body p {
  font-size: var(--cg-text-body-size);
  line-height: var(--cg-text-body-leading);
  font-weight: var(--cg-text-body-weight);
}
/* 본문 강조: UA bolder(→700)를 계약 600으로. 전역 기본이며 컴포넌트 명시 규칙이 이긴다. */
.chatgpt-reference-v1 strong,
.chatgpt-reference-v1 b {
  font-weight: var(--cg-text-strong-weight);
}
/* 라벨 표면(사이드바·컴포저/추론 메뉴): 시맨틱 토큰 소비 — 모바일 15px 승격이 여기로 전파 */
.chatgpt-reference-v1 .chatgpt-sidebar-link,
.chatgpt-reference-v1 .composer-menu-item,
.chatgpt-reference-v1 .reasoning-menu-item {
  font-size: var(--cg-text-label-size);
}
/* "최근" 섹션 헤더: 600 볼드 → meta 500 (볼드 대신 크기·색으로 위계 — 차분함 계약) */
.chatgpt-reference-v1 .chatgpt-recent-title {
  font-weight: var(--cg-text-meta-weight);
}
/* 다이얼로그 타이틀 3종 통합: 18/400 → 18/600 (GPT 실측 패리티) */
.chatgpt-reference-v1 .chatgpt-settings-head h2,
.chatgpt-reference-v1 .chatgpt-share-head h2,
.chatgpt-reference-v1 .chatgpt-delete-confirm-head h2 {
  font-size: var(--cg-text-title-size);
  font-weight: var(--cg-text-title-weight);
  line-height: var(--cg-text-title-leading);
}

/* T3 모바일 업스케일 (doc 160 R3, GPT 모바일 실측 정합): 데스크톱 px가 폰에 1:1로 실려 "작게"
   느껴지던 갭. GPT 모바일 드로어를 시뮬 3x 스크린샷 픽셀 계측한 결과 — 한글 글리프 45px=15.0pt
   (폰트 ≈17px, Apple HIG body 17과 일치), 행 피치 150px=50pt. 이에 맞춰 라벨 17px + 사이드바
   행높이 48px(피치 ~50pt). 시맨틱 토큰 재정의 한 곳으로 사이드바·메뉴·recent·account 일괄 전파. */
@media (max-width: 520px) {
  body.chatgpt-reference-v1 {
    --cg-text-label-size: 17px;
    --cg-text-meta-size: 13px;
    --cg-sidebar-item-height: 48px;
  }
  /* 17px 폰트에 고정 20px 행간은 타이트 — 라벨 leading 토큰으로 보정
     (recent line-height 별칭은 .recent-list 컴포넌트 스코프라 소비처에서 직접). */
  body.chatgpt-reference-v1 .chatgpt-sidebar-link,
  body.chatgpt-reference-v1 .recent-item,
  body.chatgpt-reference-v1 .recent-primary,
  body.chatgpt-reference-v1 .composer-menu-item,
  body.chatgpt-reference-v1 .reasoning-menu-item {
    line-height: var(--cg-text-label-leading);
  }
}

/* 터치 입력에서만 적용한다 — 화면 폭이 아니라 «손가락이냐 마우스냐»가 기준이라
   max-width 가 아니라 pointer: coarse 다(이 파일의 기존 관례, 25159행과 같은 근거).
   ㉮ 컴포저 + 메뉴 항목이 36px 였다. 메뉴 자체는 이미 하단 시트(fixed·폭 100%·반경 18px)로
      잘 떠 있는데 항목만 작아, 시트까지 가서 오조작하는 구조였다. iOS HIG 최소 44pt 로 올린다.
      ★상단바 버튼은 ::after 로 44px 를 이미 보장하고 있었는데(아래 블록) 메뉴 항목엔 없었다.
      여기선 실제 행 높이를 키운다 — 시트 안이라 이웃과 겹칠 여지가 없다.
   ㉯ 모바일 상단바 버튼 반경 8px → 원형. ChatGPT iOS 관례이고 결함은 아니다.
   ★실측(2026-08-02, 라이브 390px 에 선언을 주입해 실물 요소로 확인):
     메뉴 항목 36px→44px(44 미만 9개→0), 상단바 반경 8px→999px. 데스크톱은 pointer: fine
     이라 이 블록이 매치되지 않아 무변화다. */
@media (pointer: coarse) {
  .chatgpt-reference-v1 .composer-menu-item {
    min-height: 44px;
  }
  /* mobile-menu-row-touch-fix (doc 669 A-8, judge 판정 §5): 이 44px 수리가
     `.composer-menu-item` 한 클래스에만 걸려, 같은 바텀시트 문법을 쓰는 계정·더보기·
     최근·대화 메뉴 행 4종이 36px 로 남았었다(형제 8행 FAIL). 사본을 만들지 않고
     같은 규칙에 합류시킨다 — 계정·최근 메뉴는 이미 CSS 변수로 행 규격을 소비하므로
     컨테이너에서 변수만 올린다. */
  .chatgpt-reference-v1 .chatgpt-account-menu:not([hidden]),
  .chatgpt-reference-v1 .chatgpt-recent-menu:not([hidden]) {
    --chatgpt-account-menu-row-height: 44px;
    --chatgpt-account-menu-row-gap: 12px;
    --chatgpt-account-menu-row-icon-size: 24px;
    --chatgpt-recent-menu-row-height: 44px;
    --chatgpt-recent-menu-row-gap: 12px;
    --chatgpt-recent-menu-icon-size: 24px;
  }
  /* 더보기·계정 메뉴 컨테이너는 `display:block` 이라 시트 잡이(::before, 39217행)가
     인라인 박스로 그려져 width/height 가 무시된다(부수 발견 ①, 픽셀 스캔 0개).
     컴포저·최근·대화 메뉴처럼 grid 로 바꾸면 생성 상자가 첫 행이 된다. */
  .chatgpt-reference-v1 .chatgpt-account-menu:not([hidden]),
  .chatgpt-reference-v1 .chatgpt-more-menu:not([hidden]) {
    display: grid;
  }
  .chatgpt-reference-v1 .chatgpt-more-menu button,
  body.chatgpt-reference-v1 .chatgpt-conversation-menu button {
    height: 44px;
    min-height: 44px;
    gap: 12px;
    /* 더보기·대화 메뉴만 14px 로 남아 있었다 — 계정 시트와 같은 라벨 토큰
       (이 미디어 쿼리 바깥의 max-width:520px 블록에서 이미 17px 로 재정의됨). */
    font-size: var(--wds-font-size-body-2);
  }
  .chatgpt-reference-v1 .chatgpt-more-menu button > [data-icon] {
    flex: 0 0 24px;
    width: 24px;
    height: 24px;
  }
  body.chatgpt-reference-v1 .chatgpt-conversation-menu svg {
    width: 24px;
    height: 24px;
  }
  /* 컴포저 시트는 grid-template-columns 가 아이콘 열 폭을 정한다 — 상자만 24 로
     줄이면 열은 28 로 남아 아이콘↔라벨 간격이 16px 로 벌어진다(judge 실측). 열 자체를
     줄여야 나머지 시트와 같은 24/12 로 맞는다. */
  .chatgpt-reference-v1 .composer-menu-item {
    grid-template-columns: 24px minmax(0, 1fr) auto;
    gap: 12px;
  }
  /* mobile-menu-row-touch-fix-b (doc 669 A-8 후속, QA FAIL): 위 규칙은 아이콘 «열»만
     24px 로 줄였지 아이콘 «요소»(.composer-menu-icon)는 못박지 않았다. 그리드 아이템
     기본값(justify-self:stretch)이 인라인축을 24px 열에 맞춰 늘리는 동안 교차축은
     부모의 align-items:center 로 콘텐츠 크기(svg 기본 20px)에 머물러, 파일류 아이콘이
     24×20 비정사각으로 찌그러졌다. 앱 글리프(.composer-menu-app-glyph)는 반대로 명시
     20×20 이라 24px 열 안에서 계정·더보기 시트보다 작게 보였다. 둘 다 24×24 상자로
     고정해 내부 svg/글리프를 place-items:center(16555행, 상시 적용)로 중앙 정렬시킨다. */
  .chatgpt-reference-v1 .composer-menu-icon {
    width: 24px;
    height: 24px;
  }
  .chatgpt-reference-v1 .composer-menu-app-glyph {
    width: 24px;
    height: 24px;
    line-height: 24px;
  }
  .chatgpt-reference-v1 .chatgpt-mobile-topbar-button {
    border-radius: var(--ds-radius-pill);
  }
  /* 눌림 배경. ★이게 없으면 위 반경은 «보이지 않는 변경»이다 — 배경이 rgba(0,0,0,0)
     이라 칠할 픽셀이 없어서, 시뮬레이터 확대에서 아이콘 두 획만 보였다(2026-08-02 실측).
     :active 가 아니라 .wds-pressed 를 쓰는 이유는 app.js 주석이 이미 적어 뒀다 —
     iOS Safari 는 tap 에서 :active 를 불안정하게 발화한다. 이 버튼은 이미
     TAP_FEEDBACK_SELECTOR 에 있어 pointerdown 에 클래스가 붙고 scale 0.96 을 받는다.
     여기선 «면»을 하나 더해 눌린 것이 보이게 하고, 그 김에 반경도 드러나게 한다.
     ★20260910(mobile-app-kit K2-S1): body.chatgpt-reference-v1 접두사를 뗐다(primitives.md §①
     T-①2 — 하네스에서도 참이어야 한다) + 토큰을 --cg-hover-soft(#f4f4f4)에서
     --wizon-ai-surface-press(#e0e0e0)로 통일했다(진해진다, 의도된 변화). */
  .chatgpt-mobile-topbar-button.wds-pressed {
    background: var(--wizon-ai-surface-press);
  }
}

/* 20260710-touch-target-hitslop (아트디렉션 P1-5): coarse(터치)에서 standalone 소형 아이콘버튼의
   탭 타깃을 44px로 확장(WCAG 2.5.5 / Apple HIG 44pt). 시각 박스는 그대로 두고 투명 ::after로만
   히트영역을 넓힌다(박스 팽창=밀도 붕괴를 피함). dense 행은 탭존 겹침=오탭이라 의도적으로 제외:
   composer-toolbar(gap 6px)·recent 액션·메뉴 항목. 대상은 여백이 확보된 두 곳 —
   모바일 상단바 버튼(40px, gap 8px→겹침 없음)과 헤더/다이얼로그 아이콘버튼(34px, 고립 배치).
   두 버튼 모두 overflow·기존 ::after 없음(슬롭 미클립·무충돌). 실기기 검증 대상. */
@media (pointer: coarse) {
  .chatgpt-reference-v1 .chatgpt-mobile-topbar-button,
  .chatgpt-reference-v1 .chatgpt-icon-button {
    position: relative;
  }
  .chatgpt-reference-v1 .chatgpt-mobile-topbar-button::after,
  .chatgpt-reference-v1 .chatgpt-icon-button::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }
}

/* 20260802-answer-footer-mobile-stack (북극성 ③, 배치 2 → 배치 5 20260918 재도입, portal-
   message-actions-row-budget): 폰에서 답변 하단은 «대각선»이 아니라 왼쪽으로 정렬된 한 덩어리여야
   한다. 배포 0583703ab 실측(390px, 당시 버튼 32px): 액션 버튼 5개(합 160px)와
   .answer-meta-cluster(199px)를 한 flex 행에 담는데 가용 폭이 342px 뿐이라 17px 모자라 wrap
   하고, 클러스터의 margin-left:auto 가 «줄바꿈된 줄»에서도 우측 끝으로 밀어붙인다. 결과가 액션은
   좌하단·메타는 우하단으로 갈라진 대각선이었다.
   ★배치 4(20260802-sheet-grab-handle 과 같은 배포)는 모델칩의 「자동 · 」 접두를 빼 클러스터를
   169px 로 줄여 160+169=329≤342 «한 줄»을 만들고 이 스택을 없앴다 — 그 전제가 두 번 깨졌다:
   (1) 2026-09-11 message-actions-size-unify 가 버튼을 32→36px 로 키워 버튼 합이 160→196px 이
   됐는데 예산을 재검증하지 않았고(375/390 실측 overflow 41~56px — R3 판정
   portal-answer-quality-earc-judge 부수 발견), (2) 2026-09-18 이 후보가 TTFT 라벨을 "5.9초" →
   "첫 응답 5.9초"(doc 169 §elapsed 정의를 툴팁이 아니라 가시 텍스트에 싣는 수리, R3 §3)로 늘려
   클러스터가 167→204px(짧은 라벨) ~ 256px(긴 모델명)까지 커졌다. 재측정(Playwright 실엔진,
   chromium+webkit, 375/390/430/1440 × 짧은/긴(1분 5초)/긴 모델명): 필요 폭이 최소 411px(짧은
   라벨)인데 375~430px 컨테이너 가용폭은 314~368px 뿐이라 한 줄로 압축할 여지가 없다(버튼 5개
   196px 를 더 줄이면 36px 터치 타깃 규약을 깨고, 클러스터 텍스트를 더 줄이면 TTFT 정의가 다시
   숨는다 — 이번 수리의 목적과 충돌). 1440px 데스크톱은 가용폭 750px+ 로 최대 필요폭(462px, 긴
   모델명+긴 라벨)에도 여유가 있어 그대로 한 줄 우측 정렬을 쓴다.
   그래서 클러스터를 버튼 아래 자기 줄로 내린다. order:-1(배치2 원안)은 더 안 쓴다 — DOM 순서가
   이미 버튼→클러스터라 order 없이 flex-basis:100% 만으로 줄바꿈이 강제되고, justify-content 의
   기본값(flex-start)이 그 줄을 왼쪽에 붙인다. margin-left:0 이 배치2 버그(줄바꿈된 줄에서도
   margin-left:auto 가 우측으로 미는 대각선)의 재발을 막는 핵심이다 — 이 스택이 «대각선»이 아니라
   «두 줄 다 좌측 정렬된 한 덩어리»가 되는 것이 이번 수리의 불변식이다.
   데스크톱(>=901px)은 폭이 충분해 기존 «한 줄 우측 정렬» 그대로다. */
@media (max-width: 900px) {
  .chatgpt-reference-v1 .feedback-row.message-actions {
    /* 8px(--ds-space-2): 이제 스택이 이 폭 대의 기본 모양이므로(에지 케이스 대비 4px 이 아니라)
       아이콘 줄과 텍스트 줄 사이에 읽을 여유를 준다 — 세로 리듬 표는 report.md 참고. */
    row-gap: var(--ds-space-2, 8px);
  }
  .chatgpt-reference-v1 .feedback-row.message-actions .answer-meta-cluster {
    flex-basis: 100%;
    margin-left: 0;
  }
}

/* 20260802-sheet-grab-handle (북극성 ③ 배치 4): 바텀시트에 잡이(grab handle)를 그린다.
   위 20260709-mobile-bottom-sheet 주석은 「dimming scrim + drag handle」을 이미 약속해 놓고
   핸들을 한 번도 그리지 않았다(라이브 컴포저 시트 실측 hasHandle=false). Mobbin 에서 표본으로
   본 iOS 시트는 Obsidian·ChatGPT·Character AI·Fabric·Savee·pliability **6/6 전부** 이 잡이를
   갖는다 — 「이건 아래로 내려 닫는 판이다」를 말해 주는 표준 신호다.
   ::before 로 그리는 이유: DOM 을 늘리지 않아 메뉴에 엉뚱한 포커스 대상이 생기지 않고,
   등록된 시트 전부가 한 규칙으로 동시에 얻는다. 이 메뉴들은 display:grid 라 생성 상자가
   첫 행이 되고, 시트 블록의 align-content:start 덕에 맨 위에 앉는다. */
/* R-41 B2b(설계 §3.3) — DOM 손잡이 기본 레시피. ::before(생성 콘텐츠)는 클릭·포인터
   대상이 아니라 끌기 손잡이가 될 수 없었다 — 실제 <div data-sheet-handle> 를 시트 맨 앞에
   깔고(ensureSheetHandle) 44px 히트 영역(sticky·z1)을 준다. 시각 막대는 ::after 로 그려
   ::before 시절과 같은 36×4 모양을 유지한다(150 §K 화면 비교판 기준 불변). 데스크톱(>520·
   fine)에서는 display:none — 모션·동작 변경 금지(발주서 공통 조항), 1440 음성 캡처로 증명.
   R-41 B2b(build 2) — .chatgpt-library-popover(17 템플릿 전부)·.settings-choice-popover
   모두 이제 data-sheet-kind 를 선언해 DOM 손잡이를 받으므로, 과도기 ::before 는 완전히
   지운다(S1 과 같은 선례, test_mobile_sheet_handle_single.py 가 옛 규칙 부재를 고정). */
.wizon-sheet-handle {
  display: none;
}
@media (max-width: 520px), (pointer: coarse) {
  .wizon-sheet-handle {
    display: flex;
    position: sticky;
    top: 0;
    z-index: 1;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    touch-action: none;
  }
  .wizon-sheet-handle::after {
    content: "";
    width: 36px;
    height: 4px;
    border-radius: var(--ds-radius-pill);
    background: var(--cg-menu-border);
  }
  /* 손잡이가 첫 행을 차지하므로 시트 자신의 위 여백(옛 ::before 의 margin-top 2px)은
     손잡이가 대신 지운다 — 시트 padding-top 을 0 으로 되돌린다. */
  body.chatgpt-reference-v1 .composer-popover:not([hidden]):has(> .wizon-sheet-handle),
  body.chatgpt-reference-v1 .reasoning-popover:not([hidden]):has(> .wizon-sheet-handle),
  body.chatgpt-reference-v1 .chatgpt-more-menu:not([hidden]):has(> .wizon-sheet-handle),
  body.chatgpt-reference-v1 .chatgpt-account-menu:not([hidden]):has(> .wizon-sheet-handle),
  body.chatgpt-reference-v1 .chatgpt-conversation-menu:not([hidden]):has(> .wizon-sheet-handle),
  body.chatgpt-reference-v1 .artifact-titlebar-more-menu:not([hidden]):has(> .wizon-sheet-handle),
  body.chatgpt-reference-v1 .chatgpt-recent-menu:not([hidden]):has(> .wizon-sheet-handle) {
    padding-top: 0 !important;
  }
  /* R-41 B2b(할 일 3, 설계 §3.3) — 이 두 클래스가 쓰이는 17 템플릿 전부가 이제
     data-sheet-kind 를 선언해 핸들이 항상 있다 — 옛 ::before 는 S1 과 같은 방식으로
     완전히 지운다(더 이상 되살아날 형제가 없다). */

  /* 컴포저 「+」 시트는 여섯 항목이 한 줄기로 늘어서 있는데 성격은 셋이다 —
     첨부(사진·파일·최근 파일) / 만들기 / 연결 앱. 동적 목록 다음에 오는 항목이 정확히 그
     경계라, 그 자리에만 선을 넣어 새 개념 없이 이미 있는 묶음을 드러낸다. */
  body.chatgpt-reference-v1 .composer-menu-dynamic + .composer-menu-item {
    margin-top: 6px;
    padding-top: 10px;
    border-top: 1px solid var(--cg-menu-border);
  }
}

/* 20260802-empty-state-standard (북극성 ③): 라우트 빈 상태를 하나의 규격으로 모은다.
   390px 실측에서 셋이 서로 달랐고 둘은 제 위계가 뒤집혀 있었다 —
     /projects   제목 16px · 설명 17px · 제목 left 136 / 설명 left 16
     /scheduled  제목 15px · 설명 17px
     /drive      제목 15px (설명 없음)          ← 공유 블록. 이것만 옳았다.
   원인은 명확하다. 공유 .chatgpt-lifecycle-empty 는 제목과 설명 크기를 «각각» 선언하는데
   project/scheduled 는 컨테이너에만 font-size 를 걸어 제목이 그걸 상속하고, 설명은 자기
   라우트 본문 크기(17px)를 따로 선언했다. 그래서 설명이 자기가 설명하는 제목보다 커졌다.
   ★클래스와 기존 규칙은 그대로 둔다 — test_static_contract 가 그 선택자의 존재와 CSS 안
   위치를 구간 앵커로 쓴다. 여기서는 선언만 덮어 규격을 맞춘다. */
.chatgpt-reference-v1 .chatgpt-project-empty,
.chatgpt-reference-v1 .chatgpt-scheduled-empty {
  text-align: center;
}

.chatgpt-reference-v1 .chatgpt-project-empty strong,
.chatgpt-reference-v1 .chatgpt-scheduled-empty strong {
  color: var(--cg-text);
  font-size: var(--ds-text-body-15);
  font-weight: 500;
}

.chatgpt-reference-v1 .chatgpt-project-empty p,
.chatgpt-reference-v1 .chatgpt-scheduled-empty p {
  max-width: calc(var(--wizon-ai-space-unit) * 70);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  line-height: 1.5;
}

/* 빈 상태의 1차 액션. 에러 변형이 이미 쓰는 .chatgpt-route-action 과 같은 자리를 노리되,
   여기서는 «시작하게 하는» 버튼이라 주액션 톤을 쓴다. Mobbin iOS 표본 6/6(Coursera·
   Skyscanner·Ubank·Greenlight·Woolworths·Swiggy)이 전부 이 버튼을 갖는다. */
.chatgpt-reference-v1 .chatgpt-route-empty-action {
  margin-top: var(--wizon-ai-space-unit);
  min-height: var(--wizon-ai-action-target-sm);
  padding: 0 var(--ds-space-4, 16px);
  border: 0;
  border-radius: var(--ds-radius-pill);
  background: var(--wizon-ai-action-primary-bg);
  color: var(--wizon-ai-action-primary-fg);
  font-size: var(--ds-text-body-14);
  font-weight: 600;
  cursor: pointer;
}

/* ★모음 자체가 비었을 때만 크롬을 접는다. 검색·필터가 0건을 돌려준 것은 «다른 상태»이고
   거기서는 그 컨트롤을 고쳐야 하므로 절대 접으면 안 된다 — 그래서 이 규칙은 렌더러가
   routeCollectionIsEmpty() 로 계산해 붙이는 속성에만 걸린다. 필터 결과가 짧다고 걸리지 않는다. */
.chatgpt-reference-v1 .chatgpt-route-page-inner[data-route-empty="true"] .chatgpt-route-search,
.chatgpt-reference-v1 .chatgpt-route-page-inner[data-route-empty="true"] .chatgpt-route-tabs,
.chatgpt-reference-v1 .chatgpt-route-page-inner[data-route-empty="true"] .chatgpt-route-primary {
  display: none;
}

/* 20260803-touch-target-44 (doc 143 항목 B, 북극성 ③): 남아 있던 44pt 미만 탭 타깃을 닫는다.
   ★해법을 발명하지 않았다 — 위 20260710-touch-target-hitslop 이 이미 쓰는 «투명 ::after 로
   히트영역만 넓히고 시각 박스는 그대로» 를 그대로 재사용한다. 게이트도 그 관례대로 폭이
   아니라 pointer: coarse 다(손가락이냐 마우스냐가 기준).
   그 블록은 대상을 .chatgpt-icon-button / .chatgpt-mobile-topbar-button 둘로 한정했고, 아래
   다섯은 그 클래스를 하나도 갖지 않아 한 번도 덮인 적이 없다. 대신 다섯 전부 20260802 탭
   피드백의 scale 그룹에는 들어 있다 — 그래서 «눌리는 티는 나는데 누르기는 어렵다» 였다.

   라이브 390x844 실측(115957d0f, coarse/hover:none 미디어블록 19개·규칙 73개를 래퍼 없이
   재주입한 상태). 같은 하네스에서 .chatgpt-icon-button 이 시각 34x34 / ::after 44x44 로
   나오는 것을 대조군으로 먼저 확인했으므로, 「슬롭이 없다」는 관측의 사각이 아니라 실제 부재다.

   처방이 셋으로 갈리는 이유는 크기가 아니라 «옆에 남은 자리» 다:
   - 폭은 이미 44 이상이고 높이만 32 인 둘(.composer-model-pill 105.9x32,
     .chatgpt-recent-title 223x32) → 세로로만 넓힌다. 위아래 이웃이 16px·12px 떨어져 있어
     44 높이가 아무것도 건드리지 않는다. 가로까지 넓히면 얻는 것 없이 이웃 자리만 뺏는다.
   - .chatgpt-library-row-menu-trigger(행당 1개, 20개) → 행 안에 가로 이웃이 아예 없고 다음
     행까지 18.6px. 44x44 를 통째로 준다. ★이미 position: absolute 라 relative 를 다시
     선언하지 않는다 — 선언하면 흐름으로 되돌아가 ⋯ 버튼이 실제로 움직인다.
   - .chatgpt-library-view-control(3개, 형제 간격 6px)·.chatgpt-recent-header-action(2개,
     형제 간격 2px·타이틀과는 0px) → 44 폭으로 벌리면 형제의 탭존을 파고들어 «어느 쪽이
     먹느냐» 가 페인트 순서에 달린다. 그건 사용자가 내린 결정이 아니다. 그래서 가로는
     중간선까지만 간다: 보기 컨트롤은 calc(100% + 6px)=38px 로 두 슬롭이 정확히 맞닿고
     넘지 않으며, 헤더 액션은 왼쪽 중간선이 곧 제 경계라 가져올 가로 자리가 없어 높이만 받는다.

   실측 검증(후보 CSS 를 라이브 DOM 에 주입, elementFromPoint 로 네 변): 다섯 전부 슬롭
   가장자리에서 자기 자신이 잡히고, 6px 간격의 형제 경계에서는 중간선을 기준으로 갈렸다.
   ⚠️.recent-action(드로어 대화 행)은 여기 없다 — 계정에 대화가 0건이라 실측을 못 했고,
   터치에서는 opacity:0 + 롱프레스 시트(20260709-recent-longpress)가 따로 있어 «터치에서
   눌러야 하는 컨트롤인가» 부터 재야 한다. 재기 전에는 넣지 않는다. */
@media (pointer: coarse) {
  body.chatgpt-reference-v1 .composer-model-pill,
  body.chatgpt-reference-v1 .chatgpt-recent-title,
  body.chatgpt-reference-v1 .chatgpt-recent-header-action,
  body.chatgpt-reference-v1 .chatgpt-library-view-control {
    position: relative;
  }

  body.chatgpt-reference-v1 .composer-model-pill::after,
  body.chatgpt-reference-v1 .chatgpt-recent-title::after,
  body.chatgpt-reference-v1 .chatgpt-recent-header-action::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    height: 44px;
    transform: translateY(-50%);
  }

  body.chatgpt-reference-v1 .chatgpt-library-view-control::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(100% + 6px);
    height: 44px;
    transform: translate(-50%, -50%);
  }

  body.chatgpt-reference-v1 .chatgpt-library-row-menu-trigger::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }
}

/* 20260803-touch-target-44-sweep (doc 143 항목 B, 배치 2): 손으로 고른 여섯이 아니라 «전수»로
   찾은 나머지. 배포본 b6d12c7a4 를 390x844 에서, coarse/hover:none 미디어블록을 래퍼 없이
   재주입한 상태로 홈·드로어·라이브러리·아카이브·휴지통·컴포저 시트·설정을 훑어 보이는
   인터랙티브 요소 전부의 실제 히트영역(= 테두리 상자와 ::after 상자의 축별 최댓값)을 쟀다.
   ★아카이브·휴지통·컴포저 시트는 0건이었다 — 배치 3 과 20260729 touch-targets-a2 가 이미 닫았다.

   남은 것이 «왜 작은가»로 갈리고, 그 갈림이 이 블록의 요점이다.
   - .chatgpt-mobile-workspace-trigger 65x40, 좌우 여유 114.5px → 슬롭이 아무 대가도 치르지 않는다.
   - .composer-icon-button 36x36 / 36x36 / 38x38, 형제 간격 6px. 위 20260710 주석이 컴포저
     툴바를 제외한 이유가 바로 «6px 간격에서 44 슬롭은 겹친다» 였다. 배치 1 에서 합의한
     중간선 규칙이 그 사유를 없앤다 — calc(100% + 6px) 은 한쪽에 3px 씩만 가져가 형제 슬롭과
     정확히 맞닿고 넘지 않는다. 실측 42x44 / 42x44 / 44x44, 네 변 모두 자기 자신이 잡힌다.
     ★position: relative 를 여기서 «선언»한다. 세 버튼이 라이브에서 relative 로 나오긴 하지만
     styles.css 어디에도 .composer-icon-button 자체에 position 을 주는 규칙이 없다 — 변형
     클래스에서 흘러온 것이다. 남의 규칙의 부수효과에 절대배치를 얹으면, 그 변형이 손질되는
     날 슬롭이 조용히 엉뚱한 조상으로 탈출한다.
   - .chatgpt-route-tabs button 57.4~72.1x39.8 → ★이건 슬롭이 아니다. 레일이 <=900px 에서
     overflow-x: auto 인 가로 스크롤러이고 높이가 버튼에 딱 맞아, overflow 가 패딩 박스에서
     자르는 탓에 더 큰 ::after 는 절대 빠져나오지 못한다(패딩+음수마진으로 열어 봤더니 이번엔
     3px 밀렸다). 이 모양에 대한 답은 리포에 이미 있다 — .chatgpt-trash-category-tabs button 의
     min-height: 44px (<=900px, 배포됨). 아카이브·휴지통 탭이 44 이고 라이브러리 탭만 39.8 인
     이유가 그것이다. 기제를 새로 만들지 않고 그 결정을 공유 레일로 넓힌다. 실측: 39.8 → 44,
     아래 목록이 4.2px 내려가고, 가로 오버플로 없음, 레일은 여전히 스크롤 없이 들어간다.
     게이트가 coarse 가 아니라 <=900px 인 것도 선례 그대로다 — 보이는 상자를 바꾸는 변경이고
     데스크톱 밀도는 의도된 것이라서다.

   ⚠️.prompt-chip(96~112x40)은 일부러 뺐다. computed min-height 가 40px 로 핀돼 있어
   body.chatgpt-reference-v1 오버라이드가 못 이기고, 레일은 위와 같은 이유로 자르며, 패딩+음수
   마진으로 열면 칩이 3px 위로 밀린다(레일이 하단 앵커 그리드의 아이템이고 그 마진이 더 높은
   특이도 규칙에 핀돼 있다). 홈 히어로 행을 키우는 건 아트디렉션 결정이지 기계적 슬롭이 아니다.
   ⚠️텍스트 필드(컴포저 342x26 · 라이브러리 검색 306x34 · 설정 306x27.5)도 뺐다 — <input> 은
   ::after 가 생성되지 않아 유일한 처방이 보이는 상자를 키우는 것이다. */
@media (pointer: coarse) {
  body.chatgpt-reference-v1 .chatgpt-mobile-workspace-trigger,
  body.chatgpt-reference-v1 .composer-icon-button {
    position: relative;
  }

  body.chatgpt-reference-v1 .chatgpt-mobile-workspace-trigger::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    height: 44px;
    transform: translateY(-50%);
  }

  body.chatgpt-reference-v1 .composer-icon-button::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(100% + 6px);
    height: 44px;
    transform: translate(-50%, -50%);
  }
}

/* library-toolbar-siblings 채택 C(§4): 형제(칩·아이콘 버튼) 공용 터치 높이 —
   신규 DTCG 토큰 --wizon-ai-action-target-touch(44px). 아이콘 버튼은 높이만 이
   값을 따라가고 폭은 32(--wizon-ai-action-target-sm)로 유지한다 — 390 은 칩
   139px + 거터 12px + 아이콘 3개 폭 96px 로 가로 여유가 0px 라 폭까지 44 로
   올리면 오버플로한다(실측 §4). */
@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-route-tabs button {
    height: var(--wizon-ai-action-target-touch);
    min-height: var(--wizon-ai-action-target-touch);
  }

  body.chatgpt-reference-v1 .chatgpt-library-view-control {
    width: var(--wizon-ai-action-target-sm);
    height: var(--wizon-ai-action-target-touch);
  }

  body.chatgpt-reference-v1 .chatgpt-library-view-divider {
    height: calc(var(--wizon-ai-action-target-touch) * 0.5);
  }
}

/* 20260803-touch-target-44-answer (doc 143 항목 B, 배치 3): 대화가 있어야 존재하는 두 표면.
   배치 1·2는 이 둘을 아예 못 쟀다 — 계정 대화가 0건이라(/api/portal/conversations counts.all=0)
   답변 화면도 드로어 최근 행도 렌더되지 않았다. 대화를 하나 만들고 나서 390x844,
   coarse/hover:none 재주입 상태로 배치 2와 같은 방법으로 쟀다.

   - .message-action-button 32x38, 5개(복사·좋아요·싫어요·소리로 듣기·다시 생성), 형제 간격
     **0px**. 중간선이 곧 제 경계라 가로로 가져올 자리가 없다 → 높이만 44. 위아래 여유 16px.
   - .chatgpt-conversation-more 36x36(상단바), 이웃과 4px. ★그 이웃이
     .chatgpt-mobile-topbar-button 이고 20260710 슬롭이 이미 그 4px 중 2px 을 먹고 있다.
     calc(100% + 4px) 로 남은 2px 씩만 가져가 두 슬롭이 정확히 맞닿는다 → 40x44.
     elementFromPoint 실측: 네 변 모두 자기 자신.
   - .recent-action 28x28, 행당 2개, 서로 **0px**(293x48 행 안에서 x = -65 · -37) → 높이만 44.
     ★기하를 적어 둔다. .recent-item 이 overflow: hidden 이라 클립이 문제가 될 수 있는데,
     버튼은 행(553~601) 안에서 563~591 로 «세로 중앙»이라 위아래 10px 씩 남고, 44 슬롭은
     555~599 를 차지해 행의 클립 상자 안에 2px 여유로 들어간다. 배포 전 네 변 프로브는 못
     돌렸다 — 프로브 중에 드로어가 계속 오프캔버스 위치로 돌아갔다. 그래서 이 하나는 위
     기하와, 같은 드로어의 같은 28x28 컨트롤인 .chatgpt-recent-header-action 이 배치 1에서
     28x44 로 네 변 전부 검증된 사실에 기댄다. 배포 후 라이브에서 확인한다.

   ⚠️.trust-chip 91.5x20 은 뺐다 — 이 표면 전체에서 가장 작고 WCAG 2.5.8 의 24px 하한에도
   못 미친다. 그런데 부모 .trust-chips 가 overflow: auto 라 .prompt-chip·라우트 탭과 똑같이
   더 큰 ::after 가 잘린다. z-index 를 올려도 그대로였는데, 그게 «페인트 순서»가 아니라
   «클립»이라는 서명이다. 라우트 탭과 달리 따라갈 배포된 선례도 없고, 20px 인라인 칩을 44px
   로 키우는 건 답변이 읽히는 방식을 바꾼다. 아트디렉션 결정이 먼저다. */
@media (pointer: coarse) {
  body.chatgpt-reference-v1 .message-action-button,
  body.chatgpt-reference-v1 .chatgpt-conversation-more,
  body.chatgpt-reference-v1 .recent-action {
    position: relative;
  }

  body.chatgpt-reference-v1 .message-action-button::after,
  body.chatgpt-reference-v1 .recent-action::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    height: 44px;
    transform: translateY(-50%);
  }

  body.chatgpt-reference-v1 .chatgpt-conversation-more::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(100% + 4px);
    height: 44px;
    transform: translate(-50%, -50%);
  }
}

/* 20260803-touch-target-44-cards (doc 143 항목 B, 배치 4): 답변 «카드» 표면.
   ★배치 2 는 「모든 모바일 표면을 훑었다」고 했고 §1.6 도 그렇게 고쳐 썼는데, 둘 다 같은
   방식으로 틀렸다 — 답변 카드는 «그 카드를 만든 대화 안에서만» 렌더된다. 그때 계정 대화가
   0건이라 스윕이 볼 수가 없었고, 뉴스 카드가 든 대화를 열자 44pt 미만이 47개 더 나왔다.
   교훈: 스윕은 «도달할 수 있었던 상태»만큼만 완전하다. 「전부 쟀다」는 「내가 만들어 낼 수
   있는 상태를 전부 만들어 놓고 쟀다」여야 한다.

   배포본 390x844, coarse 재주입, ★각 요소를 scrollIntoView 한 뒤 프로브했다(앞선 시도가
   네 변 모두 null 로 나온 건 결함이 아니라 요소가 뷰포트 밖이었기 때문이다).

   - .news-bookmark 28x28 (22개) → 44x44. 오른쪽 변만 카드 경계(.news-feed)에 닿는데 컨트롤이
     아니고, 나머지 세 변은 자기 자신이 잡힌다.
   - .news-hero-autoplay 32x32 → 44x44.
   - .citation-tag 123.8~128.7x**20** (5개) → 높이 44. .trust-chip 과 높이가 같은데 이쪽은
     overflow: auto 레일 «안에 있지 않아» 슬롭이 안 잘린다 — 하나는 여기서 닫히고 다른 하나는
     아직 아트디렉션 결정을 기다리는 이유가 그 차이다.
   - .source-pill 216.3x32.4 · .news-subscribe-btn 60.5x26.4 → 높이 44.
   - .news-arrow 32x32, 서로 7px → calc(100% + 7px) 로 중간선.
   - .news-dot 24x10 · 10x10(높이가 10px 다), 서로 14px → calc(100% + 14px).
   - .news-topic 106x40 (10개) 은 .news-topics-rail 이 overflow: auto 라 더 큰 ::after 가
     잘린다 — 라우트 탭과 같은 모양이므로 같은 배포된 답을 쓴다: <=900px 에서 min-height: 44px.
     실측 토픽 40 → 44, 레일 52 → 56, 페이지 가로 오버플로 없음, 레일 스크롤 불변.

   ★정정(배치 5, 같은 날): 이 자리에 «.news-hero-nav 둘이 세로로 간격 0 으로 포개졌다»고
   적어 제외했는데 **틀렸다.** 그 좌표 (58,-587)·(38,-549) 는 y 가 음수인 데서 보이듯 요소가
   뷰포트 밖이었고, 리렌더 도중 `position` 이 잠깐 `relative` 로 읽힌 과도기 값이었다.
   **연속 3회 안정 측정**은 둘 다 `position: absolute` 로 히어로 좌·우 끝(x 58 · 294, 간격
   198px)에 나란히 있고 44x44 슬롭이 네 변 모두 자기 자신으로 잡힌다. 그래서 아래 44x44
   묶음에 합류시켰다(이미 absolute 라 position: relative 는 주지 않는다).
   교훈: **캐러셀처럼 상태가 도는 표면은 한 번 재서 판정하지 말고 같은 값이 연속으로 나오는지
   를 봐라.** 한 번의 스냅샷으로 「구조가 막는다」를 선언했다가 배포까지 갔다.

   ⚠️하나는 여전히 못 닫았고 사유는 실측이다.
   - .news-topics-more 32x32 는 44x44 로 하면 위 변이 .news-topic 버튼 «안»에 들어간다
     (T 가 CTL:news-topic-label — 그 버튼 안의 span). 토픽 레일이 x 가 겹친 채 바로 위에
     있어 위쪽 중간선이 0 이다. 오탭을 다른 오탭과 바꾸는 셈이라 뺐다. */
@media (pointer: coarse) {
  body.chatgpt-reference-v1 .news-bookmark,
  body.chatgpt-reference-v1 .news-hero-autoplay,
  body.chatgpt-reference-v1 .citation-tag,
  body.chatgpt-reference-v1 .source-pill,
  body.chatgpt-reference-v1 .news-subscribe-btn,
  body.chatgpt-reference-v1 .news-arrow,
  body.chatgpt-reference-v1 .news-dot {
    position: relative;
  }

  body.chatgpt-reference-v1 .news-bookmark::after,
  body.chatgpt-reference-v1 .news-hero-nav::after,
  body.chatgpt-reference-v1 .news-hero-autoplay::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }

  body.chatgpt-reference-v1 .citation-tag::after,
  body.chatgpt-reference-v1 .source-pill::after,
  body.chatgpt-reference-v1 .news-subscribe-btn::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    height: 44px;
    transform: translateY(-50%);
  }

  body.chatgpt-reference-v1 .news-arrow::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(100% + 7px);
    height: 44px;
    transform: translate(-50%, -50%);
  }

  body.chatgpt-reference-v1 .news-dot::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(100% + 14px);
    height: 44px;
    transform: translate(-50%, -50%);
  }
}

@media (max-width: 900px) {
  body.chatgpt-reference-v1 .news-topic {
    min-height: 44px;
  }
}

/* 20260803-touch-target-44-trust-chip (doc 143 항목 B, 배치 6): .trust-chip 은 아트디렉션
   결정이 아니었다 — 배치 3·4 의 두 번째 자기정정.

   배치 3·4 는 이 칩(91.5x20, 이 표면에서 가장 작고 WCAG 2.5.8 의 24px 하한에도 미달)을
   «레일 .trust-chips 가 overflow: auto 라 더 큰 ::after 를 자른다 → 보이는 칩을 키우는 수밖에
   없다 → 아트디렉션» 으로 뺐다. **클립은 사실이었지만 결론이 틀렸다.**
   overflow 는 «패딩 박스»에서 자르므로, 레일을 padding-block 으로 열고 같은 만큼 음수 마진으로
   되갚으면 레이아웃을 건드리지 않고 클립만 넓힐 수 있다.
   ★그 레시피를 .prompt-chip(칩이 3px 밀림)과 .chatgpt-route-tabs(통했지만 배포된 min-height
   선례가 있어 그걸 씀)에서 시험해 놓고, **이 레일엔 시험하지 않은 채 일반화**했다.
   두 레일은 같지 않다 — .prompt-chip-row 는 하단 앵커 그리드의 아이템이고 마진이 더 높은
   특이도 규칙에 핀돼 있는 반면, .trust-chips 는 .message-body 의 평범한 블록 흐름 안에 있다.

   배포본 390x844 실측(padding-block: 12px; margin-block: -10px):
     칩 상단 238.4 → 238.4 · message-body 하단 322.4 → 322.4 · 다음 콘텐츠 280.4 → 280.4
     · 레일 높이 22 → 44.  **모든 기준점 이동 0**, 연속 3회 89.5x44 에 네 변 모두 자기 자신.
   보이는 칩은 그대로다.

   ⚠️.prompt-chip 은 제외를 유지한다. 같은 레시피가 거기서는 칩 2px·히어로 제목 4px 을 위로
   밀고, 어떤 마진 조합으로도 상쇄되지 않는다(이기는 마진 규칙이 body.chatgpt-reference-v1
   오버라이드보다 특이도가 높다). 40px 은 이미 WCAG 2.5.8 을 넘고, Apple 44 까지 남은 4px 은
   홈 히어로를 조용히 밀 게 아니라 결정을 받을 일이다. */
@media (pointer: coarse) {
  body.chatgpt-reference-v1 .trust-chips {
    padding-block: 12px;
    margin-block: -10px;
  }

  body.chatgpt-reference-v1 .trust-chip {
    position: relative;
  }

  body.chatgpt-reference-v1 .trust-chip::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    height: 44px;
    transform: translateY(-50%);
  }
}

/* R-26 위젯 근거 칩 coarse 보정(중앙 09-25 재작업 ①②) — 위 공유 레시피(padding-block:12·
   margin-block:-10)는 «앞이 문단 텍스트(그 자체의 더 큰 하단 마진과 병합)» 일 때만 시각
   이동 0 이 성립한다. margin-block:-10 은 margin-top/bottom 을 곧장 -10px 로 «덮어쓰므로»,
   앞이 위젯 카드(마진 0)면 그 -10px 이 그대로 음수 간격이 되어 칩이 카드 아래 테두리를
   파고들었다(실측 after-390-weather.png, card→chip = -10px — 중앙 재작업 ① 확인).
   같은 기법(패딩으로 overflow 클립 영역을 열고 마진으로 되갚는다)을 그대로 쓰되 목표
   간격을 다시 계산한다. ★값은 손 계산이 아니라 실측(getBoundingClientRect, 칩 자체
   기준)으로 맞췄다 — margin-top:8+padding-top:12=20px(카드→칩, 실측 일치)까지는
   계산과 실측이 맞았지만, margin-bottom:2 는 칩→다음 요소가 실측 26px 로 나와
   plain(16px)과 10px 차이가 남았다(뒤에 오는 .feedback-row 와의 마진 병합이 손 계산과
   다르게 동작). margin-bottom 을 그 차이만큼 다시 낮춰 실측 16px 에 맞춘다.
   패딩은 위·아래 대칭 12px 그대로 둬 .trust-chip::after 의 44px 히트 영역(칩 위·아래 각
   22px 클리어런스 필요)이 두 변 모두 안 잘리게 유지한다 — 마진만 비대칭으로 바꿔 간격을
   고친다. */
@media (pointer: coarse) {
  body.chatgpt-reference-v1 .trust-chips--widget {
    padding-top: 12px;
    padding-bottom: 12px;
    margin-top: 8px;
    margin-bottom: -10px;
  }
}

/* 20260803-touch-target-44-final (doc 143 항목 B, 배치 7 — 마지막 둘):
   ★하나는 크기 문제가 아니었다.

   ① .prompt-chip — 배치 4~6 은 「computed min-height 가 40px 로 핀돼 body.chatgpt-reference-v1
   오버라이드가 못 이긴다」로 막혔다고 적었다. **그 규칙을 한 번도 찾아보지 않았다.**
   실제 규칙은 `.chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip { height: 40px }` 이고
   특이도가 (0,3,0) 인데 내가 쓴 오버라이드는 (0,2,1) 이었다. 리포가 막은 게 아니라 선택자가
   진 것이다. (0,3,1) 로 다시 재니 390x844 에서:
     칩 높이 40 → 44 · 제목↔칩 간격 14 → **14** · 칩↔컴포저 간격 10 → **10** · 컴포저 728 → **728**
   **간격이 하나도 안 바뀐다.** 「제목+칩」 덩어리가 4px 위로 갈 뿐이고, 그 4px 은 홈이 이미
   비워 두고 있는 568px(셸의 67%)에서 나간다. 선례는 .chatgpt-trash-category-tabs button 과
   .chatgpt-route-tabs button — 둘 다 <=900px min-height: 44px 로 배포돼 있다. 보이는 상자를
   바꾸므로 게이트도 그 선례대로 폭(<=900px)이지 coarse 가 아니다.

   ② .news-topics-more — 「44 상자가 .news-topic 과 겹친다」고 보고했는데 증상의 절반이었다.
   **이 버튼은 지금 아무 탭도 못 받는다.** 가역 A/B(중앙 + 네 변):
     현재 → 전부 .news-topic-label · z-index:4 만 → 전부 버튼(32x32) ·
     z-index:4 + 슬롭 → 전부 버튼(44x44) · 주입 제거 → 다시 .news-topic-label
   원인은 이웃 칩 «안»에 있다: .news-topic-label 이 z-index: 3, .news-topic-img 가 z-index: 1
   인데 .news-topics-more 는 z-index: auto 다. DOM 상 나중 형제여도 이웃의 자식이 스스로를
   올려 버리면 소용없다.
   ★그래서 이건 터치 어포던스가 아니라 결함이고, **1280px 데스크톱에서도 동일하게 재현된다.**
   z-index 규칙에 미디어 게이트를 걸지 않는 이유가 그것이다. 44 슬롭만 coarse 로 건다.
   ★죽은 버튼보다 나쁘다 — 탭이 삼켜지는 게 아니라 «아래 깔린 분야 칩»에 꽂힌다. 사용자가
   「더 보기」를 눌렀는데 고르지도 않은 분야가 조용히 선택된다.

   ★2026-09-08 후속(news-topic-rail-arrows, 오너 요청): .news-topics-more 자체가 사라졌다 —
   레일 양옆에 심볼만 있는 .news-topics-slide-prev/-next(날씨 위젯 .slideBtn과 같은 모양)로
   바뀌었고, 이 버튼들은 레일 «밖» .news-topics의 padding-inline 여백에 얹혀 어떤 칩과도
   좌표가 겹치지 않는다. 그래서 위에서 닫은 z-index 결함이 재현될 자리 자체가 없어 z-index
   핫픽스와 그 coarse 슬롭(아래 두 블록)을 함께 걷어냈다 — 왜 z-index:4가 있었는지는 이
   문단 위 기록으로 남긴다. */

@media (max-width: 900px) {
  body.chatgpt-reference-v1 .prompt-chip.chatgpt-action-chip {
    height: 44px;
    min-height: 44px;
  }
}

/* 20260903-touch-target-44-drive-and-actions (모바일 전수 QA 스윕, 2026-09-03): 드라이브
   경로 바와 「경로/역할 액션」 계열이 배치 1~7 의 스윕 범위 밖이었다 — 그 스윕은 대화·답변
   화면만 훑었고 업무 드라이브 탐색기는 대상이 아니었다.

   ★스윕이 «작다»고 잡은 셋 중 composer-send-button(38x38)·composer-mic-button(36x36)·
   message-action-button(32x38)은 이미 닫혀 있다 — 위 20260803 배치들이 .composer-icon-button
   과 .message-action-button 에 이미 같은 ::after 기법을 배포했다. 실측(sheet-split-view-qa,
   390x844, coarse 재주입, elementFromPoint 네 변)으로 재확인: send 38x38 은 44x44 전부
   자기 자신, mic 36x36 은 42x44(오른쪽 변만 이웃 send 버튼 — 사각지대 아니라 두 버튼이 만나는
   경계). 스윕 스크립트가 el.getBoundingClientRect() 만 재서 ::after 확장 히트영역을 못 보고
   재보고한 것이지 실제 결함이 아니다. 아래는 스윕이 맞게 잡은 나머지 셋.

   - .chatgpt-drive-history-button(이전/다음 폴더) 30x30, 형제 간격 4px → 중간선
     calc(100% + 4px)로 34x44. .chatgpt-conversation-more 와 같은 모양(4px 간격의 두 아이콘
     버튼) — 배치 3 선례를 그대로 재사용한다.
   - .chatgpt-drive-breadcrumb-item 104x28 / 62x28 — 형제 사이에 비대화형 구분선
     (.chatgpt-drive-breadcrumb-sep)이 끼어 있고 첫/마지막 폭이 내용에 따라 달라(가장 좁은
     경우 아이콘만 남는 경로 조각도 있다), message-action-button 과 같은 이유로 폭은 그대로
     두고 높이만 44 — width:100% ::after.
   - .chatgpt-route-action · .chatgpt-lifecycle-row-actions button(아카이브·휴지통·드라이브
     검사판 미리보기/다운로드 등, 90x36) — 이 클래스는 세로로 쌓이는 목록과 가로로 늘어서는
     행 양쪽에서 재사용돼 형제 간격을 한 곳씩 재지 못한다. width:100% ::after 는 버튼 자신의
     좌우 경계를 절대 벗어나지 않으므로 어느 배치에서도 이웃과 겹치지 않는다 — 세로 방향만
     44 로 넓힌다.

   셋 다 실측 위치(chatgpt-drive-mobile-stickyhead / .chatgpt-drive-gallery-stage-actions)에
   overflow:hidden 조상이 없어 44px 슬롭이 잘리지 않는다. */
@media (pointer: coarse) {
  body.chatgpt-reference-v1 .chatgpt-drive-history-button,
  body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item,
  body.chatgpt-reference-v1 .chatgpt-route-action,
  body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button {
    position: relative;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-history-button::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(100% + 4px);
    height: 44px;
    transform: translate(-50%, -50%);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item::after,
  body.chatgpt-reference-v1 .chatgpt-route-action::after,
  body.chatgpt-reference-v1 .chatgpt-lifecycle-row-actions button::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    /* drive-mobile-open-action-visible-failure(A-3, 09-11) 재핀 — 값이 같은 곳은 같은
       토큰을 가리켜야 한다는 원칙 그대로, 이 히트슬롭도 44 = --wizon-ai-action-target-touch
       다. raw 리터럴을 남겨 두면 §A 정적 스캔의 shareDialog.control_height 종류 수가 새
       is-sheet/is-unsupported 규칙 때문에 4 -> 5 로 늘어난다(래칫 위반) — 같은 값을 같은
       토큰으로 합쳐 4 를 유지한다.
       fix-b(2026-09-11, QA 실측): 그 토큰은 아직 :root/전역에 정의되지 않았다
       (travel-route-widget.css:11 .travel-guidebook-card 스코프 안뿐, 전역 정의는
       후보 33 미착지) — 이 스코프 밖에서는 var() 가 미해결로 이 선언 전체가
       무효화된다(hit-slop 높이 0). is-error/is-sheet/is-unsupported 쪽 min-height 에
       값 보존 폴백 `, 44px`를 추가한 것과 같은 이유로 여기도 폴백을 더한다 — 문자열이
       그쪽과 정확히 같아야(둘 다 `var(--wizon-ai-action-target-touch, 44px)`) §A 종류
       수가 그대로 4로 유지된다(문자열이 다르면 5로 늘어난다, 실측 확인). */
    height: var(--wizon-ai-action-target-touch, 44px);
    transform: translateY(-50%);
  }
}

/* ── 업무 드라이브 전송 진행 창 ─────────────────────────────────────────────
   ★2026-08-11. 파일을 드래그해 올렸는데 화면에 아무 변화가 없어 사용자가 새로고침을
   하고서야 올라간 걸 알았다. 진행 표시가 가던 곳(workspaceStatus)은 이 화면에서
   렌더 크기가 0×0 이었다. 그래서 이 창은 body 직속으로 «스스로» 선다. */
body.chatgpt-reference-v1 .wizon-drive-transfer {
  position: fixed;
  right: calc(var(--wizon-ai-space-unit) * 6);
  bottom: calc(var(--wizon-ai-space-unit) * 6);
  /* 업데이트 알림(.client-update-notice)과 같은 토스트 층. 같은 층에서는 DOM 순서가
     이기는데 이 창은 body 끝에 붙으므로 위로 온다. 임의의 큰 숫자를 박으면 스케일
     (overlay 1200 · modal 1300 · toast 1400 · tooltip 1500)이 무너진다. */
  z-index: var(--wds-z-toast, 1400);
  width: min(360px, calc(100vw - var(--wizon-ai-space-unit) * 8));
  padding: calc(var(--wizon-ai-space-unit) * 4);
  display: grid;
  gap: calc(var(--wizon-ai-space-unit) * 2);
  background: var(--cg-surface);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--ds-radius-md);
  box-shadow: var(--cg-menu-shadow);
  color: var(--cg-text, var(--text));
}

body.chatgpt-reference-v1 .wizon-drive-transfer[hidden] {
  display: none;
}

body.chatgpt-reference-v1 .wizon-drive-transfer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--wizon-ai-space-unit) * 2);
}

body.chatgpt-reference-v1 .wizon-drive-transfer-title {
  font-size: var(--ds-text-body-15);
  font-weight: 600;
  letter-spacing: -0.01em;
}

body.chatgpt-reference-v1 .wizon-drive-transfer-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: var(--wizon-ai-radius-control);
  background: transparent;
  color: var(--cg-muted);
  cursor: pointer;
}

body.chatgpt-reference-v1 .wizon-drive-transfer-close:hover,
body.chatgpt-reference-v1 .wizon-drive-transfer-close:focus-visible {
  background: var(--cg-hover-soft);
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .wizon-drive-transfer-track {
  height: 6px;
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--cg-hover-soft);
  overflow: hidden;
}

body.chatgpt-reference-v1 .wizon-drive-transfer-fill {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: inherit;
  background: var(--cg-text, var(--text));
  /* 레이아웃 속성이 아니라 transform 만 쓰고 싶지만, 진행률 막대는 width 가 의미 자체다.
     6px 높이 한 줄이라 리플로 비용이 사실상 없다. */
  transition: width 180ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .wizon-drive-transfer-fill {
    transition: none;
  }
}

body.chatgpt-reference-v1 .wizon-drive-transfer-detail {
  margin: 0;
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted, var(--muted));
  /* 파일명이 길어도 창을 밀어내면 안 된다. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .wizon-drive-transfer-failures {
  margin: 0;
  padding-left: calc(var(--wizon-ai-space-unit) * 4);
  max-height: 120px;
  overflow-y: auto;
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted, var(--muted));
}

body.chatgpt-reference-v1 .wizon-drive-transfer-failures li {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 완료·실패는 색이 아니라 «막대의 상태»로도 읽혀야 한다 — 흑백잉크 테마라 색만으로
   구분하면 아무것도 구분되지 않는다. */
body.chatgpt-reference-v1 .wizon-drive-transfer[data-phase="done"] .wizon-drive-transfer-track {
  opacity: 0.45;
}

body.chatgpt-reference-v1 .wizon-drive-transfer[data-phase="error"] .wizon-drive-transfer-fill {
  background: repeating-linear-gradient(
    45deg,
    var(--cg-text, var(--text)) 0 4px,
    transparent 4px 8px
  );
}

@media (max-width: 520px) {
  body.chatgpt-reference-v1 .wizon-drive-transfer {
    right: calc(var(--wizon-ai-space-unit) * 3);
    left: calc(var(--wizon-ai-space-unit) * 3);
    bottom: calc(var(--wizon-ai-space-unit) * 3);
    width: auto;
  }
}

/* ── 앱 밖 알림 설치/차단 해제 안내 시트(doc 681 §B-6, #pushHelpSheetDialog) ──────
   .chatgpt-delete-confirm-dialog 의 [open] 게이트·[data-mobile-bottom-sheet] 어댑터를
   그대로 물려받는다 — 모바일에서 자동으로 바텀시트가 된다(styles.css §11, 위 참고). */
body.chatgpt-reference-v1 .settings-v2-push-help-steps {
  margin: 0 0 var(--chatgpt-route-gap-sm);
  padding-left: calc(var(--wizon-ai-space-unit) * 4);
  font-size: var(--ds-text-body-14);
  color: var(--cg-text, var(--text));
}

body.chatgpt-reference-v1 .settings-v2-push-help-steps li + li {
  margin-top: calc(var(--wizon-ai-space-unit) * 2);
}

/* 09-25 중앙 재작업 지시(21:1x r3) 2·3 — 이 시트만 스코프한 크기 override.
   ★.chatgpt-delete-confirm-head h2 의 기본 font-weight:400(styles.css:21587,
   `body.chatgpt-reference-v1` 접두라 특이도 0,2,2)이 "통합 3종" 600 규칙
   (styles.css:43450 부근, `.chatgpt-reference-v1` 단독이라 특이도 0,2,1)보다 이겨서
   다른 자매 다이얼로그(삭제 확인 등)도 실제로는 400으로 렌더된다 — 그 공유 규칙을
   여기서 고치면 그 모든 화면이 바뀐다. 이 시트 하나만 우선순위를 올려 R-40 M-2
   "다른 안내·결정 시트와 같은 제목 굵기"를 맞춘다. */
body.chatgpt-reference-v1 #pushHelpSheetDialog .chatgpt-delete-confirm-head h2 {
  font-weight: var(--cg-text-title-weight);
}

/* R-40(671 §4.13, 669 §M M-2 안내형): "하단 «확인» 1개 = 주버튼"은 가로 전체 폭·
   48px 계열이어야 한다. 공유 .chatgpt-delete-confirm-primary-action(다른 시트에선
   36px·오른쪽 정렬 소형 버튼 — driveUploadConflictDialog 등과 공유하므로 전역
   수정 금지)의 색·상호작용은 그대로 두고, 이 시트 안에서만 크기를 늘린다. */
body.chatgpt-reference-v1 #pushHelpSheetDialog .chatgpt-delete-confirm-actions {
  display: block;
}

body.chatgpt-reference-v1 #pushHelpSheetDialog [data-push-help-sheet-confirm] {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 48px;
}

/* ── 같은 이름 파일 확인 다이얼로그 ─────────────────────────────────────────
   .chatgpt-delete-confirm-dialog 의 [open] 게이트를 그대로 물려받는다 — 닫힌 <dialog>가
   화면에 그려지는 사고를 막는 가드가 거기 붙어 있고, 다이얼로그마다 따로 다시 다는 구조라
   잊히기 쉽다. */
body.chatgpt-reference-v1 .chatgpt-drive-conflict-list {
  margin: 0 0 var(--chatgpt-route-gap-sm);
  padding-left: calc(var(--wizon-ai-space-unit) * 4);
  max-height: 168px;
  overflow-y: auto;
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted, var(--muted));
}

body.chatgpt-reference-v1 .chatgpt-drive-conflict-list li {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-drive-conflict-list li.is-more {
  list-style: none;
  margin-left: calc(var(--wizon-ai-space-unit) * -4);
  opacity: 0.75;
}

body.chatgpt-reference-v1 .chatgpt-drive-conflict-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: calc(var(--wizon-ai-space-unit) * 2);
}

/* 「업로드 취소」는 세 선택지와 같은 무게로 보이면 안 된다 — 되돌리는 문이지 선택지가 아니다. */
body.chatgpt-reference-v1 .chatgpt-drive-conflict-cancel {
  display: block;
  margin: calc(var(--wizon-ai-space-unit) * 3) 0 0 auto;
  padding: 0;
  border: 0;
  background: none;
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted, var(--muted));
  text-decoration: underline;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-conflict-cancel:hover,
body.chatgpt-reference-v1 .chatgpt-drive-conflict-cancel:focus-visible {
  color: var(--cg-text, var(--text));
}

@media (max-width: 520px) {
  body.chatgpt-reference-v1 .chatgpt-drive-conflict-actions button {
    flex: 1 1 auto;
  }
}

/* ── 버전 기록 다이얼로그 ────────────────────────────────────────────────── */
body.chatgpt-reference-v1 .chatgpt-drive-version-name {
  margin: calc(var(--wizon-ai-space-unit) * 1) 0 0;
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted, var(--muted));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-drive-version-status {
  margin: 0 0 var(--chatgpt-route-gap-sm);
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted, var(--muted));
}

body.chatgpt-reference-v1 .chatgpt-drive-version-list {
  margin: 0 0 var(--chatgpt-route-gap-sm);
  padding: 0;
  list-style: none;
  max-height: 280px;
  overflow-y: auto;
}

body.chatgpt-reference-v1 .chatgpt-drive-version-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--wizon-ai-space-unit) * 2);
  padding: calc(var(--wizon-ai-space-unit) * 2) 0;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}

body.chatgpt-reference-v1 .chatgpt-drive-version-list li:last-child {
  border-bottom: 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-version-meta {
  display: flex;
  align-items: baseline;
  gap: calc(var(--wizon-ai-space-unit) * 2);
  font-size: var(--ds-text-caption-13);
  color: var(--cg-muted, var(--muted));
  min-width: 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-version-meta strong {
  font-size: var(--ds-text-body-14);
  color: var(--cg-text, var(--text));
}

/* 「현재」는 색이 아니라 테두리로도 읽혀야 한다 — 흑백잉크 테마다. */
body.chatgpt-reference-v1 .chatgpt-drive-version-current {
  padding: 1px calc(var(--wizon-ai-space-unit) * 1.5);
  border: 1px solid var(--cg-text, var(--text));
  border-radius: var(--wizon-ai-radius-pill);
  font-size: var(--ds-text-micro-11);
  color: var(--cg-text, var(--text));
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-drive-version-restore {
  flex: 0 0 auto;
  padding: calc(var(--wizon-ai-space-unit) * 1.5) calc(var(--wizon-ai-space-unit) * 3);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-radius-control);
  background: transparent;
  color: var(--cg-text, var(--text));
  font-size: var(--ds-text-caption-13);
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-version-restore:hover,
body.chatgpt-reference-v1 .chatgpt-drive-version-restore:focus-visible {
  background: var(--cg-hover-soft);
}

/* 「업로드는 폴더 안에서」 안내. 메뉴 항목처럼 생겼지만 누를 수 없는 설명이라, 눌리는
   항목과 시각적으로 구분되어야 한다 — 안 그러면 죽은 버튼이 된다. */
body.chatgpt-reference-v1 .chatgpt-drive-new-menu-hint {
  display: flex;
  align-items: flex-start;
  gap: calc(var(--wizon-ai-space-unit) * 2);
  cursor: default;
  color: var(--cg-muted, var(--muted));
  font-size: var(--ds-text-caption-13);
  line-height: 1.45;
  white-space: normal;
  max-width: calc(var(--wizon-ai-space-unit) * 56);
}

body.chatgpt-reference-v1 .chatgpt-drive-new-menu-hint:hover {
  background: transparent;
}

/* ==========================================================================
   20260811 Drive workspace: Explorer/Finder-style docked layout.
   The app shell remains the global navigation. Inside the route, the folder tree,
   file list, and contextual preview are one continuous working surface.
   ========================================================================== */
html[data-portal-route="/drive"] #portalPage.chatgpt-route-page {
  --chatgpt-drive-content-max-width: none;
  --chatgpt-route-page-top: var(--chatgpt-route-gap-sm);
  --chatgpt-route-page-bottom: var(--chatgpt-route-gap-sm);
  /* drive-desktop-shell-sidebar-restore (2026-09-13, judge 09-11 §5-2 안 ③): tokens re-tuned
     for the restored 260px/52px global sidebar column — 216/288/720 overflowed the explorer
     by 104px at 1440 once the sidebar column came back (judge measured explOverflow:104). */
  --drive-rail-width: 200px;
  --drive-inspector-width: 280px;
  --drive-list-min-width: 440px;
  width: 100%;
  height: 100%;
  max-width: none;
  margin: 0;
  padding-inline: 24px;
  overflow: hidden;
}

body.chatgpt-reference-v1 .chatgpt-drive-page {
  --drive-selection-accent: var(--wds-color-primary);
  display: flex;
  position: relative;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  gap: 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead {
  display: flex;
  flex-direction: column;
  gap: var(--chatgpt-route-gap-xs);
}

body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead > .chatgpt-drive-breadcrumb {
  display: flex;
  margin: 0 0 var(--chatgpt-route-gap-sm);
}

body.chatgpt-reference-v1 .chatgpt-drive-filter-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--chatgpt-route-gap-sm);
  margin-bottom: 0;
  padding: var(--chatgpt-route-gap-sm) 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-titlebar,
body.chatgpt-reference-v1 .chatgpt-drive-command-bar {
  display: flex;
  align-items: center;
  min-width: 0;
  gap: var(--chatgpt-route-gap-xs);
}

/* drive-desktop-shell-sidebar-restore (D10, 2026-09-13): .chatgpt-drive-workspace-escape /
   -workspace-label rules removed along with the markup (app.js renderDrivePage) — see the
   comment left in app.js at the removal site. */

body.chatgpt-reference-v1 .chatgpt-drive-explorer {
  display: grid;
  grid-template-columns: var(--drive-rail-width) minmax(var(--drive-list-min-width), 1fr) var(--drive-inspector-width);
  flex: 1 1 auto;
  gap: 0;
  height: auto;
  min-height: 0;
  overflow: hidden;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-radius-popover, 16px);
  background: var(--cg-surface, var(--bg));
}

body.chatgpt-reference-v1 .chatgpt-drive-explorer.chatgpt-drive-explorer--tree-closed {
  grid-template-columns: minmax(var(--drive-list-min-width), 1fr) var(--drive-inspector-width);
}

body.chatgpt-reference-v1 .chatgpt-drive-explorer.chatgpt-drive-explorer--inspector-closed {
  grid-template-columns: var(--drive-rail-width) minmax(var(--drive-list-min-width), 1fr);
}

body.chatgpt-reference-v1 .chatgpt-drive-explorer.chatgpt-drive-explorer--tree-closed.chatgpt-drive-explorer--inspector-closed {
  grid-template-columns: minmax(0, 1fr);
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-pane {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  /* D5 (drive-desktop-shell-sidebar-restore, 2026-09-13): the pane used to put its own 10px
     top padding around BOTH the head row and the tree rows, so .chatgpt-drive-tree-head started
     10px lower than .chatgpt-drive-columns-head/-inspector-head even after all three were forced
     to the same 52px height (measured: tree y=169 vs the other two y=159). Moving the top
     padding onto the tree row list (below) instead of the pane lets the head start flush with
     its siblings; the visual gap above the first row is unchanged (still 10px, just scoped). */
  padding: 0 10px 10px;
  border-right: 1px solid var(--cg-menu-border, var(--line));
  background: var(--cg-bg, var(--bg));
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 38px;
  padding: 0 4px 0 10px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--wds-font-weight-medium);
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-head + .chatgpt-drive-tree-root {
  margin-top: 10px;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-root,
body.chatgpt-reference-v1 .chatgpt-drive-tree-row {
  display: flex;
  align-items: center;
  width: 100%;
  /* drive-web-row-anatomy-and-column-ladder (D1 할 일 6): 38 → 40 — 이 컴포넌트는
     max-width:900px 규칙(위)이 이미 chatgpt-drive-tree-pane 자체를 display:none 으로
     접어 데스크톱(≥901px) 전용이라, 별도 @media 로 다시 감쌀 필요가 없다. 직접 수정. */
  min-height: 40px;
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-root {
  gap: 9px;
  padding: 0 10px;
  font: inherit;
  font-size: var(--ds-text-body-14);
  text-align: left;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-root svg,
body.chatgpt-reference-v1 .chatgpt-drive-tree-icon svg {
  width: 17px;
  height: 17px;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-root.is-active,
body.chatgpt-reference-v1 .chatgpt-drive-tree-row.is-active {
  background: color-mix(in srgb, var(--wizon-ai-focus-ring) 12%, var(--cg-surface));
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-panel {
  right: 0;
  left: auto;
  width: min(360px, calc(100vw - 32px));
  max-height: min(620px, calc(100vh - 180px));
  padding: 14px;
  overflow: auto;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-panel section {
  display: grid;
  gap: 8px;
  padding: 10px 0;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-panel section:first-child {
  padding-top: 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-panel section > strong {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--wds-font-weight-semibold);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-chip {
  min-height: 34px;
  padding: 0 11px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-surface, var(--bg));
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-chip.is-selected {
  border-color: color-mix(in srgb, var(--wizon-ai-focus-ring) 58%, var(--cg-menu-border));
  background: color-mix(in srgb, var(--wizon-ai-focus-ring) 12%, var(--cg-surface));
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-chip:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-types {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-filter-reset {
  width: 100%;
  margin-top: 12px;
  justify-content: center;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-results {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
  background: var(--cg-surface, var(--bg));
}

body.chatgpt-reference-v1 .chatgpt-drive-search-results-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 58px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
}

body.chatgpt-reference-v1 .chatgpt-drive-search-results-head > span:first-child {
  display: grid;
  gap: 2px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-results-summary {
  grid-template-columns: 20px minmax(0, 1fr);
  align-items: center;
  gap: 10px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-results-summary > span {
  display: grid;
  gap: 2px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-results-head strong {
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-results-head small,
body.chatgpt-reference-v1 .chatgpt-drive-search-scope {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-scope {
  padding: 5px 9px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-pill);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-results-list {
  min-height: 0;
  overflow: auto;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  min-height: 92px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
  position: relative;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result.is-selected {
  background: var(--cg-hover-soft);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result.is-selected::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-main {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  min-width: 0;
  padding: 5px;
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-icon .wizon-file-type-icon {
  width: 40px;
  height: 40px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-copy {
  display: grid;
  min-width: 0;
  gap: 3px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-title {
  overflow: hidden;
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  font-weight: var(--wds-font-weight-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-title mark,
body.chatgpt-reference-v1 .chatgpt-drive-search-result-snippet mark {
  border-radius: var(--ds-radius-2xs, 3px);
  background: color-mix(in srgb, var(--wizon-ai-focus-ring) 22%, transparent);
  color: inherit;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-path,
body.chatgpt-reference-v1 .chatgpt-drive-search-result-meta {
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-snippet {
  display: -webkit-box;
  overflow: hidden;
  color: color-mix(in srgb, var(--cg-text) 82%, var(--cg-muted));
  font-size: var(--ds-text-caption-13);
  line-height: 1.45;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-side {
  display: grid;
  justify-items: end;
  gap: 8px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-result-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-match-badge {
  color: var(--wizon-ai-focus-ring);
  font-size: var(--ds-text-caption-12);
  font-weight: var(--wds-font-weight-semibold);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-state {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 8px;
  min-height: 280px;
  padding: 32px;
  color: var(--cg-muted);
  text-align: center;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-state > svg {
  width: 26px;
  height: 26px;
}

body.chatgpt-reference-v1 .chatgpt-drive-search-state strong {
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-drive-search-spinner {
  width: 24px;
  height: 24px;
  border: 2px solid color-mix(in srgb, var(--cg-muted) 28%, transparent);
  border-top-color: var(--wizon-ai-focus-ring);
  border-radius: 50%;
  animation: chatgpt-drive-search-spin 700ms linear infinite;
}

@keyframes chatgpt-drive-search-spin {
  to { transform: rotate(360deg); }
}

@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-search-result:hover {
    background: var(--cg-hover-soft);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-search-result:hover .chatgpt-drive-select-checkbox,
  body.chatgpt-reference-v1 .chatgpt-drive-search-result.is-selected .chatgpt-drive-select-checkbox,
  body.chatgpt-reference-v1 .chatgpt-drive-search-results-summary .chatgpt-drive-select-checkbox {
    opacity: 1;
  }
}

body.chatgpt-reference-v1 .chatgpt-drive-tree {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 2px;
}

/* D2: quota belongs to the Drive hierarchy rail, never the global WIZON navigation or the
   selection inspector. Until an API receipt proves a same-workspace current value, the rail
   deliberately exposes only this non-actionable unavailable state — no guessed meter/count. */
body.chatgpt-reference-v1 .chatgpt-drive-tree-quota {
  display: grid;
  flex: 0 0 auto;
  gap: 5px;
  margin: 10px -10px -10px;
  padding: 12px 14px max(12px, var(--chatgpt-route-gap-sm));
  border-top: 1px solid var(--cg-menu-border, var(--line));
  background: var(--cg-bg, var(--bg));
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-kicker,
body.chatgpt-reference-v1 .chatgpt-drive-tree-quota p,
body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-manage {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-quota strong {
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  font-weight: var(--wds-font-weight-semibold);
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-meter {
  display: block;
  width: 100%;
  height: 4px;
  overflow: hidden;
  appearance: none;
  border: 0;
  padding: 0;
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-hover-soft);
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-meter::-webkit-progress-bar {
  background: var(--cg-hover-soft);
  border-radius: inherit;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-meter::-webkit-progress-value {
  background: var(--wizon-ai-focus-ring);
  border-radius: inherit;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-meter::-moz-progress-bar {
  background: var(--wizon-ai-focus-ring);
  border-radius: inherit;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-manage {
  width: fit-content;
  font-weight: var(--wds-font-weight-medium);
  text-decoration: none;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-manage:hover,
body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-manage:focus-visible {
  text-decoration: underline;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-quota-manage:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 2px;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-row {
  padding-right: 4px;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-twist {
  display: grid;
  place-items: center;
  flex: 0 0 28px;
  width: 28px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-muted);
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-twist svg {
  width: 14px;
  height: 14px;
  transition: transform var(--wizon-motion-micro-duration) var(--wizon-motion-effect-fast);
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-twist.is-expanded svg {
  transform: rotate(90deg);
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-open {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 32px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--ds-text-caption-13);
  text-align: left;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-status {
  margin: 6px 10px 6px 30px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-status.is-error {
  color: var(--cg-danger-fg);
}

@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-tree-root:hover,
  body.chatgpt-reference-v1 .chatgpt-drive-tree-row:hover,
  body.chatgpt-reference-v1 .chatgpt-drive-tree-twist:hover {
    background: var(--cg-hover-soft);
  }
}

body.chatgpt-reference-v1 .chatgpt-drive-tree-root:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-tree-open:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-tree-twist:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-preview-open:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-row-open:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: -2px;
}

body.chatgpt-reference-v1 .chatgpt-drive-columns-pane {
  position: relative;
  min-width: 0;
  min-height: 0;
  overflow: auto;
  background: var(--cg-surface, var(--bg));
}

body.chatgpt-reference-v1 .chatgpt-drive-columns-head {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--cg-surface, var(--bg));
}

body.chatgpt-reference-v1 .chatgpt-drive-columns-head,
body.chatgpt-reference-v1 .chatgpt-drive-columns-row {
  grid-template-columns: 28px minmax(0, 1fr) 88px 80px 104px 40px;
  min-width: 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-columns-row {
  min-height: 46px;
  padding-block: 6px;
}

/* The registry artwork is intentionally reused, but the Explorer consumes it in a
   compact frontal slot: file names start on one optical baseline and neither the
   SVG's extension badge nor its taller viewBox can increase the list rhythm. */
body.chatgpt-reference-v1 .chatgpt-drive-columns-cell-name {
  align-items: center;
  gap: 10px;
}

body.chatgpt-reference-v1 .chatgpt-drive-columns-icon,
body.chatgpt-reference-v1 .chatgpt-drive-columns-icon .wizon-file-type-icon {
  flex: 0 0 24px;
  width: 24px;
  height: 24px;
  max-width: 24px;
  max-height: 24px;
  object-fit: contain;
}

body.chatgpt-reference-v1 .chatgpt-drive-columns-subline {
  display: none;
}

/* D1-delta 독립 검토(judge, 09-14) under24-target-full-scan FAIL: 열 머리 정렬 버튼이
   41.48×22.36 히트 영역(<24px)으로 측정됐다. COMPONENT-RULES.md §1 "열 머리 = 36"과
   위 체크박스의 "28×36 히트, 20×20 시각 상자" 패턴을 그대로 따라 min-height만 36px로
   올린다 — font-size/font-weight/color(12px·600·muted)는 그대로, 시각 텍스트는 안 변한다.
   헤드 행 자체는 이미 52px 고정(D5, .chatgpt-drive-columns-head min-height/height:52px)
   + padding-block 8px×2=16px이라 36px 버튼이 그 안에 딱 맞고 헤드 행 높이는 그대로 52px. */
body.chatgpt-reference-v1 .chatgpt-drive-column-sort {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  min-height: 36px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-column-sort.is-active {
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .chatgpt-drive-column-sort svg {
  width: var(--wds-icon-size-sm, 16px);
  height: var(--wds-icon-size-sm, 16px);
}

body.chatgpt-reference-v1 .chatgpt-drive-column-sort:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 2px;
}

@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-column-sort:hover {
    color: var(--cg-text);
  }
}

body.chatgpt-reference-v1 .chatgpt-drive-columns-row.is-selected {
  background: color-mix(in srgb, var(--drive-selection-accent) 12%, var(--cg-surface));
}

body.chatgpt-reference-v1 .chatgpt-drive-columns-row.is-selected::before {
  width: 3px;
  background: var(--drive-selection-accent);
}

/* drive-row-focus-ring-and-autofocus (오너 신고 09-13): 65c03ae91d(2026-09-08)가 이 세 규칙을
   --drive-selection-accent(행별 위즈온 잉크/선택색)에서 --wds-color-focus-ring 로 "통일"했는데,
   그 토큰은 #settingsV2Dialog/#settingsV2PasskeyNudge/#loginDialog 안에서만
   wizon-ai-shell-tokens.css가 잉크로 되묶고(그 3곳 스코프 밖 shared/wds/tokens/wds.css의
   --wds-blue-600/500 정의가 그대로 남는다) — 드라이브는 그 스코프 밖이라 파랑(rgb(26,117,235))
   그대로 샜다. 게다가 [tabindex="0"](포커스 여부와 무관 — roving tabindex 라 항상 정확히 한 행,
   기본값은 driveFocusedRowId()의 ids[0] 폴백이라 대개 "첫 행")과 :focus(포인터 클릭에도
   매치 — :focus-visible과 달리 UA가 포인터를 배제하지 않는다, 함정 메모리) 두 규칙이 "포커스가
   전혀 없어도" 또는 "마우스로 눌러도" 파란 테두리를 그렸다. 아래 line 38344의
   .chatgpt-drive-columns-row:focus-visible(올바른 --cg-focus-visible-ring, 후보 72의
   --wizon-ai-focus-visible-ring 폴백 체인)이 이미 있으므로, 여기서는 그 세 규칙을 지우고
   :focus-visible 하나로 되돌린다(오너 09-08 "테마 색상으로 통일" 의도는 유지 — 잉크 토큰
   자체가 이미 위즈온 테마 색이다). */

body.chatgpt-reference-v1 .chatgpt-drive-row-open {
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-selection-dock {
  display: flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-xs);
  position: sticky;
  left: 0;
  bottom: 0;
  z-index: 4;
  padding: 10px 12px;
  border-top: 1px solid var(--cg-menu-border, var(--line));
  background: color-mix(in srgb, var(--cg-surface) 94%, transparent);
  backdrop-filter: blur(12px);
}

body.chatgpt-reference-v1 .chatgpt-drive-selection-dock .chatgpt-drive-bulk-bar {
  border: 0;
  padding: 0;
  background: transparent;
}

body.chatgpt-reference-v1 .chatgpt-drive-selection-status {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
}

body.chatgpt-reference-v1 .chatgpt-drive-ai-launcher {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 30px;
  margin-left: auto;
  padding: 4px 9px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-surface, var(--bg));
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-12);
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-ai-launcher:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 2px;
}

body.chatgpt-reference-v1 .chatgpt-drive-ai-launcher svg {
  width: 14px;
  height: 14px;
}

/* D4 (drive-inspector-panel-hero-and-rows, v8 결정 — 전이 애니메이션 없음/즉시 스왑): the old
   slide-in keyframes are removed rather than left dead — auto-open now fires on every
   selection, so a per-open animation would replay far more often than the original "docked
   overlay" design intended, and the decision explicitly rules out any transition here.
   실폭 정본은 이 패널이 아니라 `.chatgpt-drive-explorer`의 grid-template-columns 3번째 트랙
   `--drive-inspector-width`(=280px, 42168행)다 — 이 규칙은 그 트랙을 width:auto로 채우기만
   한다(후보 inspector-width-cleanup: 39287~39315행의 죽은 clamp(320px,32%,400px) 규칙을
   삭제하며 유일하게 살아 있던 min-height:0 선언을 여기로 이관, CSSOM deleteRule 계산 스타일
   전 속성 diff로 무영향 확인). */
@media (min-width: 901px) {
  body.chatgpt-reference-v1 .chatgpt-drive-inspector {
    position: static;
    inset: auto;
    width: auto;
    height: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
    border: 0;
    border-left: 1px solid var(--cg-menu-border, var(--line));
    border-radius: 0;
    background: var(--cg-bg, var(--bg));
    box-shadow: none;
    z-index: auto;
  }
}

body.chatgpt-reference-v1 .chatgpt-drive-inspector-head {
  min-height: 52px;
  padding: 0 12px 0 16px;
}

body.chatgpt-reference-v1 .chatgpt-drive-inspector-head h2 {
  font-size: var(--ds-text-body-14);
  font-weight: 600;
}

body.chatgpt-reference-v1 .chatgpt-drive-inspector-body {
  padding: 16px;
}

/* D5 (drive-desktop-shell-sidebar-restore, 2026-09-13): the three explorer "head" rows (folder
   rail caption, column-list header, inspector tab header) had 3 different heights (judge 09-11:
   {38,52,59}) and 3 different first-content-row y offsets. This override wins the cascade over
   every earlier .chatgpt-drive-tree-head/-columns-head/-inspector-head rule in this file (same
   specificity, later source position) and forces all three to the same box-height so the first
   row of each column starts at the same y regardless of that column's own padding. */
body.chatgpt-reference-v1 .chatgpt-drive-tree-head,
body.chatgpt-reference-v1 .chatgpt-drive-columns-head,
body.chatgpt-reference-v1 .chatgpt-drive-inspector-head {
  min-height: 52px;
  height: 52px;
}

/* D7 (drive-desktop-shell-sidebar-restore, 2026-09-13): 09-11 judge measured 8 dark surfaces
   (body/main/explorer/tree/cols/colsHead/inspector/rail) all resolving to rgb(0,0,0) — --cg-bg
   and --cg-surface both flow through --wizon-ai-canvas/--wizon-ai-surface, which are BOTH #000
   in [data-theme="dark"] (shared/wds/wizon-ai-shell-tokens.css). No new color token is
   introduced — --wizon-ai-surface-raised (dark #2a2a2a) already exists and is reused by
   settings-v2.css/wizon-mobile-kit.css for this same "raised surface in dark" problem. Applying
   it to the rail/head rows gives Drive dark theme 2 distinct surface values (canvas/surface #000
   for the page and explorer body, raised #2a2a2a for the folder rail and the three sticky head
   rows) instead of judge's measured 1. A true 3rd tint (e.g. #171717) is not available without
   inventing a new token, which is out of this candidate's scope — flagged as a remaining gap in
   the report, not silently declared "3층 달성". */
[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-drive-tree-pane,
[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-drive-tree-head,
[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-drive-columns-head,
[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-drive-inspector-head,
[data-theme="dark"] body.chatgpt-reference-v1 .chatgpt-drive-view-toggle {
  background: var(--wizon-ai-surface-raised);
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: 100%;
  min-height: 38px;
  margin: -8px 0 14px;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-control);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-open svg {
  width: 16px;
  height: 16px;
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview {
  display: grid;
  place-items: center;
  width: 100%;
  min-height: 250px;
  margin-bottom: 16px;
  overflow: hidden;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-lg);
  background: var(--cg-hover-soft);
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview iframe,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview img,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview video {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 250px;
  border: 0;
  object-fit: contain;
  background: var(--cg-surface);
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview audio {
  width: calc(100% - 28px);
}

/* drive-mobile-pdf-canvas-viewer: 모바일(pointer:coarse/좁은 시트)에서 PDF 를 iframe 대신
   캔버스 갤러리로 그린다(judge 판정 -- iOS/Android 둘 다 iframe blob PDF 를 못 그린다). 위
   .chatgpt-drive-live-preview 의 grid+place-items:center+overflow:hidden 은 가운데 정렬된
   단일 미디어용이라 여러 캔버스를 세로로 나열하려면 block+세로 스크롤로 바꿔야 한다 -- 그
   전환은 이 is-pdf-canvas 한정자 안에서만 일어나고 다른 kind(image/audio/video/iframe)는
   기존 규칙 그대로 둔다(인접 표: 이미지·md 는 무변경). touch-action 은 발주서 그대로
   pan-x pan-y pinch-zoom -- 네이티브 핀치 확대를 막지 않는다(추가 제스처 구현 없음). */
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-pdf-canvas {
  display: block;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x pan-y pinch-zoom;
  padding: 12px 0 8px;
}

body.chatgpt-reference-v1 .chatgpt-drive-pdf-canvas-pages {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

body.chatgpt-reference-v1 .chatgpt-drive-pdf-canvas-page {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--cg-drive-pdf-canvas-page-radius, 4px);
  box-shadow: var(--cg-shadow-float-xs);
  background: var(--cg-surface);
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview--text {
  display: block;
  padding: 14px;
  text-align: left;
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview--text pre {
  max-height: 360px;
  margin: 0;
  overflow: auto;
  color: var(--cg-text);
  font: 12px/1.6 var(--wds-font-family-mono, monospace);
  white-space: pre-wrap;
  word-break: break-word;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-note {
  margin: 12px 0 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

/* 2026-09-05 드라이브 미리보기 형식 고도화 — md/csv/code/json·yaml·xml/zip. 넷 다
   .chatgpt-drive-live-preview 의 기본 grid+place-items:center 를 --text 와 같은 패턴으로
   블록 레이아웃으로 되돌린다(다중 자식을 가운데 정렬하면 표/코드카드/목록이 내용 폭으로
   쪼그라든다 — 실캡처로 확인). 값은 기존 토큰만 재사용한다(새 raw hex 없음).
   ★fix3(2026-09-11, 390 제목 잘림) — `display:block` 은 grid 트랙 배치를 껐지만
   `place-items:center` 의 `justify-items:center` 는 별개 속성이라 남는다. CSS Box
   Alignment(블록 레이아웃)는 justify-items/justify-self 가 block 컨테이너에서도
   자식 블록 박스를 «내용 폭으로 줄여 가운데 정렬」하는 효과를 낸다(display 리셋으로는
   못 끈다) — 표(td/th 는 padding 6px 10px 로 여유가 있어 안 보이던 것)와 달리 제목
   (h3/h4, 좌우 padding 0)은 그 폭 축소·중앙 정렬로 왼쪽이 뷰어 밖으로 밀려 잘렸다.
   실측(qa-md-table 픽스처, 390 뷰포트): body.chatgpt-drive-preview-markdown-body 가
   width 396px·left -3px(뷰어 표면 justify-items:center 가 표의 min-content 폭에 맞춰
   가운데 정렬) → `justify-items:normal` 되돌리면 width 324px·left 33px(= 패딩
   32px + 테두리 1px)로 표·본문·제목이 같은 좌측선에 온다(실측 완료, 표 로그 참고). */
body.chatgpt-reference-v1 .chatgpt-drive-live-preview--markdown,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview--code,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview--csv,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview--archive {
  display: block;
  width: 100%;
  max-height: 420px;
  padding: 14px;
  overflow: auto;
  text-align: left;
  align-items: normal;
  justify-items: normal;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-markdown-body {
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
  line-height: 1.6;
}

/* 09-11 오너 지적 — md 미리보기 YAML 머리말을 접힌 «문서 정보» 로. 기본 닫힘(details 는
   [open] 없이는 닫힌 채 렌더된다) — summary 만 보이고 해시류 값은 열기 전까지 노출되지
   않는다. 기존 .chatgpt-drive-preview-csv-meta/badge 와 같은 토큰만 재사용(새 raw 값 0). */
body.chatgpt-reference-v1 .chatgpt-drive-preview-frontmatter {
  margin: 0 0 var(--wds-space-3);
  padding: var(--wds-space-2) var(--wds-space-3);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--wizon-ai-radius-control);
  background: var(--cg-hover-soft, var(--surface-2));
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-frontmatter summary {
  cursor: pointer;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
  font-weight: 600;
  list-style: none;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-frontmatter summary::-webkit-details-marker {
  display: none;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-frontmatter-list {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--wds-space-1) var(--wds-space-3);
  margin: var(--wds-space-2) 0 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-frontmatter-row {
  display: contents;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-frontmatter-row dt {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-frontmatter-row dd {
  margin: 0;
  color: var(--cg-text);
  font-size: var(--ds-text-caption-12);
  word-break: break-word;
}

/* 라이브러리 전체화면 미리보기(is-text 서페이스)에 렌더될 때는 드라이브 인라인 패널의
   420px 상한·14px 패딩이 아니라 같은 서페이스의 종이 카드 여백(28px 32px)을 그대로
   따른다 — «텍스트 미리보기» <pre> 와 같은 자리에 앉는 변환본이라 잘림이 있으면 안 된다. */
body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-text.is-markdown-rendered {
  overflow: auto;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-text.is-markdown-rendered .chatgpt-drive-live-preview--markdown {
  max-height: none;
  padding: 28px 32px;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-text.is-markdown-rendered .chatgpt-drive-preview-markdown-body {
  font-size: var(--ds-text-body-14);
  line-height: 1.65;
}

body.chatgpt-reference-v1 .composer-attachment-media-stage > .composer-document-preview-surface.is-text .chatgpt-drive-preview-raw-toggle {
  margin: var(--wds-space-3) 0 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-csv-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-csv-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-hover-soft);
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-csv-scroll {
  overflow-x: auto;
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-lg);
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-csv-table {
  width: 100%;
  border-collapse: collapse;
  color: var(--cg-text);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-csv-table th,
body.chatgpt-reference-v1 .chatgpt-drive-preview-csv-table td {
  padding: 6px 10px;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
  white-space: nowrap;
  text-align: left;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-csv-table th {
  background: var(--cg-hover-soft);
  font-weight: 600;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-archive-list {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-archive-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--cg-menu-border, var(--line));
  color: var(--cg-text);
  font-size: var(--ds-text-caption-13);
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-archive-row:last-child {
  border-bottom: 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-archive-row.is-folder .chatgpt-drive-preview-archive-name {
  font-weight: 600;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-archive-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-archive-size {
  flex: 0 0 auto;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-loading {
  align-content: center;
  justify-items: stretch;
  gap: 10px;
  padding: 28px;
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-skeleton {
  display: block;
  width: 72%;
  height: 9px;
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-hover);
}

body.chatgpt-reference-v1 .chatgpt-drive-preview-skeleton.is-wide { width: 94%; }
body.chatgpt-reference-v1 .chatgpt-drive-preview-skeleton.is-short { width: 48%; }

/* 83-c/83-d 재작성. 문서 변환 로딩(is-converting)은 스켈레톤 아래 문구·취소 버튼이 하나
   더 붙는다 — .is-loading 의 grid 배치는 그대로 물려받고 그 안에서만 가운데 정렬한다. */
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-converting {
  justify-items: center;
  text-align: center;
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-converting .chatgpt-drive-preview-note {
  margin: 4px 0 0;
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-converting [data-drive-action="cancel-document-preview"] {
  margin-top: 4px;
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-error,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-sheet,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-unsupported {
  align-content: center;
  gap: 8px;
  padding: 24px;
  color: var(--cg-text);
  text-align: center;
}

/* 83-c/83-d 재작성. 실패 상태의 «다시 시도·다운로드» 두 버튼을 한 줄에 나란히 둔다 — 좁은
   모바일 폭에서는 wrap 되어 두 줄로 떨어진다(오버플로 0). */
body.chatgpt-reference-v1 .chatgpt-drive-live-preview-error-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

/* 20260910-drive-preview-retry-44. 드라이브 미리보기 실패 카드의 «다시 시도» 는
   이 카드에서 유일한 대화형 요소인데 공유 클래스 .chatgpt-route-action 의 기본
   min-height(34px)에서 실측 36.36px — 44px 미만이다. .chatgpt-route-action 은
   아카이브·휴지통·검색 실패·활동 실패 등 다른 화면에서도 그대로 재사용되므로(인접
   표는 보고 참고) 전역 규칙은 건드리지 않고, 이 카드 안 버튼만 pointer:coarse 에서
   시각 높이를 44px로 올린다(전역 .chatgpt-route-action 의 44px ::after 히트슬롭은
   실제 요소 rect 를 바꾸지 않아 QA 스윕이 여전히 36.36 을 읽는다 — 목업 없는 단독
   CTA라 시각 성장 쪽을 택했다). 데스크톱(마우스)은 손대지 않아 32/36 그대로다.
   A-3(drive-mobile-open-action-visible-failure, 09-11). 그 재핀은 is-error 카드 하나만
   덮었다 — 같은 모바일 미리보기 스테이지 안 형제 카드 is-sheet(«시트에서 편집»/«문서
   열기»)·is-unsupported(«다운로드») 의 .chatgpt-route-action 은 그대로 34px 였다(인접
   동작 표: 오너가 신고한 «시트에서 편집»이 정확히 이 갈래). raw 44 리터럴 대신 후보 33
   이 신설한 DTCG 토큰 --wizon-ai-action-target-touch(travel-route-widget.css:11 정의,
   44px)를 쓴다 — 값이 같은 곳은 같은 토큰을 가리켜야 한다. */
@media (pointer: coarse) {
  body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-error .chatgpt-route-action,
  body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-sheet .chatgpt-route-action,
  body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-unsupported .chatgpt-route-action {
    /* drive-mobile-open-action-visible-failure-fix-b(2026-09-11, QA 실측):
       이 토큰은 아직 :root/전역에 정의되지 않았다(travel-route-widget.css:11
       .travel-guidebook-card 스코프 안뿐 — 전역 정의는 후보 33(미착지)의 산출물).
       이 스코프 밖에서는 var() 가 미해결로 선언 전체가 무효화돼 36.36px 로 떨어졌다
       (실측). 폴백 44px 는 토큰이 아직 전역화되지 않은 동안의 안전망이고, 전역
       정의가 착지하면 폴백은 절대 쓰이지 않는다(같은 값이라 드리프트 아님). */
    min-height: var(--wizon-ai-action-target-touch, 44px);
  }
}

/* library-md-preview-fix2 QA FAIL 1(touch-target). «원문 보기»/«변환 보기» 토글
   (.chatgpt-drive-preview-raw-toggle, hydrateComposerAttachmentPreview 의 markdownRawToggle)은
   390 실측 36.36px — 헤더 아이콘 44×44 와 나란한 형제가 아니라 자기 자신이 유일한 대화형
   요소인 coarse 44px 규칙 위반이다. 위 20260910-drive-preview-retry-44 와 같은 문제·같은
   해법(전역 .chatgpt-route-action min-height 는 다른 화면에서 재사용되므로 건드리지 않고,
   이 버튼 클래스에만 pointer:coarse 에서 44px). --wizon-ai-action-target-touch 토큰은
   travel-route-widget.css 안에서만 정의돼 이 컨텍스트(라이브러리·드라이브 미리보기)에
   닿지 않으므로 위 선례와 같은 리터럴 44px 를 쓴다. 데스크톱(마우스)은 그대로 34/36. */
@media (pointer: coarse) {
  body.chatgpt-reference-v1 .chatgpt-drive-preview-raw-toggle {
    min-height: 44px;
  }
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-error > span svg,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-sheet > span svg,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-unsupported > span svg {
  width: 28px;
  height: 28px;
  color: var(--cg-muted);
}

/* A-2(drive-mobile-open-action-visible-failure). 눌린 즉시 로컬 pending — 라벨을 문구로
   바꾸는 대신(버튼마다 마크업이 다르다: 아이콘+텍스트/아이콘만) 시각적으로 덮어 하나의
   규칙으로 9곳 전부를 같은 방식으로 가린다. 진행 문구는 drivePendingButtonAttrs 가 쓴
   data-drive-pending-label 값 그대로(runDriveMobilePendingAction 이 없애지 않는 한 겹쳐
   그려지지 않는다 — 한 번에 한 액션만 pending 이다). */
body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview [aria-busy="true"],
body.chatgpt-reference-v1 .chatgpt-drive-live-preview [aria-busy="true"] {
  position: relative;
  color: transparent;
  pointer-events: none;
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview [aria-busy="true"] svg,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview [aria-busy="true"] svg {
  visibility: hidden;
}
body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview [aria-busy="true"]::after,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview [aria-busy="true"]::after {
  content: attr(data-drive-pending-label);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  white-space: nowrap;
}

/* The shared live-preview media rule intentionally expands document/image content to the
   whole stage. A registry icon is a type cue, not document content, so keep it intrinsic;
   otherwise the unsupported-state copy is pushed below the fixed preview viewport. */
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-sheet > span .wizon-file-type-icon--preview,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-unsupported > span .wizon-file-type-icon--preview {
  width: clamp(64px, 18vw, 96px);
  height: clamp(64px, 18vw, 96px);
  min-height: 0;
  background: transparent;
}

/* The 1024px inspector is deliberately narrower than the full-screen preview.
   Keep the existing registry artwork proportional so metadata remains visible
   in the same panel instead of letting the badge dominate it. */
body.chatgpt-reference-v1 .chatgpt-drive-inspector .chatgpt-drive-live-preview.is-sheet > span .wizon-file-type-icon--preview,
body.chatgpt-reference-v1 .chatgpt-drive-inspector .chatgpt-drive-live-preview.is-unsupported > span .wizon-file-type-icon--preview {
  width: 64px;
  height: 77px;
  max-width: 100%;
  max-height: 100%;
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-error p,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-sheet p,
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-unsupported p {
  margin: 0;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
}

/* 읽기 전용 시트 미리보기(오너 신고 2026-09-05). 예전 .is-sheet 는 아이콘 하나와 버튼만
   담는 카드였으므로 가운데 정렬 + 24px 패딩이 맞았다. 실제 격자가 들어오면 그 규칙이
   셀을 가운데로 밀고 세로 공간을 먹으므로, 격자가 있을 때만 왼쪽 정렬 스택으로 바꾼다.
   표 자체의 서식은 채팅 첨부 표와 같은 .composer-document-preview-* 규칙이 이미 그린다 —
   두 번째 표 스타일을 만들지 않는다. */
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-sheet.has-grid {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto auto;
  align-content: stretch;
  justify-items: stretch;
  padding: 12px;
  text-align: start;
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-sheet.has-grid p {
  text-align: start;
}

body.chatgpt-reference-v1 .chatgpt-drive-sheet-preview-grid {
  min-height: 0;
  overflow: auto;
}

/* 격자가 없는 카드 상태에서 빈 래퍼가 gap 을 벌리면 아이콘·문구 사이 리듬이 무너진다. */
body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-sheet:not(.has-grid) .chatgpt-drive-sheet-preview-grid {
  display: none;
}

body.chatgpt-reference-v1 .chatgpt-drive-live-preview.is-sheet.has-grid .chatgpt-route-action {
  justify-self: start;
}

body.chatgpt-reference-v1 .chatgpt-drive-mobile-fab,
body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview {
  display: none;
}

/* D3 (drive-inspector-panel-hero-and-rows, v8 — N3ⓐ 패널 썸네일 + 전체화면 모달): desktop
   full-screen preview reuses this SAME authenticated in-page renderer (renderDriveMobilePreview,
   real live content — pdf/image/audio/video/text/etc, keyboard Esc+Tab-trap+focus-return
   already wired via handleDriveRouteKeydown/activeDriveModal/trapDriveModalTab/
   restoreDrivePreviewContinuity) — this block only changes the container from an edge-to-edge
   fullscreen sheet to the decided centered card: min(1100px,88%) × min(760px,86%), radius 18
   (--wizon-ai-radius-sheet), L1 surface + 1px border, over a scrim (::before, so no new
   markup/JS). The overlay exists only after an explicit inspector action, so the docked hero
   thumbnail remains the default Finder workflow. */
@media (min-width: 901px) {
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview {
    /* F1(D2-fix, 09-14 judge 재검토): inset 50%/50% + transform:translate(-50%,-50%) 로
       중앙 정렬하면 이 요소가 fixed 후손(::before 스크림)의 컨테이닝 블록이 되어 ::before 의
       inset:0 이 뷰포트가 아니라 이 박스(1100×760)로 해석된다(실측: ::before 계산 크기
       1098×758, 배경 픽셀 A/B 변화 0). transform 을 없애고 inset:0 + margin:auto 로 중앙
       정렬해 컨테이닝 블록을 만들지 않는다 — 결과 위치는 동일(가로/세로 중앙, width/height 는
       margin:auto 로 자동 배분). */
    position: fixed;
    inset: 0;
    margin: auto;
    z-index: 420;
    display: grid;
    /* A-1(drive-mobile-open-action-visible-failure). 4번째 트랙(feedback)은 실패 사유가
       있을 때만 DOM 에 존재하는 요소다 — 자리를 grid-row 로 고정해 두지 않으면 그 요소가
       빠졌을 때 stage/actions 가 암묵 배치로 밀려 레이아웃이 통째로 어긋난다. */
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    width: min(1100px, 88%);
    height: min(760px, 86%);
    min-height: 0;
    border: 1px solid var(--cg-menu-border, var(--line));
    border-radius: var(--wizon-ai-radius-sheet, 18px);
    background: var(--cg-surface);
    /* F11: 규칙 §5·A10 «box-shadow 공집합» — 모달도 예외 아님. */
    box-shadow: none;
    overflow: hidden;
  }
  /* Scrim — a fixed pseudo-element covers the full viewport. F1 수리로 .chatgpt-drive-mobile-preview
     자신이 더 이상 컨테이닝 블록을 만들지 않으므로(transform 제거) 이 ::before 의 inset:0 은
     실제 뷰포트 기준으로 계산된다. */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    background: var(--cg-scrim, var(--wizon-scrim));
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-head {
    display: grid;
    grid-row: 1;
    grid-template-columns: 48px minmax(0, 1fr) 48px;
    align-items: center;
    min-height: 56px;
    padding: 8px 16px;
    border-bottom: 1px solid var(--cg-menu-border, var(--line));
    background: var(--cg-surface);
  }

  /* A-1(drive-mobile-open-action-visible-failure). 헤더 바로 아래, 스테이지 위 — 오버레이
     안에서 유일하게 사유를 그리는 자리. 페이지 배경의 .chatgpt-drive-feedback 과 같은
     마크업(renderDriveActionFeedbackCard)을 재사용하고 여백만 오버레이에 맞춘다. */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-feedback {
    grid-row: 2;
    margin: 8px 16px 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-head h2 {
    overflow: hidden;
    margin: 0;
    color: var(--cg-text);
    font-size: var(--ds-text-body-15);
    font-weight: var(--wds-font-weight-medium);
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* F12(D2-fix, 09-14 judge 재검토): D3 결정표 «머리 = 유형 타일(28/r8) + 이름 + 메타 13».
     가운데 그리드 칸을 h2 하나에서 타일+제목/메타 두 줄로 바꾼다 — 그리드 자체(3칸)는 그대로. */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-titlewrap {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-tile {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 28px;
    height: 28px;
    overflow: hidden;
    /* anti-drift(raw-border-radius 재수정): 리터럴 8px 대신 같은 값의 기존 토큰. */
    border-radius: var(--chatgpt-route-radius-control);
    background: var(--cg-hover-soft);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-tile img {
    width: 20px;
    height: 20px;
    object-fit: contain;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-titlecol {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-titlecol h2 {
    text-align: left;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-meta {
    overflow: hidden;
    color: var(--cg-muted);
    font-size: var(--ds-text-caption-13);
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* D3 결정표: 머리 버튼(내려받기·공유·닫기) 각 36 — 웹 컨트롤 1값(규칙표 §1). */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-icon {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 0;
    border-radius: var(--chatgpt-route-radius-control);
    background: transparent;
    color: var(--cg-text);
    cursor: pointer;
  }

  @media (hover: hover) {
    body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-icon:hover {
      background: var(--cg-hover-soft);
    }
  }

  /* D3/D-13 (v8 결정표): 지면 폭 640(읽기용이 아니라 «무엇인지 확인용») — place-items
     start/stretch 로 무대를 잡고 지면은 margin-inline:auto 로 가운데(고정폭을 place-items:
     center 로 다시 감싸면 100% 가 지면 폭으로 재계산되어 무대 밖으로 돌출한다, 720 실측 수리). */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage {
    grid-row: 3;
    display: grid;
    place-items: start stretch;
    min-height: 0;
    overflow: auto;
    padding: 24px clamp(24px, 5vw, 80px);
    background: var(--cg-hover-soft);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage .chatgpt-drive-live-preview {
    width: 100%;
    max-width: 640px;
    min-height: 100%;
    margin: 0 auto;
    border-radius: var(--chatgpt-route-radius-lg);
    box-shadow: var(--cg-shadow-float-lg);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage .chatgpt-drive-live-preview iframe,
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage .chatgpt-drive-live-preview img,
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage .chatgpt-drive-live-preview video {
    min-height: 100%;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-actions {
    grid-row: 4;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 56px;
    gap: var(--chatgpt-route-gap-xs);
    padding: 10px 16px;
    border-top: 1px solid var(--cg-menu-border, var(--line));
    background: var(--cg-surface);
  }

  /* F12(D2-fix, 09-14 judge 재검토): 버튼 40 + 컨테이너 padding 10×2 가 실측 발 높이를
     56 이 아니라 61.4 로 밀었다 — 버튼을 36 으로 낮추면 36+20=56 로 정확히 맞는다(D3
     결정표 «발 56·버튼 36»). */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-actions button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-width: 112px;
    min-height: 36px;
    padding: 6px 14px;
    border: 1px solid var(--cg-menu-border, var(--line));
    border-radius: var(--chatgpt-route-radius-control);
    background: var(--cg-surface);
    color: var(--cg-text);
    font: inherit;
    font-size: var(--ds-text-caption-13);
    cursor: pointer;
  }

  @media (hover: hover) {
    body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-actions button:hover {
      background: var(--cg-hover-soft);
    }
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-icon:focus-visible,
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-actions button:focus-visible {
    outline: 2px solid var(--cg-focus-visible-ring, var(--cg-focus-ring));
    outline-offset: 2px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-actions svg {
    width: 18px;
    height: 18px;
  }

  /* F12: D3 발 규격의 «Esc 닫기» 안내 — 데스크톱 모달에서만(마우스+키보드 컨텍스트). 모바일
     시트(max-width:900 블록)는 이 캡션에 별도 규칙이 없어 기본 인라인으로 남지만, 그 화면은
     터치 전용이라 아래 720 블록에서 접어 감춘다(F5/D10 스코프 밖 화면에 새 텍스트를 얹지
     않는다는 원칙과 동일). */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-hint {
    margin-left: 8px;
    color: var(--cg-muted);
    font-size: var(--ds-text-caption-12);
    white-space: nowrap;
  }
}

@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-hint {
    display: none;
  }
}

@media (min-width: 901px) and (any-pointer: coarse) {
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-actions button {
    min-height: var(--wizon-ai-action-target-touch);
  }
}

/* D3 (drive-desktop-shell-sidebar-restore, 2026-09-13, judge 09-11 권고 ⓑ): the dock used to be
   position:absolute, floating over the file list — at 1440 max-scroll it covered the last two
   rows and made the row's "···" menu unclickable (elementFromPoint hit the dock, not the row).
   .chatgpt-drive-page is display:flex/flex-direction:column (not CSS grid), so the grid-row
   equivalent here is: dock is a normal flex-column child (position:relative, not absolute),
   self-aligned to the end — the same static/last-row effect the grid recipe describes, reusing
   the positioning .chatgpt-drive-page--chat-expanded already used only in the expanded state.
   Because it is back in flow, .chatgpt-drive-explorer (flex:1 1 auto) now shrinks to leave room
   for it instead of being covered. */
body.chatgpt-reference-v1 .chatgpt-drive-chat-dock {
  display: grid;
  position: relative;
  align-self: flex-end;
  z-index: 3;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  flex: 0 0 auto;
  gap: var(--chatgpt-route-gap-xs);
  width: min(360px, 32vw);
  min-height: calc(var(--wizon-ai-space-unit) * 16);
  margin-top: var(--chatgpt-route-gap-sm);
  padding: var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-sm);
  border: 1px solid var(--cg-menu-border, var(--line));
  border-radius: var(--chatgpt-route-radius-pill);
  background: var(--cg-surface, var(--bg));
  box-shadow: var(--cg-shadow-float-xs);
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-dock[aria-busy="true"] {
  opacity: 0.72;
}

/* K-4(2.4.7 AA) — 드라이브 채팅독은 #chatForm 과 별도 마크업(.chatgpt-drive-chat-dock,
   app.js:33191)이라 메인 셸 링(19567행 .chatgpt-composer-shell:has(textarea:focus-visible))이
   적용되지 않아 마우스·Tab 포커스 표시가 전무했다(composer-focus-ring-siblings QA 09-12).
   메인과 같은 규약(셸 :has(textarea:focus-visible), 같은 토큰·두께·offset)을 이 셀렉터에
   그대로 적용한다 — 독의 반경은 --chatgpt-route-radius-pill(알약)이라 outline 이 그대로 따른다.
   candidate 62(9690e65d8)가 착지하면 셀렉터를 그 규칙 목록에 콤마로 합칠 것. */
body.chatgpt-reference-v1 .chatgpt-drive-chat-dock:has(textarea:focus-visible) {
  outline: 2px solid var(--wizon-ai-text-primary);
  outline-offset: 2px;
}

/* D3: the two `right:` offset overrides that used to compensate for the dock's absolute
   positioning (one narrowing it by --drive-inspector-width when the inspector pane was open,
   one resetting it back to auto in the chat-expanded state) are removed — the dock is in normal
   flex flow now, so it never needs a right-edge offset. */

body.chatgpt-reference-v1 .chatgpt-drive-chat-status {
  grid-column: 1 / -1;
  margin: 0;
  padding: 0 var(--chatgpt-route-gap-xs) var(--chatgpt-route-gap-2xs);
  color: var(--cg-danger-text, var(--cg-text));
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-input-wrap {
  display: flex;
  align-items: center;
  min-width: 0;
  min-height: calc(var(--wizon-ai-space-unit) * 10);
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-input {
  width: 100%;
  min-width: 0;
  min-height: calc(var(--wizon-ai-space-unit) * 6.5);
  max-height: calc(var(--wizon-ai-space-unit) * 6.5);
  padding: 0;
  overflow: hidden;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--cg-text);
  box-shadow: none;
  font: inherit;
  font-size: var(--ds-text-body-16);
  line-height: calc(var(--wizon-ai-space-unit) * 6.5);
  resize: none;
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-input::placeholder {
  color: var(--cg-input-placeholder);
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-input:focus,
body.chatgpt-reference-v1 .chatgpt-drive-chat-input:focus-visible {
  border: 0;
  outline: 0;
  box-shadow: none;
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-2xs);
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-icon-button,
body.chatgpt-reference-v1 .chatgpt-drive-chat-submit {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  width: calc(var(--wizon-ai-space-unit) * 10);
  height: calc(var(--wizon-ai-space-unit) * 10);
  padding: 0;
  border: 0;
  border-radius: var(--chatgpt-route-radius-pill);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-icon-button svg,
body.chatgpt-reference-v1 .chatgpt-drive-chat-submit svg {
  width: calc(var(--wizon-ai-space-unit) * 5);
  height: calc(var(--wizon-ai-space-unit) * 5);
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-model {
  display: inline-flex;
  align-items: center;
  gap: var(--chatgpt-route-gap-2xs);
  min-height: calc(var(--wizon-ai-space-unit) * 10);
  padding: 0 var(--chatgpt-route-gap-sm);
  border: 0;
  border-radius: var(--chatgpt-route-radius-control);
  background: transparent;
  color: var(--cg-muted);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  white-space: nowrap;
  cursor: pointer;
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-model svg {
  width: calc(var(--wizon-ai-space-unit) * 4);
  height: calc(var(--wizon-ai-space-unit) * 4);
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-submit {
  background: var(--cg-send-bg);
  color: var(--cg-send-fg);
}

@media (hover: hover) {
  body.chatgpt-reference-v1 .chatgpt-drive-chat-icon-button:hover,
  body.chatgpt-reference-v1 .chatgpt-drive-chat-model:hover {
    background: var(--cg-hover-soft);
    color: var(--cg-text);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-chat-submit:hover {
    background: var(--cg-send-bg-hover, var(--cg-send-bg));
  }
}

body.chatgpt-reference-v1 .chatgpt-drive-chat-icon-button:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-chat-model:focus-visible,
body.chatgpt-reference-v1 .chatgpt-drive-chat-submit:focus-visible {
  outline: 2px solid var(--cg-focus-ring);
  outline-offset: 2px;
}

@media (min-width: 901px) and (max-width: 1200px) {
  /* Reuse the existing canonical medium-shell band for the compact inspector.
     The prior 1100px-only split violated the shared breakpoint ratchet and
     left 1101–1200px with less metadata room for no product reason. */
  body.chatgpt-reference-v1 .chatgpt-drive-inspector-preview {
    margin-bottom: var(--chatgpt-route-gap-sm);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-inspector .chatgpt-drive-live-preview.is-sheet,
  body.chatgpt-reference-v1 .chatgpt-drive-inspector .chatgpt-drive-live-preview.is-unsupported {
    box-sizing: border-box;
    height: calc(var(--wizon-ai-space-unit) * 53);
    min-height: calc(var(--wizon-ai-space-unit) * 53);
    gap: var(--chatgpt-route-gap-2xs);
    padding: var(--chatgpt-route-gap-sm);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-inspector .chatgpt-drive-live-preview.is-sheet > span .wizon-file-type-icon--preview,
  body.chatgpt-reference-v1 .chatgpt-drive-inspector .chatgpt-drive-live-preview.is-unsupported > span .wizon-file-type-icon--preview {
    width: calc(var(--wizon-ai-space-unit) * 13);
    height: calc(var(--wizon-ai-space-unit) * 16);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-explorer {
    grid-template-columns: 180px minmax(496px, 1fr) minmax(250px, 280px);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-explorer.chatgpt-drive-explorer--tree-closed {
    grid-template-columns: minmax(0, 1fr) minmax(260px, 292px);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-explorer.chatgpt-drive-explorer--inspector-closed {
    grid-template-columns: 180px minmax(496px, 1fr);
  }
  body.chatgpt-reference-v1 .chatgpt-drive-explorer.chatgpt-drive-explorer--tree-closed.chatgpt-drive-explorer--inspector-closed {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Medium shells have less horizontal room than the desktop grid's 520px +
     292px minimum. Let the list track shrink while keeping a bounded inspector;
     otherwise the Explorer's overflow guard crops the inspector at 1024px. */
  body.chatgpt-reference-v1 .chatgpt-drive-inspector,
  body.chatgpt-reference-v1 .chatgpt-drive-inspector-body {
    min-width: 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-inspector-meta-row {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    justify-content: normal;
  }

  /* Preserve every desktop column while allowing the list to share the medium
     shell with the inspector. The wide desktop widths remain unchanged. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns-head,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row {
    grid-template-columns: 28px minmax(0, 1fr) 64px 64px 88px 40px;
    gap: 6px;
    min-width: 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns-subline {
    display: block;
  }

  /* D2 shell-fit: this band keeps the hierarchy plus a >=496px list. The selection dock
     therefore has to reflow its complete command set inside the list instead of widening the
     scroll container; hiding the horizontal overflow is a backstop, not the primary layout. */
  body.chatgpt-reference-v1 .chatgpt-drive-columns-pane {
    overflow-x: hidden;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-selection-dock {
    min-width: 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-selection-dock .chatgpt-drive-bulk-bar {
    flex-wrap: wrap;
    inline-size: 100%;
    box-sizing: border-box;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-selection-dock .chatgpt-drive-bulk-bar-count {
    flex: 1 0 100%;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-selection-dock .chatgpt-drive-bulk-bar-spacer {
    display: none;
  }
}

@media (max-width: 900px) {
  html[data-portal-route="/drive"] #portalPage.chatgpt-route-page {
    --chatgpt-route-page-top: var(--chatgpt-route-page-mobile-top);
    --chatgpt-route-page-bottom: var(--chatgpt-route-page-mobile-bottom);
    height: auto;
    padding-inline: 0;
    overflow: visible;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-page {
    display: grid;
    height: auto;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead {
    padding: 10px 14px 12px;
    border-bottom: 1px solid var(--cg-menu-border, var(--line));
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead > .chatgpt-drive-breadcrumb {
    order: -1;
    margin: 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-page .chatgpt-route-search {
    width: 100%;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-page .chatgpt-route-search input {
    width: 100%;
    min-height: 48px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-filter-row {
    padding: 10px 14px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-explorer,
  body.chatgpt-reference-v1 .chatgpt-drive-explorer.chatgpt-drive-explorer--tree-closed,
  body.chatgpt-reference-v1 .chatgpt-drive-explorer.chatgpt-drive-explorer--inspector-closed {
    display: block;
    height: auto;
    min-height: 0;
    overflow: visible;
    border: 0;
    border-radius: 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-tree-pane,
  body.chatgpt-reference-v1 .chatgpt-drive-inspector,
  body.chatgpt-reference-v1 .chatgpt-drive-selection-dock,
  body.chatgpt-reference-v1 .chatgpt-drive-chat-dock {
    display: none;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns-pane {
    overflow: visible;
  }

  /* Keep the Miller scroller inside the mobile route: its own horizontal
     scrolling remains available without widening the page past 390px. */
  body.chatgpt-reference-v1 .chatgpt-drive-explorer--columns,
  body.chatgpt-reference-v1 .chatgpt-drive-explorer--columns .chatgpt-drive-columns-pane,
  body.chatgpt-reference-v1 .chatgpt-drive-explorer--columns .chatgpt-drive-miller {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    box-sizing: border-box;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-explorer--columns .chatgpt-drive-columns-pane {
    overflow: hidden;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns-row {
    min-height: 72px;
    padding: 11px 14px;
    grid-template-columns: minmax(0, 1fr) 40px;
    min-width: 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns-head,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell-select,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--type,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--size,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--modified,
  body.chatgpt-reference-v1 .chatgpt-drive-columns-row .chatgpt-drive-columns-cell--aiindex {
    display: none;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns--select-mode .chatgpt-drive-columns-row {
    grid-template-columns: 28px minmax(0, 1fr) 40px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns--select-mode .chatgpt-drive-columns-cell-select {
    display: flex;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb {
    min-width: 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-trail {
    display: flex;
    flex: 0 1 calc(100vw - 116px);
    width: calc(100vw - 116px);
    max-width: calc(100vw - 116px);
    min-width: 0;
    overflow: hidden;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item {
    flex: 0 1 28%;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-breadcrumb-item.is-current {
    flex: 1 1 44%;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns-icon {
    flex-basis: 24px;
    width: 24px;
    height: 24px;
    max-width: 24px;
    max-height: 24px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns-icon svg {
    width: 16px;
    height: 16px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-search-filter-panel.chatgpt-drive-mobile-submenu {
    width: 100%;
    max-height: min(72vh, 620px);
    padding: 18px 16px calc(18px + var(--wizon-safe-bottom));
  }

  body.chatgpt-reference-v1 .chatgpt-drive-search-results {
    min-height: 58vh;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-search-results-head {
    min-height: 54px;
    padding: 9px 14px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-search-result {
    grid-template-columns: 20px minmax(0, 1fr);
    gap: 6px;
    padding: 11px 10px 10px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-search-result-main {
    grid-template-columns: 48px minmax(0, 1fr);
    gap: 10px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-search-result-side {
    display: flex;
    align-items: center;
    justify-content: space-between;
    grid-column: 2;
    padding-left: 58px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-search-result-side .chatgpt-route-action {
    min-height: 34px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-fab {
    position: fixed;
    right: calc(20px + var(--wizon-safe-right));
    bottom: calc(84px + var(--wizon-safe-bottom));
    z-index: 20;
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    border: 0;
    border-radius: var(--chatgpt-route-radius-pill);
    background: var(--cg-send-bg);
    color: var(--cg-send-fg);
    box-shadow: var(--cg-shadow-float-lg);
    cursor: pointer;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-fab svg {
    width: 24px;
    height: 24px;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview {
    position: fixed;
    inset: 0;
    z-index: 420;
    display: grid;
    /* A-1(drive-mobile-open-action-visible-failure) — 위 901px 블록과 같은 이유
       (grid-row 로 자리 고정). */
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    width: 100%;
    min-height: 100dvh;
    background: var(--cg-bg, var(--bg));
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-head {
    display: grid;
    grid-row: 1;
    grid-template-columns: 48px minmax(0, 1fr) 48px;
    align-items: center;
    min-height: 64px;
    padding: max(4px, var(--wizon-safe-top)) 8px 4px;
    border-bottom: 1px solid var(--cg-menu-border, var(--line));
    background: var(--cg-surface);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-feedback {
    grid-row: 2;
    margin: 8px 12px 0;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-head h2 {
    overflow: hidden;
    margin: 0;
    color: var(--cg-text);
    font-size: var(--ds-text-body-15);
    font-weight: var(--wds-font-weight-medium);
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* F12 부작용 방지: 이 재사용 렌더러가 이제 타일+제목/메타 마크업을 모바일 시트에도 함께
     내보낸다(같은 함수) — 이 화면(≤900)은 F12 스코프 밖이지만 새 요소가 스타일 없이
     인라인으로 깨지지 않게 최소 배치만 맞춘다. */
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-titlewrap {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-width: 0;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-tile {
    display: none;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-titlecol {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-meta {
    display: none;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-icon {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: var(--chatgpt-route-radius-control);
    background: transparent;
    color: var(--cg-text);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage {
    grid-row: 3;
    min-height: 0;
    overflow: auto;
    display: grid;
    align-items: stretch;
    padding: clamp(16px, 5vw, 24px) 18px;
    background: var(--cg-hover-soft);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage .chatgpt-drive-live-preview {
    width: 100%;
    min-height: 0;
    height: 100%;
    margin: 0;
    border-radius: var(--chatgpt-route-radius-control);
    box-shadow: var(--cg-shadow-float-lg);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage .chatgpt-drive-live-preview iframe,
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage .chatgpt-drive-live-preview img,
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-stage .chatgpt-drive-live-preview video {
    width: 100%;
    min-height: 100%;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-actions {
    grid-row: 4;
    display: flex;
    justify-content: space-around;
    gap: 6px;
    padding: 10px 12px calc(10px + var(--wizon-safe-bottom));
    border-top: 1px solid var(--cg-menu-border, var(--line));
    background: var(--cg-surface);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-actions button {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 5px;
    min-width: 0;
    min-height: 52px;
    padding: 4px;
    border: 0;
    border-radius: var(--chatgpt-route-radius-control);
    background: transparent;
    color: var(--cg-text);
    font: inherit;
    font-size: var(--ds-text-caption-12);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-preview-actions svg {
    width: 20px;
    height: 20px;
  }
}

@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-body {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: calc(var(--wizon-ai-space-unit) * 36);
    gap: var(--chatgpt-route-gap-xs);
    padding: var(--chatgpt-route-gap-sm);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-mobilemeta {
    display: none;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-columns-name-text:has(> .chatgpt-drive-columns-mobilemeta) > .chatgpt-drive-columns-subline {
    display: block;
  }
}

/* 20260812-drive-mobile-touch-qa: production QA at a CSS 390x844 viewport exposed three
   interaction gaps that geometry-only snapshots did not catch.

   1. In icon view the 44px document/folder artwork sits outside `.chatgpt-drive-row-open`, so
      tapping the most prominent part of a card does nothing. A transparent pseudo-element owned
      by the existing open button covers the card while the checkbox and context trigger retain
      their z-index:2 priority. This changes no markup, paint, selection, or menu wiring.
   2. Mobile breadcrumb buttons are visually compact (28px) but need a 44px vertical hit area.
      There is enough measured clearance above and below, so transparent hit slop preserves the
      approved compact header instead of making it 16px taller.
   3. Drive and row-action bottom-sheet items were 39.8px tall and directly adjacent, which means
      transparent slop would overlap the next command. Grow those visible rows to 44px instead. */
body.chatgpt-reference-v1 .chatgpt-drive-columns.is-grid-view .chatgpt-drive-row-open::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

@media (max-width: 900px) {
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead .chatgpt-drive-breadcrumb-item {
    position: relative;
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-stickyhead .chatgpt-drive-breadcrumb-item::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }

  body.chatgpt-reference-v1 .chatgpt-drive-mobile-menu .chatgpt-library-menu-item,
  body.chatgpt-reference-v1 .chatgpt-drive-mobile-submenu .chatgpt-library-menu-item,
  body.chatgpt-reference-v1 .chatgpt-drive-context-menu .chatgpt-library-menu-item {
    min-height: 44px;
  }
}

@media (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .chatgpt-drive-tree-twist svg {
    animation: none;
    transition: none;
  }
}
.wizon-file-type-icon {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
}

/* Generated-file cards use the same transparent SVG artwork as attachments. Keep the variant
   explicitly registered in CSS so it cannot silently fall back to an unstyled wrapper contract. */
.wizon-file-type-icon--attachment {
  object-position: center;
}

.chatgpt-drive-columns-icon .wizon-file-type-icon,
.chatgpt-library-file-icon .wizon-file-type-icon,
.chatgpt-library-move-entry-icon .wizon-file-type-icon,
.generated-file-icon .wizon-file-type-icon {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 1 / 1;
}

/* ═══════════════════════════════════════════════════════════════════════════
   CPQ 견적 위젯 대시보드 (doc 400 §13.4 — 오너 승인 목업 v3, 2026-08-15)
   v2 반려 사유: 「위젯 틀은 맞는데 안이 텍스트라 문서로 읽힌다」. Mobbin 재조사에서
   뽑은 규칙 하나를 전면 적용했다 — **모든 카드가 안에 차트를 품는다**
   (Apple Health 행별 인라인 막대 · Chase 당월 강조 월별 막대 · Glassdoor/Commons
   도넛+값·비중 범례 · Airwallex 커넥터 타임라인 · Remote HR 승인 카드).
   깊이 = 틴트 «캔버스» 위에 그림자로 떠 있는 카드. v2 의 흰 배경+헤어라인을 버렸다.
   위계 = 히어로 title-xl ↔ 캡션 label-s 의 크기 대비(웨이트는 팔레트 상한 600 고정).
   ═══════════════════════════════════════════════════════════════════════════ */
.cpq-dash {
  --cpqd-canvas: var(--wds-color-surface-muted);
  --cpqd-card: var(--wds-color-surface);
  --cpqd-ink: var(--wds-color-text);
  --cpqd-ink-2: var(--wds-color-text-muted);
  --cpqd-muted: var(--wds-color-text-subtle);
  --cpqd-hair: var(--wds-color-border-subtle);
  --cpqd-accent: var(--wds-color-primary-text);
  --cpqd-hw-fg: var(--wds-color-info-text);
  --cpqd-hw-bg: var(--wds-color-info-container);
  --cpqd-sw-fg: var(--wds-color-tag-teal-text);
  --cpqd-sw-bg: var(--wds-color-tag-teal-bg);
  --cpqd-ok-fg: var(--wds-color-success-text);
  --cpqd-ok-bg: var(--wds-color-success-container);
  --cpqd-gone-fg: var(--wds-badge-neutral-fg);
  --cpqd-gone-bg: var(--wds-badge-neutral-bg);
  --cpqd-sup-fg: var(--wds-color-tag-violet-text);
  --cpqd-sup-bg: var(--wds-color-tag-violet-bg);
  --cpqd-pend-fg: var(--wds-color-warning-text);
  --cpqd-pend-bg: var(--wds-color-warning-container);
  --cpqd-up: var(--wds-color-error-text);
  --cpqd-down: var(--wds-color-success-text);
  --cpqd-shadow: var(--ds-shadow-xs);
  /* 막대의 «바닥»색. ★border-subtle 을 쓰면 다크에서 카드 배경과 대비 1.00 이라
     추이 막대가 통째로 사라진다(2026-08-15 렌더 실측) — 차트가 「그 달엔 없었다」고
     거짓말하는 상태다. border-strong 이 라이트 1.47 / 다크 1.94 로 뮤트를 유지하면서
     보이는 유일한 값이었다. 후보 절반(divider·surface-sunken·neutral-*)은 아예 없는
     토큰이라 transparent 로 계산됐다 — 이름이 그럴듯하다고 쓰지 말 것. */
  --cpqd-bar: var(--wds-color-border-strong);
  display: block; background: var(--cpqd-canvas); border-radius: var(--wizon-ai-radius-sheet);
  padding: 12px; color: var(--cpqd-ink); font-size: var(--wds-font-size-body-s); line-height: 1.45;
  container-type: inline-size;
}
.cpq-dash .cpq-dash-widget { background: var(--cpqd-card); border-radius: var(--wizon-ai-radius-popover);
  box-shadow: var(--cpqd-shadow); margin-bottom: 12px; overflow: hidden; }
.cpq-dash-grid { display: grid; grid-template-columns: 1.55fr 1fr; gap: 12px; }
.cpq-dash-grid .cpq-dash-widget { margin-bottom: 12px; }
@container (max-width: 560px) { .cpq-dash-grid { grid-template-columns: 1fr; } }
@media (max-width: 520px) { .cpq-dash-grid { grid-template-columns: 1fr; } }

.cpq-dash-w-head { display: flex; align-items: center; justify-content: space-between;
  padding: 13px 16px 0; gap: 10px; }
.cpq-dash-w-label { font-size: var(--wds-font-size-caption); font-weight: 600;
  display: inline-flex; align-items: center; gap: 6px; }
.cpq-dash-w-label::before { content: ""; width: 7px; height: 7px; border-radius: var(--wizon-ai-radius-pill);
  background: currentColor; display: inline-block; }
.cpq-dash-w-label--main { color: var(--cpqd-hw-fg); }
.cpq-dash-w-label--sup { color: var(--cpqd-sup-fg); }
.cpq-dash-w-label--pend { color: var(--cpqd-pend-fg); }
.cpq-dash-w-label--cat { color: var(--cpqd-ok-fg); }
.cpq-dash-w-more { font-size: var(--wds-font-size-label-s); color: var(--cpqd-muted); }
.cpq-dash-w-more--link { background: none; border: 0; color: var(--cpqd-accent); cursor: pointer;
  font-weight: 600; padding: 0; font-family: inherit; }

/* ── 히어로: 큰 수 + 델타 배지 + 6개월 추이 막대 ── */
.cpq-dash-hero { padding: 8px 16px 16px; }
.cpq-dash-big { font-size: var(--wds-font-size-h1); font-weight: 600; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1.1; }
.cpq-dash-unit { font-size: var(--wds-font-size-body-s); font-weight: 600; color: var(--cpqd-ink-2);
  margin-left: 4px; letter-spacing: 0; }
.cpq-dash-delta { display: inline-flex; align-items: center; gap: 4px; font-size: var(--wds-font-size-label-s);
  font-weight: 600; padding: 3px 8px; border-radius: var(--wizon-ai-radius-pill); margin-left: 8px;
  vertical-align: middle; }
.cpq-dash-delta--up { background: var(--cpqd-ok-bg); color: var(--cpqd-ok-fg); }
.cpq-dash-delta--down { background: var(--cpqd-gone-bg); color: var(--cpqd-gone-fg); }
.cpq-dash-big-sub { font-size: var(--wds-font-size-caption); color: var(--cpqd-ink-2); margin-top: 6px; }
.cpq-dash-big-sub em { font-style: normal; color: var(--cpqd-pend-fg); font-weight: 600; }
.cpq-dash-spark { margin-top: 14px; }
.cpq-dash-spark-svg { display: block; width: 100%; height: 46px; }
.cpq-dash-sbar { fill: var(--cpqd-bar); }
.cpq-dash-sbar-on { fill: var(--cpqd-hw-fg); }
.cpq-dash-spark-labels { display: flex; margin-top: 5px; }
.cpq-dash-sb { flex: 1; text-align: center; font-size: var(--wds-font-size-label-s);
  color: var(--cpqd-muted); text-decoration: none; font-variant-numeric: tabular-nums; }
.cpq-dash-sb-on { color: var(--cpqd-ink); font-weight: 600; }

/* ── 상태 구성 도넛 + 범례 ── */
.cpq-dash-donutwrap { display: flex; align-items: center; gap: 14px; padding: 10px 16px 16px; }
.cpq-dash-donut { position: relative; width: 104px; height: 104px; flex: 0 0 104px; display: block; }
.cpq-dash-donut svg { transform: rotate(-90deg); }
.cpq-dash-arc { fill: none; stroke-width: 13; cursor: pointer;
  transition: stroke-width 180ms ease, opacity 180ms ease; }
.cpq-dash-arc:hover { stroke-width: 16; }
.cpq-dash-arc--ok { stroke: var(--cpqd-ok-fg); }
.cpq-dash-arc--sup { stroke: var(--cpqd-sup-fg); }
.cpq-dash-arc--gone { stroke: var(--cpqd-gone-fg); }
.cpq-dash-dim { opacity: 0.3; }
.cpq-dash-donut-mid { position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; pointer-events: none; }
.cpq-dash-donut-mid b { font-size: var(--wds-font-size-h2); font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.cpq-dash-donut-mid u { font-size: var(--wds-font-size-label-s); color: var(--cpqd-muted);
  text-decoration: none; margin-top: 1px; }
.cpq-dash-legend { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.cpq-dash-lg { display: flex; align-items: center; gap: 8px; font-size: var(--wds-font-size-caption);
  cursor: pointer; border: 0; background: none; font-family: inherit; color: inherit;
  border-radius: var(--wizon-ai-radius-control); padding: 4px 6px; text-align: left;
  transition: background 150ms ease, opacity 180ms ease; }
.cpq-dash-lg:hover { background: var(--cpqd-canvas); }
.cpq-dash-lg span { flex: 1; color: var(--cpqd-ink-2); }
.cpq-dash-lg b { font-weight: 600; font-variant-numeric: tabular-nums; }
.cpq-dash-lg em { font-style: normal; color: var(--cpqd-muted); font-size: var(--wds-font-size-label-s);
  margin-left: 5px; font-variant-numeric: tabular-nums; }
.cpq-dash-sw { width: 8px; height: 8px; border-radius: var(--wizon-ai-radius-control); flex: 0 0 8px; text-decoration: none;
  display: inline-block; }
.cpq-dash-sw--ok { background: var(--cpqd-ok-fg); }
.cpq-dash-sw--sup { background: var(--cpqd-sup-fg); }
.cpq-dash-sw--gone { background: var(--cpqd-gone-fg); }
.cpq-dash-sw--p1 { background: var(--cpqd-hw-fg); }
.cpq-dash-sw--p2 { background: var(--cpqd-sup-fg); }
.cpq-dash-sw--p3 { background: var(--cpqd-ok-fg); }
.cpq-dash-sw--p4 { background: var(--cpqd-bar); }

/* ── 세그먼트 ── */
.cpq-dash-seg { display: flex; gap: 5px; padding: 4px; background: var(--cpqd-card);
  border-radius: var(--wizon-ai-radius-pill); box-shadow: var(--cpqd-shadow);
  margin-bottom: 12px; width: fit-content; }
.cpq-dash-seg button { border: 0; background: none; font-family: inherit;
  font-size: var(--wds-font-size-caption); font-weight: 600; color: var(--cpqd-ink-2);
  padding: 6px 13px; border-radius: var(--wizon-ai-radius-pill); cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px; transition: background 180ms ease, color 180ms ease; }
.cpq-dash-seg button:hover { color: var(--cpqd-ink); }
.cpq-dash-seg .cpq-dash-seg-on { background: var(--cpqd-ink); color: var(--cpqd-card); }
.cpq-dash-seg-n { font-size: var(--wds-font-size-label-s); opacity: 0.65; font-variant-numeric: tabular-nums; }

/* ── 견적 행 (금액 인라인 막대) ── */
.cpq-dash-rows { padding: 8px; }
/* 행은 button 이다(키보드 접근). 브라우저 기본 스타일을 지우고 초점 링을 명시한다 —
   outline: none 만 두면 키보드 사용자가 «어디에 있는지» 알 수 없다. */
.cpq-dash-row { display: grid; grid-template-columns: 34px 1fr auto; gap: 11px; align-items: center;
  padding: 9px 8px; border-radius: var(--wizon-ai-radius-menu-row); cursor: pointer;
  transition: background 150ms ease; width: 100%; border: 0; background: none;
  font: inherit; color: inherit; text-align: left; }
.cpq-dash-row:hover, .cpq-dash-row--open { background: var(--cpqd-canvas); }
.cpq-dash-row:focus-visible { outline: 2px solid var(--cpqd-accent); outline-offset: 2px; }
.cpq-dash-row[aria-disabled="true"] { cursor: default; }
.cpq-dash-icon { width: 34px; height: 34px; border-radius: var(--wizon-ai-radius-menu-row);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--wds-font-size-label-s); font-weight: 600; letter-spacing: 0.02em; }
.cpq-dash-icon--hw { background: var(--cpqd-hw-bg); color: var(--cpqd-hw-fg); }
.cpq-dash-icon--sw { background: var(--cpqd-sw-bg); color: var(--cpqd-sw-fg); }
.cpq-dash-icon--cat { background: var(--cpqd-pend-bg); color: var(--cpqd-pend-fg); }
.cpq-dash-icon--lab { background: var(--cpqd-ok-bg); color: var(--cpqd-ok-fg); }
.cpq-dash-mid { min-width: 0; display: block; }
.cpq-dash-no { font-size: var(--wds-font-size-caption); font-weight: 600; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums; display: block; }
.cpq-dash-sub { font-size: var(--wds-font-size-label-s); color: var(--cpqd-muted); margin-top: 1px;
  display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cpq-dash-amtbar { display: block; height: 4px; margin-top: 5px; max-width: 230px; width: 100%; }
.cpq-dash-amtbar-track { fill: var(--cpqd-bar); }
.cpq-dash-amtbar-fill { fill: var(--cpqd-hw-fg); }
.cpq-dash-row[data-cpq-status="gone"] .cpq-dash-amtbar-fill { fill: var(--cpqd-gone-fg); }
.cpq-dash-row[data-cpq-status="sup"] .cpq-dash-amtbar-fill { fill: var(--cpqd-sup-fg); }
.cpq-dash-end { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.cpq-dash-amt { font-size: var(--wds-font-size-body-s); font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.cpq-dash-amt--unk { font-size: var(--wds-font-size-label-s); color: var(--cpqd-muted); font-weight: 400; }
.cpq-dash-pill { font-size: var(--wds-font-size-label-s); font-weight: 600; padding: 2px 8px;
  border-radius: var(--wizon-ai-radius-pill); display: inline-flex; align-items: center; gap: 4px; }
.cpq-dash-pill--ok { background: var(--cpqd-ok-bg); color: var(--cpqd-ok-fg); }
.cpq-dash-pill--gone { background: var(--cpqd-gone-bg); color: var(--cpqd-gone-fg); }
.cpq-dash-pill--sup { background: var(--cpqd-sup-bg); color: var(--cpqd-sup-fg); }
.cpq-dash-trunc { font-size: var(--wds-font-size-label-s); color: var(--cpqd-muted);
  padding: 8px 16px 12px; border-top: 1px solid var(--cpqd-hair); margin: 4px 8px 0; }
.cpq-dash-mini-empty { font-size: var(--wds-font-size-caption); color: var(--cpqd-muted); padding: 14px 8px; }

/* ── 확장 이력: 세로 커넥터 타임라인 ── */
.cpq-dash-expand { display: none; }
.cpq-dash-row--open + .cpq-dash-expand { display: block; }
/* Measured 2026-09-04 (same defect family as .artifact-titlebar-button[hidden] above and the
   unmerged .artifact-ai-drawer-close[hidden] candidate): the sibling-combinator rule above is an
   AUTHOR rule, so it beats the UA `[hidden] { display: none }` default by cascade origin, not
   specificity. Segment-filter switching (app.js's data-cpq-seg handler) sets
   `expand.hidden = !show` on a row's expand panel WITHOUT removing the row's own
   .cpq-dash-row--open class, so a previously-expanded row that gets filtered out kept showing
   its history panel — confirmed via getComputedStyle(expand).display === "block" with
   expand.hasAttribute("hidden") === true. */
.cpq-dash-row--open + .cpq-dash-expand[hidden] { display: none; }
.cpq-dash-ex-inner { background: var(--cpqd-canvas); border-radius: var(--wizon-ai-radius-popover);
  padding: 13px 14px; margin: 2px 0 8px; }
.cpq-dash-ex-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.cpq-dash-cap { font-size: var(--wds-font-size-label-s); font-weight: 600; color: var(--cpqd-muted);
  letter-spacing: 0.04em; }
.cpq-dash-pin { font-size: var(--wds-font-size-label-s); color: var(--cpqd-muted);
  background: var(--cpqd-card); padding: 3px 8px; border-radius: var(--wizon-ai-radius-pill); }
.cpq-dash-tl { list-style: none; margin: 0; padding: 0 0 0 18px; position: relative; }
.cpq-dash-tl::before { content: ""; position: absolute; left: 4px; top: 6px; bottom: 10px; width: 2px;
  background: var(--cpqd-hair); border-radius: var(--wizon-ai-radius-pill); }
.cpq-dash-tl li { position: relative; padding-bottom: 11px; }
.cpq-dash-tl li:last-child { padding-bottom: 2px; }
.cpq-dash-dot { position: absolute; left: -18px; top: 4px; width: 10px; height: 10px;
  border-radius: var(--wizon-ai-radius-pill); background: var(--cpqd-card);
  border: 2px solid var(--cpqd-gone-fg); }
.cpq-dash-tl-ok .cpq-dash-dot { border-color: var(--cpqd-ok-fg); }
.cpq-dash-tl-sup .cpq-dash-dot { border-color: var(--cpqd-sup-fg); }
.cpq-dash-tl-t1 { font-size: var(--wds-font-size-caption); font-weight: 600; }
.cpq-dash-tl-t2 { font-size: var(--wds-font-size-label-s); color: var(--cpqd-muted); margin-top: 1px;
  font-variant-numeric: tabular-nums; }
.cpq-dash-note { display: inline-block; font-size: var(--wds-font-size-label-s); color: var(--cpqd-ink-2);
  background: var(--cpqd-card); padding: 3px 9px; border-radius: var(--wizon-ai-radius-control); margin-top: 5px; }
.cpq-dash-callout { display: flex; align-items: center; justify-content: space-between; width: 100%;
  margin-top: 10px; background: var(--cpqd-sup-bg); border: 0; border-radius: var(--wizon-ai-radius-control);
  padding: 9px 12px; font-family: inherit; font-size: var(--wds-font-size-caption); font-weight: 600;
  color: var(--cpqd-sup-fg); cursor: pointer; }

/* ── 제안: 변화폭 트랙 ── */
.cpq-dash-sec-cap { font-size: var(--wds-font-size-label-s); font-weight: 600; color: var(--cpqd-muted);
  letter-spacing: 0.04em; padding: 10px 16px 2px; }
.cpq-dash-prow { display: grid; grid-template-columns: 34px 1fr auto; gap: 11px; align-items: center;
  padding: 10px 16px; }
.cpq-dash-deltabar { display: flex; align-items: center; gap: 7px; margin-top: 6px; max-width: 250px; }
.cpq-dash-track { flex: 1; height: 6px; display: block; }
.cpq-dash-track-bed { fill: var(--cpqd-bar); }
.cpq-dash-track-base { fill: var(--cpqd-muted); }
.cpq-dash-track--up { fill: var(--cpqd-pend-fg); }
.cpq-dash-track--down { fill: var(--cpqd-down); }
.cpq-dash-pct { font-style: normal; font-size: var(--wds-font-size-label-s); font-weight: 600;
  font-variant-numeric: tabular-nums; }
.cpq-dash-pct--up { color: var(--cpqd-pend-fg); }
.cpq-dash-pct--down { color: var(--cpqd-down); }
.cpq-dash-money { font-size: var(--wds-font-size-caption); font-variant-numeric: tabular-nums;
  color: var(--cpqd-muted); white-space: nowrap; }
.cpq-dash-money b { color: var(--cpqd-ink); font-weight: 600; }
.cpq-dash-acts { display: inline-flex; gap: 6px; margin-top: 6px; }
.cpq-dash-b-ok, .cpq-dash-b-no { border-radius: var(--wizon-ai-radius-pill);
  font-size: var(--wds-font-size-label-s); font-weight: 600; padding: 6px 13px; cursor: pointer;
  border: 0; font-family: inherit; }
.cpq-dash-b-ok { background: var(--cpqd-ink); color: var(--cpqd-card); }
.cpq-dash-b-no { background: var(--cpqd-canvas); color: var(--cpqd-ink-2); }
.cpq-dash-done { font-size: var(--wds-font-size-label-s); color: var(--cpqd-muted); }

/* ── 승인/반려 낙관적 UI: 처리 중 · 롤백 사유 (§13.4 위 얹는 개선) ── */
.cpq-dash-b-ok:disabled, .cpq-dash-b-no:disabled { cursor: default; opacity: .7; }
.cpq-dash-b-spin { display: inline-block; width: 9px; height: 9px; margin-right: 5px;
  border: 2px solid color-mix(in srgb, currentColor 25%, transparent);
  border-top-color: currentColor; border-radius: var(--wizon-ai-radius-pill);
  vertical-align: -1px; animation: cpq-dash-b-spin 700ms linear infinite; }
@keyframes cpq-dash-b-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .cpq-dash-b-spin { animation: none; }
}
.cpq-dash-fail-reason { display: block; margin-top: 6px; max-width: 250px;
  font-size: var(--wds-font-size-label-s); color: var(--wds-color-error-text); text-align: right; }

/* ── 카탈로그: 비중 스택바 ── */
.cpq-dash-cat-body { padding: 8px 16px 16px; }
.cpq-dash-cat-num { display: flex; align-items: baseline; gap: 6px; }
.cpq-dash-cat-n { font-size: var(--wds-font-size-h2); font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.cpq-dash-cat-s { font-size: var(--wds-font-size-label-s); color: var(--cpqd-muted); }
.cpq-dash-stack { display: block; width: 100%; height: 9px; margin: 10px 0 9px; }
.cpq-dash-stack-1 { fill: var(--cpqd-hw-fg); }
.cpq-dash-stack-2 { fill: var(--cpqd-sup-fg); }
.cpq-dash-stack-3 { fill: var(--cpqd-ok-fg); }
.cpq-dash-stack-4 { fill: var(--cpqd-bar); }

/* §11.29 수주율 카드 — 분할 비중 바. 결정(수주|실주)은 채움, 미결은 윤곽선만:
   분모(won+lost) 밖이라는 사실을 채움 여부가 말한다. */
.cpq-dash-w-label--win { color: var(--cpqd-ok-fg); }
.cpq-dash-win-body { padding: 2px 2px 0; }
.cpq-dash-winbar { display: block; width: 100%; height: 12px; margin: 10px 0 8px; }
.cpq-dash-winbar-won { fill: var(--cpqd-ok-fg); }
.cpq-dash-winbar-lost { fill: var(--cpqd-gone-fg); }
.cpq-dash-winbar-open { fill: none; stroke: var(--cpqd-hair); stroke-width: 1; }
.cpq-dash-win-legend { display: flex; gap: 14px; flex-wrap: wrap; }
.cpq-dash-win-lg { display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--wds-font-size-caption); color: var(--cpqd-ink-2); }
.cpq-dash-win-lg b { color: var(--cpqd-ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.cpq-dash-win-lg .cpq-dash-sw--open { background: transparent; border: 1px solid var(--cpqd-hair); }
.cpq-dash-sw--won { background: var(--cpqd-ok-fg); }
.cpq-dash-sw--lost { background: var(--cpqd-gone-fg); }
.cpq-dash-cat-chips { display: flex; gap: 10px 16px; flex-wrap: wrap; }
.cpq-dash-cat-chip { display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--wds-font-size-label-s); color: var(--cpqd-ink-2); }

/* --- Composer paste-preview (Phase 1): 붙여넣기 압축 칩 + 원문 다이얼로그 ---- */

/* S1(doc 665 §S1): WDS ai-widget/link-preview.module.css `.lp-*` 1:1 이식
   (youtube-livewiki-design/proto/proto.css 가 실렌더 검증한 그 선언·값·순서 그대로,
   클래스명만 02-slices.md 매핑표대로 치환). variant="composer"(전송 전, 액션 3+×)
   와 variant="message"(전송 후, 읽기 전용) 가 마크업 하나(renderLinkPreviewCardMarkup)
   를 공유한다. 색 예외는 02-slices §공통 규칙 3갈래였으나, 오너 결정 3→ⓑ(09-08 저녁,
   무채색 지적 반영)로 근거·타임스탬프·채널 갈래도 잉크로 접었다 — 남은 예외는 유튜브
   플랫폼 빨강(마크 한정)뿐이다. 나머지는 원본 --wds-* 그대로, --cg-* 는 쓰지 않는다
   (그 스코프 밖에서 조용히 빈 값이 되는 함정, memory 실측 기록). */
.wizon-link-card {
  /* §1 결정 후속(오너 09-08 ⓑ): 색을 이 변수 1곳에서만 참조 — 바뀌면 토큰 1개
     교체로 끝난다. 채널명은 «근거 링크» 대접이지만 포털은 무채색 테마라 WDS
     파랑 대신 본문 잉크를 쓴다. */
  --wizon-link-card-accent: var(--wds-color-text);
  /* S2 §2 수리: 줄 간격을 «카드가» 소유한다. 조상에 맡기면 컴포저 레인(1.72 상속)과
     사용자 버블(.message-body 1.65)이 서로 다른 밀도를 내고, 실제로 그랬다 — 캡션
     크기 요소가 컴포저 20.64px / 버블 16.8px 로 갈렸다(실렌더 계산값).
     ★두 자리에 각각 값을 적어 맞추는 대신 한 곳에서 끊는다: 값을 복사해 맞추면 그건
     손 기대값이고, 조상이 바뀌는 날 조용히 다시 갈린다. */
  line-height: var(--wds-line-height-label);
  position: relative;
  display: grid;
  grid-template-columns: 3fr 7fr;
  gap: 0;
  align-items: stretch;
  width: 100%;
  max-width: 1000px;
  border-radius: var(--wds-radius-lg);
  overflow: hidden;
  background: var(--wds-color-surface);
  box-shadow: inset 0 0 0 1px var(--wds-color-border-subtle);
}
.wizon-link-card[data-variant="composer"] {
  background: var(--wds-color-surface-raised);
  box-shadow: inset 0 0 0 1px var(--wds-color-border-subtle), var(--wds-shadow-sm);
}
.wizon-link-card[data-has-thumb="false"] {
  /* done_when 3: 썸네일 없는 비-유튜브 카드는 구멍이 아니라 텍스트 우선 블록. */
  grid-template-columns: 1fr;
}
.wizon-link-card__thumb {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  background:
    radial-gradient(120% 140% at 22% 0%, color-mix(in srgb, var(--wds-color-text) 14%, transparent), transparent 62%),
    linear-gradient(135deg, var(--wds-color-surface-muted), var(--wds-color-surface-raised));
}
.wizon-link-card__thumb[data-kind="youtube"] {
  background: linear-gradient(135deg, color-mix(in srgb, var(--ds-color-media-scrim) 70%, var(--wds-color-surface-muted)), var(--ds-color-media-scrim));
}
.wizon-link-card__thumb-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.wizon-link-card__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border-radius: var(--wds-radius-full);
  background: var(--ds-color-on-media);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--ds-color-shadow-ink) 28%, transparent);
  padding-inline-start: 3px;
}
.wizon-link-card__duration {
  position: absolute;
  right: var(--wds-space-2);
  bottom: var(--wds-space-2);
  padding: 1px var(--wds-space-2);
  border-radius: var(--wds-radius-sm);
  background: color-mix(in srgb, var(--ds-color-media-scrim) 62%, transparent);
  color: var(--ds-color-on-media);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
}
.wizon-link-card__body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-1);
  justify-content: center;
  padding: var(--wds-space-1) var(--wds-space-4);
}
/* 결함수리(링크 카드 × 겹침, 09-10): × 는 article 기준 absolute(top/right)였고
   제목(__title)은 padding-right 가 0 이라 긴 제목에서 × 가 글자 위에 얹혔다
   (styles.css 구 42192/42099 실측). × 를 헤더 행(__header)의 flex 형제 아이템으로
   옮기면 __headtext 가 남는 폭만 차지하도록 shrink 되어, 제목의 1줄 말줄임 폭이
   «× 폭 + gap 을 뺀 나머지»로 항상 정확해진다 — 두 값을 손으로 맞추는 padding
   숫자가 필요 없다(레이아웃이 계산, 값 동기화 아님). */
.wizon-link-card__header {
  display: flex;
  align-items: flex-start;
  gap: var(--wds-space-2);
}
.wizon-link-card__headtext {
  min-width: 0;
  flex: 1 1 0%;
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-1);
}
.wizon-link-card__platform { margin: 0; display: flex; align-items: center; gap: var(--wds-space-1); }
.wizon-link-card__platform-icon { display: inline-flex; }
/* 색 규칙 3(02-slices §공통): 유튜브 빨강은 플랫폼 마크 SVG fill 한정. */
.wizon-link-card__platform-mark { fill: var(--wds-color-error); }
.wizon-link-card__platform-label {
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
  color: var(--wds-color-text-muted);
}
.wizon-link-card__title {
  margin: 0;
  font-size: var(--wds-font-size-body-1);
  font-weight: var(--wds-font-weight-semibold);
  line-height: 1.35;
  color: var(--wds-color-text);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.wizon-link-card__desc {
  margin: 0;
  font-size: var(--wds-font-size-body-2);
  line-height: 1.5;
  color: var(--wds-color-text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.wizon-link-card__source {
  margin: var(--wds-space-1) 0 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--wds-space-1) var(--wds-space-2);
  font-size: var(--wds-font-size-caption);
}
.wizon-link-card__avatar {
  position: relative;
  flex: none;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: var(--wds-radius-full);
  background: var(--wds-color-surface-muted);
  box-shadow: inset 0 0 0 1px var(--wds-color-border-subtle);
  color: var(--wds-color-text-muted);
  /* 프로토 원본은 raw 10px(anti-drift 래칫 raw-font-size 여유 0 — 새 리터럴 금지).
     20px 원 안 2자 이니셜이라 캡션(12px)의 0.833배로 좁혀 같은 비례를 낸다. */
  font-size: calc(var(--wds-font-size-caption) * 0.833);
  font-weight: var(--wds-font-weight-semibold);
}
/* 채널 아바타(오너 Y5, 09-17): 요약 카드 .wizon-livewiki__avatar 와 같은 처리 —
   이니셜을 먼저 깔고(fallback) 이미지를 그 위에 얹는다. 이미지가 로드에 실패하면
   bindImageFallbacks 가 img 만 걷어 내 이니셜이 드러난다(「이미지 있으면 이미지,
   없거나 깨지면 이니셜」). */
.wizon-link-card__avatar-fallback { display: inline-flex; line-height: 1; }
.wizon-link-card__avatar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.wizon-link-card__channel {
  /* 색 규칙 2 개정(오너 09-08 ⓑ): 채널명은 근거 링크 취급이지만 무채색 테마라
     WDS 파랑 대신 본문 잉크(로컬 변수 경유). */
  color: var(--wizon-link-card-accent);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wizon-link-card__stats { color: var(--wds-color-text-subtle); white-space: nowrap; }
.wizon-link-card__actions {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--wds-space-2);
  padding: var(--wds-space-3) var(--wds-space-4);
  box-shadow: inset 0 1px 0 var(--wds-color-border-subtle);
}
.wizon-link-card__summarize {
  /* 결함수리(fix3, 나란히 배치 형제 높이 통일): 패딩+행간으로만 결정되던 암묵
     높이(390 실측 26.19px)가 __dismiss 의 명시 높이(28px)와 달라 같은 행 안에
     지정 버튼 높이가 2종(26/28)이었다 — min-height:28px 로 __dismiss 와 같은
     값을 명시해 1종으로 통일한다. position: relative 는 아래 coarse ::after
     히트슬롭의 기준. */
  position: relative;
  min-height: 28px;
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  padding: var(--wds-space-1) var(--wds-space-3);
  border: 0;
  border-radius: var(--wds-radius-full);
  /* 색 규칙 1(02-slices §공통, 오너 09-08 ⓑ로 전면 확정): 주요 액션 = 위즈온 잉크.
     원본 프로토는 여기에 --wds-color-primary-strong(파랑)을 썼다. */
  background: var(--wizon-ai-action-primary-bg);
  color: var(--wizon-ai-action-primary-fg);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
  cursor: pointer;
}
.wizon-link-card__action {
  /* 결함수리(fix3): 위 __summarize 와 같은 사유 — min-height:28px 로 통일. */
  position: relative;
  min-height: 28px;
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  padding: var(--wds-space-1) var(--wds-space-3);
  border: 0;
  border-radius: var(--wds-radius-full);
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--wds-color-border-subtle);
  color: var(--wds-color-text-muted);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  cursor: pointer;
}
/* 오너 추가 요구(composer-link-card-send-merge-fix): 복사 확인 모션. 토큰은 코드블록
   복사 버튼(.code-card-copy.is-copied)과 표·아티팩트 복사 버튼(.md-table-action.is-copied
   등)이 이미 쓰는 --wds-color-success-text 를 재사용한다 — 새 색 없음. */
.wizon-link-card__action-icon,
.wizon-link-card__action-text {
  display: inline-flex;
  align-items: center;
}
.wizon-link-card__action.is-copied {
  color: var(--wds-color-success-text);
}
.wizon-link-card__dismiss {
  /* 절대배치 제거 — __header 의 flex 형제 아이템(오른쪽 끝, headtext 가 나머지
     폭을 다 가져간 뒤 남는 자리). 시각 크기 28px 은 그대로 두고, 터치 히트영역만
     44px 로 넓힌다 — 기존 20260710-touch-target-hitslop 관례(styles.css 38989행)와
     같은 기법: 투명 ::after, pointer: coarse 게이트, 박스 팽창 없음(밀도 유지).
     fix3 에서 __action·__summarize 도 min-height:28px 로 맞춰 카드 안 지정 버튼
     높이가 «1종(28px)» — 아래 coarse ::after 는 세 셀렉터가 공유(규칙 복제 아님). */
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--wds-radius-full);
  background: var(--wds-color-surface-raised);
  box-shadow: inset 0 0 0 1px var(--wds-color-border-subtle);
  color: var(--wds-color-text-subtle);
  cursor: pointer;
}
@media (pointer: coarse) {
  .wizon-link-card__dismiss::after,
  .wizon-link-card__summarize::after,
  .wizon-link-card__action::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }
}
@media (max-width: 520px) {
  /* 520px = 이 파일의 기존 컴포저 모바일 브레이크포인트(styles.css:21388 등) —
     프로토 원본의 599px 대신 «기존 래칫 집합만» 규칙을 따른다. 이 규칙은 전송 «후»
     사용자 버블(variant="message", .wizon-link-card-group 이 이미 세로 1열 flex
     column 이라 형제끼리 폭을 나눠 가지지 않는다)에 그대로 남는다. composer 변형은
     아래 .composer-link-card-lane 블록이 더 높은 특이도로 덮는다. */
  .wizon-link-card { grid-template-columns: 1fr; }
  .wizon-link-card__thumb { height: auto; aspect-ratio: 16 / 9; }
}

/* 결함수리(링크 카드 반폭 찌그러짐, 09-10 fix2): 컴포저의 여러 링크 카드를 감싸는
   전용 래퍼. .composer-attachment-lane(flex-wrap:nowrap 가로 셸프, styles.css
   32367행)의 flex 아이템 «1개»로만 들어가 카드끼리 lane 폭을 나눠 갖지 않는다
   (실측: 카드를 lane 의 낱개 형제로 두면 width:100% 가 flex-basis 가 되어 2장일 때
   각각 ≈170px 로 찌그러졌다 — 390 캡처 after-mobile-390-cards.png). 데스크톱은
   flex-wrap:wrap 2열, 모바일/coarse 는 flex-direction:column 세로 1열로 전환한다. */
.composer-link-card-lane {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--wds-space-2);
  width: 100%;
  min-width: 0;
}
.composer-link-card-lane .wizon-link-card {
  flex: 1 1 320px;
  width: auto;
  min-width: 0;
  max-width: calc(50% - var(--wds-space-2) / 2);
}
@media (max-width: 520px), (pointer: coarse) {
  .composer-link-card-lane {
    flex-direction: column;
    /* ★실측 함정: 데스크톱 기본 규칙의 flex-wrap:wrap 이 남아 있으면
       flex-direction:column + max-height 조합에서 "줄바꿈"이 세로 오버플로가
       아니라 «새 열»을 만든다 — 카드 2장이 같은 y, 다른 x(22 / 376)에 나란히
       배치돼 두 번째 카드가 뷰포트 오른쪽 밖으로 밀려 overflow-x:hidden 에
       잘려 안 보였다(스크롤해도 안 나타남, Playwright 실측으로 발견). nowrap 로
       고정해 «위/아래»로만 쌓이게 한다. */
    flex-wrap: nowrap;
    /* 실측(390, composer_link_preview_visual_qa_server QA 하네스): 카드 2장을
       «온전한 높이»로 다 보여주면(≈341px) 위의 새 채팅 인사말·빠른 시작 버튼과
       합쳐 입력칸이 뷰포트(844) 밖(inputTop 881)으로 밀렸다 — 이 조건은 이
       수리 전에도 있었지만(구 레이아웃도 inputTop 900) 발주서가 이 수리에서
       같이 닫으라고 明시했다. 260px 는 카드 1장(~159px) + 2장째 일부(스크롤
       유도)가 보이는 높이로 재실측해 고정했다(예시가 아니라 실측값 — 292px 는
       inputTop 824 로 뷰포트 844 와 여백이 20px 뿐이라 안전 여백을 더 뒀다). */
    max-height: 260px;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
  }
  .composer-link-card-lane .wizon-link-card {
    flex: none;
    width: 100%;
    max-width: none;
  }
  /* 결함수리 계속: 모바일에서도 카드 «내부»는 썸네일 왼쪽 고정폭(96~112px) + 텍스트
     오른쪽 그대로 둔다 — 위 ≤520px 일반 규칙(그리드 1열, 썸네일 위)은 message 변형용.
     [data-variant="composer"] 로 특이도(0,2,0)를 올려 일반 규칙(0,1,0)을 덮는다
     (미디어쿼리 중첩과 무관하게 특이도가 더 높은 쪽이 항상 이긴다). */
  .wizon-link-card[data-variant="composer"] {
    grid-template-columns: 104px 1fr;
  }
  .wizon-link-card[data-variant="composer"] .wizon-link-card__thumb {
    height: 100%;
    aspect-ratio: auto;
  }
  /* 결함수리 계속(높이 동일): 출처 줄(아바타+채널명+·+통계)이 좁은 텍스트열(약
     210px)에서 flex-wrap:wrap 이면 채널명이 길 때만 2줄로 접혀 카드마다 높이가
     달라진다(실측: WIZ 카드 159px vs YouTube 카드 182px, 채널·통계가 있는 쪽만
     2줄). nowrap 으로 고정하고 채널명 쪽만(이미 ellipsis) 줄어들게 해 카드 2장의
     높이를 같게 만든다("2장이면 높이 동일" 요건 — 값 동기화가 아니라 줄바꿈
     자체를 구조적으로 없앤다). */
  .wizon-link-card[data-variant="composer"] .wizon-link-card__source {
    flex-wrap: nowrap;
    overflow: hidden;
  }
  /* ★실측 함정: nowrap 만 걸면 `.wizon-link-card__stats` 가 white-space:nowrap
     인데도 min-width:auto(기본값)라 자기 텍스트 폭 밑으로 못 줄어든다 — 수축
     압력이 전부 채널명(이미 min-width:0)으로 쏠려 «YTV on 연합뉴스TV» 가 «Y»
     한 글자로 뭉개졌다(실측: after-mobile-390-light-scrolled 캡처). 채널에
     최소폭을 주고 통계 쪽도 줄어들 수 있게 열어 «필수 정보 누락 0»(디자인
     체크리스트 §5)을 지킨다 — 둘 다 말줄임으로 끝나되 어느 한쪽만 전멸하지
     않는다. */
  .wizon-link-card[data-variant="composer"] .wizon-link-card__channel-slot {
    flex: 1 1 auto;
    min-width: 56px;
  }
  .wizon-link-card[data-variant="composer"] .wizon-link-card__stats {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* 로딩 스켈레톤 — 새 애니메이션을 만들지 않고 기존 pulse 키프레임(styles.css 12348행,
   그때까지 소비자가 없었다)을 재사용한다. */
.wizon-link-card--skeleton { grid-template-columns: 3fr 7fr; pointer-events: none; }
.wizon-link-card__thumb--skeleton { background: var(--wds-color-surface-muted); animation: pulse 1.6s ease-in-out infinite; }
.wizon-link-card__skeleton-line {
  display: block;
  height: 12px;
  border-radius: var(--wds-radius-sm);
  background: var(--wds-color-surface-muted);
  animation: pulse 1.6s ease-in-out infinite;
}
.wizon-link-card__skeleton-line--label { width: 40%; height: 9px; }
.wizon-link-card__skeleton-line--title { width: 80%; height: 15px; }
.wizon-link-card__skeleton-line--short { width: 55%; }

/* 전송 후 사용자 버블의 카드 그룹(읽기 전용) */
/* .message-body 는 .message 그리드 트랙(minmax(0, 760px))의 내용-기반 크기라
   %/그리드 트랙 순환이 생겨 .wizon-link-card 의 width:100% 가 그리드 열의
   min-content(재생 아이콘 폭)까지 붕괴한다(실측: Playwright 캡처에서 카드가
   90px 폭으로 찌그러짐). 고정 px 폭은 퍼센트가 아니므로 그리드 auto-track
   기여 계산에 그대로 반영돼 순환을 끊는다 — vw 는 처음 시도했으나 390px
   좁은 화면에서 사이드바·아바타 열을 뺀 실제 가용폭(266px)보다 88vw(343px)가
   더 커서 오른쪽으로 튀어나갔다(실측: 05-sent-bubble 캡처). minmax(0,760px)
   트랙은 min 이 0 이라 좁은 화면에서 이 고정폭도 정상적으로 줄어든다. */
.wizon-link-card-group {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-2);
  width: 480px;
  max-width: 100%;
}

/* S2 §2 수리 — 사용자 버블 카드의 «밀도»를 컴포저 카드와 같게 맞춘다.
   원인: `.message-body p { margin: 0 0 10px; line-height: 1.65 }`(styles.css 2081행)
   가 카드 안의 <p> 넷(__platform·__title·__desc·__source)을 전부 이긴다 —
   특이도가 (0,1,1) 로 `.wizon-link-card__source`(0,1,0) 보다 높아 원본의
   `margin: var(--wds-space-1) 0 0` 도 0 으로 덮인다. 그래서 전송 «후» 카드만
   줄 간격이 벌어지고, 출처 줄이 밀려 «· 조회수 …» 로 접혔다(반환 캡처
   05-sent-bubble-desktop-light-1440.png 에서 실측).
   ★대칭 규칙을 만들지 않고 «컨텍스트 상속을 끊는» 쪽을 택했다: 두 자리에 같은
     값을 두면 한쪽만 고쳐도 아무도 안 죽는 드리프트가 된다. */
.message-body .wizon-link-card p {
  margin: 0;
  line-height: inherit;
  text-wrap: initial;
}

/* ★실렌더 계산값으로 찾은 «두 번째» 새는 곳 — 줄 간격만이 아니라 «글자 크기»도 샌다.
   `.chatgpt-reference-v1 .message-body p { font-size: var(--cg-text-body-size) }` 는
   특이도 (0,2,1) 이라 카드 자신의 (0,1,0) 토큰 선언을 이긴다. 실측(1440, 같은 영상):
     컴포저 카드   제목 16px / 설명 14px / 출처 12px
     사용자 버블   제목 16px / 설명 16px / 출처 16px   ← 전부 본문 크기로 뭉개졌다
   본문 문단 규칙이 카드까지 소유하면 안 된다. 카드 안에서만 (0,3,0) 으로 되돌린다 —
   같은 특이도(0,2,1)로 맞추면 «파일에서 뒤에 오는 쪽»이 이기는 순서 의존이 되고,
   그 함정은 이 시트에 이미 기록돼 있다(7780행 부근 주석). */
.message-body .wizon-link-card .wizon-link-card__title {
  font-size: var(--wds-font-size-body-1);
  line-height: 1.35;
}
.message-body .wizon-link-card .wizon-link-card__desc {
  font-size: var(--wds-font-size-body-2);
  line-height: 1.5;
}
.message-body .wizon-link-card .wizon-link-card__source {
  font-size: var(--wds-font-size-caption);
  margin: var(--wds-space-1) 0 0;
}
.message-body .wizon-link-card .wizon-link-card__platform {
  font-size: var(--wds-font-size-caption);
}

/* 구분점은 채널과 «한 flex 아이템»으로 묶여 줄 머리로 내려가지 않는다. */
.wizon-link-card__channel-slot {
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  min-width: 0;
}
.wizon-link-card__sep { color: var(--wds-color-text-subtle); }

/* §1 결정 후속 수리(youtube-s1-followup 반환 사유 1): 링크가 준비되면 힌트를
   보여준다. WDS 정본은 이 줄을 툴바 안(＋ 버튼 옆)에 두지만, 이 포털의 실제
   컴포저 툴바 한 줄(＋·텍스트영역·모델선택·마이크·전송)은 이미 채워져 있어 같은
   자리에 넣으면 좁은 화면에서 텍스트영역이 찌그러진다. 이전 버전은 카드가 담긴
   `.composer-attachment-lane`(flex-wrap:nowrap 가로 셸프, styles.css 31515행)
   «안»에 형제로 넣었는데, nowrap flex 에서 두 아이템(카드 width:100% + 힌트
   content-width)이 공간을 나눠 가지면서 카드가 폭을 잃고 힌트도 2줄로 접혔다
   (반환 캡처 02-youtube-ready-desktop-light-1440.png, 모바일 붕괴도 동일 원인).
   힌트를 lane 밖 전용 호스트(#composerLinkIntentHint, index.html)로 옮겨
   카드 아래·입력 행 위의 **전폭 한 줄**로 둔다 — 오른쪽 열 아님. */
#composerLinkIntentHint {
  display: block;
  width: 100%;
}
#composerLinkIntentHint[hidden] {
  display: none;
}
.composer-intent-hint {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  padding: var(--wds-space-1) var(--wds-space-2) var(--wds-space-2);
  font-size: var(--wds-font-size-caption);
  /* 오너 09-08 ⓑ: 무채색 테마 — 보조 힌트는 본문 잉크(muted)로. */
  color: var(--wds-color-text-muted);
}

/* R29-P3a-2(오너 D4ⓐ «미리보기 우선 한 장 시트», 판정 report.md §B-3 규칙표): 붙여넣기 미리보기
   표면 — 모바일 시트(.composer-paste-sheet)와 데스크톱 다이얼로그(#composerPasteDialog)가 같은
   부품을 쓴다. 머리 56(타일 28 · 제목 17/600 · 메타 13 · ⋯ · ×) · 블록 칩 줄(시각 32, 히트 = 컨트롤)
   · 원문 렌더 본문(15, 표 셀 13 · 표 머리 12) · 바닥 동작(13/600, 주 동작 = 잉크 면).
   컨트롤 = 모바일 44 / 데스크톱 36(671 §9.10-1 C10). 좌측 정렬선 = 거터 16 한 줄.
   값은 전부 기존 토큰: 제목 17 = --cg-text-topbar-brand-size(HIG Headline, 671 §9.10-11 등재
   요청), 타일 반경 8 = --wizon-ai-radius-control, 메뉴 12 = --wizon-ai-menu-radius. */
.chatgpt-reference-v1 .composer-paste-surface {
  --composer-paste-title-size: var(--cg-text-topbar-brand-size);
  --composer-paste-control: 44px;
  --composer-paste-gutter: 16px;
}

.chatgpt-reference-v1 #composerPasteDialog {
  --composer-paste-control: 36px;
}

.chatgpt-reference-v1 .composer-paste-surface-head {
  display: grid;
  flex: none;
  grid-template-columns: 28px minmax(0, 1fr) auto auto;
  align-items: center;
  min-height: 56px;
  padding: 0 calc(var(--composer-paste-gutter) - (var(--composer-paste-control) - 20px) / 2) 0 var(--composer-paste-gutter);
}

.chatgpt-reference-v1 .composer-paste-surface-tile {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  overflow: hidden;
  border-radius: var(--wizon-ai-radius-control);
  background: var(--wizon-ai-hover-soft-bg);
  box-shadow: inset 0 0 0 1px var(--wizon-ai-composer-border);
}

.chatgpt-reference-v1 .composer-paste-surface-tile img,
.chatgpt-reference-v1 .composer-paste-surface-tile svg {
  width: 24px;
  height: 24px;
}

.chatgpt-reference-v1 .composer-paste-surface-heading {
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin: 0 4px 0 12px;
}

.chatgpt-reference-v1 .composer-paste-surface-heading > strong,
.chatgpt-reference-v1 .composer-paste-surface-heading > h2 {
  margin: 0;
  overflow: hidden;
  color: var(--cg-text);
  font-size: var(--composer-paste-title-size);
  font-weight: var(--ds-weight-semibold);
  line-height: 22px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .composer-paste-surface-heading > span,
.chatgpt-reference-v1 .composer-paste-surface-heading > p {
  margin: 0;
  overflow: hidden;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
  line-height: 18px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chatgpt-reference-v1 .composer-paste-surface-icon-button {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: var(--composer-paste-control);
  height: var(--composer-paste-control);
  padding: 0;
  border: 0;
  border-radius: var(--wizon-ai-radius-pill);
  background: transparent;
  color: var(--cg-text);
  cursor: pointer;
}

.chatgpt-reference-v1 .composer-paste-surface-icon-button svg {
  width: 20px;
  height: 20px;
}

.chatgpt-reference-v1 .composer-paste-surface-icon-button[aria-expanded="true"] {
  background: var(--cg-hover-soft);
}

.chatgpt-reference-v1 .composer-paste-more {
  position: relative;
  display: inline-flex;
}

/* ⋯ 메뉴 — 앱의 다른 떠 있는 메뉴와 같은 면·그림자 토큰(--cg-menu-bg · --cg-menu-shadow). */
.chatgpt-reference-v1 .composer-paste-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 2;
  display: grid;
  min-width: 200px;
  padding: 6px;
  border: 1px solid var(--cg-menu-border);
  border-radius: var(--wizon-ai-menu-radius);
  background: var(--cg-menu-bg);
  box-shadow: var(--cg-menu-shadow);
}

.chatgpt-reference-v1 .composer-paste-menu[hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-paste-menu-item {
  display: flex;
  align-items: center;
  min-height: var(--composer-paste-control);
  padding: 0 12px;
  border: 0;
  border-radius: var(--wizon-ai-radius-control);
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-body-15);
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

.chatgpt-reference-v1 .composer-paste-blocks {
  display: flex;
  flex: none;
  gap: 8px;
  padding: 0 var(--composer-paste-gutter) 8px;
  overflow-x: auto;
  scrollbar-width: none;
}

.chatgpt-reference-v1 .composer-paste-blocks[hidden] {
  display: none;
}

.chatgpt-reference-v1 .composer-paste-blocks::-webkit-scrollbar {
  display: none;
}

/* 블록 칩: 누르는 상자 = 컨트롤 높이(모바일 44), 보이는 알약 = 32(판정 §B-3 «시각 32 · 히트 44»). */
.chatgpt-reference-v1 .composer-paste-block-chip {
  display: inline-flex;
  flex: none;
  align-items: center;
  height: var(--composer-paste-control);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--cg-text);
  font: inherit;
  cursor: pointer;
}

.chatgpt-reference-v1 .composer-paste-block-chip-face {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding: 0 12px;
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--cg-hover-soft);
  font-size: var(--ds-text-caption-13);
  white-space: nowrap;
}

.chatgpt-reference-v1 .composer-paste-block-chip:focus-visible {
  outline: none;
}

.chatgpt-reference-v1 .composer-paste-block-chip:focus-visible .composer-paste-block-chip-face {
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

/* 본문 = 표면 면 그대로(옛 원문 패널의 회색 면·테두리 없음 — 그 회색 위에 표의 고정 첫 열이
   흰 조각으로 남던 Q1 류 결함의 뿌리가 사라진다). */
.chatgpt-reference-v1 .composer-paste-surface-body {
  flex: 1 1 auto;
  min-height: 0;
  padding: 12px var(--composer-paste-gutter) 16px;
  overflow: auto;
  border-top: 1px solid var(--wizon-ai-composer-border);
  background: var(--cg-surface);
  color: var(--cg-text);
  font-size: var(--ds-text-body-15);
  line-height: 1.6;
  overscroll-behavior: contain;
}

.chatgpt-reference-v1 .composer-paste-surface-body:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

.chatgpt-reference-v1 .composer-paste-surface-body .md-table td,
.chatgpt-reference-v1 .composer-paste-surface-body .md-table th {
  font-size: var(--ds-text-caption-13);
}

.chatgpt-reference-v1 .composer-paste-surface-body .md-table thead th {
  font-size: var(--ds-text-caption-12);
}

/* 표 머리 캡션(«붙여넣은 표», 답변 표 부품은 18/600)·푸터(«3열 × 3행», 원래대로 보기)는 이 표면의
   글자 사다리 {17 제목 · 15 본문 · 13 메타 · 12 눈썹} 안으로 — 시트 제목(17)보다 큰 글자가 본문
   속에 생기면 위계가 뒤집힌다(판정 B-2 #10 과 같은 종류). */
.chatgpt-reference-v1 .composer-paste-surface-body .md-table-caption {
  font-size: var(--ds-text-caption-13);
}

.chatgpt-reference-v1 .composer-paste-surface-body .md-table-footer,
.chatgpt-reference-v1 .composer-paste-surface-body .md-table-footer button {
  font-size: var(--ds-text-caption-13);
}

.chatgpt-reference-v1 .composer-paste-surface-foot {
  display: flex;
  flex: none;
  gap: 8px;
  padding: 8px var(--composer-paste-gutter);
  overflow-x: auto;
  border-top: 1px solid var(--wizon-ai-composer-border);
  scrollbar-width: none;
}

.chatgpt-reference-v1 .composer-paste-action {
  flex: none;
  height: var(--composer-paste-control);
  padding: 0 16px;
  border: 1px solid var(--wizon-ai-composer-border);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  font-weight: var(--ds-weight-semibold);
  white-space: nowrap;
  cursor: pointer;
}

.chatgpt-reference-v1 .composer-paste-action.is-primary {
  border-color: var(--cg-text);
  background: var(--cg-text);
  color: var(--cg-surface);
}

.chatgpt-reference-v1 .composer-paste-surface-icon-button:focus-visible,
.chatgpt-reference-v1 .composer-paste-menu-item:focus-visible,
.chatgpt-reference-v1 .composer-paste-action:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

@media (pointer: fine) {
  .chatgpt-reference-v1 .composer-paste-surface-icon-button:hover,
  .chatgpt-reference-v1 .composer-paste-menu-item:hover,
  .chatgpt-reference-v1 .composer-paste-action:not(.is-primary):hover {
    background: var(--cg-hover-soft);
  }

  .chatgpt-reference-v1 .composer-paste-block-chip:hover .composer-paste-block-chip-face {
    background: var(--cg-hover);
  }
}

/* 데스크톱 다이얼로그: 폭 720 → 큰 단계 960. 높이는 단계마다 «고정» — P3a-1 Q2 계약(내용이
   짧든 길든 위치가 뛰지 않는다)을 탭 대신 단계 높이로 지킨다. 짧은 표일 때 빈 여백은 본문 영역
   안에만 생기고 머리·바닥 동작은 제자리다. */
body.chatgpt-reference-v1 #composerPasteDialog {
  width: min(720px, calc(100vw - 32px));
  height: min(640px, calc(100dvh - 96px));
  max-height: none;
}

body.chatgpt-reference-v1 #composerPasteDialog[data-paste-stage="large"] {
  width: min(960px, calc(100vw - 32px));
  height: min(860px, calc(100dvh - 48px));
}

body.chatgpt-reference-v1 #composerPasteDialog .composer-paste-surface-shell {
  height: 100%;
  max-height: none;
}

body.chatgpt-reference-v1 #composerPasteDialog .composer-paste-surface-head {
  margin-top: 4px;
}

/* 모바일 시트: 기본 단계 높이 고정(목업 A 390 = 화면의 약 3/5), 큰 단계 = 보이는 화면 전체에서
   위 안전 여백 + 8. 옛 시트 안 카드(테두리 상자)와 좌우 패딩 16 은 걷고, 부품마다 거터 16 을 준다
   (구분선이 가장자리까지 닿게). */
@media (max-width: 520px), (pointer: coarse) {
  body.chatgpt-reference-v1 .composer-attachment-sheet.composer-paste-sheet {
    --composer-paste-sheet-compact: min(64vh, calc(var(--wizon-vvh, 100dvh) - 32px));
    height: var(--composer-paste-sheet-compact);
    min-height: 0;
    max-height: none;
    padding: 8px 0 0;
  }

  body.chatgpt-reference-v1 .composer-attachment-sheet.composer-paste-sheet[data-paste-stage="large"] {
    height: calc(var(--wizon-vvh, 100dvh) - env(safe-area-inset-top, 0px) - 8px);
  }

  body.chatgpt-reference-v1 .composer-paste-sheet .composer-paste-surface-foot {
    padding-bottom: calc(8px + var(--wizon-safe-bottom, 0px));
  }
}

.composer-attachment-preview-content.composer-paste-preview-content {
  padding: 0;
}

.composer-paste-preview-snippet {
  margin: 0;
  padding: 10px 12px;
  max-height: 96px;
  overflow: hidden;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: inherit;
  font-size: var(--wds-font-size-caption);
  line-height: 1.5;
  color: var(--wizon-ai-text-muted);
  background: var(--wizon-ai-hover-soft-bg);
  border-radius: var(--ds-radius-xs);
}

/* R-30 P3b(doc 679): 붙여넣은 원문 보기 — 표 구간은 표(답변과 같은 .md-table 부품), 글
   구간은 붙여 넣은 줄 그대로(<pre>). 컨테이너는 pre-wrap 을 끊는다: 렌더된 표 마크업
   사이의 공백 노드가 빈 줄로 보이지 않게. 간격 12 = 원문 패널의 기존 세로 패딩과 같은 값. */
.composer-paste-source {
  display: grid;
  gap: 12px;
  min-width: 0;
  white-space: normal;
}

.composer-paste-source-text {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font: inherit;
  color: inherit;
}

.composer-paste-source-table {
  display: grid;
  gap: 8px;
  min-width: 0;
}

/* R30-RAW-TABLE-COL(09-29 Android 에뮬 다크 실측): 표 상태에서 숨겨 둔 원본 <pre>(탭 문자 그대로)가
   전송된 카드(.message-body 안)에서는 보였다 — `.message-body pre { display: block }`(0,1,1)이
   UA `[hidden]{display:none}` 을 이겼다. 결과: 표 위에 검은 코드 채움 면 + 탭 간격만큼 넓은 첫 열.
   저자 규칙이 hidden 을 이기는 함정(4441·5098행과 같은 종류)이라 상태 가드를 여기에 둔다. */
.composer-paste-source-original[hidden] {
  display: none;
}

/* Q2 수리: 답변 표 전역 폭 정책(9856행, 120px)은 09-18 실측으로 튜닝된 값이라 여기서
   올리지 않는다(스코프 밖) — 대신 이 다이얼로그 안(더 좁은 패널·480px 실측)에서만
   특이성을 올려 min-width 를 넓힌다. 「비고」류 서술형 열이 120px 에서 3줄까지
   줄바꿈되던 것을, 가로 스크롤(.md-table-scroll, 이미 overflow-x:auto)을 그대로 쓰며
   1~2줄로 줄인다 — 줄바꿈 «금지»는 텍스트 길이에 달려 값을 보장할 수 없어, 대신 스크롤을
   우선하는 값으로 올린다. */
.composer-paste-source-table .md-table--wide th:not(.is-num),
.composer-paste-source-table .md-table--wide td:not(.is-num) {
  min-width: 160px;
}

/* R29-P3a-2 Q3(09-24 실측): 위 160 하한이 숫자 열(.is-num, 오른쪽 정렬)에도 걸려 «#»·«값» 처럼
   두세 글자 열이 160 폭이 됐고, 오른쪽 정렬 숫자가 표 왼쪽 끝에서 130px 넘게 떨어져 옆 글자 열에
   붙어 보였다(«값비고»·«10짧은 메모» 로 읽힌 뿌리 — 셀 패딩은 좌우 12 로 정상, 실측). 숫자 열은
   내용 폭으로 줄이고(1% + nowrap = 표 레이아웃의 «내용만큼» 관용구) 서술형 열만 160 하한을 둔다. */
.composer-paste-source-table .md-table--wide th.is-num,
.composer-paste-source-table .md-table--wide td.is-num {
  width: 1%;
  min-width: 0;
  white-space: nowrap;
}

.composer-paste-source-table .md-table-wrap {
  min-width: 0;
  margin: 0;
}

/* --- Phase 3: 붙여넣기 구조 카드 ------------------------------------------ */
/* 구조 차트 색. 셸 토큰(shared/wds)은 이 트랙 범위 밖이라 여기서 파생시킨다 —
   전부 기존 텍스트/표면 토큰에서 끌어와 새 색을 발명하지 않는다. */
.composer-structure-card {
  --wizon-ai-structure-prose: var(--wizon-ai-text-primary);
  --wizon-ai-structure-code: var(--wizon-ai-text-muted);
  --wizon-ai-structure-table: var(--wizon-ai-text-muted-2);
  --wizon-ai-structure-list: var(--wizon-ai-hover-bg);
  /* ★--wds-color-warning-fg 는 «존재하지 않는» 토큰이었다. 폴백이 조용히 먹어
     경고색 = 본문 검정이 되고, 컨텍스트 초과가 시각적으로 경고가 아니었다.
     정본은 --ds-feedback-warn-fg (semantic.css, 라이트 amber-700 / 다크 amber-400).
     폴백을 두지 않는다 — 폴백이 죽은 토큰을 숨긴다. */
  --wizon-ai-structure-warn: var(--ds-feedback-warn-fg);
  flex: 0 0 auto;
  width: min(420px, 100%);
  background: var(--wizon-ai-surface);
  border: 1px solid var(--wizon-ai-composer-border);
  border-radius: var(--wizon-ai-menu-radius);
  text-align: left;
}

/* 결함수리(composer-structure-card-viewport-clamp, 09-22, ★재수리로 폐기): 1차 수리는
   카드 자신에 고정 max-height: min(220px, calc(100dvh - 230px)) 를 줬다 — «카드만 열었을
   때» 는 GREEN 이었지만 입력칸이 실제로 자라는 실사용 조합(5줄 입력 등)은 그 220px 예산이
   입력칸 성장을 반영하지 못해 원래 결함이 재발했다(RED 재현: 390×844 +58px, 844×390 계열
   +92px). 카드 자신을 클램프하는 대신 위 grid fr 트랙(body.chatgpt-reference-v1
   .chatgpt-composer-shell:has(...):has(.composer-structure-card), row2 =
   #composerAttachmentRail)과 첨부 레일 자신의 overflow-y:auto
   (.composer-attachment-rail:has(.composer-structure-card), styles.css 23799행
   부근)가 «입력칸이 먼저 자기 몫을 갖고 카드는 남는 만큼만» 을 대신한다 — 카드 자신은
   고정 높이도 자기 overflow 도 갖지 않는다(스크롤 경계는 레일). 어포던스(×/펼치기/접기/
   돋보기)는 여전히 건드리지 않는다.
   ★카드가 아니라 «레일» 에 overflow-y:auto 를 준 이유(v2 초안 정정): 카드의 부모는
   .composer-attachment-lane 인데, 레인은 자기 부모(레일) 안에서 align-items:center
   로 배치돼(styles.css 34368행 부근) stretch 되지 않는다 — 즉 레인 자신의 높이가
   «definite» 가 아니라서 카드에 max-height:100% 를 줘도 퍼센트가 풀리지 않는다(실측:
   변화 없음). 레일은 셸의 grid row2 자신이라 기본 align-self:stretch 로 grid fr 트랙이
   준 높이를 곧장 물려받는 «definite height 보장 지점» — 스크롤 경계를 거기 둔다.
   레인·카드는 자기 콘텐츠만큼 자연스럽게 그려지고, 레일이 그 밖으로 넘친 부분만 감춰
   스크롤시킨다. */
.composer-structure-card {
  overflow-x: hidden;
}

.composer-structure-head {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 13px;
}

.composer-structure-icon {
  width: 30px;
  height: 30px;
  border-radius: var(--ds-radius-xs);
  background: var(--wizon-ai-hover-soft-bg);
  display: grid;
  place-items: center;
  flex-shrink: 0;
  color: var(--wizon-ai-text-muted);
}

.composer-structure-title {
  flex: 1;
  min-width: 0;
}

.composer-structure-title strong {
  display: block;
  font-size: var(--wds-font-size-body-2);
  font-weight: 600;
  color: var(--wizon-ai-text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.composer-structure-meta {
  display: block;
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted);
}

.composer-structure-actions {
  display: flex;
  /* 오너 확정 ⓒ: 아래 .composer-structure-icon-button::after 가 44px 히트 영역을
     시각 26px 박스 밖으로 (44-26)/2=9px 씩 부풀린다. 옛 gap:2px 그대로면 인접
     두 버튼(×/↻)의 44px 영역이 16px 겹쳐 오탭이 «더» 심해진다(실측 근거:
     9.5% 오탭 신고 자체가 이 겹침에서 나왔다). 겹침 0을 만드는 최소값(9*2=18px)을
     준다. */
  gap: 18px;
  flex-shrink: 0;
}

.composer-structure-icon-button {
  width: 26px;
  height: 26px;
  border: 0;
  background: transparent;
  border-radius: var(--ds-radius-xs);
  color: var(--wizon-ai-text-muted);
  cursor: pointer;
  display: grid;
  place-items: center;
  position: relative;
}

/* 오너 확정 ⓒ(paste-chip-behaviour-full-judge): 시각 26×26 은 자기 적중 31%·인접
   버튼(×/↻) 오탭 9.5%(파괴 방향) 실측이었다. 시각 크기는 형제 버튼들과 맞춰 그대로
   두고, 히트 영역만 44×44 로 넓힌다 — 기존 .md-table-more-trigger::after(10309행
   부근) · 첨부 칩 ↻ 44px 확장(09-21)과 같은 투명 ::after + position:relative/absolute
   중앙 정렬 관례. */
.composer-structure-icon-button::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
  transform: translate(-50%, -50%);
}

.composer-structure-icon-button:hover {
  background: var(--wizon-ai-hover-bg);
  color: var(--wizon-ai-text-primary);
}

.composer-structure-icon-button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

.composer-structure-section {
  padding: 12px 13px;
  border-top: 1px solid var(--wizon-ai-composer-border);
}

.composer-structure-label {
  margin: 0 0 8px;
  font-size: var(--wds-font-size-label-s);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--wizon-ai-text-muted-2);
}

.composer-structure-composition-bar {
  display: block;
  width: 100%;
  height: 8px;
}

.composer-structure-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-top: 8px;
}

.composer-structure-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted);
}

.composer-structure-legend-item b {
  color: var(--wizon-ai-text-primary);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.composer-structure-swatch {
  width: 8px;
  height: 8px;
  border-radius: var(--ds-radius-2xs);
  flex-shrink: 0;
}

.composer-structure-swatch[data-kind="prose"] { background: var(--wizon-ai-structure-prose); }
.composer-structure-swatch[data-kind="code"] { background: var(--wizon-ai-structure-code); }
.composer-structure-swatch[data-kind="table"] { background: var(--wizon-ai-structure-table); }
.composer-structure-swatch[data-kind="list"] { background: var(--wizon-ai-structure-list); }

.composer-structure-row {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) 88px 46px;
  align-items: center;
  gap: 8px;
  padding: 5px 4px;
  border-radius: var(--ds-radius-2xs);
  cursor: pointer;
  /* 브라우저 기본 삼각형 마커를 지운다 — 남기면 캐럿이 둘로 보인다. */
  list-style: none;
}

.composer-structure-row::-webkit-details-marker {
  display: none;
}

.composer-structure-row:hover {
  background: var(--wizon-ai-hover-soft-bg);
}

.composer-structure-row:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

.composer-structure-caret {
  color: var(--wizon-ai-text-muted-2);
  display: grid;
  place-items: center;
  transition: transform var(--wds-duration-fast, 150ms) ease;
}

.composer-structure-row-group[open] > .composer-structure-row .composer-structure-caret {
  transform: rotate(90deg);
}

.composer-structure-row-body {
  margin: 0;
  padding: 2px 4px 8px 22px;
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted);
  overflow-wrap: anywhere;
}

.composer-structure-row-name {
  font-size: var(--wds-font-size-body-2);
  color: var(--wizon-ai-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.composer-structure-section-bar {
  display: block;
  width: 88px;
  height: 6px;
}

.composer-structure-row-count {
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.composer-structure-more {
  margin: 6px 0 0;
  padding-left: 22px;
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted);
}

.composer-structure-budget-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.composer-structure-budget-key,
.composer-structure-budget-value {
  font-size: var(--wds-font-size-label-s);
  white-space: nowrap;
}

.composer-structure-budget-key { color: var(--wizon-ai-text-muted); }

.composer-structure-budget-value {
  color: var(--wizon-ai-text-primary);
  font-variant-numeric: tabular-nums;
}

.composer-structure-budget-bar {
  display: block;
  width: 100%;
  height: 6px;
}

.composer-structure-card[data-over-budget="true"] .composer-structure-budget-value {
  color: var(--wizon-ai-structure-warn);
  font-weight: 600;
}

.composer-structure-warning {
  margin: 8px 0 0;
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-structure-warn);
}

/* R29-P2 결정 8ⓐ — 합계 게이지: 값 색은 «그 칩»이 아니라 «합계»의 초과 여부를 따른다. */
.composer-structure-budget[data-over-budget="true"] .composer-structure-budget-value {
  color: var(--wizon-ai-structure-warn);
  font-weight: 600;
}

.composer-structure-budget-caption {
  margin: 4px 0 0;
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted);
  font-variant-numeric: tabular-nums;
}

.composer-structure-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 11px 13px 12px;
  border-top: 1px solid var(--wizon-ai-composer-border);
}

.composer-structure-chip {
  font-size: var(--wds-font-size-label-s);
  padding: 5px 11px;
  border-radius: var(--wizon-ai-radius-pill);
  border: 1px solid var(--wizon-ai-composer-border);
  background: transparent;
  color: var(--wizon-ai-text-secondary);
  cursor: pointer;
}

.composer-structure-chip:hover { background: var(--wizon-ai-hover-bg); }

.composer-structure-chip:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

.composer-structure-chip.is-primary {
  background: var(--wizon-ai-text-primary);
  color: var(--wizon-ai-surface);
  border-color: var(--wizon-ai-text-primary);
}

/* 모바일: 88px 인라인 바가 들어갈 폭이 없다 — 단어 수 숫자만으로 같은 판단이 된다. */
@media (max-width: 520px) {
  .composer-structure-card {
    width: 100%;
  }

  .composer-structure-section-bar {
    display: none;
  }

  .composer-structure-row {
    grid-template-columns: 14px minmax(0, 1fr) 46px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .composer-structure-caret {
    transition: none;
  }
}

/* --- Phase 4: 전송 후 버블 압축 + [§N] 앵커 -------------------------------- */
/* 붙여넣은 벽이 대화에 그대로 남지 않게 카드로 접는다. 새 색을 발명하지 않고
   기존 텍스트/표면 토큰만 쓴다. */
.composer-sent-paste-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.composer-sent-paste-typed {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.composer-sent-paste-card {
  border: 1px solid var(--wizon-ai-composer-border);
  border-radius: var(--ds-radius-sm);
  background: var(--wizon-ai-surface);
  overflow: hidden;
}

.composer-sent-paste-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 12px;
}

.composer-sent-paste-icon {
  width: 26px;
  height: 26px;
  border-radius: var(--ds-radius-xs);
  background: var(--wizon-ai-hover-soft-bg);
  display: grid;
  place-items: center;
  flex-shrink: 0;
  color: var(--wizon-ai-text-muted);
}

.composer-sent-paste-title {
  min-width: 0;
}

.composer-sent-paste-title strong {
  display: block;
  font-size: var(--wds-font-size-body-2);
  font-weight: 600;
  color: var(--wizon-ai-text-primary);
}

.composer-sent-paste-meta {
  display: block;
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted);
  font-variant-numeric: tabular-nums;
}

.composer-sent-paste-tabs {
  display: flex;
  gap: 14px;
  padding: 0 12px;
  border-top: 1px solid var(--wizon-ai-composer-border);
}

.composer-sent-paste-tab {
  border: 0;
  background: transparent;
  padding: 8px 0;
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted);
  cursor: pointer;
}

.composer-sent-paste-tab:hover {
  color: var(--wizon-ai-text-primary);
}

.composer-sent-paste-tab:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

.composer-sent-paste-tab.is-active {
  color: var(--wizon-ai-text-primary);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--wizon-ai-text-primary);
}

/* dead-focus-box-shadow-6(2026-09-12) 인접 발견 — .is-active 의 밑줄 box-shadow(위)와
   :focus-visible 의 링 box-shadow 는 둘 다 specificity 0,2,0 이라 소스 순서(이 규칙이
   :focus-visible 보다 뒤)가 이겨, 활성 탭(기본 선택된 "아웃라인" 탭)에 포커스가 가면
   링이 안 보였다. 0,3,0 으로 못 박아 순서와 무관하게 포커스가 항상 이기게 한다(값은
   :focus-visible 규칙과 동일 — 새 색·새 두께 없음). */
.composer-sent-paste-tab.is-active:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

.composer-sent-paste-outline {
  margin: 0;
  padding: 8px 12px 12px;
  list-style: none;
  border-top: 1px solid var(--wizon-ai-composer-border);
}

.composer-sent-paste-row {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) 46px;
  align-items: center;
  gap: 8px;
  padding: 4px 4px;
  border-radius: var(--ds-radius-2xs);
  scroll-margin-block: 80px;
}

.composer-sent-paste-row[data-sent-highlight="true"] {
  background: var(--wizon-ai-hover-bg);
}

.composer-sent-paste-row-index {
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted-2);
  font-variant-numeric: tabular-nums;
}

.composer-sent-paste-row-name {
  font-size: var(--wds-font-size-body-2);
  color: var(--wizon-ai-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.composer-sent-paste-row-count {
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.composer-sent-paste-source {
  margin: 0;
  padding: 10px 12px 12px;
  max-height: 260px;
  overflow: auto;
  /* R-30 P3b: 이제 <div> 안에 표/글 구간이 들어간다 — 줄 보존은 .composer-paste-source-text 몫. */
  white-space: normal;
  overflow-wrap: anywhere;
  font: inherit;
  font-size: var(--wds-font-size-label-s);
  color: var(--wizon-ai-text-secondary);
  border-top: 1px solid var(--wizon-ai-composer-border);
}

/* 답변 속 출처 앵커. 인라인 텍스트 흐름을 깨지 않게 baseline 에 얹는다. */
.composer-sent-anchor {
  display: inline;
  border: 1px solid var(--wizon-ai-composer-border);
  border-radius: var(--wizon-ai-radius-pill);
  background: var(--wizon-ai-hover-soft-bg);
  color: var(--wizon-ai-text-muted);
  font-size: var(--wds-font-size-label-s);
  padding: 1px 7px;
  margin: 0 2px;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}

.composer-sent-anchor:hover {
  background: var(--wizon-ai-hover-bg);
  color: var(--wizon-ai-text-primary);
}

.composer-sent-anchor:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--wizon-ai-focus-visible-ring);
}

@media (max-width: 520px) {
  .composer-sent-paste-row {
    grid-template-columns: 26px minmax(0, 1fr) 40px;
  }

  .composer-sent-paste-source {
    max-height: 200px;
  }
}

/* ── 유튜브 답변 LiveWiki 카드 (doc 665 §S2) ────────────────────────────────
   WDS `tool-summary-card.module.css` 의 `.tsc-*` 1:1 이식. 선언·값·순서는
   실렌더 검증된 프로토(youtube-livewiki-design/proto/proto.css)의 그것이고,
   클래스명만 02-slices.md 매핑표대로 바꿨다.

   ★옛 `.wizon-yt-*` 블록은 통째로 폐기했다. 이유 둘:
     ① `--cg-*` 를 참조했다 — 그 층은 `body.chatgpt-reference-v1` 스코프라 밖에서
        조용히 «빈 값»이 된다(wizon-widget-card-shell.css 의 실측 기록).
     ② 유튜브 빨강(`--cg-danger-fg`)을 tldr 세로선·축 막대 같은 «구조색»으로 썼다.
        WDS 규약에서 플랫폼 빨강은 마크 SVG fill 한정이다(01-judgement §8).
   색은 두 갈래뿐이다(오너 09-08 ⓑ, 무채색 지적으로 개정): 주 액션·근거·타임스탬프·
   채널 = 위즈온 잉크(--wds-color-text* 또는 --wizon-ai-action-primary-*), 플랫폼
   빨강 = --wds-color-error(마크 한정). --wds-color-primary* 는 이 카드 전 구간에서
   쓰지 않는다. */

/* WDS chassis AiMeta — 브랜드 + 상태(아이콘·한글·영문). 카드 «위»에 선다. */
/* ★카드 안의 `<p>` 는 자기 클래스 규칙에 «진다». 답변 본문이
   `.chatgpt-reference-v1 .message-body p`(0,2,1) 와 `.premium-product-shell-v1 .message-body p`
   로 모든 문단의 font-size·color 를 가져가기 때문이다 — CDP CSS.getMatchedStylesForNode 로
   실측했고, 그 결과 캡션(12px)이어야 할 문구가 16px 본문 잉크로 그려지고 있었다.
   그래서 아래 «문단» 규칙들만 선택자를 한 단계 올려 (0,3,0) 으로 되찾는다. 값은 한 곳에만
   있다(중복 금지) — 대신 이 카드는 반드시 `.message-body` 안에서 렌더된다는 전제가 붙는다.
   span/button 인 형제들(타임스탬프·캡션·본문)은 이 충돌이 없어 그대로 둔다. */
.message .message-body .wizon-ai-meta {
  display: flex;
  align-items: center;
  gap: var(--wds-space-2);
  margin: var(--wds-space-4) 0 0;
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-semibold);
  line-height: 1;
  color: var(--wds-color-text-subtle);
}
.wizon-ai-meta__brand { letter-spacing: 0.01em; }
.wizon-ai-meta__brand-on { color: var(--wds-color-text); }
.wizon-ai-meta__status {
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-2);
  margin-inline-start: var(--wds-space-1);
}
.wizon-ai-meta__icon { display: inline-flex; color: var(--wds-color-success); }
.wizon-ai-meta__ko { font-weight: var(--wds-font-weight-semibold); color: var(--wds-color-success-text); }
.wizon-ai-meta__en {
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-regular);
  color: var(--wds-color-text-muted);
}
.wizon-ai-meta[data-status="partial"] .wizon-ai-meta__icon { color: var(--wds-color-warning); }
.wizon-ai-meta[data-status="partial"] .wizon-ai-meta__ko { color: var(--wds-color-warning-text); }
.wizon-ai-meta[data-status="unavailable"] .wizon-ai-meta__icon { color: var(--wds-color-error); }
.wizon-ai-meta[data-status="unavailable"] .wizon-ai-meta__ko { color: var(--wds-color-error-text); }

/* 결과 카드 — .wiz-card 셸 «위»에 얹는다(02-slices 매핑표). 셸이 표면·테두리·모서리를
   소유하므로 여기서는 안쪽 배치와 타이포만 말한다. */
.wizon-livewiki {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-5);
  padding: var(--wds-space-5);
  /* .message-body p 의 line-height:1.65 가 카드 안 <p> 를 전부 늘린다. 카드는 자기
     밀도를 스스로 정한다 — 컨텍스트 상속을 여기서 끊는다(사용자 버블 카드와 같은
     수리, 아래 .message-body .wizon-link-card 참조). */
  line-height: var(--wds-line-height-label);
  color: var(--wds-color-text);
}
/* 313″ QA FAIL(youtube_livewiki_visual_qa_server.py 실렌더) — youtubeApplyWashPalette()
   가 article 에 --card-wash-* 6개를 setProperty 하지만(313 수리, app.js) 이를 소비하는
   규칙이 styles.css 에 0건이라 배경이 항상 흰색(rgb(255,255,255))으로 고정됐다(getComputedStyle
   조사: 커스텀 프로퍼티 값 자체는 항상 정확했다 — «주입은 되는데 안 그려짐»이지 전달값
   오류가 아니다). --card-wash-light/-dark 는 (테마 색이 아니라) 라이트/다크 각 테마에서
   쓸 배경 그 자체다(값 예: light=rgb(238,223,214) 크림, dark=rgb(58,45,39) 딥브라운 —
   테스트 WashRenderingTests 실측). .wiz-card 셸(wizon-widget-card-shell.css:227)의
   `background: var(--wiz-card-surface)` 위에 특이도(0,2,0 > 0,1,0)로 얹는다 — 셸 RULES.md
   가 그라디언트를 금지하므로(card/RULES.md Forbidden Patterns) 단색 칠만 한다. 텍스트 색
   (--card-wash-text1/2-*)은 이번 후보 범위 밖(QA FAIL 은 backgroundColor 만 지적) —
   다음 후보로 분리(보고서 §9). */
.wizon-livewiki[data-wash="thumbnail"] {
  background: var(--card-wash-light);
}
[data-theme="dark"] .wizon-livewiki[data-wash="thumbnail"] {
  background: var(--card-wash-dark);
}
.wizon-livewiki__head { display: flex; flex-direction: column; gap: var(--wds-space-2); }
/* ★`.message-body h3`(styles.css 7688행, 특이도 0,1,1)가 클래스 하나짜리 규칙을
   이긴다 — 실렌더 계산값이 body-1(16px)이 아니라 --ds-text-title-20(20px)이었다.
   답변 본문의 제목 스케일은 마크다운 문단용이고, 카드는 자기 타이포를 스스로 정한다.
   선택자를 한 단계 올려 카드 안에서만 컨텍스트를 끊는다. */
.wizon-livewiki .wizon-livewiki__title,
.wizon-livewiki__title {
  margin: 0;
  font-size: var(--wds-font-size-body-1);
  font-weight: var(--wds-font-weight-semibold);
  line-height: 1.4;
  color: var(--wds-color-text);
}
.wizon-livewiki__title a { color: inherit; text-decoration: none; }
.wizon-livewiki__title a:hover { text-decoration: underline; }
.wizon-livewiki__source {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--wds-space-1) var(--wds-space-2);
  font-size: var(--wds-font-size-caption);
}
.wizon-livewiki__avatar {
  position: relative;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: var(--wds-radius-full);
  display: grid;
  place-items: center;
  overflow: hidden;
  /* 오너 09-08 ⓑ: 중립 원 + 잉크 이니셜(S1 .wizon-link-card__avatar 와 같은 처리). */
  background: var(--wds-color-surface-muted);
  color: var(--wds-color-text-muted);
  /* 프로토 원본은 raw 10px — anti-drift 래칫 raw-font-size 여유 0 이라 새 리터럴을
     쓸 수 없다. 22px 원 안 2자 이니셜이므로 캡션(12px)의 0.833배로 같은 비례를 낸다
     (S1 의 .wizon-link-card__avatar 와 같은 처리). */
  font-size: calc(var(--wds-font-size-caption) * 0.833);
  font-weight: var(--wds-font-weight-medium);
}
/* ★오너 09-08 저녁 지적 «빈 원» 수리 — 이니셜을 먼저 깔고(fallback) 이미지를 그
   위에 얹는다(img). 이미지가 로드에 실패하면 bindImageFallbacks(news 썸네일과
   같은 관례)가 img 만 걷어 내 이니셜이 드러난다 — 「이미지 있으면 이미지, 없거나
   깨지면 이니셜」이 항상 성립한다(전에는 이미지 실패 시 아무것도 안 남았다). */
.wizon-livewiki__avatar-fallback { display: inline-flex; line-height: 1; }
.wizon-livewiki__avatar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* 구분점이 줄 머리에 오지 않게 채널과 «한 flex 아이템»으로 묶는다. */
.wizon-livewiki__channel-slot {
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  min-width: 0;
}
.wizon-livewiki__channel {
  /* 오너 09-08 ⓑ: 근거 링크지만 무채색 테마 — 잉크 + 호버 밑줄. */
  color: var(--wds-color-text);
  text-decoration: none;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wizon-livewiki__channel:hover { text-decoration: underline; }
.wizon-livewiki__sep { color: var(--wds-color-text-muted); }
.wizon-livewiki__source-meta { color: var(--wds-color-text-muted); font-variant-numeric: tabular-nums; }

/* 핵심 요약 번호 카드 — 이 슬라이스가 tldr 한 문단을 대체하는 자리다.
   20260917-key-points-typography-a1(Y7 판정 §15 2번 줄, D1~D3): 절 제목을 본문과 같은
   크기(위계 축 1개)로 두지 않고 settings-v2 그룹 라벨 문법(meta 12/13·600·muted 눈썹)으로
   내리고(D1), 번호를 원형 칩 대신 행잉 인덴트로 본문 축 밖에 둔다(D2). 헤드→리스트 gap 을
   space-2(8)로 낮춰 항목↔항목 gap(space-3=12)과 달리 해 «라벨/형제 == 1.00» 을 깬다.
   key-text 는 34em 상한(D3, 한글 advance 0.864em 실측 기준 39.3자)으로 고아줄을 줄인다. */
.wizon-livewiki__key {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-2);
  padding: var(--wds-space-4);
  border-radius: var(--wds-radius-lg);
  background: var(--wds-color-surface-muted);
  box-shadow: inset 0 0 0 1px var(--wds-color-border);
}
.message-body .wizon-livewiki .wizon-livewiki__key-head {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--wds-space-2);
  padding-inline-start: 28px;
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-semibold);
  color: var(--wds-color-text-muted);
}
.wizon-livewiki__key-icon { display: none; }
.wizon-livewiki__key-list {
  list-style: none;
  margin: 0;
  padding: 0;
  padding-inline-start: 28px;
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-3);
}
.wizon-livewiki__key-item { position: relative; }
.wizon-livewiki__key-num {
  position: absolute;
  left: -28px;
  top: 0;
  height: calc(var(--wds-font-size-body-2) * 1.6);
  display: flex;
  align-items: center;
  color: var(--wds-color-text-muted);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
}
.wizon-livewiki__key-text {
  display: block;
  font-size: var(--wds-font-size-body-2);
  line-height: 1.6;
  color: var(--wds-color-text);
  max-width: 34em;
  /* ★결정 15-1ⓐ(judge youtube-summary-card-next-design-judge §5-①) — pretty→balance.
     실측: 390/375/430 × body 25~105자 27칸에서 pretty 는 고아줄 12건(현행 25~35자
     하한 자체도 2폭에서 고아줄), balance 는 전 구간 0건(최저 채움 50.9%). balance
     는 Chromium 6줄까지만 균형을 잡는다 — 375px 기준 약 105자(6줄)가 상한이라
     body 를 55~90자로 둔다(2문장). 미지원 브라우저는 auto 로 떨어져 현행과 동일
     (회귀 0). */
  text-wrap: balance;
}

/* 리드(tldr, 결정 15-1ⓐ) — 논증 3층의 «리드». 목업(.yt__lead) 값을 그대로 옮긴다.
   결론(.wizon-livewiki__key-headline)과 같은 폭 상한을 공유해 리드만 유난히 넓게
   퍼지지 않게 한다. */
.wizon-livewiki__lead {
  margin: 0;
  font-size: var(--wds-font-size-body-1);
  line-height: 1.55;
  color: var(--wds-color-text);
  max-width: 34em;
  text-wrap: balance;
}

/* 결론(takeaway, 결정 15-1ⓐ) — «그래서» 라벨 + 결론 문장. 목업 .yt__takeaway 값을
   그대로 옮긴다(반경·간격·표면 전부 기존 토큰, raw hex 0). 검증 실패 시 서버가
   키를 없애므로(youtube.py parse_summary_output) 이 블록은 app.js 가 summary.takeaway
   가 있을 때만 그린다 — 빈 블록은 만들지 않는다. */
.wizon-livewiki__takeaway {
  display: flex;
  gap: var(--wds-space-3);
  padding: var(--wds-space-3) var(--wds-space-4);
  border-radius: var(--wds-radius-lg);
  background: var(--wds-color-surface);
  box-shadow: inset 0 0 0 1px var(--wds-color-border);
}
.wizon-livewiki__takeaway-mark {
  flex: none;
  width: 3px;
  border-radius: var(--wds-radius-full);
  background: var(--wds-color-text-subtle);
}
.wizon-livewiki__takeaway-body {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-1);
  min-width: 0;
}
.wizon-livewiki__takeaway-eyebrow {
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-semibold);
  color: var(--wds-color-text-subtle);
}
.wizon-livewiki__takeaway-text {
  font-size: var(--wds-font-size-body-2);
  line-height: 1.6;
  color: var(--wds-color-text);
  max-width: 34em;
  text-wrap: balance;
}

/* A2 구조(결정 12ⓐ, 20260917-key-points-a2-structure) — headline 이 있는 항목만 3부로
   그린다: 헤드라인(semibold, ink) + 본문(key-text 재사용, muted) + 타임스탬프 칩.
   목업(youtube-summary-card-typography-design-judge/mockup.html .k2)의 값을 WDS 토큰
   으로 옮겼다 — 새 토큰 발명 0. 칩은 기존 .wizon-livewiki__ts 문법(data-youtube-seek
   핸들러 재사용)에 min-height 28px 모디파이어만 얹는다(Y7 §16 #1). */
.wizon-livewiki__key-item--structured {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-1);
}
.wizon-livewiki__key-headline {
  display: block;
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-semibold);
  line-height: 1.6;
  color: var(--wds-color-text);
  max-width: 34em;
  text-wrap: balance;
}
.wizon-livewiki__key-ts-row { display: block; margin-top: 2px; }

/* 영상 블록 — S3(doc 665)에서 click-to-load 로 바뀌었다. 슬롯이 «16:9 상자»를 소유하고
   포스터(썸네일 링크)와 iframe 이 그 안에서 자리를 주고받는다. 상자를 포스터가 갖고
   있으면 포스터를 숨기는 순간 높이가 0 이 되어 iframe 이 사라진다. */
.wizon-livewiki__video-block { display: flex; flex-direction: column; gap: var(--wds-space-2); }
.wizon-livewiki__video-slot {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--wds-radius-md);
  overflow: hidden;
  background: color-mix(in srgb, var(--wds-color-text) 82%, var(--wds-color-surface));
}
[data-theme="dark"] .wizon-livewiki__video-slot {
  background: color-mix(in srgb, var(--wds-color-surface) 82%, var(--wds-color-text));
}
.wizon-livewiki__video-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
/* 로드 중 = 스켈레톤(정본 상태 3종의 가운데). iframe 이 이미 붙어 있어도 첫 프레임이
   그려지기 전까지는 이 판이 위를 덮는다 — 그래서 z-index 가 필요하다. */
.wizon-livewiki__video-slot[data-youtube-embed="loading"]::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  /* ★09-17 Y8 실측: 로딩 스켈레톤이 pointer-events 미지정이라 최초 재생 ≈1,686ms·
     시크 ≈461ms 동안 재생바 클릭을 가로챘다 — 재생바는 시각적으로만 덮이고 클릭은
     투과되어야 한다. */
  pointer-events: none;
  /* ★스켈레톤은 «영상 자리» 위에 있으므로 밝은 표면 토큰을 그대로 쓰면 어두운 포스터 →
     밝은 회색 판 → 어두운 플레이어로 두 번 번쩍인다(실렌더 02-loading-1440 에서 확인).
     같은 어두운 판 위에 표면색을 «반투명으로 섞어» 광택만 지나가게 한다 — 새 색 0. */
  background: linear-gradient(
    100deg,
    color-mix(in srgb, var(--wds-color-surface) 10%, transparent) 0%,
    color-mix(in srgb, var(--wds-color-surface) 26%, transparent) 45%,
    color-mix(in srgb, var(--wds-color-surface) 10%, transparent) 90%
  );
  background-size: 240% 100%;
  animation: wizon-livewiki-video-shimmer 1.1s linear infinite;
}
@keyframes wizon-livewiki-video-shimmer {
  from { background-position: 140% 0; }
  to { background-position: -60% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .wizon-livewiki__video-slot[data-youtube-embed="loading"]::after { animation: none; }
}
/* 재생이 시작된(또는 멈춘) 뒤에는 포스터를 치운다 — 남겨 두면 iframe 위에 썸네일이
   겹쳐 「재생 중인데 정지 화면」이 된다. */
.wizon-livewiki__video-slot:not([data-youtube-embed="idle"]):not([data-youtube-embed="external"]) .wizon-livewiki__video { display: none; }
.message-body .wizon-livewiki .wizon-livewiki__video-badge {
  margin: 0;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  padding: 2px var(--wds-space-2);
  border-radius: var(--wds-radius-md);
  background: var(--wds-color-surface-muted);
  color: var(--wds-color-text);
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
}
.message-body .wizon-livewiki .wizon-livewiki__video-badge a { color: var(--wds-color-text); }
/* 프라이버시 문구 — 「누르기 전에는 요청 0」은 계약이고, 계약은 화면에서도 말한다.
   ★재생이 시작된 뒤에는 치운다. 「누르기 전에는…」이 재생 중인 영상 밑에 남아 있으면
   그 문장 자체가 거짓이 된다(실렌더 캡처 04-seek-1440 에서 잡았다). 그 자리는 배지가
   「▶ 0:53부터 재생 중」으로 이어받는다. */
.message-body .wizon-livewiki .wizon-livewiki__video-hint {
  margin: 0;
  font-size: var(--wds-font-size-caption);
  line-height: 1.5;
  color: var(--wds-color-text-subtle);
}
.wizon-livewiki__video-slot:not([data-youtube-embed="idle"]):not([data-youtube-embed="external"]) ~ .wizon-livewiki__video-hint {
  display: none;
}
/* 포스터는 인라인 재생이 가능하면 <button>, 아니면 <a> 다(app.js 주석 참조). 두 태그가
   같은 상자를 쓰므로 버튼의 UA 기본값(테두리·패딩·폰트)을 여기서 지운다. */
.wizon-livewiki__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: start;
  cursor: pointer;
  display: grid;
  place-items: center;
  border-radius: var(--wds-radius-md);
  overflow: hidden;
  /* 바탕은 슬롯이 갖는다(위). 포스터는 그 위에 얹히는 투명한 판이라 썸네일이 없어도
     «영상 자리»의 어두운 판이 그대로 보인다 — 테마 반전 사유는 슬롯 규칙에 적었다. */
  background: transparent;
}
.wizon-livewiki__video-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.wizon-livewiki__video-play {
  position: relative;
  width: 54px;
  height: 38px;
  border-radius: var(--wds-radius-md);
  /* 색 규칙 3: 유튜브 빨강은 플랫폼 마크 한정 — 재생 버튼이 그 마크다. 마크는 테마를 따라
     뒤집지 않는다(슬롯이 양 테마 모두 어두운 판) — 다크에서 밝아지는 에러 토큰(red-400,
     흰 아이콘 2.77:1) 대신 양 테마 red-700 인 역할 토큰(흰 아이콘 6.47:1). */
  background: var(--ds-color-platform-video-mark);
  color: var(--ds-color-on-solid);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.wizon-livewiki__video-caption {
  position: absolute;
  left: var(--wds-space-3);
  bottom: var(--wds-space-3);
  max-width: 68%;
  padding: 2px var(--wds-space-2);
  border-radius: var(--wds-radius-sm);
  background: var(--wds-color-surface-raised);
  color: var(--wds-color-text);
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-medium);
  font-variant-numeric: tabular-nums;
}
/* ══ 결정 15-2ⓑ(judge youtube-summary-card-next-design-judge §5-②, mockup.html 값
   이식 — raw hex 0, 기존 토큰만) — 탭 3칸(타임라인·스크립트·관련 영상) + 스크립트
   검색·챕터 묶음 + 타임라인 T2a 스트립·현재 위치 + 복사 분리 ══════════════════════ */

/* 탭 — tabDefs.length<=1 이면 app.js 가 이 블록 자체를 그리지 않는다(D3: 1칸 죽은
   radiogroup 재발 방지). 2칸 이상일 때만 실제 선택지가 있다. */
.wizon-livewiki__tabs {
  overflow-x: auto;
  overflow-y: hidden;
  padding-block: var(--wds-space-1);
  margin-block: calc(-1 * var(--wds-space-1));
  scrollbar-width: none;
}
.wizon-livewiki__tabs::-webkit-scrollbar { display: none; }
.wizon-livewiki__panel {
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-3);
  padding-top: var(--wds-space-1);
}
/* ★실렌더 계측(09-17, youtube_livewiki_visual_qa_server 포트 8931) — `hidden` 속성만
   으로는 안 접힌다. UA 스타일시트의 `[hidden]{display:none}` 은 특이도 (0,0,1) 이라
   바로 위 `.wizon-livewiki__panel`(0,1,0)의 display:flex 에 진다 — 스크립트 접이식과
   같은 함정(46607 부근 주석)이 탭 패널에서도 재발했다. 비활성 탭이 계속 그려져
   있었다(실측 스크린샷으로 확인). */
.wizon-livewiki__panel[hidden] { display: none; }

/* 타임라인 T2a — 구간 비례 스트립 + 현재 위치 줄 + 기존 목록. */
.wizon-livewiki__strip {
  display: flex;
  gap: 2px;
  min-height: 28px;
  border-radius: var(--wds-radius-md);
  /* 중앙 결정(09-18) — strip-item 히트 44px 확장은 세로만이라(::after inset-inline:0,
     가로 폭 자기 자신과 동일) 가로 방향 클리핑(overflow-x:hidden)은 그대로 두고, 세로만
     visible 로 바꿔 확장분이 이 컨테이너 경계에서 잘리지 않게 한다. 실제(보이는) 콘텐츠는
     항목·컨테이너 모두 min-height:28px 로 같아 세로로 넘치는 실체가 없다 — 코너 둥글림에
     보이는 변화는 없다(::after 는 투명, content 없음). */
  overflow-x: hidden;
  overflow-y: visible;
  background: var(--wds-color-surface-muted);
}
.wizon-livewiki__strip-item {
  position: relative;
  min-height: 28px;
  border: 0;
  padding: 0 var(--wds-space-2);
  background: var(--wds-color-surface);
  color: var(--wds-color-text-muted);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  text-decoration: none;
  cursor: pointer;
  /* text-overflow:ellipsis 는 가로 클리핑만 있으면 된다 — 세로는 visible 로 열어 자기
     자신의 ::after 히트박스(아래)가 자기 overflow:hidden 에 먼저 잘리지 않게 한다
     (부모 .wizon-livewiki__strip 의 overflow-x/-y 분리와 같은 이유, 09-18). */
  overflow-x: hidden;
  overflow-y: visible;
  white-space: nowrap;
  text-overflow: ellipsis;
  box-shadow: inset 0 0 0 1px var(--wds-color-border);
}
/* 같은 결정 — 시각 28px 유지, 히트만 44px. inset-inline:0 은 항목 자신의 실제 폭(flex-
   grow 로 JS 가 매긴 비례 가중치, youtubeApplyStripWeights)을 그대로 쓰고 그 폭을
   벗어나지 않는다 — 가로 확장 0, 인접 항목과 히트 겹침 0(2px gap 은 각 항목의 ::after
   가 자기 padding-box 안에서만 그려지므로 그대로 유지). */
@media (pointer: coarse) {
  .wizon-livewiki__strip-item::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    top: 50%;
    height: 100%;
    min-height: 44px;
    transform: translateY(-50%);
  }
}
.wizon-livewiki__strip-item[aria-current="true"] {
  background: var(--wds-color-text);
  color: var(--wds-color-surface);
  font-weight: var(--wds-font-weight-semibold);
}
.wizon-livewiki__strip-item:focus-visible { outline: 2px solid var(--wds-color-text); outline-offset: -2px; }
/* «여기서 재생 · m:ss» — 시크 전에는 숨겨 거짓 위치를 말하지 않는다(youtubeUpdateCurrentMarkers). */
.wizon-livewiki__nowline {
  margin: 0;
  font-size: var(--wds-font-size-caption);
  color: var(--wds-color-text-muted);
  font-variant-numeric: tabular-nums;
}
.wizon-livewiki__nowline[hidden] { display: none; }
.wizon-livewiki__timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-3);
}
.wizon-livewiki__tl-item { display: flex; align-items: flex-start; gap: var(--wds-space-2); border-radius: var(--wds-radius-md); }
.wizon-livewiki__tl-item[aria-current="true"] { background: var(--wds-color-surface-muted); }
.wizon-livewiki__tl-text { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.wizon-livewiki__tl-title { font-size: var(--wds-font-size-body-2); font-weight: var(--wds-font-weight-medium); line-height: 1.55; color: var(--wds-color-text); }
.wizon-livewiki__tl-desc { font-size: var(--wds-font-size-caption); line-height: 1.55; color: var(--wds-color-text-muted); }
/* 색 규칙 2 개정(오너 09-08 ⓑ): 타임스탬프는 «누를 수 있는 근거»지만 무채색 테마 —
   중립 채움 + 잉크 글자. 숫자는 font-variant-numeric: tabular-nums 로 고정폭 정렬.
   Y9-2(D1·D2, 09-17) — 최소변 28px(SC 2.5.8 AA)·최소폭 56px 우측 정렬로 뒤 텍스트
   좌측선을 고정한다(현행 최대 어긋남 19.2px 실측 → 0). */
.wizon-livewiki__ts {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  min-height: 28px;
  min-width: 56px;
  padding: 2px var(--wds-space-2);
  border: 0;
  border-radius: var(--wds-radius-md);
  background: var(--wds-color-surface-muted);
  color: var(--wds-color-text);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  cursor: pointer;
}
.wizon-livewiki__ts--inline { background: transparent; padding-inline: 0; justify-content: flex-start; }

/* 스크립트 — 검색 + 챕터 묶음(sticky 헤더) + 현재 줄. D4 처방: 평면 121~240행을
   챕터로 나눠 스크롤 중 «지금 어느 구간인지»가 항상 보인다. */
.wizon-livewiki__script-tools { display: flex; gap: var(--wds-space-2); align-items: center; }
.wizon-livewiki__script-search {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 32px;
  padding: 0 var(--wds-space-3);
  border: 0;
  border-radius: var(--wds-radius-md);
  background: var(--wds-color-surface-muted);
  color: var(--wds-color-text);
  font: inherit;
  font-size: var(--wds-font-size-body-2);
  box-shadow: inset 0 0 0 1px var(--wds-color-border);
}
.wizon-livewiki__script-search::placeholder { color: var(--wds-color-text-subtle); }
.wizon-livewiki__script-count {
  flex: none;
  font-size: var(--wds-font-size-caption);
  color: var(--wds-color-text-subtle);
  font-variant-numeric: tabular-nums;
}
.wizon-livewiki__script-list {
  list-style: none;
  margin: 0;
  padding: var(--wds-space-2);
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-1);
  max-height: 320px;
  overflow-y: auto;
  border-radius: var(--wds-radius-md);
  background: var(--wds-color-surface-muted);
}
.wizon-livewiki__script-group { display: flex; flex-direction: column; gap: var(--wds-space-1); }
.wizon-livewiki__script-group[hidden] { display: none; }
.wizon-livewiki__script-group-head {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  width: 100%;
  padding: var(--wds-space-1) var(--wds-space-2);
  border: 0;
  border-radius: var(--wds-radius-sm);
  background: var(--wds-color-surface);
  color: var(--wds-color-text-muted);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  font-weight: var(--wds-font-weight-semibold);
  font-variant-numeric: tabular-nums;
  text-align: start;
  cursor: pointer;
}
/* 중앙 결정(09-18) — QA 영수증 touch-target 은 인터랙티브 요소 전수를 본다. 시각 높이
   26.2px 는 그대로 두고(밀도 변경 없음) 히트만 44px 로 넓힌다 — .wizon-segmented__
   segment 와 같은 관례(투명 ::after, pointer:coarse). 폭은 inset-inline:0 으로 요소
   자신의 실제 너비(이미 100%, 단일 열 리스트라 가로 이웃 없음)만 쓰고 min-width 를
   두지 않는다 — 세로만 확장. `.wizon-livewiki__script-list`(부모)는 overflow-y:auto
   가 320px 스크롤에 필요해 못 없앤다 — 헤더가 sticky top:0 으로 «고정»되는 순간에는
   확장분 위쪽 절반이 스크롤 클립 경계와 겹쳐 그 부분만 못 누른다(measure_toggle_
   strip_hit.py 실측: 고정 상태에서도 헤더 자체 클릭은 그대로 되고, 확장 히트박스
   아래쪽 절반은 유효 — 위쪽 절반만 실측 불가로 보고). */
@media (pointer: coarse) {
  .wizon-livewiki__script-group-head::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    top: 50%;
    height: 100%;
    min-height: 44px;
    transform: translateY(-50%);
  }
}
.wizon-livewiki__script-group-lines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-2);
}
/* ★`hidden` 속성만으로는 안 접힌다 — UA 스타일시트의 `[hidden] { display: none }` 은
   특이도 (0,0,1) 이라 바로 위 `.wizon-livewiki__script-group-lines`(0,1,0)의
   display:flex 에 진다(옛 접이식 결함, 09-17 이전과 같은 함정). */
.wizon-livewiki__script-group-lines[hidden] { display: none; }
.wizon-livewiki__script-line { display: flex; align-items: flex-start; gap: var(--wds-space-2); }
.wizon-livewiki__script-line[hidden] { display: none; }
.wizon-livewiki__script-text { min-width: 0; font-size: var(--wds-font-size-caption); line-height: 1.55; color: var(--wds-color-text-muted); }
.wizon-livewiki__script-line[aria-current="true"] .wizon-livewiki__script-text { color: var(--wds-color-text); }
.wizon-livewiki__script-text mark { background: var(--wds-color-text); color: var(--wds-color-surface); border-radius: var(--wds-radius-sm); padding: 0 2px; }
.wizon-livewiki__script-more { font-size: var(--wds-font-size-caption); color: var(--wds-color-text-subtle); }

/* 관련 영상 셸프(Y9-D2, 결정 15-3ⓑ 배선) — 가로 스크롤 썸네일 행. 애플 위젯 밀도
   (8~12px)에 맞춰 gap 은 --wds-space-3(위계상 8~12px 대역)을 그대로 쓴다. */
.wizon-livewiki__shelf {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - 2 * var(--wds-space-3)) / 3);
  gap: var(--wds-space-3);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.wizon-livewiki__shelf::-webkit-scrollbar { display: none; }
.wizon-livewiki__shelf-item {
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  gap: var(--wds-space-1);
  min-width: 0;
  min-height: 44px;
  color: inherit;
  text-decoration: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.wizon-livewiki__shelf-item:focus-visible {
  outline: 2px solid var(--wizon-ai-focus-visible-ring);
  outline-offset: 2px;
  border-radius: var(--wds-radius-sm);
}
.wizon-livewiki__shelf-item-thumb {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--wds-radius-md);
  object-fit: cover;
  background: var(--wds-color-surface-muted);
}
.wizon-livewiki__shelf-item-title {
  font-size: var(--wds-font-size-body-2);
  line-height: 1.45;
  color: var(--wds-color-text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.wizon-livewiki__shelf-item-meta { font-size: var(--wds-font-size-caption); color: var(--wds-color-text-muted); font-variant-numeric: tabular-nums; }

/* 카드 액션 행 — Y9-5(D8): 복사는 타임라인 밖으로. 구간(sections) 0개여도 산다. */
.wizon-livewiki__actions { display: flex; align-items: center; gap: var(--wds-space-2); }
.wizon-livewiki__copy {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--wds-space-1);
  padding: var(--wds-space-1) var(--wds-space-2);
  border: 0;
  border-radius: var(--wds-radius-md);
  background: var(--wds-color-surface-muted);
  color: var(--wds-color-text-muted);
  font: inherit;
  font-size: var(--wds-font-size-caption);
  cursor: pointer;
}
.wizon-livewiki__copy[data-youtube-copied="true"]::after { content: " 복사됨"; }

/* 상태 — 부분(배너) · 자막 없음/비공개/지역 차단(warning NoticeCard) ·
   레이트리밋/LLM 실패(error NoticeCard + 다시 시도). */
.message-body .wizon-livewiki .wizon-livewiki__partial {
  display: flex;
  align-items: center;
  gap: var(--wds-space-2);
  margin: 0;
  padding: var(--wds-space-2) var(--wds-space-3);
  border-radius: var(--wds-radius-md);
  background: color-mix(in srgb, var(--wds-color-warning) 12%, transparent);
  color: var(--wds-color-warning-text);
  font-size: var(--wds-font-size-caption);
}
.wizon-livewiki__partial-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: var(--wds-radius-full);
  background: var(--wds-color-warning);
}
.message-body .wizon-livewiki .wizon-livewiki__disclaimer {
  margin: 0;
  font-size: var(--wds-font-size-caption);
  line-height: 1.5;
  color: var(--wds-color-text-muted);
}
.wizon-livewiki--notice {
  display: flex;
  flex-direction: row;
  gap: var(--wds-space-3);
  align-items: flex-start;
  padding: var(--wds-space-5);
}
.wizon-livewiki--notice[data-tone="warning"] { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wds-color-warning) 42%, transparent), var(--wds-shadow-md); }
.wizon-livewiki--notice[data-tone="error"] { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wds-color-error) 42%, transparent), var(--wds-shadow-md); }
.wizon-livewiki__notice-icon { flex: none; display: inline-flex; }
.wizon-livewiki--notice[data-tone="warning"] .wizon-livewiki__notice-icon { color: var(--wds-color-warning); }
.wizon-livewiki--notice[data-tone="error"] .wizon-livewiki__notice-icon { color: var(--wds-color-error); }
.wizon-livewiki__notice-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--wds-space-1); }
.wizon-livewiki__notice-video {
  display: flex;
  align-items: center;
  gap: var(--wds-space-2);
  padding-bottom: var(--wds-space-2);
  margin-bottom: var(--wds-space-1);
  border-bottom: 1px solid var(--wds-color-border-subtle);
}
.wizon-livewiki__notice-thumb {
  flex: none;
  display: block;
  width: 64px;
  height: 36px;
  border-radius: var(--wds-radius-sm);
  overflow: hidden;
  background: var(--wds-color-surface-pressed);
}
.wizon-livewiki__notice-thumb-img { display: block; width: 100%; height: 100%; object-fit: cover; }
.wizon-livewiki__notice-video-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.wizon-livewiki__notice-video-title {
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-semibold);
  color: var(--wds-color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wizon-livewiki__notice-video-meta { font-size: var(--wds-font-size-caption); color: var(--wds-color-text-muted); }
.wizon-livewiki__notice-title { font-size: var(--wds-font-size-body-1); font-weight: var(--wds-font-weight-semibold); color: var(--wds-color-text); }
.wizon-livewiki__notice-detail { font-size: var(--wds-font-size-body-2); line-height: 1.5; color: var(--wds-color-text-muted); }
.wizon-livewiki__notice-actions { margin-top: var(--wds-space-2); }
.wizon-livewiki__notice-action {
  display: inline-flex;
  align-items: center;
  height: var(--wds-control-height-sm);
  padding: 0 var(--wds-space-4);
  border: 1px solid var(--wds-color-border);
  border-radius: var(--wds-radius-control-sm);
  background: var(--wds-color-surface);
  color: var(--wds-color-text);
  font: inherit;
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-medium);
  text-decoration: none;
  cursor: pointer;
}

/* WDS SegmentedButton (size=sm) — 원본 CSS 그대로. ★Y9-C(09-17) — 세그먼트가 2~3개로
   늘면서 옛 «단일 raised thumb»(x/width 를 JS 로 계산해야 하는 절대배치 판)를 걷어
   냈다: 각 세그먼트가 자기 aria-checked 상태로 스스로 raised 표면을 그린다 — thumb
   요소·좌표 계산 없이 값은 그대로(surface-raised·border-subtle·shadow-md). */
.wizon-segmented {
  position: relative;
  display: inline-flex;
  align-items: stretch;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--wds-color-border);
  border-radius: var(--wds-radius-control-sm);
  background: var(--wds-color-surface-pressed);
  overflow-x: auto;
  overflow-y: visible;
  scrollbar-width: none;
}
.wizon-segmented::-webkit-scrollbar { display: none; }
.wizon-segmented__segment {
  position: relative;
  z-index: 1;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--wds-control-height-sm);
  padding-inline: var(--wds-space-3);
  border: 1px solid transparent;
  border-radius: calc(var(--wds-radius-control-sm) - 3px);
  background: transparent;
  color: var(--wds-color-text-muted);
  font-family: var(--wds-font-sans);
  font-size: var(--wds-font-size-body-2);
  font-weight: var(--wds-font-weight-medium);
  line-height: var(--wds-line-height-tight);
  white-space: nowrap;
  cursor: pointer;
}
/* 312″ QA FAIL(playwright-mobile 390, h=32px<44px WCAG 2.2 SC 2.5.8) — 시각 높이(32px,
   --wds-control-height-sm)는 유지하고 히트 영역만 기존 20260729-touch-target-a2/
   20260710-touch-target-hitslop 관례(투명 ::after, min-width/min-height 44px,
   translate(-50%,-50%))로 넓힌다. .wizon-segmented 는 overflow-x:auto 만 선언해도 스펙상
   overflow-y 계산값이 auto 로 강제돼(둘 중 하나만 visible 이 아니면 다른 쪽도 auto) 6px 위아래
   확장분이 스크롤 클립에 잘린다 — 위에서 overflow-y:visible 을 명시해 그 부작용을 막았다.
   세그먼트 가로 폭은 이미 44px 이상(레이블 2~4자 + padding-inline var(--wds-space-3))이라
   min-width 는 안전망일 뿐 실제로 폭을 키우지 않는다(실측: report.md §「히트 높이 전수 표」). */
@media (pointer: coarse) {
  .wizon-segmented__segment::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    min-width: 44px;
    min-height: 44px;
    transform: translate(-50%, -50%);
  }
}
.wizon-segmented__segment[aria-checked="true"] {
  /* 오너 09-08 ⓑ 계승 — 활성 탭은 raised 표면(중립)이 «채움»을 맡는다. 글자는
     무채색 잉크로 강조(포털 기존 탭 관례, styles.css:4401
     .artifact-ribbon-tab[aria-selected="true"] 와 같은 처리). */
  background: var(--wds-color-surface-raised);
  border-color: var(--wds-color-border-subtle);
  box-shadow: var(--wds-shadow-md);
  color: var(--wds-color-text);
  font-weight: var(--wds-font-weight-semibold);
}
.wizon-segmented__segment:focus-visible { outline: 2px solid var(--wds-color-text); outline-offset: 1px; }

@media (max-width: 520px) {
  .wizon-livewiki { padding: var(--wds-space-4); gap: var(--wds-space-4); }
  .wizon-livewiki__key { padding: var(--wds-space-3); }
  .wizon-livewiki--notice { padding: var(--wds-space-4); }
}

/* ══ 권한 오류 표면 — doc 496 §3 ═══════════════════════════════════════════════
   세 표면 모두 새 색·새 간격을 만들지 않는다. 값은 전부 라이브 토큰에서 온다
   (--cg-*, --ds-*, --wds-z-toast) 이고, 전면 표면은 기존 .chatgpt-lifecycle-empty
   규격 위에 아이콘·계정 줄·액션 행만 얹은 것이다.

   ★상태 카드는 키보드만으로 도달하는 경우가 많다. :focus-visible 링을 반드시 준다. */
body.chatgpt-reference-v1 .permission-state-icon {
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin-bottom: 2px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-pill, 999px);
  color: var(--cg-muted);
}

body.chatgpt-reference-v1 .permission-state-icon svg { width: 20px; height: 20px; }

body.chatgpt-reference-v1 .permission-full span:not(.permission-state-icon):not(.permission-full-account) {
  max-width: 44ch;
}

body.chatgpt-reference-v1 .permission-full-account {
  margin-top: 2px;
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-12);
}

body.chatgpt-reference-v1 .permission-state-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 10px;
}

body.chatgpt-reference-v1 .permission-state-actions .chatgpt-route-action.is-primary {
  border-color: var(--cg-text);
  background: var(--cg-text);
  color: var(--cg-surface);
}

body.chatgpt-reference-v1 .permission-state-actions .chatgpt-route-action.is-primary:hover,
body.chatgpt-reference-v1 .permission-state-actions .chatgpt-route-action.is-primary:focus-visible {
  background: var(--cg-text);
  opacity: 0.86;
}

/* 「다른 계정으로 로그인」은 조용한 3차 액션이다 — 로그인 경로를 없애지 않되
   1차로 올리지 않는다. 1차로 올리면 지금 고치는 그 오해가 그대로 돌아온다. */
body.chatgpt-reference-v1 .permission-state-actions .chatgpt-route-action.is-quiet {
  border-color: transparent;
  background: none;
  color: var(--cg-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
}

body.chatgpt-reference-v1 .permission-state-actions .chatgpt-route-action.is-quiet:hover,
body.chatgpt-reference-v1 .permission-state-actions .chatgpt-route-action.is-quiet:focus-visible {
  background: var(--cg-hover);
  color: var(--cg-text);
}

body.chatgpt-reference-v1 .permission-state-actions .chatgpt-route-action:focus-visible,
body.chatgpt-reference-v1 .permission-inline .chatgpt-route-action:focus-visible,
body.chatgpt-reference-v1 .permission-toast button:focus-visible {
  outline: 2px solid var(--cg-text);
  outline-offset: 2px;
}

/* ── 표면 B · 인라인 ── 뒤의 목록을 지우지 않는다. 배너만 얹는다. */
body.chatgpt-reference-v1 .permission-inline {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
  padding: 12px 14px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-sm, 10px);
  background: var(--cg-hover);
}

body.chatgpt-reference-v1 .permission-inline .permission-state-icon {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  margin-bottom: 0;
  border-color: transparent;
}

body.chatgpt-reference-v1 .permission-inline .permission-state-icon svg { width: 18px; height: 18px; }

body.chatgpt-reference-v1 .permission-inline-copy {
  display: grid;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}

body.chatgpt-reference-v1 .permission-inline-copy strong {
  color: var(--cg-text);
  font-size: var(--ds-text-body-14);
  font-weight: 500;
}

body.chatgpt-reference-v1 .permission-inline-copy span {
  color: var(--cg-muted);
  font-size: var(--ds-text-caption-13);
}

body.chatgpt-reference-v1 .permission-inline .chatgpt-route-action { flex: 0 0 auto; }

/* ── 표면 C · 토스트 ── 한 문장 + 액션 하나. 계정·2차 액션은 넣지 않는다. */
.permission-toast-host {
  position: fixed;
  right: 16px;
  bottom: 16px;
  left: auto;
  display: grid;
  justify-items: end;
  pointer-events: none;
  z-index: var(--wds-z-toast, 1400);
}

.permission-toast {
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: min(92vw, 460px);
  padding: 11px 14px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-sm, 10px);
  background: var(--cg-surface);
  color: var(--cg-text);
  box-shadow: var(--cg-shadow-float-lg);
  font-size: var(--ds-text-caption-13);
  pointer-events: auto;
}

.permission-toast button {
  flex: 0 0 auto;
  min-height: 34px;
  padding: 6px 10px;
  border: 1px solid var(--ds-color-line);
  border-radius: var(--ds-radius-xs, 8px);
  background: var(--cg-surface);
  color: var(--cg-text);
  font: inherit;
  font-size: var(--ds-text-caption-13);
  cursor: pointer;
}

.permission-toast button:hover { background: var(--cg-hover); }

/* 좁은 화면에서는 토스트가 가로를 채우고, 액션이 아래로 내려간다. 44px 터치 슬롭은
   기존 20260803-touch-target-44 규칙을 그대로 탄다.
   toast-above-composer(09-25): 컴포저(#chatForm[data-composer-root])가 화면 바닥에 붙어
   있는 화면에서는 기존 고정 16px 이 토스트를 컴포저 속에 깔았다(중앙 iOS 시뮬 09-24 실측:
   토스트 782~828 vs 컴포저 728~834). --wizon-toast-composer-gap 은 그 컴포저의 실제 렌더
   top 에서 8px 띄운 값이고(키보드 인셋까지 이미 반영됨 — app.js syncToastComposerGap 주석
   참고), 토스트를 띄울 때마다 갱신된다. 컴포저가 없는 화면(로그인·문서 뷰어 등)은 gap 이
   0 이라 max() 가 기존 16px 로 되돌아간다. */
@media (max-width: 520px) {
  .permission-toast-host {
    right: 12px;
    left: 12px;
    justify-items: stretch;
    bottom: max(16px, var(--wizon-toast-composer-gap, 0px));
  }
  .permission-toast { flex-wrap: wrap; max-width: none; }
  .permission-toast button { flex: 1 1 auto; }
  body.chatgpt-reference-v1 .permission-inline { flex-wrap: wrap; }
  body.chatgpt-reference-v1 .permission-inline .chatgpt-route-action { flex: 1 1 auto; }
  body.chatgpt-reference-v1 .permission-state-actions .chatgpt-route-action { flex: 1 1 auto; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   CPQ 공수 기준표 관리 «화면» (doc 505 C 트랙) — cpq-dash 위젯과 같은 시각 언어:
   틴트 캔버스 위에 그림자로 뜬 카드, --wds 시맨틱 토큰 별칭(--cpqp-*), tabular-nums,
   히어로 title ↔ 캡션 label 의 크기 대비. 상태(대기/승인/반려)는 색으로 한눈에 읽힌다.
   ═══════════════════════════════════════════════════════════════════════════ */
.cpq-page {
  --cpqp-canvas: var(--wds-color-surface-muted);
  --cpqp-card: var(--wds-color-surface);
  --cpqp-ink: var(--wds-color-text);
  --cpqp-ink-2: var(--wds-color-text-muted);
  --cpqp-muted: var(--wds-color-text-subtle);
  --cpqp-hair: var(--wds-color-border-subtle);
  --cpqp-line: var(--wds-color-border);
  --cpqp-accent: var(--wds-color-primary);
  --cpqp-accent-ink: var(--wds-color-primary-text);
  --cpqp-on-accent: var(--wds-color-on-primary);
  --cpqp-ok-fg: var(--wds-color-success-text);
  --cpqp-ok-bg: var(--wds-color-success-container);
  --cpqp-warn-fg: var(--wds-color-warning-text);
  --cpqp-warn-bg: var(--wds-color-warning-container);
  --cpqp-err-fg: var(--wds-color-error-text);
  --cpqp-err-bg: var(--wds-color-error-container);
  --cpqp-gone-fg: var(--wds-color-text-subtle);
  --cpqp-gone-bg: var(--wds-color-surface-muted);
  /* 견적 이력의 «개정»과 «복원»은 성공도 경고도 아니다 — 정보/태그 축을 따로 빌린다.
     색만 다른 게 아니라 뜻이 다르므로 ok/warn 을 재사용하지 않는다. */
  --cpqp-info-fg: var(--wds-color-info-text);
  --cpqp-info-bg: var(--wds-color-info-container);
  --cpqp-back-fg: var(--wds-color-tag-violet-text);
  --cpqp-back-bg: var(--wds-color-tag-violet-bg);
  --cpqp-shadow: var(--ds-shadow-xs);
  display: block; background: var(--cpqp-canvas); color: var(--cpqp-ink);
  border-radius: var(--wizon-ai-radius-sheet); padding: clamp(16px, 2vw, 28px);
  font-size: var(--wds-font-size-body-s); line-height: 1.5; container-type: inline-size;
}
.cpq-page-head { display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; margin-bottom: 22px; }
.cpq-page-head-text { max-width: 62ch; }
.cpq-eyebrow { font-size: var(--wds-font-size-label-s); font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--cpqp-accent-ink); }
.cpq-title { font-size: var(--wds-font-size-h1); font-weight: 600; letter-spacing: -0.02em;
  line-height: 1.1; margin: 6px 0 8px; color: var(--cpqp-ink); }
.cpq-lead { font-size: var(--wds-font-size-body-s); color: var(--cpqp-ink-2); margin: 0; }
.cpq-rolechip { display: inline-flex; align-items: center; gap: 6px; margin-top: 12px;
  font-size: var(--wds-font-size-label-s); font-weight: 600; padding: 4px 10px;
  border-radius: var(--wizon-ai-radius-pill); color: var(--cpqp-muted);
  background: var(--cpqp-card); box-shadow: inset 0 0 0 1px var(--cpqp-line); }
.cpq-page-acts { display: flex; gap: 10px; flex-wrap: wrap; }

/* ── 버튼: 채운 accent / 고스트 / 승인·반려 ── */
.cpq-btn { font-family: inherit; font-size: var(--wds-font-size-body-s); font-weight: 600;
  border: 0; border-radius: var(--wizon-ai-radius-control); padding: 9px 16px; cursor: pointer;
  transition: transform 120ms ease, box-shadow 160ms ease, background 160ms ease, opacity 160ms ease; }
.cpq-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.cpq-btn:not(:disabled):active { transform: translateY(1px); }
.cpq-btn:focus-visible { outline: 2px solid var(--cpqp-accent); outline-offset: 2px; }
.cpq-btn--primary { background: var(--cpqp-accent); color: var(--cpqp-on-accent);
  box-shadow: var(--cpqp-shadow); }
.cpq-btn--primary:not(:disabled):hover { box-shadow: 0 4px 14px -4px var(--cpqp-accent); }
.cpq-btn--ghost { background: var(--cpqp-card); color: var(--cpqp-ink);
  box-shadow: inset 0 0 0 1px var(--cpqp-line); }
.cpq-btn--ghost:not(:disabled):hover { box-shadow: inset 0 0 0 1px var(--cpqp-accent);
  color: var(--cpqp-accent-ink); }
.cpq-btn--approve { background: var(--cpqp-ok-bg); color: var(--cpqp-ok-fg); }
.cpq-btn--approve:not(:disabled):hover { box-shadow: inset 0 0 0 1px var(--cpqp-ok-fg); }
.cpq-btn--reject { background: var(--cpqp-card); color: var(--cpqp-err-fg);
  box-shadow: inset 0 0 0 1px var(--cpqp-line); }
.cpq-btn--reject:not(:disabled):hover { box-shadow: inset 0 0 0 1px var(--cpqp-err-fg); }

/* ── 알림 배너 ── */
.cpq-banner { border-radius: var(--wizon-ai-radius-control); padding: 12px 16px; margin-bottom: 16px;
  font-size: var(--wds-font-size-body-s); font-weight: 500; }
.cpq-banner--ok { background: var(--cpqp-ok-bg); color: var(--cpqp-ok-fg); }
.cpq-banner--err { background: var(--cpqp-err-bg); color: var(--cpqp-err-fg); }

/* ── 제안 폼 (카드) ── */
.cpq-form { background: var(--cpqp-card); border-radius: var(--wizon-ai-radius-popover);
  box-shadow: var(--cpqp-shadow); padding: 20px; margin-bottom: 22px; }
.cpq-form-head { margin-bottom: 16px; }
.cpq-form-title { font-size: var(--wds-font-size-body-2); font-weight: 600; margin: 0 0 4px; }
.cpq-form-sub { font-size: var(--wds-font-size-caption); color: var(--cpqp-ink-2); margin: 0; }
.cpq-form-hint { font-size: var(--wds-font-size-caption); color: var(--cpqp-warn-fg); margin: 0 0 12px; }
.cpq-form-err { font-size: var(--wds-font-size-caption); color: var(--cpqp-err-fg); font-weight: 600;
  margin: 4px 0 0; }
.cpq-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
@container (max-width: 560px) { .cpq-form-grid { grid-template-columns: 1fr; } }
.cpq-field { display: flex; flex-direction: column; gap: 6px; }
.cpq-field--full { margin-top: 14px; }
.cpq-field--preview { justify-content: flex-start; }
.cpq-label { font-size: var(--wds-font-size-label-s); font-weight: 600; color: var(--cpqp-ink-2); }
.cpq-input { font-family: inherit; font-size: var(--wds-font-size-body-s); color: var(--cpqp-ink);
  background: var(--cpqp-canvas); border: 0; box-shadow: inset 0 0 0 1px var(--cpqp-line);
  border-radius: var(--wizon-ai-radius-control); padding: 9px 12px; }
.cpq-input:focus-visible { outline: 0; box-shadow: inset 0 0 0 2px var(--cpqp-accent); }
.cpq-textarea { resize: vertical; min-height: 44px; }
.cpq-current { font-size: var(--wds-font-size-body-s); color: var(--cpqp-ink-2);
  font-variant-numeric: tabular-nums; padding: 9px 0; align-self: flex-start; }
.cpq-form-acts { display: flex; gap: 10px; margin-top: 18px; }

/* ── 섹션 ── */
.cpq-section { margin-bottom: 26px; }
.cpq-section-title { font-size: var(--wds-font-size-body-2); font-weight: 600; margin: 0 0 12px;
  display: flex; align-items: center; gap: 8px; }
.cpq-count { font-size: var(--wds-font-size-label-s); font-weight: 600; color: var(--cpqp-warn-fg);
  background: var(--cpqp-warn-bg); border-radius: var(--wizon-ai-radius-pill);
  padding: 2px 9px; font-variant-numeric: tabular-nums; }

/* ── 제안 카드 (좌측 상태 스트립으로 색을 한눈에) ── */
.cpq-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
.cpq-card { position: relative; background: var(--cpqp-card); border-radius: var(--wizon-ai-radius-popover);
  box-shadow: var(--cpqp-shadow); padding: 16px 18px 16px 20px; overflow: hidden; }
.cpq-card::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 4px; background: var(--cpqp-line); }
.cpq-card--pending::before { background: var(--cpqp-warn-fg); }
.cpq-card--ok::before { background: var(--cpqp-ok-fg); }
.cpq-card--gone::before { background: var(--cpqp-gone-fg); }
.cpq-card-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.cpq-cardid { font-size: var(--wds-font-size-label-s); font-weight: 600; color: var(--cpqp-muted);
  font-variant-numeric: tabular-nums; }
.cpq-chip { font-size: var(--wds-font-size-label-s); font-weight: 600; padding: 3px 10px;
  border-radius: var(--wizon-ai-radius-pill); }
.cpq-chip--pending { background: var(--cpqp-warn-bg); color: var(--cpqp-warn-fg); }
.cpq-chip--ok { background: var(--cpqp-ok-bg); color: var(--cpqp-ok-fg); }
.cpq-chip--gone { background: var(--cpqp-gone-bg); color: var(--cpqp-gone-fg); }
.cpq-card-cell { font-size: var(--wds-font-size-body-s); font-weight: 600; margin: 12px 0 8px;
  color: var(--cpqp-ink); }
.cpq-card-change { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-variant-numeric: tabular-nums; }
.cpq-md { font-size: var(--wds-font-size-body-s); }
.cpq-md--old { color: var(--cpqp-muted); }
.cpq-md--new { font-weight: 600; color: var(--cpqp-ink); font-size: var(--wds-font-size-body-2); }
.cpq-arrow { color: var(--cpqp-muted); }
.cpq-delta { font-size: var(--wds-font-size-label-s); font-weight: 600; padding: 2px 8px;
  border-radius: var(--wizon-ai-radius-pill); }
.cpq-delta--up { background: var(--cpqp-err-bg); color: var(--cpqp-err-fg); }
.cpq-delta--down { background: var(--cpqp-ok-bg); color: var(--cpqp-ok-fg); }
.cpq-delta--flat { background: var(--cpqp-gone-bg); color: var(--cpqp-gone-fg); }
.cpq-card-meta { font-size: var(--wds-font-size-caption); color: var(--cpqp-muted); margin-top: 10px;
  font-variant-numeric: tabular-nums; }
.cpq-note { font-size: var(--wds-font-size-caption); color: var(--cpqp-ink-2); margin: 8px 0 0;
  padding-left: 10px; box-shadow: inset 2px 0 0 var(--cpqp-hair); }
.cpq-card-acts { display: flex; gap: 8px; margin-top: 14px; }
.cpq-await, .cpq-applied { display: inline-block; margin-top: 14px; font-size: var(--wds-font-size-label-s);
  font-weight: 600; color: var(--cpqp-muted); }
.cpq-applied { color: var(--cpqp-ok-fg); }

/* ── 현재 기준표 그리드 (가로 스크롤은 자기 안에서) ── */
.cpq-grid-wrap { overflow-x: auto; background: var(--cpqp-card); border-radius: var(--wizon-ai-radius-popover);
  box-shadow: var(--cpqp-shadow); padding: 6px; }
.cpq-grid { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }
.cpq-grid-cap { caption-side: bottom; text-align: left; font-size: var(--wds-font-size-caption);
  color: var(--cpqp-muted); padding: 10px 12px; }
.cpq-grid th, .cpq-grid td { padding: 4px; text-align: center; }
.cpq-grid thead th { font-size: var(--wds-font-size-label-s); font-weight: 600; color: var(--cpqp-ink-2);
  padding: 10px 8px; }
.cpq-grid tbody th[scope="row"] { text-align: left; font-size: var(--wds-font-size-caption);
  font-weight: 600; color: var(--cpqp-ink); padding: 4px 12px 4px 8px; white-space: nowrap; }
.cpq-cell { display: block; width: 100%; min-width: 56px; font-family: inherit;
  font-size: var(--wds-font-size-body-s); color: var(--cpqp-ink); background: var(--cpqp-canvas);
  border: 0; border-radius: var(--wizon-ai-radius-control); padding: 10px 6px; cursor: pointer;
  transition: box-shadow 140ms ease, color 140ms ease; }
.cpq-cell:hover { box-shadow: inset 0 0 0 1px var(--cpqp-accent); color: var(--cpqp-accent-ink); }
.cpq-cell:focus-visible { outline: 2px solid var(--cpqp-accent); outline-offset: 1px; }
.cpq-cell--static { cursor: default; background: transparent; }
.cpq-cell--static:hover { box-shadow: none; color: var(--cpqp-ink); }

/* ── 빈/로딩 상태 ── */
.cpq-empty { background: var(--cpqp-card); border-radius: var(--wizon-ai-radius-popover);
  box-shadow: var(--cpqp-shadow); padding: 22px; text-align: center; color: var(--cpqp-muted);
  font-size: var(--wds-font-size-body-s); }
.cpq-empty--soft { box-shadow: inset 0 0 0 1px var(--cpqp-hair); background: transparent; }
.cpq-skeleton { display: grid; gap: 14px; }
.cpq-skeleton span { display: block; height: 92px; border-radius: var(--wizon-ai-radius-popover);
  background: linear-gradient(100deg, var(--cpqp-card) 30%, var(--cpqp-canvas) 50%, var(--cpqp-card) 70%);
  background-size: 200% 100%; animation: cpq-shimmer 1.3s ease-in-out infinite; }
@keyframes cpq-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .cpq-skeleton span { animation: none; } }
/* ── 견적서 목록·조회 (doc 505 C 트랙) ──
   같은 시각 언어를 그대로 잇는다: 틴트 캔버스 위의 카드, 왼쪽 4px 상태 레일,
   tabular-nums. ★상태를 «색으로만» 말하지 않는다 — 레일(형태) + 칩(글자) + 배지가
   같은 사실을 세 번 겹쳐 말하므로 색을 못 보는 사용자도 읽는다. */
.cpq-band { font-size: var(--wds-font-size-body-2); font-weight: 600; color: var(--cpqp-ink);
  margin: 34px 0 14px; padding-top: 18px; box-shadow: inset 0 1px 0 var(--cpqp-hair); }
.cpq-section--quotes { margin-bottom: 8px; }

.cpq-qbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
/* ★N3(doc 655 §6.1)가 4칸을 6칸(수주·실주 추가)으로 늘리며 375px 실측에서 드러난 문제:
   .cpq-qseg-btn 은 기본 flex-shrink:1 이고 white-space 도 기본값(normal)이라, 6칸이
   부모 폭을 넘으면 «가로 오버플로»가 아니라 **버튼이 짜부러지며 한글이 음절 사이에서
   줄바꿈**된다(「전체」가 「전\n체」로 — CJK 는 word-break:normal 이 글자 사이 줄바꿈을
   허용한다). 스크롤 칩 대신 2줄 감싸기를 골랐다: 가로 스크롤은 375px 화면에서 뒤 칸이
   화면 밖에 있다는 단서가 «없다»(discoverability 0) — 감싸기는 6칸이 항상 다 보인다. */
.cpq-qseg { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px; background: var(--cpqp-card);
  border-radius: var(--wizon-ai-radius-pill); box-shadow: inset 0 0 0 1px var(--cpqp-hair); }
.cpq-qseg-btn { font-family: inherit; font-size: var(--wds-font-size-label-s); font-weight: 600;
  display: inline-flex; align-items: center; gap: 5px; border: 0; cursor: pointer;
  background: none; color: var(--cpqp-ink-2); padding: 5px 13px; flex-shrink: 0; white-space: nowrap;
  border-radius: var(--wizon-ai-radius-pill); transition: background 120ms ease, color 120ms ease; }
.cpq-qseg-btn:hover { color: var(--cpqp-ink); }
.cpq-qseg-btn:focus-visible { outline: 2px solid var(--cpqp-accent); outline-offset: 2px; }
.cpq-qseg-btn--on { background: var(--cpqp-ink); color: var(--cpqp-card); }
.cpq-qseg-n { font-style: normal; font-size: var(--wds-font-size-label-s); opacity: 0.65;
  font-variant-numeric: tabular-nums; }
.cpq-qperiod { font-size: var(--wds-font-size-label-s); color: var(--cpqp-muted);
  font-variant-numeric: tabular-nums; margin-right: auto; }

/* ── N7 현황 요약(doc 655 §N7) — 기간·타일 4·상태 분포 1. .cpq-dash 이하는
   위젯(cpq_dashboard) 팔레트를 재사용한다(renderCpqDashDonut 재사용) — 새 색을
   만들지 않는다. */
.cpq-section--dash-summary { margin-bottom: 22px; }
.cpq-dcustom { display: flex; align-items: center; gap: 8px; margin: -4px 0 14px; }
.cpq-dcustom-input { max-width: 152px; }
.cpq-dcustom-sep { color: var(--cpqp-muted); font-size: var(--wds-font-size-label-s); }
.cpq-dtiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 16px; }
@media (max-width: 520px) { .cpq-dtiles { grid-template-columns: repeat(2, 1fr); } }
.cpq-dtile { background: var(--cpqp-card); border-radius: var(--wizon-ai-radius-popover);
  box-shadow: var(--cpqp-shadow); padding: 14px 16px; display: flex; flex-direction: column; gap: 4px; }
.cpq-dtile-label { font-size: var(--wds-font-size-label-s); color: var(--cpqp-muted); font-weight: 600; }
.cpq-dtile-value { font-size: var(--wds-font-size-h3); font-weight: 600; color: var(--cpqp-ink);
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.cpq-dtile-sub { font-size: var(--wds-font-size-label-s); color: var(--cpqp-muted); }

/* 견적 검색(doc 655 §6.2 N2). ★.cpq-input 을 그대로 쓴다 — 새 팔레트를 만들지 않는다. */
.cpq-qsearch { position: relative; flex: 1 1 180px; min-width: 140px; }
.cpq-qsearch-input { width: 100%; padding-right: 30px; }
.cpq-qsearch-clear { position: absolute; top: 50%; right: 4px; transform: translateY(-50%);
  width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--wizon-ai-radius-pill); background: none; cursor: pointer;
  font-size: var(--wds-font-size-label-s); color: var(--cpqp-muted); line-height: 1; }
.cpq-qsearch-clear:hover { color: var(--cpqp-ink); background: var(--cpqp-canvas); }
.cpq-qsearch-clear:focus-visible { outline: 2px solid var(--cpqp-accent); outline-offset: 1px; }

.cpq-qlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.cpq-qitem { position: relative; background: var(--cpqp-card); overflow: hidden;
  border-radius: var(--wizon-ai-radius-popover); box-shadow: var(--cpqp-shadow); }
.cpq-qitem::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 4px;
  background: var(--cpqp-line); }
.cpq-qitem:has(.cpq-qchip--gone)::before { background: var(--cpqp-gone-fg); }
.cpq-qitem:has(.cpq-qchip--amend)::before { background: var(--cpqp-info-fg); }
.cpq-qitem:has(.cpq-qchip--back)::before { background: var(--cpqp-back-fg); }
.cpq-qitem:has(.cpq-qchip--won)::before { background: var(--cpqp-ok-fg); }
.cpq-qitem:has(.cpq-qchip--lost)::before { background: var(--cpqp-err-fg); }

/* 행은 «진짜 button» 이다 — div+click 이면 키보드로 본문을 영영 못 연다. */
.cpq-qrow { display: grid; grid-template-columns: 38px minmax(0, 1fr) auto; gap: 12px;
  align-items: center; width: 100%; text-align: left; font-family: inherit; border: 0;
  background: none; cursor: pointer; padding: 12px 16px 12px 20px; color: var(--cpqp-ink);
  transition: background 120ms ease; }
.cpq-qrow:hover { background: var(--cpqp-canvas); }
.cpq-qrow:focus-visible { outline: 2px solid var(--cpqp-accent); outline-offset: -2px; }
.cpq-qitem--open .cpq-qrow { background: var(--cpqp-canvas); }
.cpq-qkind { display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: var(--wizon-ai-radius-menu-row);
  font-size: var(--wds-font-size-label-s); font-weight: 600; letter-spacing: 0.02em; }
.cpq-qkind--hw { background: var(--cpqp-info-bg); color: var(--cpqp-info-fg); }
.cpq-qkind--sw { background: var(--cpqp-ok-bg); color: var(--cpqp-ok-fg); }
.cpq-qmid { min-width: 0; display: block; }
.cpq-qno { display: block; font-size: var(--wds-font-size-body-s); font-weight: 600;
  letter-spacing: -0.01em; font-variant-numeric: tabular-nums; color: var(--cpqp-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cpq-qmeta { display: block; margin-top: 2px; font-size: var(--wds-font-size-label-s);
  color: var(--cpqp-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cpq-qsup { margin-left: 6px; color: var(--cpqp-info-fg); font-weight: 600; }
.cpq-qexp { color: var(--cpqp-warn-fg); font-weight: 600; }
.cpq-qend { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
.cpq-qamt { font-size: var(--wds-font-size-body-s); font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--cpqp-ink); white-space: nowrap; }
.cpq-qamt--unk { font-size: var(--wds-font-size-label-s); font-weight: 400;
  color: var(--cpqp-muted); }
.cpq-qchip { font-size: var(--wds-font-size-label-s); font-weight: 600; padding: 3px 10px;
  border-radius: var(--wizon-ai-radius-pill); white-space: nowrap;
  background: var(--cpqp-gone-bg); color: var(--cpqp-muted); }
.cpq-qchip--ok { background: var(--cpqp-ok-bg); color: var(--cpqp-ok-fg); }
.cpq-qchip--amend { background: var(--cpqp-info-bg); color: var(--cpqp-info-fg); }
/* ★철회 칩의 바탕(--cpqp-gone-bg)은 캔버스와 «같은 토큰»이다 — 펼친 행은 배경이
   캔버스로 바뀌므로 칩이 통째로 사라진다(1440 실측). 다른 칩과 달리 테두리를 준다. */
.cpq-qchip--gone { background: var(--cpqp-gone-bg); color: var(--cpqp-gone-fg);
  box-shadow: inset 0 0 0 1px var(--cpqp-line); }
.cpq-qchip--back { background: var(--cpqp-back-bg); color: var(--cpqp-back-fg); }
.cpq-qchip--won { background: var(--cpqp-ok-bg); color: var(--cpqp-ok-fg); }
.cpq-qchip--lost { background: var(--cpqp-err-bg); color: var(--cpqp-err-fg); }

/* ── 거래처 검토 화면(038) — 견적 칩과 같은 상자, 새 팔레트만 더한다 ── */
.cpq-qchip--needs_review { background: var(--cpqp-back-bg); color: var(--cpqp-back-fg); }
.cpq-qchip--auto { background: var(--cpqp-info-bg); color: var(--cpqp-info-fg); }
.cpq-qchip--confirmed { background: var(--cpqp-ok-bg); color: var(--cpqp-ok-fg); }
.cpq-qchip--self { background: var(--cpqp-ink); color: var(--cpqp-card); margin-left: 6px; }
.cpq-qchip--reason, .cpq-qchip--kind {
  background: var(--cpqp-gone-bg); color: var(--cpqp-muted); margin-left: 6px;
  box-shadow: inset 0 0 0 1px var(--cpqp-line);
}
/* 필터 칩은 버튼이라 상태(선택 여부)가 있다 — cpq-qseg-btn 과 같은 on/off 규칙. */
.cpq-qchip--filter { font-family: inherit; border: 0; cursor: pointer;
  background: var(--cpqp-card); color: var(--cpqp-muted);
  box-shadow: inset 0 0 0 1px var(--cpqp-line); }
.cpq-qchip--filter:hover { color: var(--cpqp-ink); }
.cpq-qchip--filter:focus-visible { outline: 2px solid var(--cpqp-accent); outline-offset: 2px; }
.cpq-qchip--filter.cpq-qchip--on { background: var(--cpqp-ink); color: var(--cpqp-card);
  box-shadow: none; }
.cpq-p-reasons { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.cpq-p-search-form { display: contents; }
.cpq-p-search-form .cpq-input { min-width: 160px; flex: 1 1 160px; }

/* ── 펼친 본문: 캔버스 위에 얹힌 안쪽 판 ── */
.cpq-qdetail { background: var(--cpqp-canvas); padding: 4px 18px 18px 20px;
  box-shadow: inset 0 1px 0 var(--cpqp-hair); }
.cpq-qfacts { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px 18px; margin: 14px 0 4px; }
.cpq-qfact dt { font-size: var(--wds-font-size-label-s); color: var(--cpqp-muted); }
.cpq-qfact dd { margin: 2px 0 0; font-size: var(--wds-font-size-body-s); font-weight: 600;
  color: var(--cpqp-ink); font-variant-numeric: tabular-nums; }
.cpq-qsub-title { font-size: var(--wds-font-size-label-s); font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--cpqp-muted);
  margin: 20px 0 8px; }
.cpq-qlines th, .cpq-qlines td { text-align: right; white-space: nowrap; }
.cpq-qlines thead th { text-align: right; }
.cpq-qlines thead th:first-child, .cpq-qlines tbody th[scope="row"] { text-align: left;
  white-space: normal; }
.cpq-qline-sec { display: block; font-style: normal; font-size: var(--wds-font-size-label-s);
  font-weight: 600; letter-spacing: 0.04em; color: var(--cpqp-accent-ink); }
.cpq-qline-sub { display: block; font-style: normal; font-weight: 400;
  font-size: var(--wds-font-size-label-s); color: var(--cpqp-muted); }

/* 세로 커넥터 + 사건별 색점. 점의 «색»이 사건 종류를 인코딩하지만 라벨이 바로 옆에
   글자로 있으므로, 색을 못 봐도 무엇이 있었는지 읽힌다. */
.cpq-qtl { list-style: none; margin: 0; padding: 0 0 0 18px; position: relative; }
.cpq-qtl::before { content: ""; position: absolute; left: 4px; top: 6px; bottom: 6px;
  width: 1px; background: var(--cpqp-hair); }
.cpq-qtl-item { position: relative; padding: 0 0 14px; }
.cpq-qtl-item:last-child { padding-bottom: 0; }
.cpq-qtl-dot { position: absolute; left: -18px; top: 5px; width: 9px; height: 9px;
  border-radius: var(--wizon-ai-radius-pill); background: var(--cpqp-line);
  box-shadow: 0 0 0 3px var(--cpqp-canvas); }
.cpq-qtl-item--ok .cpq-qtl-dot, .cpq-qtl-item--won .cpq-qtl-dot { background: var(--cpqp-ok-fg); }
.cpq-qtl-item--amend .cpq-qtl-dot { background: var(--cpqp-info-fg); }
.cpq-qtl-item--gone .cpq-qtl-dot { background: var(--cpqp-gone-fg); }
.cpq-qtl-item--back .cpq-qtl-dot { background: var(--cpqp-back-fg); }
.cpq-qtl-item--lost .cpq-qtl-dot { background: var(--cpqp-err-fg); }
.cpq-qtl-head { display: block; font-size: var(--wds-font-size-body-s); font-weight: 600;
  color: var(--cpqp-ink); }
.cpq-qtl-rev { font-style: normal; margin-left: 8px; font-weight: 400;
  font-size: var(--wds-font-size-label-s); color: var(--cpqp-muted);
  font-variant-numeric: tabular-nums; }
.cpq-qtl-meta { display: block; margin-top: 1px; font-size: var(--wds-font-size-label-s);
  color: var(--cpqp-muted); }
.cpq-qtl-note { margin: 6px 0 0; padding-left: 10px; font-size: var(--wds-font-size-caption);
  color: var(--cpqp-ink-2); box-shadow: inset 2px 0 0 var(--cpqp-hair); }
.cpq-qacts { display: flex; gap: 10px; align-items: center; margin-top: 20px;
  padding-top: 14px; box-shadow: inset 0 1px 0 var(--cpqp-hair); }
.cpq-qempty { margin: 0; font-size: var(--wds-font-size-caption); color: var(--cpqp-muted); }
.cpq-qtrunc { margin: 10px 0 0; font-size: var(--wds-font-size-caption);
  color: var(--cpqp-muted); }

@container (max-width: 560px) {
  .cpq-page-head { flex-direction: column; align-items: stretch; }
  .cpq-cards { grid-template-columns: 1fr; }
  .cpq-qrow { grid-template-columns: 30px minmax(0, 1fr); row-gap: 8px; }
  .cpq-qkind { width: 30px; height: 30px; }
  .cpq-qend { grid-column: 1 / -1; flex-direction: row; align-items: center;
    justify-content: space-between; }
  .cpq-qperiod { width: 100%; margin-right: 0; }
  /* 검색창이 세그먼트·기간과 한 줄을 다투면 375px 에서 입력칸이 너무 좁아진다 —
     자기 줄로 내린다. */
  .cpq-qsearch { flex-basis: 100%; min-width: 0; }
}
/* ── Multi-security finance group (MS-A1+MS-A2) ───────────────────────────────
   The selected comparison structure is adapted to the current single-stock family:
   one theme surface, large equal quote summaries, one normalized chart, period tabs,
   then data-backed metrics and sources. Series hue/dash identify companies; separate
   signed words and semantic colors communicate price direction. */
.finance-group-card {
  /* The selected comparison uses a red/blue company identity pair. Solid/dashed
     marks keep company identity distinct from signed daily-direction wording. */
  /* v-C D-1(★F3 수리 09-14): 강조 1선(1위, index 0)만 그 종목의 실제 등락색(--fin-up/-down,
     JS가 방향에 따라 --fin-series-0을 인라인으로 재정의). 나머지 3선은 색이 아니라 dash로만
     구분되는 무채 3단(비강조 선-선 대비 붕괴를 막는다 — 이전 값 green-700/amber-700은 유채라
     "종목마다 다른 색"으로 보여 D-1을 어겼다). */
  /* R-3 v-E′ S1(A-9d 후보): 55/50/45% — 웰(#f4f4f4/#2a2a2a) 위 대비 라이트 4.19/3.59/3.06 · 다크 4.79/4.21/3.68
     전부 ≥3:1(WCAG 1.4.11). 옛 55/38/24% 는 비강조 2·3선이 2.50/1.71 로 미달이었다. 구분은 여전히 대시 + 직접 라벨. */
  --fin-series-1: color-mix(in srgb, var(--fin-on-surface) 55%, transparent);
  --fin-series-2: color-mix(in srgb, var(--fin-on-surface) 50%, transparent);
  --fin-series-3: color-mix(in srgb, var(--fin-on-surface) 45%, transparent);
  /* R-3 v-E′ S3 — 종목별 요약 타일 가격은 값 사다리 본문 15/600 이다(옛 «컴팩트 32–36» 토큰 --fin-group-compact-price-size 는 «자세히» 지표 접이식과 함께 사라졌다 —
     한 카드에 큰 숫자 = 히어로 1개). 아래 .fin-group-item > * 블록이 타일 타이포를 값 사다리로 정한다. */
  --fin-group-item-surface: color-mix(in srgb, var(--fin-on-surface) 1%, transparent);
  /* S2h R10 — S2g review report §A-3: this card's panel/button radii measured 4 distinct
     values (20 card, 14 quote tile, 12 details panel/empty-state, 8 badge/pill/chip)
     against the ≤2 target. The outer card keeps its own --wds-radius-xl (20, the one
     "outer boundary" tier); every nested panel/control below folds onto this single
     --ds-radius-sm (10, already the value .fin-vc-rank's own frame and .fin-vc-row's
     first/last corners use) instead of each keeping its own literal — one shared token
     both empty-state and .fin-data-panel below reference, so a future third value here
     cannot reintroduce a silent third radius. */
  --fin-group-empty-radius: var(--ds-radius-sm);
  /* R-3 v-E′ S3 — 필·직접 라벨 칩 반경 = 8(값 사다리 «필·썸·칩 8»). 옛 10 은 트랙·타일·버튼 몫이다. */
  --fin-badge-radius: var(--wizon-ai-radius-control);
  --fin-group-muted-key-shadow: inset 0 0 0 2px currentColor;
  container-type: inline-size;
  container-name: finance-group;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--wds-space-4);
  box-sizing: border-box;
  min-width: 0;
  max-width: 100%;
  width: min(100%, 860px);
  /* R-3 v-E′ S3 — 위젯 외곽 패딩 = 단일 카드와 같다(웹 20 / 모바일 16, 값 사다리). 외곽 반경 20(결정 8) 유지. */
  padding: 20px;
  border: 0;
  border-radius: var(--wds-radius-xl);
}
@media (max-width: 520px) {
  .finance-group-card {
    padding: 16px;
  }
}
.fin-group-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.finance-surface.finance-group-card .fin-group-title { margin: 0; font-size: var(--fin-type-body); font-weight: 600; line-height: 1.3; }
/* S2b §3 — «함께 비교하기» demoted to an eyebrow (12px, muted, uppercase-free) once the
   v-C headline (.fin-vc-title) exists, so the header reads as context, not the answer. */
.finance-group-card[data-mode="normalized_comparison"] .fin-group-title {
  font-size: var(--fin-type-eyebrow); font-weight: 600; color: var(--fin-muted); line-height: 1.35;
}
.finance-surface.finance-group-card .fin-group-eyebrow { color: var(--fin-muted); font-size: var(--fin-type-meta); font-weight: 600; }
.fin-group-count { flex: none; color: var(--fin-muted);
  font-size: var(--fin-type-meta); font-variant-numeric: tabular-nums; }
/* R-3 v-E′ S3 — 알림 줄(웰 면): 지연 시세 안내 · 비교 불가 · 부분 확인(제외 종목) · 조회 실패가 같은 외곽 안에서 한 문법을 쓴다 —
   웰 면(L2) + 1px 웰 선 + 반경 10 + 패딩 12 + 본문 13. 옛 «왼쪽 파란 띠 + 정보 컨테이너 틴트» 는 폐기(상태 색을 면에 칠하지 않는다). */
.finance-surface.finance-group-card .fin-group-banner, .finance-surface.finance-group-card .fin-group-notice,
.finance-surface.finance-group-card .fin-group-unavailable, .finance-surface.finance-group-card .fin-vc-failed {
  margin: 0; padding: 12px; border-radius: var(--ds-radius-sm); background: var(--fin-well);
  box-shadow: inset 0 0 0 1px var(--fin-well-line); color: var(--fin-on-surface); font-size: var(--fin-type-meta); line-height: 1.4; }
.finance-surface.finance-group-card .fin-group-notice b { display: block; margin-bottom: 4px; font-size: var(--fin-type-body); font-weight: 600; }
.fin-group-notice ul { margin: 0; padding-left: 18px; color: var(--fin-muted); }
/* R-3 v-E′ S3 — 종목별 요약 타일 그리드(비교 불가·부분 확인 카드)는 .fin-group-cards 한 개다. 옛 .fin-group-summaries(지표 접이식 안 요약 타일 + 701px 이상
   subgrid 행 정렬)는 «자세히»가 접이식 표를 걷으며 마크업이 사라져 규칙도 걷었다(css-orphan 래칫). */
.fin-group-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.finance-group-card .fin-group-item {
  position: relative;
  min-width: 0;
  padding: 16px;
  overflow: hidden;
  /* S2h R10 — was var(--wds-radius-lg) (14, its own 3rd radius kind). See the
     --fin-group-empty-radius comment above (card gap) for the ≤2 rationale. */
  border-radius: var(--ds-radius-sm);
  background: var(--fin-group-item-surface);
  box-shadow: inset 0 0 0 1px var(--fin-panel-border);
}
/* R-3 v-E′ S3 — 종목별 요약 타일(비교 불가 · 부분 확인 카드): 값 사다리 {15,13,12} 만 쓴다. 옛 «컴팩트 가격 32» 는 히어로(40) 와 같은 숫자로
   읽혀 «한 카드에 큰 숫자가 여러 개» 가 되던 결함이라 타일 값 15/600 으로 내렸다(큰 숫자 = 히어로 1개). */
.finance-group-card .fin-group-item > * {
  --fin-price-size: var(--fin-type-body);
  --fin-identity-size: var(--fin-type-body);
  --fin-daily-size: var(--fin-type-meta);
  --fin-support-size: var(--fin-type-eyebrow);
}
.fin-group-item.is-krx-market { --fin-up: var(--wds-color-error-text); --fin-down: var(--fin-krx-down); }
.fin-group-item.is-western-market { --fin-up: var(--wds-color-success-text); --fin-down: var(--wds-color-error-text); }
.finance-group-card .fin-group-item .fin-price-row { margin-top: 10px; }
.finance-group-card .fin-group-item .fin-price { white-space: nowrap; }
.finance-group-card .fin-group-item .fin-currency { font-size: var(--fin-type-meta); }
.finance-group-card .fin-group-item .fin-provenance { gap: 6px; }
.finance-group-card .fin-group-item .fin-source { overflow-wrap: anywhere; }
.fin-group-item .fin-source a { margin-left: 5px; color: var(--wds-color-primary-text); text-underline-offset: 3px; }
.fin-group-direction { color: var(--cg-muted); font-weight: 600; }
.fin-group-direction--krx-up, .fin-group-direction--western-down { color: var(--wds-color-error-text); }
.fin-group-direction--krx-down { color: var(--fin-krx-down); }
.fin-group-direction--western-up { color: var(--wds-color-success-text); }
.fin-group-item.is-series-muted { background: color-mix(in srgb, var(--fin-on-surface) 2.5%, transparent); }
[data-fin-group-context-id].is-series-muted { color: var(--fin-muted); }
[data-fin-group-context-id].is-series-muted b,
[data-fin-group-context-id].is-series-muted strong { color: var(--fin-muted); }
.fin-group-series-controls { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px; }
.fin-group-series-toggle { display: inline-flex; align-items: center; gap: 7px; min-height: 36px; padding: 6px 8px;
  border: 0; border-radius: var(--ds-radius-sm); background: transparent;
  color: var(--fin-on-surface); font: inherit; cursor: pointer; }
.fin-group-series-toggle:hover { background: color-mix(in srgb, var(--fin-on-surface) 7%, transparent); }
.fin-group-series-toggle b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--cg-text-meta-size); }
.fin-group-series-value { display: none; margin-left: auto; color: var(--fin-muted); font-size: var(--cg-text-meta-size); font-variant-numeric: tabular-nums; white-space: nowrap; }
.fin-group-series-toggle.is-muted { color: var(--fin-on-surface); text-decoration: line-through; text-decoration-color: var(--fin-muted); }
.fin-group-series-toggle.is-muted .fin-group-series-key { background: transparent; box-shadow: var(--fin-group-muted-key-shadow); }
/* S2e §2 — web (>=720 container width) 2-column grid: rank card | chart panel.
   .fin-vc-layout wraps only [data-fin-group-rank-content]/[data-fin-group-chart-content]
   (mockup-source/card.css .wx-body: grid-template-columns:316px 1fr); the period
   segments and the details fold stay full-width siblings below (they sit outside
   .wx-body in the mockup). display:contents at narrower widths keeps the pre-S2e
   vertical stack + .finance-group-card's own gap between the two content blocks
   (S2c/S2d's :not(mobile) container width was 757px alone — 601-720 still stacks). */
.fin-vc-layout { display: contents; }
/* S2e §2 — card-height gate (<=460, mockup 435): the mockup's wx-stock uses a uniform
   12px (--s12) gap between its top-level sections; this card's default gap
   (--wds-space-4 = 16px) is the shared value every finance-group mode uses, so scope
   the tighter value to normalized_comparison only rather than lowering it globally. */
.finance-group-card[data-mode="normalized_comparison"] { gap: var(--wds-space-3); }
/* S2e §2 note — the container query below is evaluated against .finance-group-card's
   own CONTENT box (container-type: inline-size resolves against content-box per spec),
   not its outer/frame width. R-3 v-E′ S3: the card's horizontal padding is now 20px*2=40px (was 22px*2),
   so an outer card width of 716px is the first to measure 676px — the threshold is unchanged on purpose
   (회귀 0: 720 outer 는 여전히 2열). */
@container finance-group (min-width: 676px) {
  .finance-group-card[data-mode="normalized_comparison"] .fin-vc-layout {
    display: grid;
    grid-template-columns: minmax(260px, 358px) 1fr;
    align-items: stretch;
    gap: var(--wds-space-4);
  }
  .finance-group-card[data-mode="normalized_comparison"] [data-fin-group-rank-content] { min-width: 0; }
  .finance-group-card[data-mode="normalized_comparison"] [data-fin-group-chart-content] { min-width: 0; }
  /* S2e §2 — card height gate (target <=460, mockup 435). At the stacked (<676) width
     the chart still wants its own taller min-height/plot so it reads well full-width;
     once it sits beside the 224px-tall rank-4 list (align-items:stretch, mockup
     wx-chart == wx-rank height), let the grid row's own height govern instead of a
     fixed min-height, and match the mockup's plot svg height (150, viewBox 0 0 373
     150 — 1:1, vs the stacked view's taller stretch) so the two columns land at a
     comparable height rather than the chart forcing the row ~60px past the rank list. */
  .finance-group-card[data-mode="normalized_comparison"] .fin-group-chart { min-height: 0; }
  .finance-group-card[data-mode="normalized_comparison"] .fin-group-chart svg { height: 196px; }
}
/* S2e §4 — chart panel gets a filled L2 surface (mockup grammar: the chart is a "well"
   inside the card, not a bare border-top divider). --surface-2 is the repo's existing
   L2 token (tokens.css: var(--ds-color-surface-muted)) — dark resolves near black (121212 계열),
   light to a pale gray, matching the work order's target without a new raw literal. */
/* R-3 v-E′ S3 — 차트 = 단일 카드와 같은 웰(L2 면 · 1px 선 · 반경 14 · 패딩 12). 캡션 = «{선택 종목} 강조»(13/600, 강조선과 같은 등락 텍스트색) +
   «시작 = 100»(12 muted). 모바일에서 웰 배경을 걷던 옛 규칙(@container 560)은 폐기 — 두 기기 같은 문법. */
.fin-group-chart { display: grid; gap: 8px; min-width: 0; min-height: 0; padding: var(--wds-space-3); border-radius: var(--wds-radius-lg);
  background: var(--fin-well); box-shadow: inset 0 0 0 1px var(--fin-well-line); }
.fin-group-chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.fin-group-chart-title { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; width: 100%; min-width: 0; }
.fin-group-chart-title > b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--fin-muted); font-size: var(--fin-type-meta); font-weight: 600; }
.fin-group-chart[data-fin-accent="up"] .fin-group-chart-title > b { color: var(--fin-up-text); }
.fin-group-chart[data-fin-accent="down"] .fin-group-chart-title > b { color: var(--fin-down-text); }
.fin-group-chart-title > span {
  flex: none; color: var(--fin-muted); font-size: var(--fin-type-eyebrow); font-weight: 600; }
/* D-4(애플 문법): 비교 카드의 등락 필은 목업처럼 반경 8(스타디움 999가 아니다) ·
   높이 20 근사. §9.10-4 반경 사다리(≤3+예외)의 8=칩/필/세그먼트/차트라벨 통합값 —
   단일 종목 카드(.finance-widget .fin-badge)는 손대지 않는다(발주서 §1 범위 제한). */
.finance-group-card .fin-badge { border-radius: var(--fin-badge-radius, 8px); padding: 3px 10px; }
/* S2h R10 — .fin-data-panel is a shared class (also the single-stock .finance-widget's
   details fold, out of this repair's scope per §1 위 "단일 종목 카드는 손대지 않는다").
   Scoped override only, not an edit to the 12px shared rule above (test_finance_group_
   vc_contract.py's `.finance-group-card .fin-badge { border-radius: var(--fin-badge-
   radius, 8px)` string assertion also stays intact — that line is untouched, the new
   radius comes from --fin-badge-radius's own definition changing above, not from
   rewriting this selector's text). */
.finance-group-card .fin-data-panel { border-radius: var(--ds-radius-sm); }
.fin-group-item .fin-badge--flat { color: var(--fin-muted); }
.fin-group-item .fin-badge--up {
  color: var(--fin-up);
  background: color-mix(in srgb, var(--fin-up) 18%, transparent);
}
.fin-group-item .fin-badge--down {
  color: var(--fin-down);
  background: color-mix(in srgb, var(--fin-down) 18%, transparent);
}
.finance-group-card .fin-segments { margin-top: 0; }
/* S2h R8 — (옛 «카드 한정 min-height 44» 는 R-3 v-E′ S3 에서 폐기: 높이는 공통 --fin-ctl-h 가 정한다 — 웹 36, 굵은 포인터 44.) */
/* S2h R8 — was the shared `.fin-segment:disabled { opacity: .32 }` (S2g review report:
   measured 1.56:1, "안 보임" not "비활성"). Scoped to this card only: a real ink-mix token,
   the same 55%-alpha pattern this file already uses for rank 2's chart line/label
   (--fin-series-1), instead of a plain opacity fade — legible as "off" without
   pretending to clear the interactive-text 4.5 floor a disabled control is exempt from. */
.finance-group-card .fin-segment:disabled { opacity: 1; color: color-mix(in srgb, var(--fin-muted) 55%, transparent); }
.fin-group-chart svg { display: block; width: 100%; height: 150px; overflow: visible; }
/* D-1(★F3 수리): 강조선(1위/index 0) 색은 data-fin-accent(JS가 방향만 이름 붙인다)로
   CSS 가 정한다 — inline style= 대신 data 속성 + 셀렉터(이 리포의 data-mode/
   .is-krx-market 관례와 동일, anti-drift inline-style 래칫도 피한다). */
.fin-group-chart[data-fin-accent="up"] { --fin-series-0: var(--fin-up-line); }
.fin-group-chart[data-fin-accent="down"] { --fin-series-0: var(--fin-down-line); }
.fin-group-chart[data-fin-accent="flat"] { --fin-series-0: var(--fin-muted); }
.fin-group-plot { position: relative; touch-action: pan-y; }
.fin-group-direct-labels, .fin-group-y-labels, .fin-group-endpoint-dots { position: absolute; inset: 0; pointer-events: none; }
.fin-group-y-labels span { position: absolute; left: 0; top: var(--fin-group-tick-y); transform: translateY(-50%);
  color: var(--fin-muted); font-size: var(--cg-text-meta-size); font-variant-numeric: tabular-nums; }
/* S2b §2 → R-3 v-E′ S3 — 직접 라벨 칩: 선 끝 값 «한 줄» 칩(반경 8 · 높이 20 · 12/600 · 줄바꿈 0). 390 에서 «SK하이닉스 104.07» 이름+값 칩이
   18% 폭 안에서 3줄로 접히고 값이 잘리던(실측 S1 캡처) 결함을 이름을 칩에서 빼는 것으로 푼다 — 이름은 머리(선택 종목)와 순위 행이 이미
   말하고, 칩은 값만 든다(마크업의 <b> 이름 노드와 data-full 은 그대로 — 끝값 무결성 계약은 무변경, 표시만 CSS 로 감춘다). 선택 종목(index 0)
   칩은 등락색으로 채운다(글자: 라이트 흰 / 다크 검 — 대비 ≥4.5 실측). */
.fin-group-direct-label { position: absolute; right: 1%; top: var(--fin-group-label-y); transform: translateY(-50%);
  display: inline-flex; align-items: center; padding: 0 5px; border-radius: var(--fin-badge-radius, 8px);
  background: color-mix(in srgb, var(--fin-on-surface) 12%, var(--fin-well));
  color: var(--fin-on-surface); font-size: var(--fin-type-eyebrow); font-weight: 600; line-height: 20px; white-space: nowrap;
  text-align: left; font-variant-numeric: tabular-nums; }
.fin-group-direct-label b, .fin-group-direct-label small { color: currentColor; font-size: inherit; font-weight: 600; }
.fin-group-direct-label b { display: none; }
.fin-group-direct-label small { display: inline-block; }
.fin-group-direct-label--0 { background: var(--fin-series-0); color: var(--wds-white); }
[data-theme="dark"] .fin-group-direct-label--0 { color: var(--wds-black); }
.fin-group-readout { min-height: 1.4em; margin: 6px 0 0; color: var(--fin-muted); font-size: var(--cg-text-meta-size); font-variant-numeric: tabular-nums; }
.fin-group-baseline { stroke: var(--fin-on-surface); stroke-opacity: .2; stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.fin-group-grid { stroke: var(--fin-on-surface); stroke-opacity: .1; stroke-width: 1; vector-effect: non-scaling-stroke; }
.fin-group-series { color: var(--fin-series-0); }
.fin-group-series--1 { color: var(--fin-series-1); }
.fin-group-series--2 { color: var(--fin-series-2); }
.fin-group-series--3 { color: var(--fin-series-3); }
.fin-group-series polyline { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round;
  stroke-linejoin: round; }
/* S2b §2 — the accent line (rank 1 / index 0, chart-reordered in financeGroupChartHtml)
   is the only solid, heavier stroke; ranks 2-4 stay the shared 1.8px neutral gray ladder
   distinguished by dash only (D-1/★F3), never a second color. */
.fin-group-series--0 > polyline:not(.fin-group-leader) { stroke-width: 2.8; }
.fin-group-series--1 polyline:not(.fin-group-leader) { stroke-dasharray: 5 3; }
.fin-group-series--2 polyline:not(.fin-group-leader) { stroke-dasharray: 1.5 2.5; }
.fin-group-series--3 polyline:not(.fin-group-leader) { stroke-dasharray: 7 2 1.5 2; }
.fin-group-series polyline.fin-group-leader { stroke-width: 1; fill: none; }
.fin-group-series circle { fill: currentColor; stroke: var(--cg-surface); stroke-width: 1; }
.fin-group-series .fin-group-marker { fill: var(--wds-white); stroke: currentColor; stroke-width: 2.5; }
.fin-group-endpoint-dot, .fin-group-hover-dot { position: absolute; z-index: 1; width: 10px; height: 10px;
  transform: translate(-50%, -50%); border: 2px solid currentColor; border-radius: 50%; background: var(--cg-surface); pointer-events: none; }
/* S2g §1(fix) — was a bare `left: 50%` that only matched the plotted line's real
   endpoint by coincidence (S2f's FINANCE_GROUP_CHART_X1 was itself 50). No script sets
   --fin-group-endpoint-x, so this always resolves through the fallback — kept as a
   var() rather than a plain literal so the two numbers (this fallback and app.js's
   FINANCE_GROUP_CHART_X1) read as one intentional pairing, commented at both ends,
   instead of a bare number a future edit to the JS constant could silently strand
   (exactly what happened here: FINANCE_GROUP_CHART_X1 moved from 50 to 76 and this
   literal did not, until this fix). If FINANCE_GROUP_CHART_X1 changes again, update
   this fallback (76%) to match — apps/ai-portal/tests/test_finance_multi_stock_card.py
   has no automated check tying the two together yet (see this task's report, 남은
   위험). */
.fin-group-endpoint-dot { left: var(--fin-group-endpoint-x, 74%); top: var(--fin-group-endpoint-y); }
.fin-group-hover-dot { left: var(--fin-group-marker-x, 50%); top: var(--fin-group-marker-y, 50%); background: var(--wds-white); }
.fin-group-endpoint-dot--0, .fin-group-hover-dot--0 { color: var(--fin-series-0); }
.fin-group-endpoint-dot--1, .fin-group-hover-dot--1 { color: var(--fin-series-1); }
.fin-group-endpoint-dot--2, .fin-group-hover-dot--2 { color: var(--fin-series-2); }
.fin-group-endpoint-dot--3, .fin-group-hover-dot--3 { color: var(--fin-series-3); }
[data-fin-group-series-id][hidden] { display: none !important; }
.fin-group-series text { fill: currentColor; font-size: var(--ds-text-micro-10); font-weight: 600; paint-order: stroke;
  stroke: var(--wds-color-surface); stroke-width: 3.75px; }
.fin-group-series .fin-group-direct-label { fill: var(--cg-text); }
.fin-group-axis { display: flex; justify-content: space-between; color: var(--fin-muted);
  font-size: var(--cg-text-meta-size); font-variant-numeric: tabular-nums; }
.fin-group-guide { position: absolute; top: 0; bottom: 0; left: var(--fin-group-pointer-x, 50%); width: 1px;
  transform: translateX(-50%); background: color-mix(in srgb, var(--fin-on-surface) 34%, transparent); pointer-events: none; }
.fin-group-tooltip { position: absolute; z-index: 2; top: 8px;
  left: clamp(7rem, var(--fin-group-pointer-x, 50%), calc(100% - 7rem));
  display: grid; gap: 5px; min-width: 13rem; padding: 9px 11px; transform: translateX(-50%);
  border: 1px solid var(--cg-menu-border); border-radius: var(--ds-radius-sm); background: var(--cg-surface);
  color: var(--fin-on-surface); box-shadow: var(--ds-shadow-md); pointer-events: none; }
.fin-group-tooltip > strong { font-size: var(--cg-text-meta-size); font-variant-numeric: tabular-nums; }
.fin-group-tooltip-row { display: grid; grid-template-columns: auto minmax(5.5rem, auto) minmax(8rem, 1fr); align-items: center; gap: 7px;
  font-size: var(--cg-text-meta-size); font-variant-numeric: tabular-nums; }
.fin-group-tooltip-row > span { justify-self: end; color: var(--fin-muted); }
.fin-group-guide[hidden], .fin-group-tooltip[hidden], .fin-group-hover-dot[hidden] { display: none; }
.fin-group-empty { position: absolute; z-index: 3; inset: 28% 18%; display: grid; place-content: center;
  justify-items: center; gap: 10px; padding: 16px; border: 1px solid var(--cg-menu-border); border-radius: var(--fin-group-empty-radius);
  background: var(--cg-surface); color: var(--fin-on-surface); text-align: center; box-shadow: var(--ds-shadow-md); }
.fin-group-empty[hidden] { display: none; }
.fin-group-empty p { margin: 0; font-size: var(--cg-text-meta-size); }
.fin-group-empty button { min-height: 38px; padding: 7px 14px; border: 0; border-radius: var(--ds-radius-pill);
  background: var(--fin-on-surface); color: var(--cg-surface); font: inherit; font-weight: 600; cursor: pointer; }
body.chatgpt-reference-v1 .finance-group-card .fin-group-series-toggle:focus-visible,
body.chatgpt-reference-v1 .finance-group-card .fin-group-plot:focus-visible,
body.chatgpt-reference-v1 .finance-group-card .fin-group-empty button:focus-visible {
  outline: 2px solid var(--fin-on-surface);
  outline-offset: 2px;
}
.fin-group-series-key { display: inline-block; width: 10px; height: 10px; border: 0; border-radius: 50%; background: currentColor; }
.fin-group-series-key--0 { color: var(--fin-series-0); }
.fin-group-series-key--1 { color: var(--fin-series-1); }
.fin-group-series-key--2 { color: var(--fin-series-2); }
.fin-group-series-key--3 { color: var(--fin-series-3); }
/* R-3 v-E′ S3 — 옛 «출처·계산 펼치기» 접이식 지표 표(.fin-group-details · -metrics · -foot · -source-details)는 «자세히»(발 + 펼침/시트)로 바뀌며
   마크업이 사라져 CSS 도 걷었다. 키보드 초점 링(터치 기기 하드웨어 키보드)은 아래 «자세히» 규칙이 이어받는다. */
body.chatgpt-reference-v1 .finance-group-card .fin-source > a:focus-visible,
body.chatgpt-reference-v1 .finance-group-card .fin-group-sources a:focus-visible,
body.chatgpt-reference-v1 .finance-group-card .fin-vc-more:focus-visible {
  outline: 2px solid var(--fin-on-surface);
  outline-offset: 2px;
}
.fin-group-sources { display: grid; gap: 6px; padding-top: 6px; }
.fin-group-sources p { margin: 0; color: var(--fin-muted); font-size: var(--cg-text-meta-size); }
.fin-group-sources ul { display: grid; gap: 5px; margin: 0; padding: 0; list-style: none; }
.fin-group-sources li { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  color: var(--fin-muted); font-size: var(--cg-text-meta-size); }
.fin-group-sources li b { color: var(--fin-on-surface); }
.fin-group-sources a { color: var(--wds-color-primary-text); text-underline-offset: 3px; }

/* R-3 v-E′ S3 — 기간 탭 아래 사유 한 줄(12 muted) · 발(기준·출처 12 muted + «자세히» --fin-ctl-h) · 카드 안 펼침(.fin-group-sources 재사용,
   웰 면 + 1px 웰 선 + 반경 10 + 패딩 12). 펼침 패널은 hidden 속성이 접힘 상태 — 클래스 display 가 hidden 을 덮지 않게 [hidden] 을 못 박는다. */
.finance-surface.finance-group-card .fin-vc-segs-note { margin: -4px 0 0; color: var(--fin-muted); font-size: var(--fin-type-eyebrow); line-height: 1.4; }
.fin-vc-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px;
  color: var(--fin-muted); font-size: var(--fin-type-eyebrow); line-height: 1.4; }
.fin-vc-foot-meta { min-width: 0; }
.fin-vc-more { min-height: var(--fin-ctl-h); padding: 0 12px; border: 0; border-radius: var(--ds-radius-sm); background: var(--fin-well);
  box-shadow: inset 0 0 0 1px var(--fin-well-line); color: var(--fin-on-surface); font: inherit; font-size: var(--fin-type-meta);
  font-weight: 600; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.fin-vc-more:focus-visible { outline: 2px solid var(--fin-on-surface); outline-offset: 2px; }
.fin-group-sources.fin-vc-details { gap: 8px; padding: 12px; border-radius: var(--ds-radius-sm); background: var(--fin-well);
  box-shadow: inset 0 0 0 1px var(--fin-well-line); font-size: var(--fin-type-meta); }
.fin-vc-details[hidden] { display: none; }
.fin-vc-dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px 12px; margin: 0; font-size: var(--fin-type-meta); line-height: 1.4; }
.fin-vc-dl dt { color: var(--fin-muted); }
.fin-vc-dl dd { margin: 0; color: var(--fin-on-surface); overflow-wrap: anywhere; }
/* 금액 덩어리(쉼표·«원»)는 줄바꿈 금지 — WebKit 이 «325,» / «000원» 으로 끊었다. */
.fin-vc-amt { white-space: nowrap; }
.fin-vc-dl a { color: var(--wds-color-primary-text); text-underline-offset: 3px; }

/* R-3 v-E′ S3(결정 9, 669 §M) — 모바일 «자세히» 안내형 바텀시트. 모바일 경계(≤520) 밖에서는 아예 그리지 않는다(웹 = 카드 안 펼침).
   반경 = --wizon-ai-radius-sheet(M-4) · 그림자 = --wizon-ai-sheet-shadow · 스크림 = 시트 앞 형제 [data-sheet-scrim](--wizon-scrim, M-11, 위 전역 규칙) ·
   손잡이 = 공통 .wizon-sheet-handle(36×4, 히트 44, M-3) · 하단 «확인» = 공통 .wizon-sheet-confirm-row(48, M-2) · 열림·닫힘 = 공통 키프레임
   wizon-sheet-up/-down(M-13). 카드 밖(body)에 붙으므로 위젯 변수(--fin-*)는 여기서 다시 정의한다. 새 색·새 키프레임 0. */
.fin-vc-sheet { display: none; }
@media (max-width: 520px) {
  body.chatgpt-reference-v1 .fin-vc-sheet {
    --fin-muted: var(--cg-text-soft);
    --fin-on-surface: var(--cg-text);
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    box-sizing: border-box;
    width: 100%;
    max-height: calc(100dvh - var(--wizon-safe-top) - 40px);
    overflow-y: auto;
    margin: 0;
    padding: 0 16px calc(8px + var(--wizon-safe-bottom));
    border-radius: var(--wizon-ai-radius-sheet) var(--wizon-ai-radius-sheet) 0 0;
    background: var(--wizon-ai-surface-sheet);
    color: var(--cg-text);
    box-shadow: var(--wizon-ai-sheet-shadow);
    z-index: 200;
    translate: 0 0;
    animation: wizon-sheet-up var(--wds-spring-spatial-default-duration) var(--wds-spring-effect-default) backwards;
  }
  body.chatgpt-reference-v1 .fin-vc-sheet[data-motion-state="closing"] {
    animation: wizon-sheet-down var(--wizon-sheet-exit-ms) var(--wizon-sheet-exit-ease) forwards !important;
  }
  body.chatgpt-reference-v1 .fin-vc-sheet .fin-vc-sheet-title { margin: 0 0 8px; font-size: var(--ds-text-body-15); font-weight: 600; outline: none; }
  body.chatgpt-reference-v1 .fin-vc-sheet .fin-vc-sheet-body { padding-bottom: 12px; }
  body.chatgpt-reference-v1 .fin-vc-sheet .fin-vc-dl,
  body.chatgpt-reference-v1 .fin-vc-sheet .fin-vc-dl dt,
  body.chatgpt-reference-v1 .fin-vc-sheet .fin-vc-dl dd { font-size: var(--ds-text-caption-13); }
  body.chatgpt-reference-v1 .fin-vc-sheet .wizon-sheet-confirm-row { display: block; flex: none; }
}
@media (max-width: 520px) and (prefers-reduced-motion: reduce) {
  body.chatgpt-reference-v1 .fin-vc-sheet {
    animation: wizon-scrim-in var(--wds-spring-effect-fast-duration) linear both;
    animation-duration: var(--wds-spring-effect-fast-duration) !important;
    translate: 0 0 !important;
  }
  body.chatgpt-reference-v1 .fin-vc-sheet[data-motion-state="closing"] {
    animation: wizon-scrim-out var(--wds-spring-effect-fast-duration) linear forwards !important;
    translate: 0 0 !important;
  }
  /* R-3 v-E′ S4(669 §M-15) — 스크림도 시트와 같이 124ms 흐려짐(전역 스크림은 240ms·300ms 라 «이동 0» 만 지켜서는 모자란다). 새 키프레임 0. */
  body.chatgpt-reference-v1 [data-sheet-scrim]:has(+ .fin-vc-sheet) {
    animation-duration: var(--wds-spring-effect-fast-duration) !important;
  }
  body.chatgpt-reference-v1 [data-sheet-scrim]:has(+ .fin-vc-sheet[data-motion-state="closing"]) {
    animation-duration: var(--wds-spring-effect-fast-duration) !important;
  }
}

/* R-54 S1 — 답변 활동 머리줄(안 B, 오너 10-03 «①ⓐ ②ⓐ′ ③ⓐ»). 답변 위 한 줄(아이콘·지금 하는 일·경과 초·⌄) →
   웹(>520) = 그 자리 펼침(.aa-trace-panel), 모바일(≤520) = 위 .fin-vc-sheet 안내형 시트(.aa-sheet).
   ★구분은 면·간격·헤어라인·라벨로만 한다 — 카드·인용 블록 왼쪽 강조선 금지(오너 상시 규칙, 10-02).
   ★움직임은 transform·opacity 만(펼침 160ms·점 맥박·⌄ 회전), prefers-reduced-motion 이면 끈다. 새 색·새 키프레임 토큰 0
   (색은 --cg-*, 반경은 --ds-radius-*, 글자 크기는 --ds-text-*). 쉬머(.thinking-shimmer)·스트림 스크롤 CSS 는 건드리지
   않는다(병행 레인 r54-s1f-motion 소관) — 머리줄 라벨이 같은 클래스를 그대로 쓴다. */
.aa-trace {
  margin: 0 0 8px;
  min-width: 0;
}
.aa-trace-head {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  min-height: 32px;
  margin: 0 0 0 -6px;
  padding: 0 10px 0 6px;
  border: 0;
  border-radius: var(--ds-radius-pill, 999px);
  background: transparent;
  color: var(--cg-muted, var(--muted));
  font: inherit;
  font-size: var(--ds-text-caption-13);
  line-height: 1.4;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* 호버는 마우스가 있을 때만 — 터치 기기에서 탭한 뒤 회색 알약이 남지 않게(judge-r54-chain-polish).
   쉬머 띠는 «바탕색» 으로 칠하므로 알약 면 위에서는 그 면 색을 띠 색으로 넘긴다(어두운/밝은 네모가 지나가던 결함). */
@media (hover: hover) {
  .aa-trace-head:hover {
    --shimmer-band-bg: var(--cg-hover-soft);
    background: var(--cg-hover-soft);
    color: var(--cg-text, var(--text));
  }
}
.aa-trace-head:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 2px;
}
@media (pointer: coarse) {
  .aa-trace-head {
    min-height: 44px;
  }
}
.aa-trace-head .aa-ic svg,
.aa-trace-head .aa-chevron svg {
  display: block;
  width: 14px;
  height: 14px;
}
/* 문구 교체(R-54 S1 통합): 새 문구 노드를 같은 칸(grid 한 셀)에 얹어 180ms 겹쳐 교차한다 — 빈 칸 0.
   교차 애니메이션은 S1-F 의 .status-fade-enter/.status-fade-exit 를 그대로 쓴다. */
.aa-trace-head .aa-label-stack {
  display: inline-grid;
  grid-template-areas: "label";
  align-items: center;
  min-width: 0;
  overflow: hidden;
}
.aa-trace-head .aa-label {
  grid-area: label;
  display: inline-block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}
.aa-trace-head .aa-elapsed {
  flex: none;
  font-variant-numeric: tabular-nums;
}
/* «·» 양옆은 글 속 «·»(«웹 검색 실패 · 확인된 범위로 답변») 과 같은 폭으로 — 알약 gap 6px 그대로면 두 점의 간격이 달라 보인다. */
.aa-trace-head .aa-sep {
  margin-inline: -2px;
}
.aa-trace-head .aa-chevron {
  flex: none;
  display: inline-flex;
  transition: transform 160ms ease-out;
}
.aa-trace-head[aria-expanded="true"] .aa-chevron {
  transform: rotate(180deg);
}
.aa-trace[data-failed] .aa-ic,
.aa-trace[data-failed] .aa-label {
  color: var(--cg-warning-fg);
}
/* 머리줄이 «지금 하는 일» 을 맡으므로 같은 문구를 .stream 안 상태줄이 또 그리지 않는다. */
.aa-trace ~ .stream .stream-status {
  display: none;
}
.aa-trace-panel {
  /* 머리줄 알약(margin-left -6px)과 왼쪽 면을 맞춘다(옛: 패널이 알약보다 6px 안쪽에서 시작). */
  margin: 6px 0 0 -6px;
  padding: 2px 12px;
  border-radius: var(--ds-radius-sm);
  background: var(--cg-hover-soft);
}
.aa-trace-panel[hidden] {
  display: none;
}
.aa-trace-panel.aa-panel-enter {
  animation: aa-panel-in 160ms ease-out both;
}
@keyframes aa-panel-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
}
@media (max-width: 520px) {
  /* 모바일은 인라인 패널이 아니라 하단 시트(.aa-sheet)다. */
  .aa-trace-panel {
    display: none;
  }
}
.aa-timeline {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}
.aa-row {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr) auto;
  column-gap: 8px;
  align-items: start;
  padding: 8px 0;
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-13);
  line-height: 1.45;
}
.aa-row + .aa-row {
  border-top: 1px solid var(--cg-menu-border, var(--line));
}
.aa-row-ic {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 1.45em;
  color: var(--cg-muted, var(--muted));
}
.aa-row-ic svg {
  width: 14px;
  height: 14px;
}
.aa-dot {
  display: block;
  width: 6px;
  height: 6px;
  border-radius: var(--ds-radius-pill, 999px);
  background: currentColor;
  animation: aa-dot-pulse 1.1s ease-in-out infinite;
}
@keyframes aa-dot-pulse {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 1;
  }
}
.aa-row-main {
  display: grid;
  min-width: 0;
  gap: 2px;
}
.aa-row-title {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--cg-text, var(--text));
}
.aa-row-detail {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--cg-muted, var(--muted));
}
.aa-row-detail:empty,
.aa-row-time:empty {
  display: none;
}
.aa-row-time {
  font-variant-numeric: tabular-nums;
  color: var(--cg-muted, var(--muted));
}
.aa-row[data-status="failed"] .aa-row-ic,
.aa-row[data-status="failed"] .aa-row-title {
  color: var(--cg-warning-fg);
}
.aa-sources {
  display: grid;
  gap: 4px;
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
}
.aa-sources li {
  display: flex;
  gap: 8px;
  min-width: 0;
}
.aa-src-domain {
  flex: none;
  color: var(--cg-muted, var(--muted));
}
.aa-src-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.aa-src-more {
  color: var(--cg-muted, var(--muted));
}
.aa-sheet .aa-sheet-status {
  margin: 0 0 4px;
  color: var(--cg-text-soft);
  font-size: var(--ds-text-caption-13);
}
.aa-sheet .aa-sheet-scroll {
  /* 진행 중 행이 붙을 때마다 시트 윗변이 37px 씩 튀어 오르지 않게, 흔한 검색 답(행 3 + 출처 목록)이 들어갈 높이를
     먼저 잡는다(judge-r54-chain-polish D3 — 390 실측 윗변 600→476px, 4번 계단). */
  min-height: min(28dvh, 220px);
  max-height: min(46dvh, 360px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.aa-sheet .aa-sheet-scroll:focus-visible {
  outline: 2px solid var(--cg-focus-ring, var(--accent));
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .aa-trace-head .aa-chevron {
    transition: none;
  }
  .aa-trace-panel.aa-panel-enter,
  .aa-dot {
    animation: none;
  }
}

/* ── v-C S2a — headline + rank-4 rows (mockup-source/card.css 값 정본, .fin-vc-* 신규
   접두. 색은 S1 이 만든 --fin-up/--fin-down/--fin-krx-down·.is-krx-market 그대로
   재사용해 raw-color 0, 반경은 --fin-badge-radius(8, 필/칩)·--ds-radius-sm(10, 리스트
   틀) 재사용해 raw-border-radius 0, 글자 크기는 --ds-text-* 스케일만 써서
   raw-font-size 0. 차트·기간세그먼트·출처줄은 손대지 않는다(S2b/S2c 몫). ── */
/* ★답변 본문 타이포(`.premium-product-shell-v1 .message-body p`·`.chatgpt-reference-v1 .message-body p` (0,2,1), `.message-body h3`)가 클래스 하나짜리 규칙을
   이긴다 — 옛 눈썹·기간 줄이 12/13 이 아니라 16 으로 그려졌다(실측: 비교 카드 글자 크기 {12,13,15,16,18,20,32,10.8}). 카드는 자기 타이포를 스스로
   정하므로 이 카드의 <p> 규칙은 `.finance-surface.finance-group-card` 를 앞에 붙여 (0,3,0) 으로 올린다. 머리 수치도 <h3> 가 아니라 role="heading" <p> 다 —
   `.assistant .message-body h3::before` 의 «제목 번호»("1.") 가 숫자 앞에 붙던 결함(옛 문장 헤드에도 있었다)을 이 태그 선택으로 함께 끊는다. */
/* R-3 v-E′ S3 — 머리 = 단일 카드와 같은 숫자 헤드: 눈썹 12/600 → 선택 종목 이름 15/600 + «N위 · 티커» 13 → 수익률 40/600
   (등락 «텍스트» 색 — S1 역할 토큰 --fin-*-text, 시장 클래스가 푼다. 다크 KRX 하락 = blue-400 은 토큰이 이미 들고 있어 옛 R7 전용 규칙은 폐기) →
   기간 줄 13 muted. 요약 문장은 시각 숨김 설명문이다(aria-describedby). */
.finance-surface.finance-group-card .fin-vc-head { display: grid; gap: 4px; }
.finance-surface.finance-group-card .fin-vc-eyebrow { margin: 0; color: var(--fin-muted); font-size: var(--fin-type-eyebrow); font-weight: 600; line-height: 1.35; }
.finance-surface.finance-group-card .fin-vc-who { display: flex; align-items: baseline; gap: 0 8px; min-width: 0; margin: 0; }
.finance-surface.finance-group-card .fin-vc-who b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fin-type-body); font-weight: 600; line-height: 1.3; }
.finance-surface.finance-group-card .fin-vc-who span { flex: none; color: var(--fin-muted); font-size: var(--fin-type-meta); white-space: nowrap; }
.finance-surface.finance-group-card .fin-vc-title { min-height: 44px; margin: 0; color: var(--fin-on-surface); font-size: var(--fin-type-hero); font-weight: 600;
  line-height: 44px; letter-spacing: -.02em; }
.finance-surface.finance-group-card .fin-vc-title em { font-style: normal; color: inherit; }
.finance-surface.finance-group-card .fin-vc-title .up { color: var(--fin-up-text); }
.finance-surface.finance-group-card .fin-vc-title .dn { color: var(--fin-down-text); }
.finance-surface.finance-group-card .fin-vc-sub { margin: 0; color: var(--fin-muted); font-size: var(--fin-type-meta); font-weight: 400; line-height: 1.4; }

/* R-3 v-E′ S3 — 순위 목록 = 웰 면(L2) 반경 14 한 덩이, 행 = <button aria-pressed>(높이 56 · 패딩 6/12, 결정 5·6). 눌린 행(선택 종목)은
   잉크 8% 얹음, 호버는 hover 기기에서만(4%). 행 사이 헤어라인 = 웰 선. 안쪽 초점 링(overflow 가 잘라 내므로 안쪽으로 그린다). */
.fin-vc-rank { display: grid; margin: 0; padding: 0; list-style: none; overflow: hidden;
  border-radius: var(--wds-radius-lg); background: var(--fin-well); box-shadow: inset 0 0 0 1px var(--fin-well-line); }
.fin-vc-rank li + li .fin-vc-row { box-shadow: inset 0 1px 0 var(--fin-well-line); }
.fin-vc-row { display: grid; grid-template-columns: 16px minmax(0, 1fr) 56px auto; align-items: center; gap: 8px;
  width: 100%; min-height: 56px; margin: 0; padding: 6px 12px; border: 0; border-radius: 0;
  background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer;
  -webkit-tap-highlight-color: transparent; }
.fin-vc-row[aria-pressed="true"] { background: color-mix(in srgb, var(--fin-on-surface) 8%, transparent); }
@media (hover: hover) {
  .fin-vc-row:hover { background: color-mix(in srgb, var(--fin-on-surface) 4%, transparent); }
  .fin-vc-row[aria-pressed="true"]:hover { background: color-mix(in srgb, var(--fin-on-surface) 8%, transparent); }
}
.fin-vc-row:focus-visible { outline: 2px solid var(--fin-on-surface); outline-offset: -2px; }
.fin-vc-seed { color: var(--fin-muted); font-size: var(--fin-type-eyebrow); font-weight: 600; line-height: 1.35; }
.fin-vc-name { display: grid; gap: 1px; min-width: 0; }
.fin-vc-name b { font-size: var(--fin-type-body); font-weight: 600; line-height: 1.2; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fin-vc-name small { color: var(--fin-muted); font-size: var(--fin-type-eyebrow); font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fin-vc-spark { display: block; width: 56px; height: 28px; color: var(--fin-muted); }
.fin-vc-spark[data-sign="up"] { color: var(--fin-up-line); }
.fin-vc-spark[data-sign="down"] { color: var(--fin-down-line); }
.fin-vc-val { display: grid; gap: 3px; justify-items: end; }
.fin-vc-price { font-size: var(--fin-type-body); font-weight: 600; line-height: 1.35; color: var(--fin-on-surface); text-align: right;
  white-space: nowrap; }
.fin-vc-pill { display: inline-flex; align-items: center; justify-content: center; gap: 2px; min-width: 64px; min-height: 20px;
  padding: 0 8px; border-radius: var(--fin-badge-radius, 8px); font-size: var(--fin-type-eyebrow); font-weight: 600;
  white-space: nowrap; }
.fin-vc-sign { display: inline-block; margin-right: 2px; font-size: var(--fin-type-eyebrow); }
/* S2b §1 → R-3 v-E′ S3 — 채운 필: 면 색은 S1 역할 토큰(--fin-up-fill / --fin-down-fill, 시장 클래스가 푼다 — 다크 KRX 상승 = danger-bg,
   하락 = stock-down), 글자는 라이트 흰 / 다크 검(아래 규칙). 옛 «--fin-up/--fin-down + 다크 전용 3줄 덮어쓰기» 는 토큰 한 벌로 합쳤다.
   S2h R1/R2 의 다크 검은 글자 규칙(대비 ≥4.5)은 그대로다. */
.fin-vc-pill[data-sign="up"] { color: var(--wds-white); background: var(--fin-up-fill); }
.fin-vc-pill[data-sign="down"] { color: var(--wds-white); background: var(--fin-down-fill); }
.fin-vc-pill[data-sign="flat"] { color: var(--wds-white); background: var(--fin-on-surface); }
[data-theme="dark"] .fin-vc-pill { color: var(--wds-black); }
/* S2f §4 — mockup-source/mobile-light.html keeps "· KOSPI" visible at its own 358px
   card width (viewport 390); the previous 460px threshold hid it at every mobile
   width the mockup itself shows it at. Narrow the drop-rule to the width it is
   actually needed at (320, per the S2f work order) instead of the whole mobile band.
   ★ container-type:inline-size resolves against .finance-group-card's CONTENT box,
   not its outer width — normalized_comparison's own padding is 22px per side (44px
   total), so an outer 320px card measures as a 276px content box. Using 320px here
   verbatim reproduced the exact same off-by-padding bug the >=720 grid breakpoint
   already hit once this session (676 vs 720) — measured directly (358px outer card
   still hid the suffix) rather than assumed this time. */
@container finance-group (max-width: 276px) { .fin-vc-mk { display: none; } }
/* R-3 v-E′ S3 — 아주 좁은 카드(콘텐츠 ≤270, 대략 320 폭 기기): 순위 행의 스파크라인(56)을 접어 이름이 잘리지 않게 하고, 직접 라벨 칩 안 여백을
   줄여 끝점 마커를 덮지 않게 한다(390·1440 은 해당 없음 — 실측 겹침 0 · 잘림 0). */
@container finance-group (max-width: 270px) {
  .fin-vc-row { grid-template-columns: 16px minmax(0, 1fr) auto; }
  .fin-vc-spark { display: none; }
  .fin-group-direct-label { padding: 0 3px; }
}

/* ── S2c §2 — state ③"조회 실패": mode="unavailable"(전량 실패, items.length===0).
   0건(정상적으로 비교 불가능한 결과)과 구분되는 통신 실패 프레임 — 헤드라인 자리에
   실패 문구, 재시도 액션. role="status" 는 렌더 함수에서만(순수 CSS는 상태를 모른다). ── */
.fin-vc-failed { display: grid; gap: 6px; }
.finance-surface.finance-group-card .fin-vc-failed-title { margin: 0; color: var(--fin-on-surface); font-size: var(--fin-type-body); font-weight: 600; }
.finance-surface.finance-group-card .fin-vc-failed-sub { margin: 0; color: var(--fin-muted); font-size: var(--fin-type-meta); }
.fin-vc-failed-reasons { display: grid; gap: 2px; margin: 4px 0 0; padding: 0; list-style: none; color: var(--fin-muted); font-size: var(--fin-type-eyebrow); }
.fin-vc-failed-reasons b { color: var(--fin-on-surface); font-weight: 600; margin-right: 4px; }
.fin-vc-failed-actions { display: flex; gap: 8px; margin-top: 6px; }
/* S2j → R-3 v-E′ S3 — 재시도 = 오류 카드의 유일한 복구 액션: 반경 10(옛 알약 999) · 높이 --fin-ctl-h(웹 36 / 터치 44). */
.fin-vc-failed-actions .fin-vc-failed-retry { min-height: var(--fin-ctl-h); padding: 0 16px; border: 0; border-radius: var(--ds-radius-sm);
  background: var(--fin-on-surface); color: var(--cg-surface); font-size: var(--fin-type-meta); font-weight: 600; cursor: pointer; }
.fin-vc-failed-actions .fin-vc-failed-retry:hover { opacity: .92; }
.fin-vc-failed-actions .fin-vc-failed-retry:focus-visible { outline: 2px solid var(--fin-on-surface); outline-offset: 2px; }

/* R-3 v-E′ S3 — 옛 «부분 확인 <details>»(.fin-vc-partial*) · «출처 본문»(.fin-group-sources-body) · 요약 타일(.fin-vc-tiles)은 마크업이 없던(또는 이번에
   사라진) 죽은 규칙이라 걷었다. 부분 확인·비교 불가·조회 실패·지연 안내는 아래 «알림 줄(웰 면)» 한 문법을 쓴다. */
.fin-group-mobile-multiples { display: none; }
.fin-group-mobile-multiple { padding: 12px; border: 1px solid var(--line);
  border-radius: var(--ds-radius-sm); background: var(--cg-surface-secondary); }
.fin-group-mobile-normalized { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; color: var(--cg-text-soft); font-size: var(--cg-text-meta-size);
  font-variant-numeric: tabular-nums; }
.fin-group-mobile-normalized b { color: var(--cg-text); }
.fin-group-mobile-quote { display: grid; gap: 4px; padding-bottom: 6px;
  border-bottom: 1px solid var(--line); }
.fin-group-mobile-quote-head, .fin-group-mobile-quote-meta { display: flex;
  align-items: flex-start; justify-content: space-between; gap: 8px; }
.fin-group-mobile-quote-head > span { display: grid; min-width: 0; }
.fin-group-mobile-quote-head b, .fin-group-mobile-quote-head strong { color: var(--cg-text);
  font-variant-numeric: tabular-nums; }
.fin-group-mobile-quote small, .fin-group-mobile-quote-meta { color: var(--cg-text-soft);
  font-size: var(--cg-text-meta-size); }
.fin-group-mobile-quote-meta a { color: var(--wds-color-primary-text); text-underline-offset: 3px; }
.fin-group-mobile-quote-meta .is-up { color: var(--wds-color-success-text); font-weight: 600; }
.fin-group-mobile-quote-meta .is-down { color: var(--wds-color-error-text); font-weight: 600; }
.fin-group-mobile-multiple svg { display: block; width: 100%; height: 74px; margin-top: 8px; overflow: visible; }
.finance-surface.finance-group-card .fin-group-unavailable-title { margin: 0 0 8px; font-size: var(--fin-type-body); font-weight: 600; }
/* C1 — the always-visible one-line summary the "더 보기" collapse must not hide: it
   carries the "요청 N중 M종목 시세를 받지 못했습니다" denominator even while <details>
   below stays closed by default. */
.finance-surface.finance-group-card .fin-group-unavailable-summary { margin: 0 0 8px; color: var(--fin-muted); font-size: var(--fin-type-meta); }
.fin-group-unavailable-details summary { cursor: pointer; color: var(--fin-muted); font-size: var(--fin-type-meta); list-style: none; }
.fin-group-unavailable-details summary::-webkit-details-marker { display: none; }
.fin-group-unavailable-details[open] summary { margin-bottom: 8px; }
.fin-group-unavailable ul { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; }
.fin-group-unavailable li { display: flex; justify-content: space-between; gap: 14px; color: var(--fin-muted); }
.fin-group-unavailable li b { color: var(--fin-on-surface); }
.fin-group-narrowing { margin: 0; padding: 0; border: 0; }
.fin-group-narrowing legend { font-weight: 600; }
.fin-group-narrowing > p { margin: 5px 0 14px; color: var(--cg-text-soft); }
.fin-group-choice-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.fin-group-choice { display: flex; align-items: center; gap: 10px; min-height: 54px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--ds-radius-sm); cursor: pointer; }
.fin-group-choice input[type="checkbox"] { flex: none; width: 22px; min-width: 22px; height: 22px;
  margin: 0; accent-color: var(--wds-color-wizon-accent); }
.fin-group-choice:has(input:checked) { border-color: var(--wds-color-wizon-accent); background: var(--accent-soft); }
.fin-group-choice:focus-within { outline: 2px solid var(--wds-color-wizon-accent); outline-offset: 2px; }
.fin-group-choice.is-unavailable { cursor: not-allowed; opacity: .62; }
.fin-group-choice span { display: grid; }
.fin-group-choice small { color: var(--cg-text-soft); }
.fin-group-narrowing-actions { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 16px; }
.fin-group-narrowing-actions span { color: var(--cg-text-soft); font-size: var(--cg-text-meta-size); }
.fin-group-narrowing-actions button { min-height: 42px; padding: 0 16px; border: 0;
  border-radius: var(--ds-radius-pill); background: var(--wds-color-wizon-accent);
  color: var(--wds-color-text-on-primary); font-weight: 600; cursor: pointer; }
.fin-group-narrowing-actions button:disabled { opacity: .45; cursor: not-allowed; }
.stream-card-skeleton[data-card-skeleton="finance_group"] { min-height: 340px; background: var(--cg-surface); }
.stream-card-skeleton .sk-finance-group-legend { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.stream-card-skeleton .sk-finance-group-legend .sk-line { width: 100%; height: 46px; }
.stream-card-skeleton .sk-finance-group-chart { height: 170px; }
/* S2c §2 state ④"로딩" — v-C 프레임 실루엣(눈썹+헤드라인 → 4행 순위 리스트 → 차트).
   행 피치(56px)를 .fin-vc-row 와 맞춰, 로딩→실데이터 교체 때 카드 높이가 튀지 않는다. */
.stream-card-skeleton .sk-vc-head { display: grid; gap: 6px; margin-bottom: 4px; }
.stream-card-skeleton .sk-vc-eyebrow { width: 46%; height: 10px; }
.stream-card-skeleton .sk-vc-title { width: 56%; height: 40px; }
.stream-card-skeleton .sk-vc-rows { display: grid; margin: 0; padding: 0; list-style: none; }
.stream-card-skeleton .sk-vc-row { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; min-height: 56px; padding: 0 2px; }
.stream-card-skeleton .sk-vc-row-name { width: 42%; height: 14px; }
.stream-card-skeleton .sk-vc-row-pill { width: 64px; height: 20px; border-radius: var(--fin-badge-radius, 8px); }

@container finance-group (max-width: 600px) {
  /* S2f §2 — this block's original comment ("the endpoint text belongs beside its
     persistent series toggle") describes the pre-S2e legend row, which S2e removed
     entirely. Hiding .fin-group-direct-labels here left mobile widths (<=600) with
     NEITHER the legend NOR the end-value labels — the "라벨이 전부 안 보인다" defect.
     The gutter fix (FINANCE_GROUP_CHART_X1/LABEL_X0) makes the labels safe to show at
     every width, mobile included, so the hide rule is removed rather than patched.
     The now-orphaned series-controls/series-value rules are left in place (0 matching
     elements post-S2e, harmless) since S2e already made that call. */
  .fin-group-series-controls { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); justify-content: stretch; gap: 4px; }
  .fin-group-series-toggle { min-width: 0; min-height: 32px; padding: 5px 7px; }
  .fin-group-series-value { display: inline; }
}
@container finance-group (max-width: 360px) {
  .fin-group-series-controls { grid-template-columns: 1fr; }
}

@container (max-width: 560px) {
  .finance-group-card { padding: 16px; gap: 12px; }
  .fin-group-head { align-items: center; }
  .fin-group-title { line-height: 1.25; }
  .fin-group-cards, .fin-group-choice-grid { grid-template-columns: 1fr; }
  .finance-group-card .fin-group-item { padding: 14px; }
  .fin-group-chart > .fin-group-plot { display: block; }
  .fin-group-chart > .fin-group-axis { display: flex; }
  .fin-group-tooltip { min-width: 11rem; padding: 7px 9px; }
  /* The shared normalized plot and metrics are now readable on phones. Keep the
     legacy duplicated mini-series presentation out of that same comparison flow. */
  .finance-group-card[data-mode="normalized_comparison"] .fin-group-mobile-multiples { display: none; }
  .fin-group-mobile-multiple { padding: 9px; }
  .fin-group-mobile-multiple svg { height: 30px; margin-top: 3px; }
  .fin-group-sources li { align-items: flex-start; flex-direction: column; gap: 3px; }
  .fin-group-unavailable li { display: grid; gap: 2px; }
}
@media (pointer: coarse) {
  .fin-group-series-toggle,
  .fin-group-empty button { min-height: 44px; }
}

/* 런처의 CPQ 카드는 «링크»라(라우트 이동) — .chatgpt-app-card 스타일을 그대로 쓰되
   링크 기본 밑줄·색만 지운다. */
.cpq-app-card { text-decoration: none; color: inherit; }

/* ==========================================================================
 * 20260903-mobile-sheet-ai-bottom-sheet — 폰의 AI 하단 시트 (doc 635 §2·§4)
 *
 * (1) 새는 `hidden` 을 봉한다. `.artifact-titlebar-button { display: inline-flex }`(이 파일 위쪽)
 *     은 **저자** 스타일이라 UA 스타일시트의 `[hidden] { display: none }` 을 «출처 우선순위»로
 *     이긴다 — 특이도 문제가 아니라서 선택자를 더 좁혀도 안 고쳐진다. 실측(2026-09-03, 390×844
 *     실렌더): data-artifact-ai-drawer-toggle 과 data-workbook-history-toggle 이
 *     `hasAttribute('hidden') === true` 인데 `getComputedStyle().display === 'flex'` 로 그려지고
 *     있었다. 폰에서 ✦AI 는 눌러도 artifactAiDrawerAvailable() 이 false 라 아무 일도 하지
 *     않는 «죽은 버튼»이었다. 음성 대조가 같은 줄에 있다 — data-workbook-save 만
 *     `.artifact-titlebar-save-button` 이라는 다른 클래스라서 display 덮어쓰기를 안 받아
 *     혼자 정상적으로 사라진다.
 *     ★이 리포는 이 함정을 이미 알고 있어서 `.artifact-cell-source-bar[hidden]`,
 *       `.md-table-more-menu[hidden]` 처럼 컴포넌트마다 손으로 다시 적어 왔다. 여기서
 *       빠져 있던 한 줄을 채운다.
 * ========================================================================== */
.artifact-titlebar-button[hidden] {
  display: none;
}

/* (2) 터치 타깃 44 (doc 544 §5.4-R4). 지금까지 `[data-artifact-range-ask]` 한 개만 44 를 받고
 *     나머지 아이콘 버튼은 34, 닫기는 32 였다 — 한 줄에 세 크기가 섞여 오너가 「정렬·배치가
 *     문제」라고 본 것이 이것이다. 조건을 `pointer: coarse` 로 두는 것은 이 파일의 관례를
 *     따른 것이고(뷰포트 폭이 아니라 «손가락으로 만지는가»가 히트영역의 기준이다), 위의
 *     range-ask 전용 규칙과 같은 미디어 조건이라 폰에서 둘이 갈리지 않는다. */
@media (pointer: coarse) {
  #artifactPanel .artifact-route-head-actions .artifact-titlebar-button,
  #artifactPanel .artifact-route-head-actions .trust-panel-close {
    width: 44px;
    height: 44px;
  }
}

/* (3) AI 하단 시트 — «옮기지 않는다»를 세로로 돌린 것.
 *
 * doc 544 §27 이 데스크톱에서 쓴 기법은 «대화를 패널 안으로 옮기는» 것이 아니라 «패널이
 * 오른쪽 390px 를 비켜 주는» 띠 예약이었다. 폰에서도 같은 이유로 재부모화가 불가능하다 —
 * #artifactPanel 은 section.chat-pane 의 **자식**이라 대화를 패널 안으로 넣으면 패널이 자기
 * 조상의 자손이 된다. 그래서 여기서도 아무 것도 옮기지 않고 **패널의 아래 변만 들어올린다.**
 *
 * ★가르는 값(실측 2026-09-03, 390×844, elementFromPoint 히트테스트):
 *     패널 bottom 0   → 컴포저 중앙 히트 = `panel:canvas`  (대화가 완전히 덮여 있다)
 *     패널 bottom 338 → 컴포저 중앙 히트 = `composer:textarea`  (진짜 컴포저가 잡힌다)
 *     두 경우 모두 그리드 중앙 히트 = `panel:canvas` → **non-modal 이 성립한다**
 *     그리드 rect 높이 588 → 250 으로 «잘리는» 게 아니라 «리플로»된다
 *   즉 드러나는 띠는 우리가 새로 그린 대화가 아니라 **진짜 모바일 대화**(메시지 꼬리 +
 *   컴포저)다. 새 채팅 렌더러를 만들지 않는다는 §27.4 의 선을 그대로 지킨다.
 *
 * 높이는 JS 가 --artifact-ai-sheet-height 에 px 로 넣는다. 숫자를 CSS 에 한 벌 더 적지
 * 않는 이유는 §27.5 와 같다 — 두 벌이 조용히 어긋난다. 폴백 `0px` 은 「변수가 없으면 예약도
 * 없다」로 우아하게 퇴화한다(여기서는 §27 과 달리 0 이 맞는 폴백이다: bottom:0 = 현행 풀스크린). */
@media (max-width: 900px) {
  body[data-artifact-ai-sheet="on"] #artifactPanel.chatgpt-route-page {
    bottom: var(--artifact-ai-sheet-height, 0px);
  }

  .artifact-ai-sheet-grip {
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--artifact-ai-sheet-height, 0px);
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    /* 패널(z-index 100) «위»에 서야 잡이가 보인다. 패널 안에 두면 패널이 만든 쌓임 맥락에
       갇혀 절대 올라오지 못한다 — 그래서 마크업도 패널 밖(.chat-pane 직속)에 있다. */
    z-index: 120;
    padding-inline: 8px;
    background: var(--cg-surface, var(--bg));
    border-top: 1px solid var(--cg-menu-border, var(--line));
    touch-action: none;
  }
  .artifact-ai-sheet-grip[hidden] {
    display: none;
  }
  /* Material 3 의 바텀시트 잡이 규격을 따른다: 시각 막대는 얇게, 히트영역은 넉넉히
     (M3 는 48dp 를 권한다 — 여기서는 이 앱의 44 관례에 맞춘다). */
  .artifact-ai-sheet-grip-button {
    display: grid;
    place-items: center;
    width: 100%;
    height: 44px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: grab;
    touch-action: none;
  }
  .artifact-ai-sheet-grip-button:active {
    cursor: grabbing;
  }
  .artifact-ai-sheet-grip-bar {
    display: block;
    width: 36px;
    height: 4px;
    border-radius: var(--ds-radius-pill, 999px);
    background: var(--cg-menu-border, var(--line));
  }
  .artifact-ai-sheet-grip-close {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: var(--ds-radius-pill, 999px);
    background: transparent;
    color: var(--cg-text-soft, var(--muted));
    cursor: pointer;
  }
  .artifact-ai-sheet-grip-button:focus-visible,
  .artifact-ai-sheet-grip-close:focus-visible {
    outline: 2px solid var(--cg-focus-ring, var(--accent));
    outline-offset: -2px;
  }
}

/* ==========================================================================
 * 20260903-mobile-ribbon-context-sheet — doc 635 §6 2단계: 폰 리본 하단 컨텍스트 패널
 * (서식/삽입/함수). §3.2 업계 정합대로 「리본 전체를 하단」이 아니라 「탭 줄은 상단 유지 +
 * 서식/삽입만 하단 패널」이다.
 *
 * 위치: .chat-pane 직속(index.html 주석) — #artifactPanel 안이면 z-index:100 쌓임 맥락에
 * 갇히고, #artifactRibbon 안이면 그 container-type:inline-size(§12) 가 만드는 layout
 * containment 때문에 position:fixed 자손이 뷰포트가 아니라 리본 자신을 기준으로 앵커된다
 * (doc 544 §20 이 색상/테두리 팝오버를 body 포털로 뺀 것과 같은 함정, 4283행 주석).
 *
 * detent: AI 하단 시트(위 블록)와 같은 변수/게이트/non-modal/스크림 없음 관례를 재사용하되,
 * 이 시트는 내용이 고정된 명령 그리드라 0.4/0.9 두 detent 를 고를 이유가 없다 — 열림/닫힘
 * 하나뿐이고 높이는 콘텐츠가 정한다(app.js 주석에 같은 설명).
 *
 * ★doc635 §2.1 이 잡은 함정을 되풀이하지 않는다: [hidden] 규칙을 이 파일의 다른 authored
 * display 규칙보다 특이도를 «더 준다»(class+attribute) — .artifact-titlebar-button[hidden]
 * 과 같은 처방. */
@media (max-width: 520px) {
  .artifact-ribbon-sheet-bar,
  .artifact-ribbon-sheet {
    position: fixed;
    left: 0;
    right: 0;
    z-index: 120;
    background: var(--cg-surface, var(--bg));
  }
  .artifact-ribbon-sheet-bar[hidden],
  .artifact-ribbon-sheet[hidden] {
    display: none;
  }
  /* 20261001-mobile-sheet-status-bar-occluded — 이 바는 position:fixed 라 흐름 안 상태바
     (footer.artifact-status-bar, 시트 패널 최하단)를 그대로 덮었다(실측 390/375/360:
     elementFromPoint 가 바 버튼). 저장 실패·성공 사유가 눈에 안 보이고 role=status 낭독으로만
     전달됐다. 바의 «실제 렌더 높이» 변수(syncArtifactRibbonSheet 가 잰다 — 숫자를 한 벌 더
     적지 않는다)만큼 상태바를 위로 올린다 — 시트 본문은 flex 로 그만큼 줄어 가려지지 않는다.
     바가 숨겨지면 변수가 0px 이라 변화가 없다. 홈 인디케이터 여백은 바가 이미 그 자리를
     덮으므로 바 높이만큼 덜어 낸다(상태바 위에 빈 띠가 생기지 않게). ★.artifact-status-bar
     기본/900px 규칙과 특이도가 같아 소스 순서(이 블록이 뒤)로 이긴다. */
  .artifact-status-bar {
    margin-bottom: var(--artifact-ribbon-sheet-bar-height, 0px);
    padding-bottom: max(0px, calc(var(--wizon-safe-bottom, 0px) - var(--artifact-ribbon-sheet-bar-height, 0px)));
  }
  .artifact-ribbon-sheet-bar {
    bottom: var(--artifact-ribbon-sheet-bar-offset, 0px);
    display: flex;
    border-top: 1px solid var(--cg-menu-border, var(--line));
    padding-inline: 4px;
    /* AI 그립(z-index 120)과 같은 높이 — 이 바는 그 위(화면 더 안쪽)로 쌓일 필요가 없다.
       둘이 동시에 보이는 경우는 AI 시트가 열려 있을 때뿐이고, 그때는 --artifact-ribbon-sheet-bar-offset
       가 AI 시트 높이만큼 이 바를 그 위로 올려 겹치지 않는다(app.js syncArtifactRibbonSheet). */
  }
  .artifact-ribbon-sheet-bar-btn {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: 44px;
    padding: 4px;
    border: none;
    background: transparent;
    color: var(--cg-text-soft, var(--muted));
    font: inherit;
    font-size: var(--ds-text-caption-12);
    cursor: pointer;
  }
  .artifact-ribbon-sheet-bar-btn[aria-expanded="true"] {
    background: var(--ad-blue-tint);
    color: var(--ad-blue);
  }
  .artifact-ribbon-sheet-bar-btn:disabled {
    cursor: not-allowed;
    opacity: 0.45;
  }
  .artifact-ribbon-sheet-bar-btn:focus-visible {
    outline: 2px solid var(--cg-focus-ring, var(--accent));
    outline-offset: -2px;
  }
  .artifact-ribbon-sheet-bar-btn-glyph {
    font-weight: 600;
    font-size: var(--ds-text-body-14);
    line-height: 1;
  }
  .artifact-ribbon-sheet {
    bottom: calc(var(--artifact-ribbon-sheet-bar-offset, 0px) + var(--artifact-ribbon-sheet-bar-height, 0px));
    display: flex;
    flex-direction: column;
    max-height: 46vh;
    border-top: 1px solid var(--cg-menu-border, var(--line));
    border-radius: var(--ds-radius-lg, 16px) var(--ds-radius-lg, 16px) 0 0;
    /* raw-rgb 래칫(design-system/governance/anti-drift-static-baseline.json) 은 var() 폴백
       «안»의 rgba() 리터럴도 줄 단위로 그대로 센다 — 폴백 없이 토큰만 쓴다(:root 에 항상
       정의돼 있어 폴백이 필요 없다). */
    box-shadow: 0 -4px 16px var(--wizon-ai-focus-ring);
  }
  .artifact-ribbon-sheet-grip {
    display: grid;
    grid-template-columns: 44px 1fr 44px;
    align-items: center;
    flex: 0 0 auto;
    touch-action: none;
  }
  .artifact-ribbon-sheet-grip-button {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
  }
  .artifact-ribbon-sheet-grip-bar {
    display: block;
    width: 36px;
    height: 4px;
    border-radius: var(--ds-radius-pill, 999px);
    background: var(--cg-menu-border, var(--line));
  }
  .artifact-ribbon-sheet-title {
    text-align: center;
    color: var(--cg-text, var(--text));
    font-size: var(--ds-text-caption-12);
    font-weight: 600;
  }
  .artifact-ribbon-sheet-close {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--cg-text-soft, var(--muted));
    cursor: pointer;
  }
  .artifact-ribbon-sheet-grip-button:focus-visible,
  .artifact-ribbon-sheet-close:focus-visible {
    outline: 2px solid var(--cg-focus-ring, var(--accent));
    outline-offset: -2px;
  }
  .artifact-ribbon-sheet-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
    gap: 4px;
    padding: 4px 8px calc(8px + env(safe-area-inset-bottom, 0px));
    overflow-y: auto;
  }
  .artifact-ribbon-sheet-grid[hidden] {
    display: none;
  }
  .artifact-ribbon-sheet-cmd {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: 44px;
    padding: 4px;
    border: none;
    border-radius: var(--ds-radius-xs, 6px);
    background: transparent;
    color: var(--cg-text-soft, var(--muted));
    font: inherit;
    /* raw-font-size 래칫(위와 같은 baseline 파일) 은 var() 로 안 감싼 값을 센다 — 그리드가
       44px 라 12px 캡션 토큰이 이 파일의 다른 소형 라벨(§5.2-M1 탭 pill 등)과 같은 크기다. */
    font-size: var(--ds-text-caption-12);
    cursor: pointer;
  }
  .artifact-ribbon-sheet-cmd[aria-pressed="true"] {
    background: var(--ad-blue-tint);
    color: var(--ad-blue);
  }
  .artifact-ribbon-sheet-cmd:disabled {
    cursor: not-allowed;
    opacity: 0.45;
  }
  .artifact-ribbon-sheet-cmd:focus-visible {
    outline: 2px solid var(--cg-focus-ring, var(--accent));
    outline-offset: -2px;
  }
  .artifact-ribbon-sheet-cmd .artifact-ribbon-glyph {
    font-weight: 600;
    font-size: var(--ds-text-body-14);
    line-height: 1;
  }

  /* doc 635 §6 발주 항목 1 — 홈/삽입/수식 탭의 리본 행은 하단 패널이 대신하므로 숨는다
     ("나머지 탭 내용은 하단 패널이 대신"). ★58 레인의 탭바 스크롤 힌트(.artifact-ribbon-tabs
     등, 이 파일 위쪽 5433행 블록)의 어떤 선택자도 고치지 않는다 — 여기서는 새 하위 선택자만
     더한다. 데이터 탭은 이 두 시트(서식/삽입) 어느 쪽에도 안 들어가 남긴다(app.js
     ARTIFACT_RIBBON_SHEET_TAB_TO_KIND 와 짝 — doc 635 §6 표). */
  .artifact-ribbon-panel[data-ribbon-panel="home"] .artifact-ribbon-row,
  .artifact-ribbon-panel[data-ribbon-panel="insert"] .artifact-ribbon-panel-note,
  .artifact-ribbon-panel[data-ribbon-panel="formula"] .artifact-ribbon-row {
    display: none;
  }
}

/* doc 635 §6 발주 항목 2 「수식바가 키보드 위에 붙도록」 — ★시도했다가 되돌렸다, 이유를 남긴다.
   처음에는 data-formula-bar-expanded="true"(html.is-keyboard-open 과 함께) 를 «셀 편집 중»의
   대리 신호로 썼다. apps/ai-portal/tests/test_sheet_phone_formula_bar.py 를 읽고서 그 전제가
   틀렸다는 것을 확인했다: 이 속성은 doc 544 §5.2-M3 의 **다른** 제스처(수식바 자체를 탭해
   3줄로 펼치는 것)에서만 세워진다 — 발주 문장의 «더블탭 편집»(doc 544 §55, 셀을 더블탭해
   벤더 편집으로 들어가는 것)과는 별개 경로라 상관을 보장하지 못한다. 겹치는 경우도 있겠지만
   («셀 편집 중이면 항상 참»이 아니라 «수식바를 직접 탭했을 때만 참») 확인 없이 커플링하면
   실제로는 편집 중이 아닌데 수식바가 sticky 로 붙거나, 진짜 더블탭 편집 중에는 아무 효과가
   없는 «있어 보이는데 틀린» 규칙이 된다. 새 신호(포인터/포커스 리스너)를 새로 달아 만드는
   것은 §635 의 「새 리스너를 달지 않는다」 관례와도 어긋난다 — 이 작업은 다음 후보로 남긴다
   (남은 위험, doc 635 §8). */

/* R-41 B2b(할 일 5, 설계 §3.3) — 종류 셀렉터 1벌. 개별 요소 클래스(.chatgpt-share-dialog·
   #composerAttachmentDialog 등)마다 반복해 온 .is-dragging/.is-sheet-snapping/::backdrop
   규칙을 [data-sheet-kind] 를 가진 시트 전체에 한 번만 건다 — 값 자체는 위 shareDialog
   블록(21242행 부근)과 같다(새 애니메이션 0개, wizon-sheet-snap 재사용).
   R-40 ②파도 build A(D1) — ★특이도를 올렸다. 처음엔 `[data-sheet-kind].is-dragging`(0,2,0)
   이라 «소스 순서로 공존» 한다고 적었지만 실측(09-29, Chromium·WebKit 390)은 반대였다:
   표면별 열림 규칙(`body.chatgpt-reference-v1 .orchestration-dock:not([hidden])`·라이브러리
   팝오버·설정 선택·드라이브 모바일 메뉴 등, 0,3,x)이 `animation: wizon-sheet-up …` 과
   `translate: 0 0` 을 걸어 종류 규칙에 이겨, 끌기 중 계산 translate 가 0px 로 남고 시트가 손가락을
   안 따라왔다(`--wizon-sheet-drag-y:110px` 인데 top 불변). 그래서 앞머리 `body.chatgpt-reference-v1`
   을 붙여 0,3,x 이상으로 올리고 표면 규칙과 같은 특이도에서는 파일 끝이라는 소스 순서로 이긴다.
   `!important` 는 쓰지 않는다(모션 줄이기 하한의 `animation:none !important` 등과 싸우지 않는다). */
body.chatgpt-reference-v1 [data-sheet-kind].is-dragging {
  transition: none;
  animation: none;
  translate: 0 var(--wizon-sheet-drag-y, 0);
}
body.chatgpt-reference-v1 [data-sheet-kind].is-dragging::backdrop {
  animation: none;
  opacity: calc(1 - var(--wizon-sheet-drag-progress, 0));
}
body.chatgpt-reference-v1 [data-sheet-kind].is-sheet-snapping {
  transition: none;
  animation: wizon-sheet-snap var(--wds-spring-effect-default-duration, 188ms)
    var(--wds-spring-effect-default, cubic-bezier(0.16, 1, 0.3, 1)) forwards;
}
/* R-40 ②파도 build A(D6) — 끌기 복귀(is-sheet-snapping, 위)가 끝나면 컨트롤러가 이 표식을 달고
   클래스를 뗀다. 표식이 없으면 표면별 열림 규칙의 `animation: wizon-sheet-up …` 이 다시 적용돼
   열림 애니메이션이 처음부터 재생된다(시트가 바닥으로 내려갔다 올라옴 — 공유 시트에서도 실측).
   복귀는 «열린 자리로 돌아온 것» 이라 열림 모션을 다시 틀지 않는다. 닫힘 상태는 예외로 둬 닫힘
   키프레임을 막지 않는다(:not 이 특이도를 0,4,1 로 올려 표면 열림 규칙 0,3,x 를 이긴다).
   `!important` 없음 — 모션 줄이기 하한과 싸우지 않는다. */
body.chatgpt-reference-v1 [data-sheet-kind][data-sheet-settled]:not([data-motion-state="closing"]) {
  animation: none;
}
