/* `dark:` variant 정의도 함께 제거했다 — 실사용 0건이었고, 정의만 남아 있으면
   "다크모드가 이미 있다"는 오해로 다시 켜질 함정이 된다.
   2재질(서고/열람대)은 OS 테마가 아니라 [data-surface]가 담당한다. */

:root {
  --font-size: 16px;

  /* ── 앱 셸 레이어 토큰 (모바일 M3) ──────────────────────────
     하드코딩 z-index(9980~10010 + 인라인 4종)의 목표 스케일.
     신규 코드는 반드시 이 4층만 사용한다. 기존 값 이관은 점진(사고 이력 지대).
     content(씬 내부) < nav(탭바·레일·트레이) < sheet(바텀시트·모달) < alert(파괴적 확인·토스트) */
  --z-content: 100;
  --z-nav: 9998;
  --z-sheet: 10001;
  --z-alert: 10005;

  /* 네이티브 키보드 인셋 — native-app.ts initKeyboardInsets()가 갱신.
     하단 도킹 컴포저/CTA는 padding-bottom 등에 이 값을 더해 키보드를 회피한다. */
  --kb-inset: 0px;

  /* 앱 셸이 <main> 위에 잡아 두는 상태바·노치 인셋 — index.html 의 viewport-fit=cover 때문에
     설치형 앱뿐 아니라 펀치홀·노치 기기의 모바일 브라우저에서도 0 이 아니다.
     ★ 이 띠를 잡는 쪽(jit-layout <main>)과 그 위를 칠하는 쪽(탈퇴 배너 뒤판)이 **같은 값**을
       써야 한다. 두 곳이 env() 를 따로 적었다가 어긋나면 아무도 안 칠하는 검은 띠가 남는다
       (2026-08-19 QA F#71 재확인 "탈퇴 배너가 라이브러리 탭에서 빈 검정 영역").
       변수로 둔 덕에 브라우저 실측에서 인셋을 주입해 재현·검증할 수 있다. */
  --jit-shell-top-inset: env(safe-area-inset-top, 0px);

  /* ── Architectural Material-based BI ── */
  /* Brand: Structure Purple */
  --structure-purple: #8A6FB8;
  --structure-deep: #6D5594;
  --dusty-purple: #C6BCD6;
  --pale-structure: #EBE7F0;

  /* Material Accents */
  --oxidized-green: #769382;
  --deep-oxidized: #3E4E45;
  --pale-green: #E8F0EB;
  --slate-blue: #8DAAB0;
  --deep-slate: #5D7A80;

  /* Brick & Brass */
  --brass-gold: #C4A484;
  --brick-red: #A64444;

  /* Caution — 어스 오커. brass(#C4A484)는 라이브러리 전용 액센트라 경고색으로 쓰지 않는다.
     다크 서고 대비 ~5.5:1 / 열람대 종이 ~3:1. (디자인 시스템 확정 2026-07-15) */
  --earth-ochre: #B5822E;

  /* Charcoal & Material Greys */
  --charcoal-black: #2B2A28;
  --charcoal-deep: #1C1B1A;
  --charcoal-mid: #4A4947;
  --cement-grey: #F1F1F1;
  --concrete-grey: #D2D1CE;
  --sand-beige: #E0D9D1;
  --clay-brown: #958A81;
  --stone-border: #BCB9B4;

  /* 열람대 페이퍼 — §4.3 목록엔 없지만 design-system §1.4가 "시트 규격 상수"로 명문화한 값.
     서고 위에서는 이 페이퍼 톤이 그대로 잉크가 된다(같은 재질의 반전). */
  --paper: #FAF9F7;

  /* ── 표면 토큰 (열람대 = 기본값) ─────────────────────────────────
     컴포넌트는 자기 색을 모른다. 상위 [data-surface]가 색을 정하고 컴포넌트는 순응한다.
     design-guide.md §5 "컴포넌트는 semantic token에 의존해야 브랜드 팔레트가 바뀌어도
     수정 범위가 작아진다"의 실행 장치다.

     ★ 값은 전부 §4.3 중성 8색을 그대로 참조한다 — 새 뉴트럴을 뽑지 않는다.
     ★ 전 뉴트럴은 웜(R ≥ G ≥ B). external §4.3 "warm and material-like rather than
       sterile" · §4.4 "avoid blue-heavy styling" — Tailwind gray-*(hue 261)는 이 조항 위반. */
  --surface: var(--paper);               /* 열람대 시트 바닥 */
  --surface-card: #FFFFFF;               /* §7 "카드 표면: white 또는 매우 밝은 톤" —
                                            웜 시트(#FAF9F7) 위 흰 카드가 §7 권장 구성이다 */
  --surface-recess: #F1EDE6;             /* 리세스 — 호버·트랙·선택 행 (열람대 파생, shared/design-tokens ERP.recess) */
  --surface-sunken: var(--sand-beige);   /* 깊은 리세스 — §4.3 "따뜻한 중성 배경" */

  --ink: var(--charcoal-black);          /* §4.3 "핵심 텍스트" */
  --ink-mid: var(--charcoal-mid);        /* §4.3 "보조 텍스트/보더" */
  --ink-mut: var(--clay-brown);          /* §4.3 "세컨더리 텍스트" (external: muted text) */
  --ink-faint: var(--stone-border);      /* §4.3 "약한 보더" — 보더·장식 전용.
                                            ★ 텍스트에 쓰지 말 것: §4.3의 텍스트 중성은
                                            charcoal-black / charcoal-mid / clay-brown 3단뿐이다 */

  /* 헤어라인 — charcoal 베일(투명)이라 어떤 면 위에서도 성립한다.
     강조 보더만 §4.3 concrete-grey("구분선, 패널 경계") 실색을 쓴다. */
  --hair-soft: rgba(43, 42, 40, 0.06);
  --hair: rgba(43, 42, 40, 0.11);
  --hair-firm: var(--concrete-grey);

  /* 행동색 — 표면에 따라 자동 전환된다.
     정본 §1.1 "다크 배경 위 퍼플 텍스트는 dusty-purple"을 사람이 아니라 토큰이 지킨다. */
  --action: var(--structure-purple);
  --action-deep: var(--structure-deep);

  /* ── Semantic mappings ── */
  /* P2 — 앱 바닥은 서고 하나. 이전엔 body #2B2A28 과 시트씬 #1C1B1A 이중 바닥이었다.
     design-system §6.1 "다크 서고 #1C1B1A + 24px 도트 그리드"가 전 씬 공통 지면이다. */
  --background: var(--charcoal-deep);
  --foreground: #ffffff;
  --card: #1E1E1E;
  --card-foreground: #ffffff;
  --popover: #1E1E1E;
  --popover-foreground: #ffffff;
  --primary: var(--structure-purple);
  --primary-foreground: #ffffff;
  --secondary: #252525;
  --secondary-foreground: #ffffff;
  --muted: var(--charcoal-mid);
  --muted-foreground: #8A8A8A;
  --accent: var(--slate-blue);
  --accent-foreground: var(--charcoal-black);
  --destructive: var(--brick-red);
  --destructive-foreground: #ffffff;
  /* 부정 신호는 퍼플 금지 — 주의=warning(앰버), 위험/오류=destructive */
  --warning: var(--earth-ochre);
  --warning-foreground: #ffffff;
  /* 주의 배너의 면 — earth-ochre 를 서고 지면(#1C1B1A)에 20% 섞은 **실색**이다.
     ① 실색인 이유: alpha 유틸(bg-warning/20)은 Tailwind v4 에서 color-mix() 로 나가는데,
        그러면 서고(#1C1B1A) 위냐 시트(#FAF9F7) 위냐에 따라 카드 색이 달라진다.
     ② 지면과 달라야 하는 이유: 탈퇴 배너가 bg-charcoal-deep 이던 시절 배경이 지면과
        같은 색(대비 1.00:1)이라 카드가 아예 안 보였다(2026-08-14 QA F#71).
     오커 헤어라인 대비 — 지면 5.05:1 · 이 면 3.81:1, 흰 78% 글자 대비 8.55:1. */
  --warning-surface: #3B301E;
  --border: var(--charcoal-mid);
  --input: transparent;
  --input-background: #2a2a2a;
  --switch-background: #cbced4;
  --font-weight-medium: 500;
  --font-weight-normal: 400;
  --ring: var(--structure-purple);
  --chart-1: oklch(0.646 0.222 41.116);
  --chart-2: oklch(0.6 0.118 184.704);
  --chart-3: oklch(0.398 0.07 227.392);
  --chart-4: oklch(0.828 0.189 84.429);
  --chart-5: oklch(0.769 0.188 70.08);
  /* 반경 정본 — design-system.md §6.1 4단(12/16/20/24).
     구 `--radius: 0.625rem`(10px) + calc 파생은 6/8/10/14px를 만들어 4단을 9단으로 벌렸다.
     shadcn 잔여 참조를 위해 이름은 남기되 값은 4단 최하단(12px)에 맞춘다. */
  --radius: 12px;
  --sidebar: #1E1E1E;
  --sidebar-foreground: #ffffff;
  --sidebar-primary: var(--structure-purple);
  --sidebar-primary-foreground: #ffffff;
  --sidebar-accent: #252525;
  --sidebar-accent-foreground: #ffffff;
  --sidebar-border: var(--charcoal-mid);
  --sidebar-ring: var(--structure-purple);
}

/* ── 서고 표면 — 다크 크롬(레일·독·콘솔·푸터) ─────────────────────
   구 `.dark` 블록(shadcn 기본 oklch 무채색)은 삭제했다.
   붙이는 코드 0건 · `dark:` variant 실사용 0건 · 값이 BI와 무관해
   켜지는 순간 짓닷이 아니게 되는 사문화된 함정이었다. 2재질은 아래 표면 토큰이 담당한다. */
[data-surface="vault"] {
  --surface: var(--charcoal-deep);   /* #1C1B1A */
  --surface-card: var(--charcoal-black); /* #2B2A28 */
  --surface-recess: rgba(255, 255, 255, 0.04);
  --surface-sunken: rgba(255, 255, 255, 0.07);

  --ink: var(--paper);               /* 서고 잉크 = 페이퍼 톤. §4.4 '순백을 정체성으로 쓰지 말 것' */
  --ink-mid: rgba(255, 255, 255, 0.70);
  --ink-mut: rgba(255, 255, 255, 0.45);
  --ink-faint: rgba(255, 255, 255, 0.28);

  --hair-soft: rgba(255, 255, 255, 0.05);
  --hair: rgba(255, 255, 255, 0.09);
  --hair-firm: rgba(255, 255, 255, 0.16); /* 서고에선 concrete-grey 실색 대신 흰 베일 */

  /* 다크 위 퍼플은 원색이 대비 부족 — dusty-purple로 승계 (정본 §1.1) */
  --action: var(--dusty-purple);
  --action-deep: var(--structure-purple);
}

/* 열람대 표면 — 서고 안에 놓인 시트가 종이 톤으로 복귀할 때 명시 선언 */
[data-surface="desk"] {
  --surface: var(--paper);
  --surface-card: #FFFFFF;
  --surface-recess: #F1EDE6;
  --surface-sunken: var(--sand-beige);

  --ink: var(--charcoal-black);
  --ink-mid: var(--charcoal-mid);
  --ink-mut: var(--clay-brown);
  --ink-faint: var(--stone-border);

  --hair-soft: rgba(43, 42, 40, 0.06);
  --hair: rgba(43, 42, 40, 0.11);
  --hair-firm: var(--concrete-grey);

  --action: var(--structure-purple);
  --action-deep: var(--structure-deep);
}

@theme inline {
  /* ── Font Family Override ── */
  --font-sans: 'Satoshi', 'SUIT Variable', sans-serif;
  --font-mono: 'Satoshi', 'SUIT Variable', sans-serif;

  /* 모든 transition-* 유틸의 기본 이징을 정본 단일 커브로 — 하드코딩 커브 이탈 방지 */
  --default-transition-timing-function: var(--jit-ease);

  /* ── BI Color Tokens ── */
  --color-structure-purple: var(--structure-purple);
  --color-structure-deep: var(--structure-deep);
  --color-dusty-purple: var(--dusty-purple);
  --color-pale-structure: var(--pale-structure);
  --color-oxidized-green: var(--oxidized-green);
  --color-deep-oxidized: var(--deep-oxidized);
  --color-pale-green: var(--pale-green);
  --color-slate-blue: var(--slate-blue);
  --color-deep-slate: var(--deep-slate);
  --color-brass-gold: var(--brass-gold);
  --color-brick-red: var(--brick-red);
  --color-earth-ochre: var(--earth-ochre);
  --color-charcoal-black: var(--charcoal-black);
  --color-charcoal-deep: var(--charcoal-deep);
  --color-charcoal-mid: var(--charcoal-mid);
  --color-cement-grey: var(--cement-grey);
  --color-concrete-grey: var(--concrete-grey);
  --color-sand-beige: var(--sand-beige);
  --color-clay-brown: var(--clay-brown);
  --color-stone-border: var(--stone-border);

  /* ── 표면·잉크 토큰 ──
     `@theme inline`이라 유틸이 var(--surface) 등을 직접 참조한다 →
     [data-surface] 조상에 따라 요소마다 다르게 해석된다(이게 순응의 핵심). */
  --color-surface: var(--surface);
  --color-surface-card: var(--surface-card);
  --color-surface-recess: var(--surface-recess);
  --color-surface-sunken: var(--surface-sunken);
  --color-ink: var(--ink);
  --color-ink-mid: var(--ink-mid);
  --color-ink-mut: var(--ink-mut);
  --color-ink-faint: var(--ink-faint);
  --color-hair-soft: var(--hair-soft);
  --color-hair: var(--hair);
  --color-hair-firm: var(--hair-firm);
  --color-action: var(--action);
  --color-action-deep: var(--action-deep);
  /* 열람대 글줄 폭 — 와이드 모니터에서 씬이 휑해 보이지 않게 상한을 올린다.
     1280(max-w-7xl)은 27" 이상에서 좌우가 크게 비었다. 카드 그리드는 이 폭 안에서 단이 늘어난다. */
  --sheet-max: 1680px;

  /* 서고 고정색 — 표면에 순응하지 않는 크롬 자체의 재질 */
  --color-vault: var(--charcoal-deep);
  --color-vault-rail: var(--charcoal-black);
  /* 페이퍼 실색 — 다크 위 밝은 텍스트/면. §4.4 "순백을 정체성으로 쓰지 말 것"의 대안 */
  --color-paper: var(--paper);

  /* ── Semantic Tokens ── */
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-warning: var(--warning);
  --color-warning-foreground: var(--warning-foreground);
  --color-warning-surface: var(--warning-surface);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-input-background: var(--input-background);
  --color-switch-background: var(--switch-background);
  --color-ring: var(--ring);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  /* ── 반경 4단 (design-system.md §6.1) ──────────────────────────
     계산식이 아니라 헌법 4단에 직접 고정한다. 계산식일 때의 실측값은
     sm 6 / md 8 / lg 10 / xl 14 / 2xl 16(TW 기본) 으로 4단 어디에도 없었다.
     sm·md·lg → 12(최소 단) · xl → 16 · 2xl → 20(§6.1 "시트·큰 카드·모달 20~24") · 3xl → 24. */
  --radius-sm: 12px;
  --radius-md: 12px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-2xl: 20px;
  --radius-3xl: 24px;
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);
}

