/* jpark & Co. 공통 스타일
 *
 * 허브(index.html)의 인라인 <style> 에서 추출했다. 페이지가 여러 장이 되면서
 * 같은 토큰을 복붙하면 드리프트가 나므로 여기 한 곳만 고치도록 한다.
 *
 * 빌드는 없다. 각 페이지가 <link rel="stylesheet" href="/assets/site.css"> 로 받는다.
 *
 * ⚠️ 색 토큰·스크롤바·한글 조판·푸터 글자 크기는 이 파일에 없다 —
 * tokens.css 로 뽑았다(2026-09-07, jpark-library `ref-jparkandco-web` §2-1).
 * 값을 바꿀 일이 생기면 tokens.css 를 고친다. 색상은 블로그
 * tailwind.config.ts 가 원본이라 그쪽도 함께 고쳐야 한다 —
 * tokens.css 헤더의 "복사 대상" 참조.
 */

@import url("tokens.css");

*{box-sizing:border-box}

/* body 를 flex column + min-height:100vh 로 — 블로그의 <div class="flex flex-col
   min-h-screen"> 과 같은 발상이다. .wrap 이 flex:1 로 남는 공간을 정확히 채우므로
   콘텐츠 길이와 무관하게 footer 가 항상 화면 하단에 붙거나 자연스럽게 아래로
   밀린다 — "22rem ≈ 푸터 높이" 같은 매직넘버 어림값이 필요 없어진다.
   (2026-09-07 jpark 육안 지적: jparkandco.com 하단에만 흰 여백이 보임 — 그
   매직넘버가 실제 footer 높이·margin-top 과 안 맞아 19px 오차가 났었다) */
body{
  margin:0; min-height:100vh;
  display:flex; flex-direction:column;
  background:var(--bg); color:var(--fg);
  font-family:"Pretendard Variable",Pretendard,-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans KR",sans-serif;
  -webkit-font-smoothing:antialiased;
  letter-spacing:-.02em;   /* 블로그 globals.css 와 동일 */
}
/* 스크롤은 html 이 담당한다(tokens.css) — body 는 flex 라 overflow-y 가
   뷰포트 스크롤바로 승격되지 않는다(3px 축 이동 버그의 원인이었던 것). */

/* ── GNB 헤더 ──────────────────────────────────────────── */
/* 블로그 Header.tsx 와 같은 골격이다 — sticky, 높이 4rem, 반투명 흰 배경에
   블러, 좌측 지면명(굵게) + 우측 카테고리. production 이 blog 의 형제로
   읽히려면 이 골격이 같아야 한다. 검색은 두지 않는다(항목이 적다). */
.gnb{position:sticky; top:0; z-index:50}
.gnb .bar{
  background:rgba(255,255,255,.7); backdrop-filter:blur(16px);
  box-shadow:0 1px 3px rgba(0,0,0,.05);
  border-bottom:1px solid rgba(255,255,255,.4);
}
.gnb .inner{
  max-width:72rem; margin:0 auto; padding:0 1.5rem; height:4rem;
  display:flex; align-items:center; justify-content:space-between; gap:1.5rem;
}
.gnb .logo{
  font-size:1.25rem; line-height:1.4;   /* 블로그 text-xl: 20/28 */
  /* ⚠️ -.025em 은 블로그 tracking-tight 값이다. body 전역 -.02em 과 다르며,
     블로그도 로고·메뉴 제목에만 이 값을 덮어쓴다. -.02em 으로 두면 20px
     기준 0.4px 좁아진다(2026-09-18 실측: "Menu" 폭 51.44 vs 51.04). */
  font-weight:700; letter-spacing:-.025em;
  color:var(--fg); text-decoration:none; white-space:nowrap;
}
.gnb .logo:hover{color:var(--accent)}
.gnb nav{display:flex; align-items:center; gap:1.75rem}
.gnb nav a{
  font-size:.875rem; font-weight:500; color:var(--fg); text-decoration:none;
  white-space:nowrap; transition:color .18s ease;
}
.gnb nav a:hover{color:var(--accent)}

/* ── 모바일 메뉴 ───────────────────────────────────────── */
/* 블로그 Header.tsx 와 같은 골격이다 — 1024px 미만에서 데스크톱 nav 를 감추고
   햄버거로 전환, 누르면 전체화면 오버레이가 덮는다.
   경계값을 블로그와 같은 64rem(1024px)으로 두는 이유: 두 지면이 같은 시각
   언어를 쓰기로 한 이상 "언제 메뉴가 되는가"도 같아야 한다. 허브는 항목이
   2개뿐이라 지금 당장은 좁은 폭에서도 안 깨지지만, 항목이 3개가 되면
   360px 에서 6px 넘친다(2026-09-08 실측). 예방 겸 정합.

   ⚠️ 오버레이 헤더는 GNB 바와 같은 규격이어야 한다 — 좌 24px, 높이 4rem,
   글자 20px/700. 열고 닫을 때 제목이 제자리에 있어야 하기 때문이다.
   (블로그가 p-6 + text-lg 를 써서 10px 내려앉고 2px 작아졌던 것을
   2026-09-08 jpark 이 지적 — 양쪽 다 이 규격으로 맞췄다) */

.gnb .menubtn{
  display:none;
  padding:.5rem; margin-right:-.5rem;   /* 우측 광학 정렬: 아이콘 끝을 nav 끝에 */
  background:none; border:0; cursor:pointer;
  color:var(--muted); line-height:0;
  transition:color .18s ease;
}
.gnb .menubtn:hover{color:var(--fg)}
.gnb .menubtn svg{width:1.25rem; height:1.25rem; display:block}

/* 오버레이 — 기본은 숨김. [hidden] 이 아니라 display 로 제어하면 JS 가
   hidden 속성만 토글하면 된다. */
