/* Vinc 사이트 공용 레이어 — 토큰 정본은 /assets/tokens.css (Vinc-design 허브 생성물).
   이 파일은 수기 유지: 레거시 별칭 + 사이트 공통 보정만 담는다. */

/* 브라우저 기본값은 <button>=border-box, <a>/<div>=content-box 로 서로 다르다.
   구 페이지들은 각자 로컬 <style>에 *{box-sizing:border-box}를 갖고 있었는데,
   신 아키텍처(vinc-site.js/gnb.js) 이식 때 그 로컬 스타일 블록을 통째로 지우면서
   이 한 줄만 어디에도 안 남았다(index.html 만 자기 인라인 스타일에 개별로 갖고 있음).
   그 결과 같은 padding/width:100%로 짝지은 <button>+<a> 버튼 쌍이 서로 다른 폭으로
   렌더된다(실측: /unsubscribe 확인 카드, 2026-08-14). 사이트 전역에서 한 번만. */
*{box-sizing:border-box}

/* 모션 토큰(--ease-* · --dur-*)은 tokens.css(허브 생성물, SSoT 는 Vinc/design-hub 의
   design-tokens.json motion 그룹)에서 온다. 여기서 다시 정하지 않는다. 등장·퇴장·호버
   이동은 --ease-out, 화면 위 이동은 --ease-in-out. 호버의 색 변화는 기본 ease. */
/* 테마 전환: 토글 직후 280ms 동안만 색을 잇는다(gnb.js 가 html.theming 을 붙였다 뗀다).
   평소에 색 전이를 켜 두면 모든 호버가 굼떠지므로 그 순간에만 연다. */
html.theming, html.theming *, html.theming ::before, html.theming ::after {
  transition: background-color .22s ease, color .22s ease, border-color .22s ease, fill .22s ease !important;
}
/* 누르면 눌린다: 버튼꼴은 :active 에서 살짝 줄어든다. 카드의 호버 리프트(!important)가
   있는 곳은 그쪽이 이기므로 자연히 빠진다. transform 전이는 각 요소가 자기 transition 에 싣는다. */
/* style-hover 가 없는 버튼꼴(히어로 CTA 등)도 누름이 부드럽게 돌아오도록 기본 전이를 준다.
   인라인 transition 이 있는 요소는 그쪽(호버 컴파일러가 transform 을 함께 싣는다)이 이긴다. */
button:not([disabled]), [role="button"], a[style*="inline-flex"][href] { transition: transform var(--dur-fast) var(--ease-out); }
button:not([disabled]):active, [role="button"]:active, a[style*="inline-flex"][href]:active { transform: scale(.97); }

/* ── Pretendard @font-face ────────────────────────────────────────────────
   2026-08-26 에 assets/fonts.css 에서 여기로 합쳤다. **정본은 site.css 여야 한다** —
   fonts.css 를 <link> 한 페이지가 index.html 하나뿐이라 나머지 31쪽이 전부 조용히
   폴백(Windows: Segoe UI + Malgun Gothic)으로 그려지고 있었다. tokens.css 의
   --font-ui 는 'Pretendard' 를 1순위로 부르는데 @font-face 가 없으니 아무 일도
   안 일어난다: 오류도 경고도 없이 글자만 다르다. 그래서 홈에서 나가고 들어올 때마다
   letterform 이 바뀌었다(발주자 표현: "딱딱하게 넘어간다").
   site.css 는 **모든 페이지가 이미 로드한다** — 그래서 31쪽에 <link> 를 더하는 대신
   여기로 옮겼다. @import 가 아닌 것은 그게 render-blocking 요청 사슬을 한 칸 늘리기
   때문이다(site.css 를 받아야 fonts.css 를 시작한다).
   ⚠ url() 은 이 파일 기준 상대경로다 — site.css 와 fonts.css 가 같은 /assets/ 에
   있었으므로 경로는 그대로 유효하다. 이 파일을 옮기면 폰트가 404 난다.
   출처: node_modules/pretendard 의 단일파일 woff2 서브셋(SIL OFL). "dynamic subset"
   변형이 아니다 — 그건 CDN 엣지 캐시용으로 굵기마다 ~90개 unicode-range 파일로
   쪼개는 것이라 self-host 에서는 얻는 게 없다. Signal 디자인 시스템과 같은 4굵기. */