/* ── 그림자 2단 (design-system.md §5b · 헌법 「숫자 세 벌」 세 번째 벌) ──────────
   【QA F#469 · 재희님 결정 2026-09-09】값은 종전 그대로이고, 이름이 생겼다.
   같은 그림자가 소스에 다섯 벌로 흩어져 있어서(짓피티 시트 · 공개 추적 카드 · 오류 폴백 ·
   출입 안내 · 씬 전환 칩) 한쪽만 고쳐지는 자리였다.

   ★ 두 쓰임이 다 있다: Tailwind 유틸(`shadow-sheet`·`shadow-card` — 시트·칩)과
     인라인 style(`var(--shadow-sheet)` — 오류 폴백 카드 · 출입 안내 HTML).
     이 블록이 위 색 토큰(`@theme inline`)과 **따로 서 있는 것은 읽는 사람을 위한 구분**이다.
     【반증 정정 2026-09-09】종전 주석은 「inline 이면 :root 변수가 안 나온다」고 적었는데,
     tailwindcss 4.1.12 실측으로는 **두 표기의 산출 CSS 가 같다**: 어느 쪽이든 (ㄱ) 유틸은
     값을 자기 선언에 박고(`--tw-shadow: 0 8px 40px var(--tw-shadow-color, …)`), (ㄴ) `:root`
     변수는 **소스에 이름이 참조될 때만**(=`var(--shadow-sheet)` 문자열이 스캔에 걸릴 때만)
     실린다. 즉 인라인 style 을 살리는 것은 블록 표기가 아니라 **그 문자열 그대로**다 —
     이름을 조립하면(`'var(--shadow-' + k + ')'`) 스캐너가 못 보고 그림자만 조용히 사라진다.
     그 두 가지를 `styles/theme.test.ts` 가 잠근다.
   ★ 표면에 순응하지 않는다(색 토큰과 다른 점): 그림자는 「무엇 위에 떠 있나」가 아니라
     「무엇이 떠 있나」로 고른다. 시트는 서고 위, 카드는 종이 위라 자리가 이미 갈린다. */