.mobilemenu{
  position:fixed; inset:0; z-index:60;
  background:var(--bg);
  display:none; flex-direction:column;
}
.mobilemenu[data-open]{display:flex}

/* GNB 바와 동일 규격 — 높이 4rem, 좌우 1.5rem */
.mobilemenu .head{
  display:flex; align-items:center; justify-content:space-between;
  height:4rem; padding:0 1.5rem;
  border-bottom:1px solid #f3f4f6;
  flex:none;
}
.mobilemenu .head .title{
  /* 로고와 같은 규격이어야 한다 — 블로그 Header.tsx 가 그렇게 맞춰 뒀다.
     letter-spacing 은 .gnb .logo 와 같은 이유로 -.025em. */
  font-size:1.25rem; font-weight:700; letter-spacing:-.025em;
  line-height:1.4; color:var(--fg);
}
.mobilemenu .closebtn{
  padding:.5rem; margin-right:-.5rem;
  background:none; border:0; cursor:pointer;
  color:var(--muted); line-height:0;
  transition:color .18s ease;
}
.mobilemenu .closebtn:hover{color:var(--fg)}
.mobilemenu .closebtn svg{width:1.25rem; height:1.25rem; display:block}

.mobilemenu nav{padding:1.5rem; overflow-y:auto}
.mobilemenu ul{
  margin:0; padding:0; list-style:none;
  display:flex; flex-direction:column; gap:1.5rem;   /* space-y-6 */
}
.mobilemenu a{
  /* ⚠️ line-height 를 빼먹으면 안 된다 — 블로그의 text-lg 는 font-size
     1.125rem 과 line-height 1.75rem 이 한 세트다. 크기만 가져오면 줄높이가
     normal(≈21.5px)로 떨어져 항목 주기가 45.5px 이 되고, 블로그(52px)보다
     6.5px 좁아진다. 항목이 3개만 돼도 눈에 띈다.
     (2026-09-18 실측: 두 메뉴를 390px 뷰포트에서 열어 대조 — 첫 항목 시작이
      허브 88px / 블로그 91px 로 어긋나 있었다) */
  font-size:1.125rem; line-height:1.75rem;
  color:var(--fg); text-decoration:none;
  transition:color .18s ease;
}
.mobilemenu a:hover{color:var(--accent)}

@media (max-width:63.999rem){
  .gnb nav{display:none}
  .gnb .menubtn{display:block}
}
/* 데스크톱으로 넓히면 오버레이가 열려 있어도 숨긴다 — 회전·리사이즈 때
   메뉴가 남아 화면을 덮는 것을 막는다. JS 없이 CSS 로 처리. */
@media (min-width:64rem){
  .mobilemenu[data-open]{display:none}
}

/* GNB 가 있는 페이지는 상단 여백을 줄인다 — 헤더가 이미 공간을 차지한다 */
/* ⚠️ 상단 패딩은 0으로 비우고 .hero-main/.pagehead 가 전담한다 — 블로그가
   지면 종류별로 다른 값(히어로 96px vs 카테고리 64px)을 쓰기 때문이다.
   .wrap 의 기본 padding-top(4rem)을 그대로 두면 그 위에 또 더해져 겹친다
   (2026-09-07 jpark 육안 지적으로 발견 — 처음엔 24px 부족, 고치자 이번엔
   .wrap 64px + .hero-main 96px 이 겹쳐 160px 초과로 반대쪽 실수).
   GNB 없는 페이지(게이트웨이·privacy 문서)는 이 규칙 대상이 아니라
   .wrap 기본 패딩을 그대로 받는다. */
/* ⚠️ 이 두 규칙은 오랫동안 죽어 있었다 — .gnb 와 .wrap 사이에 .mobilemenu
   오버레이가 들어가면서 인접 형제(+)가 끊겼다(2026-09-13 실측: matches() false).
   .wrap 이 padding 4rem 이던 시절엔 .doc 이 받아야 할 값과 우연히 같아
   드러나지 않았다. 상단 패딩을 2rem 으로 줄이자 .doc 만 32px 로 얕아졌다.
   일반 형제(~)로 고쳐 .mobilemenu 유무와 무관하게 걸리도록 한다. */
.gnb ~ .wrap{padding-top:0}
/* 문서형(.doc)은 .hero-main/.pagehead 를 안 쓰므로 위 규칙이 비운 상단
   패딩을 스스로 받아야 한다 — 블로그 privacy 의 article.py-16(64px)과 동일. */
.gnb ~ .wrap.doc{padding-top:4rem}

/* 본문 래퍼 — 푸터가 화면 폭 전체를 쓰므로 패딩은 body 가 아니라 여기에 준다.
   max-width 는 푸터 .inner 와 같은 72rem 이어야 본문과 푸터의 좌측 축이 맞는다.
   .narrow 는 읽는 문서(privacy)에만 쓴다 — 글줄 길이를 줄이려는 목적이고,
   그 페이지는 축이 어긋나 보여도 본문 가독성이 우선이다. */
/* flex:1 로 body(flex column) 안에서 남는 공간을 정확히 채운다 — 블로그가
   <main class="flex-grow"> 로 하는 것과 같다. 콘텐츠가 짧아도 길어도 footer 는
   항상 화면 하단에 붙거나 자연스레 밀려난다.
   (2026-09-07 jpark 육안 지적: jparkandco.com 하단에만 19px 흰 여백 — 이전엔
   min-height:calc(100vh - 22rem) 매직넘버로 흉내냈는데, 22rem 이 실제
   footer 높이(269px)+margin-top(64px)=333px 과 안 맞아 오차가 났다. flex 로
   바꾸면 이 어림값 자체가 필요 없어진다.) */
.wrap{
  width:100%; max-width:72rem; margin:0 auto;
  padding:4rem 1.5rem;
  flex:1;
}
.wrap.narrow{max-width:48rem}

/* 게이트웨이 — 진입점 한 장짜리라 세로 중앙 정렬을 유지한다. */
body.home .wrap{
  display:flex; flex-direction:column; justify-content:center;
}