@font-face {
  font-family: 'Pretendard';
  src: url('fonts/Pretendard-Regular.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Pretendard';
  src: url('fonts/Pretendard-Medium.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Pretendard';
  src: url('fonts/Pretendard-SemiBold.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Pretendard';
  src: url('fonts/Pretendard-Bold.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}

/* ── 페이지 전환(MPA View Transitions) — 켜되, 두 상황에서는 건너뛴다 ────
   사이트는 전체 리로드형 MPA 다. 이 한 줄이면 브라우저가 옛 화면 스냅샷과 새 화면을
   크로스페이드한다(실측: /pricing 재방문 load 39ms — 느린 게 아니라 이어지는 게 없었다).
   ⚠ **양쪽 문서가 모두 opt-in 해야 발동한다.** site.css 에 두는 이유가 그것이다 —
   모든 페이지가 이 파일을 로드하므로 사이트 안의 모든 이동이 자동으로 짝을 이룬다.
   ⚠ reduced-motion 을 UA 가 알아서 빼 주지 않는다 — @media 로 감싸면 규칙 자체가 없는
   것이 되어 전환이 아예 안 만들어진다(::view-transition-* 에 animation:none 을 거는
   것보다 확실하다: 그쪽은 애니메이션만 죽이고 스냅샷 합성은 그대로 돌아 첫 프레임이 튄다).

   ⚠ 이력. 2026-09-01 에 카카오톡·인스타그램 인앱 브라우저에서 같은 출처 이동 뒤 화면이
   검은 채로 멎는 보고가 왔고(안드로이드·iOS 둘 다, 첫 로드는 멀쩡), 그때 이 줄을 통째로
   걷었다(#61). 그 판정은 커밋이 스스로 적었듯 **재현이 아니라 소거**였고, 대가는 모든
   브라우저에서 크로스페이드가 사라지는 것이었다. 지금은 옵트인을 두고 **assets/gnb.js 가
   그 두 상황에서만 전환을 건너뛴다** — 알려진 인앱 브라우저, 그리고 창이 가려져 있을 때
   (가려진 문서는 애니메이션 프레임을 못 받아 전환이 못 끝나고, 그 모양이 "검은 채로
   멎는" 것이다). pagereveal 에서 skipTransition() 하면 이 줄이 막고 있던 새 문서의 첫
   페인트가 즉시 풀린다. 그래서 이 줄과 gnb.js 의 안전판은 **한 쌍**이다 —
   scripts/gate_copy.mjs 의 vt-guard 규칙이 둘 중 하나만 남는 것을 막는다.
   실기기에서 확인하려면 /vt-check/ 가 **안전판 없는** 대조군이다(gnb.js 가 그 경로에서는
   건너뛰기를 하지 않는다): 거기서 검게 멎고 본 사이트에서는 안 멎으면 안전판이 일하는 것이다. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}

:root {
  /* 레거시 그림자 별칭 — 구 페이지 CSS가 참조. 신규 작성은 --shadow-elev-* 사용 */
  --shadow-2: var(--shadow-elev-2);
  --shadow-3: var(--shadow-elev-3);
}

/* ── 환경 배경(PageBackground) ───────────────────────────────────────────
   4레이어: (1) 오로라 4블롭 · (2) 하프톤 도트(canvas) · (3) 스포트라이트 ·
   (4) 필름 그레인. 외부 리소스 0.
   ⚠ 그레인은 canvas 를 toDataURL 로 굳혀 쓰므로 **data: URI 를 만든다.** 그래서
   CSP 에 `img-src 'self' data:` 가 반드시 있어야 한다. 이 주석이 예전에
   "data: URI 0" 이라고 적혀 있었고 CSP 가 그 말을 믿고 쓰여, 배포에서만
   그레인이 차단됐다(2026-08-17). 로컬 정적 서버는 CSP 헤더를 안 보내서 보였다.

   ⚠ **이 파일에는 배치 값이 없다.** 레이어의 위치·색·알파는 페이지 마크업의
   인라인 style 이 쥐고, 도트와 그레인 타일은 assets/vinc-site.js 가 그린다
   (랜딩은 자기 인라인 복사본을 쓴다). 여기 남은 것은 **인라인이 못 쥐는 것 하나**,
   그레인의 블렌드 모드뿐이다.

   2026-08-17 까지는 여기에 `.bg` · `.bg-aurora` · `.bg-halftone` · `.bg-spotlight`
   · `.bg-grain` 로 된 **두 번째 체계**가 통째로 있었다. 어느 HTML 도 그 클래스를
   쓰지 않았고(정적 사용 0 · JS 동적 삽입 0) 값도 이미 갈라져 있었다(그레인 .30/.48
   대 .08/.14). 죽은 채로 남아 있던 것이 실제로 해를 끼쳤다 — 그 블록이 올바른
   `mix-blend-mode` 를 갖고 있어서, 사람도 도구도 "블렌드는 되어 있다"고 읽었고
   정작 화면이 타는 경로에는 없었다. **읽히지 않는 코드가 아니라 잘못 읽히는 코드였다.**
   그래서 지운다. 지운 것 중 되살릴 것이 있다면 그때 정본(Vinc Homepage.dc.html)에서
   가져오지, 이 파일에서 꺼내지 않는다.

   함께 사라진 것 하나를 적어 둔다: `.bg-aurora` 에 걸려 있던 `bgdrift` 60초 드리프트
   애니메이션. 정본에는 없고(확인함) 살아 있는 오로라도 정지 상태라, 은퇴한 것이지
   유실이 아니다.

   **어느 페이지가 배경을 쓰는가**는 Figma 정본이 정한다 — PageBackground 인스턴스는
   마케팅 7프레임(랜딩·다운로드·프라이싱·커뮤니티·릴리스·프라이버시·404)에만 꽂혀 있다.
   문서(/docs)는 같은 재구축 패스에서 16프레임을 손보고도 **일부러 안 넣었다** —
   읽기 표면이라 배경을 얹지 않는 것이 정본이다. 여기에 임의로 추가하지 말 것.
   레이어는 각각 독립 요소라, 페이지가 원하지 않는 층은 마크업에서 빼면 된다
   (예: /welcome 은 오로라+그레인 2층만 쓴다). */

/* (4) 필름 그레인의 블렌드 — 무채색 노이즈는 아래 층과 섞여야 종이 결로 읽힌다.
   블렌드가 없으면 결이 아니라 회색 베일이고, 그 상태에서 농도를 올려도 뿌옇기만
   진해진다(2026-08-17 실측, 발주자 지적).

   블렌드를 CSS 가 쥐는 이유: 마크업의 인라인 style 이 opacity 를 쥐고 있어 농도는
   여기서 못 이기지만, mix-blend-mode 는 인라인에 없어 클래스 규칙으로 걸린다.
   그러면 페이지마다 갈릴 여지가 없다 — JS 로 넣으면 또 갈린다.
   농도(다크 .14 · 라이트 .08)는 vinc-site.js 가 계속 쥔다. 둘은 한 쌍이니
   한쪽만 고치지 말 것. 배경 컨테이너의 isolation:isolate 가 이 블렌드를 본문
   텍스트로 새지 않게 가둔다(그 속성은 마크업의 인라인에 있다). */
/* (4) 필름 그레인의 블렌드 — **질감을 만드는 것은 노이즈가 아니라 블렌드다.**
   무채색 난수를 그냥 얹으면 뿌연 베일이고, 아래 오로라·도트와 섞여야 결이 된다.
   라이트 soft-light(밝은 곳은 살리고 어두운 곳만 눌러 종이결) · 다크 overlay
   (어두운 바탕의 대비를 키워 입자감). 배경 컨테이너의 isolation:isolate 가
   블렌딩을 배경 레이어 안에서 끝내 본문 텍스트로 번지지 않게 한다.

   ⚠ 2026-08-17 에 제가 이걸 한 번 뺐다. 평평한 배경 한 점에서 결과 표준편차만
   재서 "블렌드가 결을 죽인다"고 판단했는데, 그 수치는 **변동 폭**이지 결로
   읽히는지가 아니다. 편차가 큰 균일 노이즈는 결이 아니라 먼지로 읽힌다.
   숫자로 디자인을 반박하지 말 것 — 이 층은 지각의 문제다.

   농도는 vinc-site.js 가 쥔다(다크 .14 · 라이트 .08). 이 값은 예전 SVG
   feTurbulence 체계의 .30/.48 과 **의도적으로 다르다**: 픽셀 단위 고주파 난수가
   같은 수치에서 훨씬 세게 읽혀 낮춰 잡은 값이다. 두 체계의 값을 맞추지 말 것.
   (그 SVG 체계는 어느 페이지도 그리지 않아 2026-08-17 에 제거했다 — feTurbulence
   는 이제 주석에만 남아 있다.) */
[data-bg-grain]{mix-blend-mode:soft-light}
[data-theme="dark"] [data-bg-grain]{mix-blend-mode:overlay}

/* ── 상단 GNB 자리 예약 ─────────────────────────────────────────────────
   GNB 마크업은 [[공유 GNB 단일소스]] 규율대로 assets/gnb.js 한 곳에서만 나온다
   (페이지에 헤더를 다시 인라인하지 말 것 — 예전 3중 중복·드리프트의 원인).
   그런데 그 스크립트는 defer 라 문서 파싱이 끝난 뒤에 돈다. 큰 페이지는 그 전에
   이미 한 프레임을 그리고, 그 뒤 헤더가 body 맨 앞에 끼어들며 본문 전체를
   3.25rem 밀어냈다(실측: /pricing CLS 0.024, 시프트 소스 .wrap).

   그래서 **자리는 CSS 가 먼저 비우고**, 스크립트는 비워 둔 자리에 fixed 로 들어간다.
   스크립트가 언제 돌든 레이아웃은 한 번도 움직이지 않는다.

   ⚠ 앱(web skin)은 이 규칙 밖이다 — app.css 의 #shell{height:calc(100vh - 3.25rem)}
   가 "GNB 가 흐름을 차지한다"는 전제 위에 서 있어서, gnb.js 는 isApp 일 때만
   sticky(in-flow) 를 쓴다. 앱은 site.css 를 로드하지 않으므로 이 padding 도 안 닿는다.
   ⚠ 높이 3.25rem 은 gnb.js 의 .gnb-inner 와 app.css 세 곳이 함께 쓰는 값이다.
   ⚠ +1px 은 헤더의 border-bottom 이다. 이걸 빼면 본문 첫 줄이 테두리 밑으로
   1px 들어간다(실측: 헤더 실높이 = inner 52px + border 1px). */
/* ── 캔버스 색 ─────────────────────────────────────────────────────────────
   페이지의 어두운 색이 **`position:fixed` 배경 레이어 하나에서만** 나오고 있었다.
   `html` 도 `body` 도 배경이 `rgba(0,0,0,0)` 이라(실측 2026-08-27), 그 레이어가
   잠깐이라도 보이는 영역을 못 덮으면 브라우저 기본 캔버스가 그대로 드러난다 —
   흰색이다. 데스크탑에서는 늘 덮으니 안 보였고, 실제 모바일에서 스크롤 중 주소창이
   접혔다 펴지며 visual viewport 가 바뀌는 순간에 아래쪽이 흰 띠로 찢어졌다
   (발주자 제보 + 스크린샷: 어두운 절 아래로 흰 바탕과 회색 목업).

   `html` 에 색을 주면 브라우저가 그것을 **캔버스로 전파**하므로, 오버스크롤 영역과
   레이어가 못 덮는 어떤 틈도 배경색이 된다. fixed 레이어는 그 위에 오로라만 얹는
   역할로 남는다 — 색의 유일한 출처가 아니게 되는 것이 요점이다.

   토큰으로 쓰는 이유: 테마가 `<html data-theme>` 로 뒤집히고 `--background` 가 그
   선택자에서 다시 정의된다. 값을 박으면 라이트 테마에서 검은 캔버스가 남는다. */
html { background: hsl(var(--background)); }
/* 폼 컨트롤·스크롤바도 테마를 따라간다. 안 주면 다크에서 흰 스크롤바가 남는다. */
:root { color-scheme: light; }
[data-theme="dark"] { color-scheme: dark; }

body { padding-top: calc(3.25rem + 1px); }

/* ── 드롭다운(<select>) — 브라우저 기본 팝업을 우리 것으로 ─────────────────
   appearance:base-select 는 <select> 의 버튼 면과 목록(::picker) 둘 다 CSS 로 그리게
   한다. 시맨틱·키보드·폼 제출은 네이티브 그대로다. 미지원 브라우저는 이 블록을 통째로
   무시하고 지금까지의 네이티브 팝업으로 떨어진다 — 깨지는 것이 아니라 전과 같다.
   지시 2026-09-05: 팀 역할 선택(/account/team)·커뮤니티 정렬 같은 OS 기본 드롭다운을
   우리 디자인으로. 버튼 면의 크기·모서리는 각 페이지 인라인이 그대로 쥐고, 여기는
   목록·항목·캐럿·열림 모션만 정한다. 모양의 정본은 앱 모드 메뉴(.tb-mrow)와 한 줄:
   카드 바탕 · hairline · ring/.1 호버 · 체크는 오른쪽. 같은 블록이
   Vinc/app/src/app.css 에 있다(앱은 오프라인이라 이 파일을 못 부른다). 바꾸면 둘 다. */
select, ::picker(select) { appearance: base-select; }
select { display: inline-flex; align-items: center; gap: .55rem; }
select::picker-icon {
  content: ""; flex: none; margin-left: auto; width: .42em; height: .42em;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  opacity: .7;   /* 앱과 같은 방식(앱 가드 테스트가 select 계열 규칙의 color 재선언을 막는다) */
  transform: translateY(-.12em) rotate(45deg);
  transition: transform var(--dur-fast) var(--ease-out);
}
select:open::picker-icon { transform: translateY(.12em) rotate(225deg); }
::picker(select) {
  /* 자리. UA 는 position-try-order:most-block-size 라 셀렉트가 화면 중앙선 아래에 있으면 아래 공간이
     넉넉해도 더 넓은 위쪽을 고른다(Chromium 152 실측 2026-09-05 — 커뮤니티 정렬이 늘 위로 떴다).
     normal 로 되돌려 아래가 기본, 넘칠 때만 위로. max-height 를 고정하는 이유: UA 기본 stretch 는
     좁은 틈에 목록을 42px 로 짜부라뜨려 넘침 자체를 없애므로 위로 뒤집히는 일이 영영 없다. */
  position-try-order: normal; max-height: 60vh;
  margin: .3rem 0 0; padding: .3rem; min-width: anchor-size(width);
  border: 1px solid hsl(var(--border)); border-radius: .7rem;
  background: hsl(var(--card)); color: hsl(var(--foreground));
  box-shadow: var(--shadow-elev-2);
}
option {
  display: flex; align-items: center; gap: .6rem;
  padding: .48rem .7rem; border-radius: .5rem;
  font: inherit; cursor: pointer;
}
option:hover, option:focus-visible { background: hsl(var(--ring) / .1); outline: none; }
option:checked { font-weight: 600; }
option::checkmark { order: 1; margin-left: auto; content: "✓"; font-family: var(--font-mono); color: hsl(var(--ring)); }
@media (prefers-reduced-motion: no-preference) {
  ::picker(select) {
    opacity: 1; transform: translateY(0);
    transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out),
      overlay var(--dur-fast) allow-discrete, display var(--dur-fast) allow-discrete;
  }
  ::picker(select):not(:popover-open) { opacity: 0; transform: translateY(-.25rem); }
  @starting-style { ::picker(select):popover-open { opacity: 0; transform: translateY(-.25rem); } }
}