@theme {
  /* 서고 위에 뜬 종이 판 — 시트·모달·오류 폴백 카드 */
  --shadow-sheet: 0 8px 40px rgba(0, 0, 0, 0.45);
  /* ── 시트의 방향 변형 둘 (design-system.md §5b · QA F#573 · 재희님 결정 2026-09-11) ──
     크기·농도는 --shadow-sheet 와 같고 **방향만** 다르다. 방향이 뜻을 지는 자리에만 쓴다.
     등재된 방향은 위·왼쪽 둘뿐이다 — 오른쪽·아래 변형을 여기 새로 뽑지 않는다(정본 개정이 먼저).
     ★ Tailwind 유틸 `shadow-sheet-up`·`shadow-sheet-left` 와 `:root` 변수를 함께 낸다.
       인라인 style 소비처는 `var(--shadow-sheet-up)` 처럼 이름을 **문자열 그대로** 적어야
       스캐너가 보고 `:root` 에 변수가 실린다(조립 금지 — 위 ★ 주석과 같은 함정). */
  /* 아래에서 올라오는 시트(앱 바텀시트) — 위쪽 내용과 갈린다 */
  --shadow-sheet-up: 0 -8px 40px rgba(0, 0, 0, 0.45);
  /* 오른쪽에서 들어온 판(시트 씬 열람대·우측 드로어) — 왼쪽 농도층 위로 */
  --shadow-sheet-left: -8px 0 40px rgba(0, 0, 0, 0.45);
  /* 종이 위에 놓인 카드·칩 — 잉크 톤 두 겹(회색으로 뜨지 않는다) */
  --shadow-card: 0 1px 2px rgba(43, 42, 40, 0.04), 0 5px 18px rgba(43, 42, 40, 0.06);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }

  body {
    @apply bg-background text-foreground;
    font-family: 'Satoshi', 'SUIT Variable', sans-serif;
  }

  /**
  * Default typography styles for HTML elements (h1-h4, p, label, button, input).
  * These are in @layer base, so Tailwind utility classes (like text-sm, text-lg) automatically override them.
  */

  html {
    font-size: var(--font-size);
    /* macOS에서 의도보다 굵게 렌더되는 것 방지 — 9~13px 고밀도 타이포일수록 체감 큼 */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }

  h1 {
    font-size: var(--text-2xl);
    font-weight: var(--font-weight-medium);
    line-height: 1.5;
  }

  h2 {
    font-size: var(--text-xl);
    font-weight: var(--font-weight-medium);
    line-height: 1.5;
  }

  h3 {
    font-size: var(--text-lg);
    font-weight: var(--font-weight-medium);
    line-height: 1.5;
  }

  h4 {
    font-size: var(--text-base);
    font-weight: var(--font-weight-medium);
    line-height: 1.5;
  }

  label {
    font-size: var(--text-base);
    font-weight: var(--font-weight-medium);
    line-height: 1.5;
  }

  button {
    font-size: var(--text-base);
    font-weight: var(--font-weight-medium);
    line-height: 1.5;
  }

  input {
    font-size: var(--text-base);
    font-weight: var(--font-weight-normal);
    line-height: 1.5;
  }

  /* 입력칸 글자색 — body 가 text-foreground(=#fff) 라 입력 요소는 상속으로 '흰 글자'가 된다.
     컴포넌트들은 각자 text-charcoal-black 을 붙여 빠져나갔지만 input/textarea/select 는
     아무도 안 붙여, 흰 필드에 흰 글자 = 타이핑한 값이 안 보였다
     (마이프로젝트 수량 칸에서 실제로 발견, 2026-08-08).
     --ink 는 표면 순응 토큰이라 열람대=차콜 / 서고=페이퍼로 알아서 뒤집힌다.
     개별 text-* 유틸은 base 레이어를 이기므로 기존 지정은 그대로 산다. */
  input,
  textarea,
  select {
    color: var(--ink);
  }

  input::placeholder,
  textarea::placeholder {
    color: var(--ink-mut);
  }
}