/* ── 페이지 머리 (블로그 카테고리 페이지와 동일 구조) ──── */
/* 블로그는 머리와 본문을 별개 섹션으로 나누고 그 사이에 구분선을 둔다
   (app/category/.../page.tsx: py-12 md:py-16 + border-b).
   .wrap 안에 두되 좌우 패딩을 뚫고 나가야 선이 화면을 가로지른다. */
/* 블로그 카테고리 머리 실측:
     section  py-12 md:py-16 (64px) + border-b border-gray-100
     h1       36px / lh 40px(leading-tight) / mb-3(12px)
     subtitle 18px / lh 28px(text-lg 기본) / muted */
/* ⚠️ .gnb ~ .wrap 이 상단 패딩을 0 으로 만드므로 이 값이 GNB 와의 간격 전부다.
   구 4rem + .wrap 4rem = 128px 이었고, 본문 시작까지 하단 패딩 + 보더 +
   margin 으로 129px 이 더 붙어 화면 절반이 여백이었다(2026-09-13 실측).
   상단 3.5rem = 56px 로 .hero-main 과 맞추고, 하단·margin 은 본문과의
   분리가 필요하므로 3rem 을 유지한다. */
/* 구분선은 화면 전체 폭이다(2026-10-08 — 블로그 카테고리와 통일, GNB 와 같은 폭).
   .wrap(72rem) 안이라 border-bottom 으로는 콘텐츠 폭에서 끊긴다 → ::after 로 뷰포트 폭만큼 그린다.
   100vw 는 세로 스크롤바 폭만큼 넘치므로 body 의 overflow-x:clip 으로 가로 스크롤을 막는다. */
.pagehead{
  position:relative;
  padding-top:3.5rem; padding-bottom:calc(3rem + 1px); margin-bottom:3rem;   /* +1px = 선이 차지하던 두께 */
}
.pagehead::after{
  content:""; position:absolute; bottom:0; left:50%;
  width:100vw; margin-left:-50vw; height:1px;
  background:#f3f4f6;   /* gray-100 — 블로그와 같은 계열 */
}
body{overflow-x:clip}

h1{
  margin:0 0 .75rem; font-weight:700; letter-spacing:-.03em;
  font-size:1.875rem; line-height:1.25;   /* text-3xl / leading-tight */
}
@media (min-width:48rem){h1{font-size:2.25rem; line-height:1.111}}  /* md:text-4xl, lh 40px */

/* 라벨 — production 메인처럼 지면 계열을 밝힐 때만 쓴다.
   카테고리 페이지(apps·works)에는 두지 않는다: 제목이 곧 그 카테고리라
   같은 말이 두 번 나온다(블로그 카테고리 페이지도 라벨 없이 제목부터다). */
.label{
  margin:0 0 .9rem; font-size:.875rem; font-weight:500; color:var(--accent);
  line-height:1.43;
}
/* 지면 메인의 히어로 — 블로그 홈(app/page.tsx)과 같은 형식.
   카테고리 페이지(.pagehead)와는 다른 규격이다:
     구분선 없음 · 텍스트 폭 max-w-2xl(672px)
     eyebrow 14px 대문자 tracking-wider · h1 48px/48px · lead 18px

   ⚠️ .gnb ~ .wrap 이 상단 패딩을 0 으로 만드므로 이 값이 GNB 와의 간격 전부다.
   (구 6rem + .wrap 4rem = 160px → 112px → jpark "반 정도" 지시로 56px) */
.hero-main{padding:3.5rem 0 3rem; max-width:42rem}
.hero-main h1{
  font-size:2.25rem; line-height:1.1; margin:0 0 1.5rem; letter-spacing:-.02em;
}
@media (min-width:48rem){.hero-main h1{font-size:3rem; line-height:1}}
.hero-main .lead{font-size:1.125rem; line-height:1.625}

.eyebrow{
  margin:0 0 1rem; font-size:.875rem; font-weight:500; color:var(--fg);
  line-height:1.43; text-transform:uppercase; letter-spacing:.05em;
}
.lead{
  margin:0; font-size:1.125rem; line-height:1.556; color:var(--muted);  /* text-lg: 18/28 */
}
.meta{
  margin:.75rem 0 0; font-size:.8rem; color:var(--dim); letter-spacing:.02em;
}
/* 스토어 설치 버튼 — 소개 페이지 머리에서 가장 먼저 눈에 들어와야 하는 행동 */
.store{
  display:inline-block; margin-top:1.25rem; padding:.7rem 1.4rem; border-radius:.75rem;
  background:var(--fg); color:#fff; font-size:.95rem; font-weight:600;
  text-decoration:none; transition:opacity .18s ease;
}
.store:hover{opacity:.85}

/* 문서형 본문 (privacy 등).
   블로그 privacy(app/privacy/page.tsx)의 prose 스케일을 그대로 맞춘다 —
   본문 1rem/1.75, h2 1.5rem(mt-12 mb-4), h3 1.25rem(mt-8 mb-3).
   본문 색이 muted 가 아니라 primary 인 것도 블로그와 같다: 읽는 문서라
   본문이 흐리면 안 된다. */
.doc h2{margin:3rem 0 1rem; font-size:1.5rem; font-weight:700; letter-spacing:-.02em}
.doc h3{margin:2rem 0 .75rem; font-size:1.25rem; font-weight:700; letter-spacing:-.01em}
.doc p,.doc li{font-size:1rem; line-height:1.75; color:var(--fg)}
.doc p{margin:0 0 1.25rem}
.doc ul,.doc ol{margin:1rem 0; padding-left:1.25rem}
.doc li{margin-bottom:.25rem}
.doc strong{color:var(--fg); font-weight:600}
.doc a{color:var(--accent); text-decoration:none}
.doc a:hover{text-decoration:underline}
/* .back 이 .doc 내부(문서 페이지)에 있으면 .doc a(클래스+태그, specificity 더 높음)가
   이겨서 파란 링크색으로 덮인다 — muted 회색을 강제로 되찾는다.
   (2026-09-07 jpark 육안 지적: "홈으로 돌아가기 색상 봐봐" — 실측해보니 accent 였다) */