/* Force Satoshi/SUIT on Figma imports that hardcode Pretendard */
[class*="font-['Pretendard"] {
  font-family: 'Satoshi', 'SUIT Variable', sans-serif !important;
}

/* Noise texture for material cards */
.noise-texture {
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
}
/* ── 시트 내부 스크롤 섀도 — 스크롤 시 상단에만 얕은 그림자 (미스트 대체) ── */
/* OS 다크모드(prefers-color-scheme) 미대응은 의도된 결정(2026-07-11 점검) —
   서고(다크 크롬) + 열람대(종이)의 2재질 구조가 자체 명암 체계를 가지므로 시스템 테마를 따르지 않음.
   아래 color-scheme 은 그 결정의 선언이다 — 없으면 삼성인터넷 강제 다크가 폼 컨트롤을
   어둡게 재도색해 수량 인풋 숫자가 사라졌다(2026-08-13 QA #6, 갤럭시/삼성인터넷)

   ★ `light` 가 아니라 `only light` 여야 한다(2026-08-15). `light` 는 "이 페이지는 라이트 전용"
     이라는 신고일 뿐이고, 크로미엄 Auto Dark Theme·삼성인터넷 다크 모드가 **강제로 어둡게
     칠하는 대상이 바로 그 페이지**다. UA 재도색을 금지하는 키워드는 `only` 다
     (developer.chrome.com/blog/auto-dark-theme — "opt-out ... color-scheme: only light").
     즉 위 QA #6 주석이 적어 둔 의도는 지금까지 절반만 걸려 있었다. */