.doc a.back{color:var(--muted)}
.doc a.back:hover{color:var(--fg)}

/* 문서 머리 — 블로그는 메타 줄(라벨 · 시행일) 다음에 제목을 둔다 */
.docmeta{
  display:flex; align-items:center; gap:.5rem; flex-wrap:wrap;
  margin:0 0 1.5rem; font-size:.875rem;
}
.docmeta .kind{font-weight:500; color:var(--accent)}
.docmeta .sep,.docmeta .when{color:var(--muted)}
.doc h1{margin-bottom:3rem}

/* 다국어 문서 — 한 주소에 언어별 <section data-lang> 을 두고 하나만 보인다 (2026-10-04).
   html[data-doclang] 은 페이지 <head> 스크립트가 박는다. 속성이 없으면(JS 꺼짐)
   두 섹션이 모두 보이고, 전환 링크는 #ko/#en 앵커로 동작한다. */
html[data-doclang="ko"] .doc section[data-lang="en"],
html[data-doclang="en"] .doc section[data-lang="ko"]{display:none}
/* 앱이 #ko/#en 으로 열면 브라우저가 섹션 머리로 스크롤해 GNB 아래에 제목·전환 줄이 가린다
   (2026-10-04 실기기 확인). 상단 여백(GNB 4rem + .wrap.doc 4rem)만큼 띄워 페이지 맨 위에 멈추게 한다. */
.doc section[data-lang]{scroll-margin-top:8rem}
.docmeta .langswitch{margin-left:auto; display:flex; gap:.25rem}
.doc .docmeta .langswitch a{color:var(--muted); padding:.125rem .375rem; border-radius:.375rem}
.doc .docmeta .langswitch a:hover{color:var(--fg); text-decoration:none}
.doc .docmeta .langswitch a[aria-current="true"]{color:var(--fg); font-weight:600}

/* 되돌아가기 링크 — 문서 페이지(privacy)에만 쓴다.
   블로그 privacy 와 같이 본문 끝, 구분선 아래에 둔다. 읽기 전에 나가라고
   권하는 위치(상단)가 아니라 다 읽은 뒤의 다음 행동이다. */
/* 블로그 privacy 실측 재확인 2회차(2026-09-07, jpark 이 "본문-선 간격이 다르다"고
   재지적 — 실제로 재보니 맞았다). raw HTML 구조:
     <div class="prose ... mb-12">...본문...</div>          본문 컨테이너 자체가 mb-12(48px)
     <div class="pb-12 border-b border-gray-200"></div>      pb-12(48px) 뒤에 선
     <div ...><nav class="pt-12">...</nav></div>             선 뒤 pt-12(48px)
   즉 "본문 → 48px(prose 의 mb-12) → 48px(divider 의 pb-12) → 선 → 48px(nav 의
   pt-12) → 링크" 로, 본문과 선 사이가 96px 이지 48px 이 아니다.
   1차 재조사(직전 커밋) 때는 선의 유무만 잡고 이 이중 간격을 놓쳤다 —
   getComputedStyle 로 개별 요소만 보고 전체 간격을 합산하지 않은 게 원인.
   허브는 .docfoot 하나로 뭉쳐 구현하므로 margin-top 을 96px 로 잡아야 한다. */
.docfoot{
  margin-top:6rem; padding-top:3rem; border-top:1px solid #e5e7eb;
}
.back{
  display:inline-flex; align-items:center; gap:.5rem;
  font-size:1rem; line-height:1.5; color:var(--muted); text-decoration:none;
  transition:color .18s ease;
}
.back:hover{color:var(--fg)}
.back svg{width:1.25rem; height:1.25rem; flex:none}

/* ── 게이트웨이 진입 메뉴 ──────────────────────────────── */
/* 항목 자체가 히어로다. 타이틀은 작게 두고 목록이 화면을 이끈다. */

.gate{
  margin:0; padding:0; list-style:none;
  display:flex; flex-direction:column;
}
.gate a{
  display:flex; align-items:center; justify-content:space-between; gap:1.5rem;
  padding:1.1rem 0;
  /* 메인 h1(.hero-main h1)과 같은 규격 — 두 지면의 첫 글자가 같은 무게로
     보여야 한다(2026-09-13 실측: 44px/600 vs 48px/700 으로 어긋나 있었다). */
  font-size:clamp(1.875rem,6vw,3rem); font-weight:700; letter-spacing:-.02em;
  color:var(--fg); text-decoration:none;
  border-bottom:1px solid var(--line);
  transition:color .18s ease;
}
.gate li:first-child a{border-top:1px solid var(--line)}
.gate a:hover,.gate a:focus-visible{color:var(--accent); outline:none}
.gate a::after{
  content:"→"; flex:none;
  font-size:.5em; color:var(--dim); font-weight:400;
  transition:transform .18s ease, color .18s ease;
}
.gate a:hover::after{color:var(--accent); transform:translateX(.25rem)}

/* 브랜드명. 목록보다 한 단계 작게 두고 간격을 벌린다 —
   같은 크기로 두면 목록의 한 항목처럼 보여 어색했다. 위치상 표제이지
   선택지가 아니라는 것이 크기 차이로 드러나야 한다. */
.brand{
  margin:0 0 1.6rem;
  /* 2026-09-13 jpark 지시로 4px 축소 — 상한 30.4px → 26.4px.
     하한도 같은 폭으로 내려 좁은 화면의 비례를 유지한다. */
  font-size:clamp(1.1rem,3.5vw,1.65rem); font-weight:600; letter-spacing:-.03em;
  line-height:1.2; color:var(--dim);
}

/* ── production 전시 카드 ──────────────────────────────── */

/* 배너는 1단이다. 항목이 늘어도 세로로 쌓는다 —
   전시 대상이 앱 단위라 하나하나가 충분히 커야 하고,
   2단으로 나누면 좌우 중 한쪽이 비는 상황이 잦다.
   grid-template-columns 를 명시하지 않으면 자동 컬럼이 콘텐츠 폭을 따라가
   카드가 컨테이너를 넘친다(1440px 실측에서 확인). */
.showcase{
  margin:2rem 0 0; padding:0; list-style:none;
  display:grid; grid-template-columns:minmax(0,1fr); gap:1.25rem;
}
.card{
  display:block; text-decoration:none; color:inherit;
  border:1px solid var(--line); border-radius:1rem; overflow:hidden;
  background:var(--bg);
  transition:transform .3s ease, box-shadow .3s ease, border-color .18s ease;
}
a.card:hover{
  transform:translateY(-4px); box-shadow:0 10px 25px rgba(15,23,42,.10);
  border-color:transparent;
}

/* 카드 상단 비주얼. 스크린샷이 있으면 img 를 넣고, 없으면 .ph 가 자리를 지킨다.
   빈 색면만 남기지 않으려고 플레이스홀더에도 이니셜을 새긴다.

   ⚠️ overflow 는 hidden 이 아니다 — 아이콘이 비주얼 아래로 넘어가 본문에
   겹쳐야 하기 때문이다(아래 .visual.banner .appicon 참조). 배너 사진은
   .bg 쪽에서 따로 잘라낸다. */
.card .visual{
  position:relative; aspect-ratio:16/9; background:var(--surface);
  display:flex; align-items:center; justify-content:center;
}
/* 카드 전체를 덮는 배너 이미지용. 아이콘(.appicon)·캡처(.peek)는 제외한다 —
   :not() 없이 쓰면 아래에서 지정한 크기를 이 규칙이 덮어쓴다. */
.card .visual img:not(.appicon):not(.peek){
  width:100%; height:100%; object-fit:cover; display:block;
}
.card .visual .appicon,.card .visual .peek{width:auto; display:block}
.card .visual .ph{
  font-size:2.5rem; font-weight:700; letter-spacing:-.04em; color:var(--dim);
}
/* 카드 본문 — 카테고리 / 타이틀 / 설명 한 줄. 블로그 PostCard 와 같은 위계다.
   (카테고리 라벨 → 제목 → 요약) */
.card .body{padding:1.35rem 1.5rem 1.5rem}
.card .cat{
  margin:0 0 .4rem; font-size:.8rem; font-weight:500; color:var(--accent);
  display:flex; align-items:center; gap:.5rem; flex-wrap:wrap;
}
.card .name{
  margin:0; font-size:1.15rem; font-weight:600; letter-spacing:-.02em;
}
.card .desc{margin:.45rem 0 0; font-size:.9rem; line-height:1.6; color:var(--muted)}

/* 상태 배지 — 출시 여부를 한눈에. 준비 중인 항목이 대부분이라 눈에 띄어야 한다.
   카테고리 줄에 붙으므로 accent 를 쓰지 않는다(라벨과 색이 겹친다). */
.badge{
  flex:none; font-size:.7rem; font-weight:500; letter-spacing:.02em;
  padding:.2rem .5rem; border-radius:.35rem;
  background:var(--surface); color:var(--muted); border:1px solid var(--line);
}

/* 링크가 아직 없는 카드(준비 중)는 hover 반응을 주지 않는다 — 누를 수 있다는 오해를 막는다 */
.card.off{cursor:default}

/* 배너 사진 위에 아이콘을 얹는 카드 (2026-09-10).
   사진이 카드를 꽉 채우고 아이콘이 그 위에 떠 있는 형태 — 실사용 장면을
   먼저 보여주고 어떤 앱인지는 아이콘이 말한다.
   .visual 이 flex 로 아이콘을 가운데 두므로, 배너는 흐름에서 빼내
   (position:absolute) 배경처럼 깐다. */
.card .visual.banner{
  background:var(--fg);
  /* 가운데 정렬을 풀고 왼쪽에 붙인다 — 사진 한가운데에 폰이 있어서
     아이콘을 중앙에 두면 정작 보여줘야 할 앱 화면을 덮는다(실측: 100% 겹침).
     왼쪽 원목 여백이 비어 있으므로 거기에 놓는다. */
  justify-content:flex-start;
  padding-left:clamp(1.5rem,5%,4rem);
}
/* 배너 사진만 비주얼 영역에 가둔다 — .visual 의 overflow 를 풀었으므로
   둥근 모서리를 여기서 직접 지킨다. */
.card .visual.banner .bg{
  position:absolute; inset:0;
  width:100%; height:100%; object-fit:cover; display:block;
  border-radius:inherit;
}
/* 사진 위 아이콘의 대비를 확보한다 — 원목 배경이 밝아 아이콘 흰 테두리가 묻힌다.
   왼쪽만 살짝 어둡게 깔아 폰이 있는 오른쪽 사진은 그대로 보이게 한다. */
.card .visual.banner::after{
  content:""; position:absolute; inset:0;
  background:linear-gradient(90deg, rgba(15,23,42,.45) 0%, rgba(15,23,42,.22) 28%, rgba(15,23,42,0) 52%);
}
/* 아이콘을 우측 하단으로 내려 비주얼과 본문 경계에 걸친다.
   사진(앱 화면)을 덮지 않으면서 "이 카드의 주인"을 표시하는 자리다.
   translateY 로 절반쯤 본문으로 내려보내 두 영역이 하나로 묶여 보인다.
   (.visual 의 overflow 를 풀어둔 것이 이 겹침의 전제다.) */