:root { color-scheme: only light; }
/* ── 모션 토큰 — 마이크로 150ms / 씬·오버레이 400ms · 단일 이징 ── */
:root {
  --jit-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --jit-dur-micro: 150ms;
  --jit-dur-scene: 400ms;
}
/* 카드 호버 리프트 — 공용 마이크로 인터랙션.
   【QA F#520 · 2026-09-10】hover 에 `0 10px 28px rgba(0,0,0,.1)` 을 얹던 자리다. 그 값은
   §5b 두 토큰(시트·카드) 어느 쪽도 아니어서 「그림자 세 번째 벌」이 몰래 서 있는 자리였다.
   들어올린 사실은 **거리**(translateY −2px)가 이미 말하므로 그림자를 뺐다 —
   §1-8 「선이 아니라 면·거리·명도」. 카드 자신의 `shadow-card` 는 그대로 남는다. */
.jit-lift {
  transition: transform var(--jit-dur-micro) var(--jit-ease);
}
.jit-lift:hover {
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .jit-lift, .jit-lift:hover { transition: none; transform: none; }
}

/* ── 프레스 피드백 — "뗄 때"가 아니라 "누르는 순간" 반응 (Apple: Response) ──
   모든 버튼에 기본 적용. 미세한 축소(0.98)라 카드형 버튼에도 안전.
   개별 컴포넌트의 active:scale-*가 있으면 그 값이 우선(더 구체적)이다. */
@layer base {
  button:not(:disabled) {
    /* scale 포함 — Tailwind v4의 active:scale-* 유틸이 네이티브 scale 속성을 쓰므로
       base도 같은 속성을 써야 "개별 값 우선"이 성립(transform이면 곱으로 겹침) */
    transition-property: transform, scale;
    transition-duration: 100ms;
    transition-timing-function: var(--jit-ease);
  }
  button:not(:disabled):active {
    scale: 0.98;
  }
}
/* 비-버튼 프레스 요소용 (role=button div, 행 등) */
.jit-press { transition: transform 100ms var(--jit-ease), scale 100ms var(--jit-ease); }
.jit-press:active { scale: 0.98; }

/* ── 재질화 진입 — 모달/시트가 '나타나는' 게 아니라 '놓이는' 감각 (Apple: Materialize) ──
   블러+스케일이 함께 풀리며 실물이 도착한다. 단순 opacity 페이드 대체. */