.card .visual.banner .appicon{
  position:absolute; z-index:2;    /* ::after 그림자 + 본문 위로 */
  left:auto; right:clamp(1.25rem,4%,2.5rem);
  bottom:0; transform:translateY(38%);
  border:1.5px solid var(--bg);    /* 본문 흰 면과 겹칠 때 경계를 만든다 */
  box-shadow:0 8px 24px rgba(15,23,42,.22);
}
/* 아이콘이 우측 하단으로 갔으므로 왼쪽 그늘은 더 이상 필요 없다.
   사진을 가리지 않도록 걷어낸다. */
.card .visual.banner::after{display:none}
/* 아이콘이 본문 위로 내려오는 만큼 우측 상단에 자리를 비워준다 —
   없으면 배지·제목이 아이콘에 가린다. */
.card .visual.banner + .body{padding-right:clamp(7rem,14%,9.5rem)}

/* 앱 아이콘을 비주얼로 쓰는 카드.
   스크린샷은 세로(9:19)라 가로 배너로 자르면 화면 일부만 남아 무엇인지 안 보인다.
   대신 아이콘을 크게 놓는다. 가로 배너용 그래픽(Play Store 1024x500)이
   준비되면 .visual 안의 img 를 그것으로 교체한다. */
/* 화면이 좁아지면 아이콘도 줄어야 한다 — 고정 크기면 모바일에서 카드 폭의
   1/3 을 먹어 과하게 보인다(2026-09-13 jpark 지적).
   뷰포트 비례로 두되 하한 4.5rem(72px)을 둬 너무 작아지지 않게 한다. */
.card .visual .appicon{
  width:clamp(4.5rem,17vw,6.25rem); height:clamp(4.5rem,17vw,6.25rem);
  border-radius:clamp(1.15rem,4.4vw,1.6rem);
  box-shadow:0 6px 20px rgba(15,23,42,.16);
}

/* 아이콘 옆에 실기기 캡처를 세워 앱의 생김새가 함께 보이게 한다.
   좁은 화면에서는 아이콘만 남긴다(캡처가 작아져 오히려 지저분해진다). */
.card .visual{gap:2rem}
.card .visual .peek{display:none}

/* 1단 배너는 폭이 72rem 이라 21:9 로 두면 세로가 과하게 커진다.
   가로로 더 눕혀 띠처럼 보이게 한다. */
@media (min-width:44rem){
  /* 4:1 — 32:9(폭 1102px 에서 310px)보다 얇게 (2026-10-08 jpark: 최대 280 정도).
     max-height 대신 비율로 줄이는 이유: 폭이 줄어도 같은 모양을 유지한다. */
  .showcase .visual{aspect-ratio:4/1}
  .showcase .appicon{
    width:clamp(5.5rem,10vw,7.5rem); height:clamp(5.5rem,10vw,7.5rem);
    border-radius:clamp(1.35rem,2.5vw,1.85rem);
  }
  /* 캡처는 아래를 넘겨 잘리게 둔다 — 기기 화면이 카드 밖으로 이어지는 인상을 준다 */
  .showcase .visual .peek{
    display:block; align-self:flex-end;
    height:80%; border-radius:.9rem .9rem 0 0;
    border:1px solid var(--line); border-bottom:0;
    box-shadow:0 -4px 20px rgba(15,23,42,.10);
  }
}

/* ── A/B 실험 exp02 (B안: PC 2단 카드 / 모바일 가로형 카드) ────
   [상태] exp02 종료(2026-10-06), 현재 OFF — 이 블록의 규칙은 html 에 data-ab-type=B 가
   달릴 때만 적용되므로 지금은 비활성이다. 다음 실험 때 새 변형으로 교체하거나 삭제한다.
   재가동 절차는 production/index.html 의 배정 스크립트 주석을 참조.
   ⚠️ 실험 exp02_prod_card 한정 — 위 ".showcase 는 1단" 원칙의 예외다.
   (exp01_prod_card 의 B 규칙을 그대로 잇고, 36rem 미만 모바일 B 만 가로형으로 바꿨다.
    exp01 운영 실측에서 모바일 B(1단 컴팩트)가 A 와 사실상 구분되지 않았기 때문.)
   실험 종료 시 이 블록째 삭제한다(production/index.html 의 배정 스크립트와 함께).
   배정: production/index.html <head> 스크립트가 html[data-ab-type] 을 단다.
   A안(속성 없음 또는 "A")은 기존 규칙 그대로 — 여기 규칙은 전부 B 스코프에만 건다.

   · 36rem 이상 2단. 카드가 1장이라 오른쪽 칸이 비는 것은 의도된 결정(①안)이다.
     빈칸을 채우거나 더미 카드를 넣지 말 것.
   · 아이콘: 기존 17vw(뷰포트 기준)는 반폭 카드에서 과대해진다(1440px 에서
     카드 542px·배너 152px 높이에 아이콘 100px). 비주얼(=카드) 폭의 %로 바꾼다 —
     absolute 요소의 width % 는 .visual 폭 기준이라 카드 폭을 그대로 따라간다.
   · 본문 우측 여백: 아이콘이 본문으로 내려오는 깊이(아이콘 높이의 38%)는
     카테고리 줄까지만 닿는다. 본문 전체 대신 .cat 에만 비켜줄 여백을 줘서
     좁은 카드에서 제목·설명 폭을 지킨다.
   · 모바일 B(36rem 미만) = 가로형 작은 카드: 왼쪽 썸네일 + 오른쪽 텍스트.
     마크업은 A 와 같다(GTM 트리거가 같은 DOM 에 의존) — CSS 만으로 배치를 바꾼다.
     썸네일은 배너 사진 대신 앱 아이콘 — 100px 안팎으로 줄이면 사진(손·폰 화면)은
     무엇인지 읽히지 않고, 아이콘은 그 크기에서도 앱을 식별시킨다.
     36rem 이상 B 는 이 미디어쿼리 밖이라 exp01 과 픽셀 동일하다. */
html[data-ab-type="B"] .card .visual.banner{aspect-ratio:2/1}
html[data-ab-type="B"] .card .visual.banner .appicon{
  width:clamp(3.5rem,18%,5.5rem); height:auto; aspect-ratio:1/1;
  border-radius:24%;
}
html[data-ab-type="B"] .card .visual.banner + .body{padding:1.1rem 1.25rem 1.25rem}
html[data-ab-type="B"] .card .visual.banner + .body .cat{
  /* 아이콘 폭 + 아이콘 우측 오프셋(위 .appicon right) + 간격 .75rem */
  padding-right:calc(clamp(3.5rem,18%,5.5rem) + clamp(1.25rem,4%,2.5rem) + .75rem);
}
html[data-ab-type="B"] .card .name{font-size:1.05rem}
html[data-ab-type="B"] .card .desc{font-size:.85rem}
@media (min-width:36rem){
  html[data-ab-type="B"] .showcase{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:35.999rem){
  html[data-ab-type="B"] .card{
    display:grid; grid-template-columns:34% minmax(0,1fr); align-items:stretch;
  }
  /* 썸네일 칸 — 배너 사진·그늘을 걷고 옅은 면 위에 아이콘만 가운데 둔다 */
  html[data-ab-type="B"] .card .visual.banner{
    aspect-ratio:auto; padding:.75rem; justify-content:center;
    background:var(--surface); border-right:1px solid var(--line);
  }
  html[data-ab-type="B"] .card .visual.banner .bg{display:none}
  html[data-ab-type="B"] .card .visual.banner .appicon{
    position:static; transform:none;
    width:min(100%,5rem); height:auto; aspect-ratio:1/1;
    box-shadow:0 4px 12px rgba(15,23,42,.14);
  }
  html[data-ab-type="B"] .card .visual.banner + .body{
    padding:.9rem 1rem; align-self:center; min-width:0;
  }
  /* 아이콘이 본문으로 내려오지 않으므로 .cat 의 비켜주기 여백은 필요 없다 */
  html[data-ab-type="B"] .card .visual.banner + .body .cat{padding-right:0; margin-bottom:.3rem}
  html[data-ab-type="B"] .card .name{font-size:1rem}
  /* 설명은 2줄까지 — 넘치면 말줄임 */
  html[data-ab-type="B"] .card .desc{
    margin-top:.3rem; font-size:.8rem; line-height:1.5;
    display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2;
    overflow:hidden;
  }
}

/* ── 상세 페이지 ───────────────────────────────────────── */

/* 아이콘을 오른쪽 끝으로 보내고 텍스트는 전부 왼쪽 축에 건다.
   (2026-09-13 jpark 지시) 마크업 순서는 아이콘이 먼저라 order 로 뒤집는다 —
   순서를 바꾸면 스크린리더가 앱 이름보다 아이콘 alt 를 먼저 읽는다. */
.hero{
  display:flex; align-items:center; gap:1.5rem; flex-wrap:wrap;
}
.hero .icon{
  width:5rem; height:5rem; border-radius:1.25rem; flex:none;
  box-shadow:0 4px 14px rgba(15,23,42,.12);
  order:2; margin-left:auto;
}
.hero .headings{flex:1; min-width:14rem; order:1}

/* 좁은 화면에서 아이콘을 텍스트 왼쪽에 두면 제목이 아이콘 폭만큼 밀려
   본문 좌측 축과 어긋난다(390px 실측: 제목 128px vs 본문 24px).
   지면 전체가 좌정렬이므로 아이콘을 제목 위로 올려 축을 지킨다. */
@media (max-width:34rem){
  .hero{flex-direction:column; align-items:flex-start; gap:1.25rem}
  .hero .icon{order:0; margin-left:0}
  .hero .headings{order:1; min-width:0; width:100%}
}

.section{margin-top:3rem}
/* pagehead 가 이미 4rem 아래 여백을 갖는다 — 첫 섹션까지 더하면 벌어진다 */
.pagehead + .section{margin-top:0}
.showcase:first-child,.pagehead + .showcase{margin-top:0}
.section h2{
  margin:0 0 1.25rem; font-size:.8rem; font-weight:500; color:var(--fg);
  text-transform:uppercase; letter-spacing:.1em;
}

/* 기능 목록 — 카드가 아니라 구분선 리스트. 항목이 많아 카드로 하면 산만해진다. */
.features{margin:0; padding:0; list-style:none}
.features li{
  padding:1rem 0; border-bottom:1px solid var(--line);
  font-size:.95rem; line-height:1.65;
}
.features li:first-child{border-top:1px solid var(--line)}
.features b{display:block; font-weight:600; margin-bottom:.2rem}
.features span{color:var(--muted); font-size:.9rem}

/* 스크린샷 — 세로 캡처라 가로 스크롤로 늘어놓는다 */
.shots{
  display:flex; gap:1rem; overflow-x:auto; padding-bottom:.5rem;
  scroll-snap-type:x mandatory;
}
.shots figure{margin:0; flex:none; width:min(15rem,62vw); scroll-snap-align:start}
/* width:100% 만 두고 height 를 비워두면 img 의 height="1560" 속성이
   고정값으로 남아 폭만 줄어든 채 세로로 늘어난다(2026-09-20 jpark 육안
   지적 — width/height 속성을 CLS 방지용으로 넣은 2026-09-15 커밋 이후 생긴
   회귀). height:auto 로 폭에 비례하게 만든다. */
.shots img{
  width:100%; height:auto; border-radius:1rem; border:1px solid var(--line); display:block;
}
.shots figcaption{margin-top:.6rem; font-size:.8rem; color:var(--muted)}

/* 스크린샷 좌·우 이동 버튼 — assets/shots.js 가 .shots 를 .shots-wrap 으로 감싸고 만든다.
   숨은 칸이 있는 쪽 화살표만 보인다(hidden 속성). 터치 기기는 스와이프가 있어 숨긴다. */
.shots-wrap{position:relative}
.shots-nav{
  position:absolute; top:calc(50% - 1.5rem); z-index:2;
  width:2.75rem; height:2.75rem; padding:0; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--surface); color:var(--fg);
  border:1px solid var(--line); box-shadow:0 2px 10px rgba(15,23,42,.18);
  cursor:pointer; transition:background .15s, border-color .15s;
}
.shots-nav[hidden]{display:none}
.shots-nav:hover{background:var(--bg); border-color:var(--accent); color:var(--accent)}
.shots-nav:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.shots-nav svg{width:1.25rem; height:1.25rem}
.shots-nav-prev{left:.6rem}
.shots-nav-next{right:.6rem}
@media (pointer:coarse){.shots-nav{display:none}}