@keyframes jit-materialize {
  from { opacity: 0; transform: translateY(14px) scale(0.97); filter: blur(6px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
.jit-materialize { animation: jit-materialize var(--jit-dur-scene) var(--jit-ease) both; }
@media (prefers-reduced-motion: reduce) {
  .jit-materialize { animation: none; }
}

/* ── 팔레트 담김 알림 토스트 — 공용 한 벌 (QA F#559 → F#575 ⑧ 로 이사) ─────────
   `showPaletteDropToast()`(app/components/jit-layout.tsx)가 body 에 붙이는 클래스다.
   ⚠️ 종전 자리는 JitLayout 의 스코프 `<style>` 블록이었다. 그 함수는 레이아웃 밖에서도
      불리는데(레일 드롭존·하단 드롭바는 앱 셸 쪽에서도 산다), JitLayout 이 안 그려진
      문서에는 규칙 자체가 없어 같은 알림이 **무양식 검은 글자**로 떴다. 그래서 전역 정본인
      이 파일로 옮겼다 — 값은 한 글자도 바꾸지 않았다(F#559 의 「옮기기만 한다」 그대로).
   면은 행동색 토큰 · 그림자는 §5b 정본 토큰 · 반경은 4단의 12 · 크기는 사다리의 14. */
@keyframes fadeInOut {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
  10% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  90% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
}
.jit-copy-toast {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--action);
  color: #FFFFFF;
  padding: 12px 24px;
  border-radius: 12px;
  font-size: 14px;
  font-weight: 700;
  z-index: 9999;
  box-shadow: var(--shadow-sheet);
  animation: fadeInOut 2s forwards;
}

/* 포인터 드래그 중 드롭존 호버 — 배급색 퍼플로 '받을 준비' 표시 */
.jit-dropzone-hot {
  outline: 2px solid var(--structure-purple);
  outline-offset: 2px;
  border-radius: 14px;
}

/* ── reduced-motion — 임의 키프레임 진입 애니메이션 전면 강등 (Apple: 접근성) ──
   animate-[fadeIn_…] 류의 arbitrary 키프레임만 표적(브래킷 클래스).
   animate-spin(로딩)·animate-pulse는 미표적 — 스피너는 상태 표시라 유지. */
@media (prefers-reduced-motion: reduce) {
  [class*="animate-["] {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
  }
  button:not(:disabled):active, .jit-press:active { transform: none; scale: none; }
}

.jit-sheet-scroll {
  background:
    linear-gradient(#FAF9F7 30%, rgba(250, 249, 247, 0)) top / 100% 40px,
    radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, 0.10), transparent) top / 100% 14px;
  background-repeat: no-repeat;
  background-attachment: local, scroll;
  background-color: #FAF9F7;
}

/* 화이트 패널용 스크롤 엣지 — 하드 디바이더 대신, 스크롤됐을 때만 드리우는 얕은 그늘 */
.jit-scroll-edge {
  background:
    linear-gradient(#ffffff 30%, rgba(255, 255, 255, 0)) top / 100% 40px,
    radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, 0.08), transparent) top / 100% 12px;
  background-repeat: no-repeat;
  background-attachment: local, scroll;
  background-color: #ffffff;
}

/* 스크롤바 감추기 — 시트·칩 레일의 스크롤 어피던스는 .jit-sheet-scroll /
   .jit-scroll-edge 의 스크롤 그늘이 담당하므로 막대 자체는 감춘다.
   2026-08-08 이전까지 정의 없이 컴포넌트 <style> 9곳에 흩어져 있었다 —
   그 컴포넌트가 마운트된 동안만 우연히 먹던 클래스라 여기로 일원화. */
.scrollbar-hide {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.scrollbar-hide::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

/* ── 모바일 기초 (apple-design 점검 2026-07-17) ─────────────────────
   앱 셸에서 웹 기본 동작이 '앱답지 않게' 새는 지점들을 막는다.
   ★ 조건은 lib/viewport.ts MOBILE_SHELL_MEDIA_QUERY 와 같은 뜻이다(QA F#141) —
     눕힌 폰에서 셸만 모바일이고 이 기초가 빠지면 인풋 포커스 줌·사진 들림이 되살아난다.
     콤마 목록인 이유도 그쪽과 같다(모르는 절만 버려지고 폭 규칙은 남는다). */
@media (max-width: 767px), (max-height: 500px) and (orientation: landscape) and (pointer: coarse) {
  html, body {
    /* 브라우저 네이티브 당겨서-새로고침·바운스 체이닝 차단 — 앱 자체 PTR과 충돌 방지 */
    overscroll-behavior-y: none;
    /* 가로 회전 시 iOS 자동 폰트 확대 방지 */
    -webkit-text-size-adjust: 100%;
  }
  /* iOS: 16px 미만 인풋 포커스 시 강제 줌 방지 — 모바일 인풋은 16px 고정 */
  input[type="text"], input[type="email"], input[type="password"], input[type="search"],
  input[type="tel"], input[type="number"], input:not([type]), textarea, select {
    font-size: 16px;
  }
  /* 자재 이미지 롱프레스 시 iOS 저장 시트가 제스처를 끊는 것 방지.
     ★ 이 속성은 WebKit(사파리·iOS) 전용이다. Blink(안드로이드 크롬·삼성인터넷)에는
       아예 없다 — 2026-08-17 재실측(Chromium 148):
         CSS.supports('-webkit-touch-callout','none')  → false
         el.style.setProperty('-webkit-touch-callout','none') 뒤 getPropertyValue → ''
       선언이 파서 단계에서 버려지므로 이 줄은 안드로이드에서 아무 일도 하지 않는다.
       그래서 갤럭시·삼성인터넷에서는 사진을 길게 누르면 저장 메뉴가 그대로 뜬다.

     그건 사양이다 (QA F#12, 2026-08-16 갤럭시·삼성인터넷 리포트).
       · 모바일에서 이미지 저장을 완전히 막는 방법은 없다 — 스크린샷·직접 URL 로 우회된다.
         자재 사진은 파트너 원본 URL 을 <img src> 로 그대로 물리므로 로그인과 무관하게 받아진다.
       · 데스크톱 우클릭은 애초에 열려 있다(제품 코드에 contextmenu 차단 0건).
         모바일만 막으면 지키는 것 없이 플랫폼만 어긋난다.
       · 자재 사진을 저장해 고객에게 보내는 건 우리 카탈로그가 퍼지는 일이다.
         사진에는 파트너명·공급가가 없다(그 두 값은 DB 레벨에서 잠겨 있다).
       · 넓게 막으면 정상 동작이 죽는다 — 이미지 주소 복사·새 탭 열기, 그리고
         user-select 까지 손대면 자재코드·규격 텍스트 선택 복사가 함께 죽는다.
     → 여기에 contextmenu 차단을 새로 넣지 말 것. '저장 금지'는 우리 목표가 아니다.

     ※ 다만 "길게 누르기"가 저장이 아닌 다른 뜻을 가진 자리는 다르다. 팔레트 카드
       재정렬(moodboard-grid: TouchBackend delayTouchStart 180ms, ignoreContextMenu)이
       그 자리이고, 그 카드의 <img> 에는 indib-raw-mode 가 이미 쓰고 있는
       draggable={false} 같은 좁은 가드가 없다. 안드로이드에서 저장 메뉴가 끌기를
       가로채는지는 갤럭시 실기기에서만 확인할 수 있어 아직 실측하지 못했다.
       확인되면 전역이 아니라 그 카드에만 좁게 걸 것. */

  /* ★ -webkit-user-drag: none 은 위 callout 과 짝이다 (2026-08-19 QA F#143, 이현석·아이폰).
       WebKit 에서 <img> 는 기본적으로 드래그 소스다. touch-callout 을 끄면 저장 시트가
       안 뜨는 대신, 길게 눌러 움직였을 때 **사진이 손가락을 따라 들리는**(drag lift)
       동작만 남는다 — 신고 문구 그대로다. 저장 메뉴도 없고 들리지도 않는 상태가
       라이브러리(indib-raw-mode 는 <img draggable={false}>)의 동작이므로, 화면마다
       갈리던 것을 이 한 줄로 맞춘다(QA 기대: "두 화면의 동작이 서로 같아야 한다").
       · 데스크톱 드래그-담기는 영향 없다 — 이 블록은 좁은 화면 전용이다.
       · 팔레트 카드 재정렬(TouchBackend)은 네이티브 드래그가 아니라 touch 이벤트라
         이 속성과 무관하다. 오히려 재정렬 중 사진이 함께 들리던 것이 사라진다.
       · 저장 자체를 막는 규칙이 아니다(위 정책 그대로) — 주소 복사·새 탭 열기는
         Blink 에서 그대로 살아 있고, 아이폰도 공유 시트·스크린샷 경로는 열려 있다. */
  img { -webkit-touch-callout: none; -webkit-user-drag: none; }
}
/* ── 수량 입력 — number 스피너 제거 ────────────────────────────────
   폭이 좁은 수량칸(44px)에서 크롬 스피너가 오른쪽을 먹어 text-center 가
   왼쪽으로 밀려 보인다. 증감은 옆의 -/+ 버튼이 담당하므로 스피너는 불필요. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

/* 탭 하이라이트 회색 플래시 제거 — 프레스 피드백은 :active scale이 담당 */
* { -webkit-tap-highlight-color: transparent; }

/* ── 채널톡 메신저 안전영역 (QA F#28) ─────────────────────────────────
   메신저 본체는 #ch-plugin-script > iframe#ch-plugin-script-iframe **안에서** 렌더된다 —
   그 문서 안에는 우리 CSS 를 넣을 수 없고, 거기서는 env(safe-area-inset-*) 도 0 이다.
   그래서 부모 문서 쪽 컨테이너인 SDK **공개 루트**(#ch-plugin — 로더 스크립트가 우리 문서에
   직접 만드는 상자)를 안전영역만큼 좁혀, 안쪽 iframe 래퍼(inset:0)가 따라 들어오게 한다.
   두께는 lib/channel-talk-safe-area.ts 가 실측해 채우고, 메신저가 떠 있는 동안만 켠다.
   근거·계산은 그 파일 머리말 참조. 내부 클래스명에는 절대 걸지 않는다(빌드 해시라 계약이 아니다).

   ★ padding 이 아니라 border 인 이유: 절대·고정 자손의 컨테이닝 블록은 조상의 padding box 다.
     padding 으로 잡으면 안쪽 전체화면 레이어(position:fixed; inset:…)가 한 픽셀도 안 밀린다.
     border 는 padding box 자체를 줄이므로 레이어가 따라 들어오고, 그 띠에 색까지 칠해진다.
     색은 플러그인 경로가 상태바에 칠하는 값과 같은 페이퍼 톤이다(native-app.ts) —
     두 경로가 같은 화면을 서로 다른 색으로 만들지 않게.

   ★★ !important 인 이유 (2026-08-25 실측 · 지우지 말 것):
     채널톡 코어가 부모 문서에 `#ch-plugin{position:relative !important; display:block !important;}`
     를 주입한다. 우리 비-important 선언은 특이도가 더 높아도 그 !important 에 진다 —
     종전 규칙에서 살아남은 것은 border·contain·z-index·pointer-events 뿐이었고,
     상자는 화면 전체를 덮지 않아 안쪽 fixed 레이어(inset:0)가 밀리지 않았다.
     그래서 기하 세 줄만 우리도 !important 로 올린다(display 는 건드리지 않는다 —
     코어가 숨기려 할 때 우리가 억지로 띄우면 유령 상자가 남는다).
     height 를 auto 로 못박는 것도 같은 이유다: 코어의 `#ch-plugin{height:100%}` 가 남으면
     top·bottom·height 가 동시에 지정돼 bottom 이 무시되는 과제약 상태가 된다. */
html.jit-channel-safe #ch-plugin {
  position: fixed !important;
  inset: 0 !important;
  height: auto !important;
  box-sizing: border-box !important;
  border-top: var(--jit-channel-safe-top, 0px) solid #FAF9F7;
  border-bottom: var(--jit-channel-safe-bottom, 0px) solid #FAF9F7;
  /* 안쪽 fixed 레이어의 컨테이닝 블록이 되게 한다.
     레이아웃 컨테인먼트는 transform 과 달리 글자를 흐리지 않는다. */
  contain: layout;
  /* contain 이 스태킹 컨텍스트를 만들어 안쪽 z-index 가 밖으로 못 나간다 —
     루트가 대신 최상단을 잡는다(메신저는 우리 시트 z-10001·탭바 z-9998 위여야 한다). */
  z-index: 2147483000;
  /* ★ 이 층의 실패 모드를 '미관'에서 내려놓는 안전핀 — 지우지 말 것.
     위 세 줄은 #ch-plugin 을 화면 전체(inset:0)를 덮는 최상단 상자로 만든다. 메신저가
     닫히는 순간 클래스가 걷히면 아무 일도 없지만, 한 번이라도 걷히지 않으면(clear 경로가
     안 불리는 언마운트·스크립트 예외·SDK 가 루트를 비운 채 남기는 경우) **보이지도 않는
     빈 상자가 앱 전체의 탭을 삼킨다** — 겹침 미관 문제가 전면 먹통으로 격상된다.
     그래서 루트 자신은 포인터를 받지 않고, 자식(=메신저가 실제로 그린 것)에서만 되살린다.
     pointer-events 는 상속되므로 자식에 auto 를 주면 그 아래 전체가 정상 동작한다.
     ★ 근거를 「루트가 비어 있으면 되살릴 자식이 없다」로 적어 두었던 것은 실물과 다르다
       (2026-08-26 로더 배포본 대조 정정). ch-plugin-web.js 는 `#ch-plugin.innerHTML +=` 로
       #ch-plugin-entry 와 #ch-plugin-script 를 **영구히** 심으므로 `#ch-plugin > *` 는
       언제나 그 둘을 잡아 auto 를 준다 — 루트가 비는 일은 없다.
       그래도 통과하는 진짜 이유는 그 둘이 **차지하는 면적이 없다**는 것이다:
       닫힌 메신저의 래퍼(#ch-plugin-script)는 코어가 visibility:hidden(+display:none)으로
       두므로 포인터를 안 먹고, entry 의 라이트 DOM 자식은 open shadow host 하나뿐이라
       높이가 0 이다. 즉 안전핀은 '자식이 없어서'가 아니라 '남은 자식이 안 보이거나
       0 높이라서' 성립한다 — 다음 사람이 잘못된 이유로 이 줄을 지우지 않도록 적어 둔다. */
  pointer-events: none;
}
html.jit-channel-safe #ch-plugin > * {
  pointer-events: auto;
}

html:has(.public-document),body:has(.public-document){height:auto;overflow:auto} .public-document{font-family:Satoshi,"SUIT Variable",sans-serif;background:var(--paper);color:var(--charcoal-black);padding:32px;max-width:960px;margin:0 auto;min-height:100vh;line-height:1.8}.public-document h1{font-size:32px;line-height:1.3;margin:24px 0;font-weight:700}.public-document h2{font-size:24px;margin:32px 0 16px}.public-document h3{font-size:20px;margin:24px 0 12px}.public-document p,.public-document ul,.public-document ol{margin:16px 0}.public-document ul,.public-document ol{padding-left:24px}.public-document a{color:var(--structure-purple);text-decoration:underline}.public-document img{max-width:100%;height:auto;object-fit:contain}.public-document dd{margin:0 0 16px}.public-document dt{font-weight:700}.public-document footer{margin-top:32px}.public-document .material-upscale-note{font-size:10px;text-decoration:underline;white-space:nowrap;overflow-x:auto}.public-document figure{margin:16px 0}.public-document figure img{display:block}.public-document figcaption{margin-top:8px;font-size:11px;color:var(--ink-mid)}@media(max-width:640px){.public-document{padding:24px}.public-document h1{font-size:24px}}