/* 실사용 목업 — 히어로 바로 아래 풀폭 이미지.
   .pagehead 가 이미 아래 여백 4rem 을 갖고 있어 위쪽은 그대로 받는다. */
.showpiece{
  margin:0 0 1rem; border-radius:1rem; overflow:hidden;
  background:var(--surface); line-height:0;
}
.showpiece img{width:100%; height:auto; display:block}

/* 서술형 본문. 워드마크는 2026-09-13 제거했다(jpark 지시):
   앱 아이콘이 v3 타일형으로 바뀌면서 「ca」 워드마크가 이 앱의 표식이
   아니게 됐다. 브랜드 로고로는 design/logo/ 에 존속한다.

   1단으로 지면 폭을 그대로 쓴다. 42rem 으로 끊어 우측이 비었던 것을
   2026-09-13 에 한 번 2단 컬럼으로 고쳤으나 의도와 달라 되돌렸다 —
   원한 것은 "전체 폭을 쓰는 1단"이다. */
.why-text{
  /* 1단 전폭이라 한 줄이 길다 — 1.75 는 행끼리 멀어 시선이 다음 줄을
     못 찾는다(2026-09-13 jpark 지적). 1.6 으로 좁힌다. */
  font-size:.95rem; line-height:1.6; color:var(--muted);
}
/* 문단 사이가 14px 이면 절마다 2~3 번 반복돼 글이 성기게 보인다 —
   행간을 줄여도 이 틈이 남아 있으면 효과가 없다(2026-09-13 실측). */
.why-text p{margin:0 0 .6rem}
.why-text p:last-child{margin-bottom:0}
.why-text ul{margin:0; padding-left:1.1rem}
.why-text li{margin-bottom:.35rem}
.why-text li:last-child{margin-bottom:0}

/* 정보 표 */
.spec{width:100%; border-collapse:collapse; font-size:.9rem}
.spec th,.spec td{
  text-align:left; padding:.85rem 0; border-bottom:1px solid var(--line);
  vertical-align:top;
}
/* 라벨이 길면(예: "개인정보 처리방침") 고정폭 8.5rem 을 정확히 채워 값과
   붙는다(2026-09-13 실측 간격 0px). 폭을 넓히고 우측 여백을 둔다. */
.spec th{width:10.5rem; padding-right:1.5rem; font-weight:500; color:var(--muted)}

/* 좁은 화면에서는 라벨 고정폭(8.5rem)이 표의 절반을 먹는다 — 320px 에서
   272px 중 136px 이 라벨이라 값이 줄바꿈됐다(2026-09-08 실측).
   행마다 라벨 위에 값을 쌓아 값이 전체 폭을 쓰게 한다. */
@media (max-width:30rem){
  .spec,.spec tbody,.spec tr,.spec th,.spec td{display:block; width:auto}
  .spec tr{padding:.85rem 0; border-bottom:1px solid var(--line)}
  .spec th,.spec td{padding:0; border:0}
  .spec th{margin-bottom:.15rem; font-size:.8rem}
}

/* ── 푸터 ──────────────────────────────────────────────── */
/* 블로그 Footer.tsx 와 같은 골격(4컬럼·다크). 2번째 컬럼만 지면마다 다르다 —
   허브는 Services, production 은 Products, 블로그는 Categories.
   각 지면을 독립 서비스로 운영하기로 했으므로 서로의 목록을 넣지 않는다.
   글자 크기(14px/16px)는 tokens.css 에서 온다 — 여기서는 골격(레이아웃)만. */

footer{
  width:100%;
  background:var(--footer-bg); color:var(--footer-fg); margin-top:4rem;
}
footer .inner{max-width:72rem; margin:0 auto; padding:3rem 1.5rem}
footer .cols{
  display:grid; gap:2rem; grid-template-columns:1fr;
}
@media (min-width:36rem){footer .cols{grid-template-columns:repeat(2,1fr)}}
@media (min-width:60rem){footer .cols{grid-template-columns:repeat(4,1fr)}}

footer h4{margin:0 0 1rem; font-weight:700; color:var(--footer-strong)}
footer h5{margin:0 0 1rem; font-weight:500; color:var(--footer-strong)}
footer ul{margin:0; padding:0; list-style:none; display:flex; flex-direction:column; gap:.5rem}
footer a{color:var(--footer-muted); text-decoration:none; transition:color .18s ease}
footer a:hover{color:var(--footer-strong)}

footer .bottom{
  margin-top:2rem; padding-top:2rem; border-top:1px solid var(--footer-line);
}
/* 한 줄 푸터 — 앱에서 링크되는 방침 페이지용. 컬럼이 없으니 구분선·위쪽 여백이 필요 없다. */
footer.slim .inner{padding:2rem 1.5rem}
footer.slim .bottom{margin-top:0; padding-top:0; border-top:0}

/* 키보드 포커스 — 블로그 globals.css 와 동일 규칙 */
a:focus-visible,button:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
*:focus:not(:focus-visible){outline:none}
