* { margin: 0; padding: 0; box-sizing: border-box; }
html { background: var(--bg); scroll-behavior: smooth; }
/* 랜딩의 밤하늘을 CSS 만으로 흉내낸다. 캔버스를 하나 더 띄우지 않는다 —
   이 호스트에서는 파일 개수와 매 프레임 비용이 곧 지연이다. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(90% 52% at 18% -6%,  rgba(20,86,235,.42), transparent 62%),
    radial-gradient(70% 42% at 88% 4%,   rgba(8,44,170,.34), transparent 66%),
    radial-gradient(80% 40% at 50% 102%, rgba(0,47,255,.16), transparent 64%);
}
body {
  /* 배경은 html 이 칠한다. 여기서 칠하면 body::before 의 성운이 가려진다. */
  color: var(--text);
  /* 한국어 줄바꿈.
     브라우저 기본은 한글을 '음절' 단위로 끊는다. 그래서 "로컬" 이
     "로 / 컬" 로 갈리고, "AliExpress에서는" 이 "AliExpress / 에서는" 으로 갈린다.
     keep-all 은 띄어쓰기(어절)에서만 끊는다.
     대신 긴 URL·식별자는 안 끊기므로 그런 자리에는 overflow-wrap 을 따로 준다. */
  word-break: keep-all;
  font-family: "Pretendard Variable", Pretendard, -apple-system, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif;
  font-size: var(--t-body); line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
}
.font-display { font-family: a2z, "Pretendard Variable", Pretendard, sans-serif; }
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--accent-3); outline-offset: 3px; border-radius: 6px; }

.wrap { max-width: var(--measure); margin-inline: auto; padding-inline: var(--gutter); }

/* ══════════════════════ 유리 ══════════════════════
   유리는 '재질'이 아니라 층으로 읽힌다. 네 겹이 다 있어야 유리로 보인다.

     1  뒤가 비친다        투명한 tint + backdrop-filter (굴절·채도)
     2  가장자리가 반사한다 ::after 의 1px 그라디언트 테두리 (빛 방향 고정)
     3  표면에 빛이 앉는다  ::before 의 sheen — 위-왼쪽이 밝고 아래로 죽는다
     4  판이 떠 있다        안쪽 윗선/아랫선 + 바깥 그림자

   전에는 2 만 있고 바탕이 rgba(...,.72) 로 거의 불투명했다. 그래서
   "테두리만 빛나는 판때기"로 보였다 — 평면 인상의 원인이 이것이다.

   backdrop-filter 를 쓰는 이유: 이 화면은 랜딩 마퀴와 달리 카드가 움직이지
   않는다. 매 프레임 뒤를 다시 읽지 않으므로 흐림을 감당할 수 있다
   (랜딩 PostCard 가 흐림을 뺀 이유는 24장이 계속 가로로 움직여서였다). */
/* 가장자리는 **랜딩과 같은 코드**다 (src/app/globals.css 의 .glass-edge).
   전에는 mask-composite 그라디언트 테두리라는 다른 기법을 쓰고 있었다 —
   같은 사이트에서 유리 테가 두 가지면 같은 재질로 안 읽힌다. */
.glass-edge {
  --edge-scale: 1;
  box-shadow:
    /* 좌상단에서 빛이 들어온다 */
    inset calc(2px * var(--edge-scale)) calc(2px * var(--edge-scale)) 0
      calc(-2px * var(--edge-scale)) oklch(1 0 0 / 0.45),
    /* 아래 모서리에서 되비친다 — 유리 두께가 여기서 보인다 */
    inset calc(-2px * var(--edge-scale)) calc(-2px * var(--edge-scale))
      calc(4px * var(--edge-scale)) calc(-4px * var(--edge-scale)) oklch(1 0 0 / 0.5),
    /* 판의 테. 있는 듯 없는 듯 */
    inset 0 0 0 1px oklch(1 0 0 / 0.07);
}

.glass {
  position: relative; isolation: isolate;
  background: var(--glass-fall), var(--glass-tint);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  box-shadow: var(--glass-edge-shadow), var(--glass-depth);
  transition: background var(--dur-1) var(--ease), box-shadow var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
/* 3 — 표면광. z-index:-1 로 내용 뒤·바탕 앞에 둔다. 0 으로 두면 위치를
   갖지 않은 글자(버튼 라벨·입력값)를 덮는다. isolation:isolate 가 있어
   -1 이 부모 밖으로 새지 않는다. */
.glass::before {
  content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit;
  pointer-events: none; background: var(--glass-sheen);
}
/* 2 — 가장자리는 .glass-edge 와 같은 box-shadow 다. 위 --glass-depth 에 합쳐 넣는다. */
/* 호버 반응은 **누를 수 있는 것**에만 준다.
   .glass 를 통째로 호버시켰더니 글 바탕(.article-surface)까지 색이 변했다 —
   누를 수 없는 판이 마우스에 반응하면 누를 수 있는 것처럼 보인다. */
a.glass:hover, button.glass:hover, .glass-hit:hover {
  background: var(--glass-tint-hi); box-shadow: var(--glass-edge-shadow), var(--glass-depth-hi);
}

@media (prefers-reduced-transparency: reduce) {
  /* 투명도를 줄여달라고 한 사람에게는 흐림을 걷고 바탕을 채운다 */
  .glass { backdrop-filter: none; -webkit-backdrop-filter: none; background: rgba(10,14,28,.92); }
  .glass::before { display: none; }
}

/* ── 눈썹 → 제목 → 설명 → CTA. 랜딩 섹션 문법 그대로 ── */
.eyebrow {
  font-size: var(--t-eyebrow); line-height: var(--lh-eyebrow); letter-spacing: .12em;
  font-weight: 700; color: var(--accent-3); text-transform: uppercase;
}
.sec-title { font-size: var(--t-h2); line-height: var(--lh-head); letter-spacing: -.01em; }
.sec-desc { font-size: var(--t-sub); line-height: var(--lh-sub); color: var(--text-2); text-wrap: pretty; }
.mt-label { margin-top: var(--gap-label); }
.mt-lead  { margin-top: var(--gap-lead); }
.mt-act   { margin-top: var(--gap-action); }

/* 누를 수 있는 것은 전부 이 규격이다 — 높이 44 · 글자 --t-body · 좌우 22.
   전에는 .btn 이 --t-sub(17), 검색 버튼이 상속 16, 칩이 --t-cap(12.5) 로
   토큰이 셋이었다. 같은 줄에 서면 1px 씩 어긋나 보인다.
   글자 크기는 랜딩 버튼(cta-link.tsx)과 같은 --t-body 로 맞춘다. */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 0 22px; border-radius: 999px;
  font-size: var(--t-body); font-weight: 600; border: 0; cursor: pointer;
  transition: transform var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.btn-solid { background: var(--accent); color: #fff; }
.btn-solid:hover { background: var(--accent-2); transform: translateY(-1px); }
/* 바탕은 .glass 가 깐다 — 여기서 또 깔면 뒤가 안 비쳐 유리가 막힌다. */
.btn-ghost { color: var(--text); --edge-scale: 1; }
.btn-ghost:hover { transform: translateY(-1px); }

/* ══════════════════════ 찾기 줄 ══════════════════════
   지금은 칩 8개로 카테고리 16개를 가린다. 전부 닿게 하되 한 번에 쏟지 않는다.
   상위 7개는 늘 보이고, 하위 9개는 "+9" 로 편다 (ui-ux-pro-max: 칩 오버플로).
   칩은 <button aria-pressed> 다 — div 가 아니다 (접근성 Critical).            */
form.search { flex: 1 1 260px; display: flex; min-width: 0; border-radius: 999px; overflow: hidden;
  --edge-scale: 1; --glass-blur: 14px; }
/* 입력·버튼은 표면광 위로 올린다 */
form.search input, form.search button { position: relative; z-index: 1; }
form.search input { flex: 1; min-width: 0; background: none; border: 0; color: var(--text);
  padding: 0 20px; height: 44px; font: inherit; font-size: 16px; } /* 16px — iOS 확대 방지 */
form.search input::placeholder { color: var(--text-3); }
form.search button { border: 0; background: var(--accent); color: #fff; padding: 0 22px;
  font: inherit; font-size: var(--t-body); font-weight: 600; cursor: pointer; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  height: 36px; padding: 0 15px; border-radius: 999px; border: 1px solid var(--line);
  background: rgba(255,255,255,.04); color: var(--text-2);
  font: inherit; font-size: var(--t-cap); font-weight: 600; white-space: nowrap; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  transition: color var(--dur-1), background var(--dur-1), border-color var(--dur-1);
}
.chip:hover { color: var(--text); background: rgba(255,255,255,.08); }
/* 터치 기기에서는 칩이 44px 이어야 한다. 마우스에서는 36px 로 둔다 —
   여덟 개가 한 줄에 서는 화면에서 44px 은 지나치게 굵다. */
@media (pointer: coarse) {
  .chip { height: 44px; padding-inline: 17px; }
}

/* 지금 보고 있는 분류. 칩은 <a> 라 aria-current 를 쓴다 —
   전에는 CSS 가 aria-pressed 를 보고 있어서 활성 표시가 아예 안 떴다. */
.chip[aria-current], .chip[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: #fff;
}

/* ══════════════════════ 카드 ══════════════════════
   랜딩 PostCard(blocks/post-card.tsx)와 같은 구성이다. 같은 글을 보여주는
   같은 카드가 화면마다 다르게 생길 이유가 없다.

     사진 180 (그 위 왼쪽 아래에 분류 알약)
     제목 두 줄 고정  ← A2Z regular, 본문 크기
     날짜 / 읽는 시간

   발췌와 구분선은 없다 — 랜딩에서 뺀 것과 같은 이유로 뺐다.
   높이를 못 박아야 카드마다 날짜가 같은 자리에 선다. */
.grid { display: grid; gap: 20px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* 높이를 못 박으면 내용이 짧은 카드 아래에 빈 칸이 크게 남는다.
   격자가 한 줄 안의 카드 높이를 알아서 맞춰 주므로(align-items: stretch)
   높이는 내용이 정하게 두고, 자리를 맞추는 일은 격자에 맡긴다. */
.card {
  display: flex; flex-direction: column; overflow: hidden;
  height: auto; min-height: 0; border-radius: var(--card-radius); --edge-scale: 1.6;
  transition: transform var(--dur-1) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.card:hover { transform: translateY(-4px); }
.card-thumb {
  position: relative; z-index: 1; height: var(--card-thumb-h); flex-shrink: 0;
  overflow: hidden; background: #0a0f1e;
}
/* 사진을 절대 배치한다. 흐름에 두면 height:100% 가 높이 없는 부모에서 풀려
   원본 비례(768×768)대로 칸을 밀어 올린다 — 큰 카드가 통째로 길어졌던 원인이다.
   랜딩이 next/image 의 fill 을 쓰는 것과 같은 자리다. */
.card-thumb img { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; transition: transform .5s var(--ease); }
.card:hover .card-thumb img { transform: scale(1.05); }
.card-thumb-none { display: flex; align-items: center; justify-content: center; height: 100%; font-size: 28px; }
/* 분류 알약이 사진 위에 앉으므로 아래쪽에 그늘을 깐다.
   사진이 어떤 색으로 오든(노랑·하양 일러스트가 많다) 알약이 읽혀야 한다. */
.card-thumb::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 62%;
  pointer-events: none; z-index: 1;
  background: linear-gradient(180deg, rgba(3,5,15,0), rgba(3,5,15,.62) 55%, rgba(3,5,15,.80));
}
.card-cats--on-thumb { z-index: 2; }
/* 분류 알약 — 사진 위에 얹힌다. 여기서는 뒤가 사진이라 흐림이 실제로 보인다.
   파랑은 진하게 유지한다. 투명하게 두면 밝은 썸네일 위에서 씻겨 연보라가 된다
   (랜딩에서 같은 문제를 겪고 내린 결론). */
/* 분류는 지금 화면처럼 **썸네일 아래 본문 영역**에 둔다. 사진 위에 얹으면
   밝은 썸네일에서 읽기 어렵고, 각각 링크라 누를 자리도 사진과 겹친다. */
.card-cats { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
/* 해시태그 — 분류 알약과 다른 모양이다. 알약은 '분류', 해시태그는 '꼬리표'다.
   한 줄로 두고 넘치는 것은 오른쪽 끝에서 사라진다. */
/* 넘치면 **말줄임표**로 자른다. 마스크로 흐리게 지웠더니 "가려진" 것으로
   보였다 — 잘린 것과 가려진 것은 다르게 읽힌다.
   flex 상자에는 text-overflow 가 안 먹으므로 한 줄짜리 블록으로 둔다.
   메타(날짜·읽는 시간) 바로 위에 붙인다 — 카드 높이가 고정이라
   margin-top: auto 를 메타에 주면 태그와 날짜 사이만 크게 벌어진다. */
/* 빈 공간을 태그 위로 옮기지 않는다 — 그러면 발췌와 태그 사이가 벌어진다.
   발췌 → 태그 → 날짜는 붙어 있고, 남는 공간은 카드 **맨 아래**에 둔다. */
.card-tags {
  display: block; margin-top: 8px;
  font-size: var(--t-cap); line-height: 1.6; color: var(--accent-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  /* 태그가 없는 글에서도 **자리를 비워 둔다.** 안 그러면 같은 줄에 선
     카드끼리 날짜 높이가 어긋나고, 큰 카드에서는 글 덩어리가 통째로 밀린다. */
  min-height: calc(1.6em);
}
.card-tags span + span { margin-left: 8px; }
/* 큰 카드는 사진이 크다 — 분류를 사진 안 왼쪽 아래에 얹는다.
   격자 카드는 사진이 작아 글자가 안 읽히므로 본문 영역에 둔다. */
/* 사진 칸을 기준으로 얹는다. 카드 전체를 기준으로 두면 카드 맨 아래에 붙어
   날짜를 덮는다 — 실제로 그렇게 보였다. */
.card-media { position: relative; display: flex; flex-direction: column; flex: 0 0 auto; }
.card-hero .card-media { flex: 1 1 0; min-height: 0; }
/* 남는 높이는 **사진**이 먹는다. 본문이 먹으면 글자 아래에 빈 칸이 남는다. */
.card-hero .card-body { flex: 0 0 auto; }
.card-cats--on-thumb { position: absolute; z-index: 2; left: 16px; bottom: 16px; margin: 0; }
.pill-cat {
  --edge-scale: .7; --glass-blur: 10px;
  border-radius: 999px; padding: 6px 12px; line-height: 1; color: #fff; font-weight: 600;
  font-size: var(--t-cap); background: rgba(0,72,235,.9);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 4px 12px -4px rgba(0,0,0,.6);
}
/* 썸네일과 글자를 겹치지 않는다 — 지금 모바일에서 제목이 이미지 위에 얹혀 안 읽힌다 */
.card-body { position: relative; z-index: 1; display: flex; flex-direction: column; flex: 1;
  padding: var(--card-pad); }
/* 제목은 자르지 않는다 — "글 제목은 원문 그대로" 가 상위 규칙이다.
   지금 화면도 클램프가 없다. 세 줄까지 자리를 잡아 두고 카드 높이를 맞춘다
   (지금 화면 관측 최대가 세 줄이다). */
/* 제목 상자가 본문 줄간격(1.72)을 물려받아 32px 제목의 줄 사이가 55px 이었다.
   제목은 제목 줄간격을 쓴다 — 글자가 커질수록 줄은 좁아져야 덩어리로 읽힌다. */
.card-title { flex-shrink: 0; line-height: var(--lh-head); }
.card-title a {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  font-size: var(--t-card-title); font-weight: 400; line-height: var(--lh-head); color: var(--text);
  min-height: calc(2em * var(--lh-head));
}
.card:hover .card-title a { color: var(--accent-3); }
/* 발췌 — 지금 화면에 있는 것이다. 없으면 목록에서 글을 고를 근거가 제목뿐이 된다. */
.card-excerpt {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  margin-top: 8px; font-size: var(--t-cap); line-height: 1.6; color: var(--text-2);
}
/* 구분선을 두지 않는다. 랜딩 카드에 없고, 없어야 카드가 한 덩어리로 읽힌다. */
.card-meta {
  margin-top: 8px; display: flex; align-items: center; justify-content: space-between;
  /* --text-3 는 12px 글자에서 3.45:1 이라 기준(4.5)에 못 미친다. 실측해서 올린다. */
  font-size: var(--t-cap); color: var(--text-2); font-variant-numeric: tabular-nums;
}
.card-mins { display: inline-flex; align-items: center; gap: 6px; }

/* ══════════════════════ 머리 — 최신 글 ══════════════════════
   전에는 큰 카드가 높이를 정하고 레일이 거기 맞춰 늘어났다. 큰 카드가
   사진(최대 360) + 제목 3줄 + 발췌 3줄이라 너무 높았고, 레일 세 줄은
   78px 썸네일 하나를 안고 150px 짜리 칸에 떠 있었다 — 그래서 오른쪽이
   헐거워 보였다.

   방향을 뒤집는다. **레일이 높이를 정하고, 큰 카드의 사진이 남는 만큼
   늘어난다.** 빈 칸이 어디에도 안 생기고, 사진은 클수록 좋은 요소다.
   폭도 1.35:1 → 1.55:1 로 좁혀 레일 제목이 제 폭을 쓰게 한다. */
.lead-grid { display: grid; gap: 20px; grid-template-columns: 1.55fr 1fr; align-items: stretch; }
@media (max-width: 899px) { .lead-grid { grid-template-columns: 1fr; } }

.card-hero { height: 100%; }
/* 사진이 남는 높이를 먹는다. 아래가 레일보다 짧아지지 않게 최소치만 준다. */
/* 사진이 남는 높이를 **다 먹으면** 글 영역이 눌린다. 반대로 사진에 최대치만
   주면 사진 아래에 빈 칸이 남고, 분류 알약이 그 빈 칸 바닥에 붙는다
   (알약은 .card-media 를 기준으로 앉는다).
   **사진을 비율로 못 박고 글은 제 높이를 갖게 한다.** 그러면 태그가 있든
   없든 카드 비율이 흔들리지 않는다. */
/* 사진이 남는 높이를 먹는다. 글 영역은 제 높이를 갖고, 카드는 오른쪽
   두 장과 같은 높이까지 늘어난다 — 한쪽 아래만 비면 비율이 엉망으로 보인다.
   알약은 .card-media 를 기준으로 앉으므로, media 가 사진과 같은 높이여야
   알약이 사진 안 왼쪽 아래에 앉는다 (max-height 를 주면 media 가 사진보다
   커져서 알약이 사진 밖 빈 칸에 붙는다 — 실제로 그랬다). */
.card-hero .card-media { flex: 1 1 auto; min-height: 0; }
.card-hero .card-thumb { flex: 1 1 0; height: 100%; min-height: clamp(200px, 20vw, 260px); }
.card-hero .card-body { padding: var(--card-pad) calc(var(--card-pad) + 4px) calc(var(--card-pad) + 4px); }
/* 24(--t-h3) 를 줬더니 섹션 제목과 같은 크기가 됐다. 카드 제목이 섹션 제목과
   같으면 위계가 사라진다. 보통 카드(16)와 섹션(24) 사이에 둔다. */
/* 크기를 .card-title 에만 주면 안 된다 — 글자는 그 안의 <a> 에 있고
   기본 규칙(.card-title a)이 --t-card-title(16)로 다시 덮는다.
   실제로 큰 카드 제목이 오른쪽 카드와 같은 크기로 떴다. */
.card-hero .card-title { font-size: var(--t-card-title-hero); -webkit-line-clamp: 3; height: auto; }
.card-hero .card-title a { font-size: inherit; font-weight: 700; }
/* 발췌도 한 단계 크게 — 큰 카드는 글 덩어리 전체가 커야 큰 카드로 읽힌다 */
/* 제목이 16 → 32 로 커졌으면 그 아래 간격도 따라 커져야 한다.
   8px 은 16px 제목에 맞춘 값이었다. */
.card-hero .card-excerpt { font-size: var(--t-sub); -webkit-line-clamp: 3; margin-top: 14px; }
.card-hero .card-meta { font-size: var(--t-sub); }
@media (max-width: 899px) {
  .card-hero { height: auto; }
  .card-hero .card-thumb { flex: 0 0 auto; height: clamp(200px, 46vw, 300px); }
}

/* 오른쪽 칸도 **같은 카드 컴포넌트** 두 장이다.
   전에는 왼쪽만 카드고 오른쪽은 글줄 목록이라 둘의 비율이 계속 어긋났다.
   같은 부품을 쓰면 비율이 저절로 맞는다. */
.rail { display: flex; flex-direction: column; gap: 20px; }
/* flex: 1 1 0 을 줬더니 두 장이 0 을 기준으로 눌려 띠가 됐다.
   카드는 제 내용만큼 높고, 큰 카드가 그 합에 맞춰 늘어난다. */
.card-side { height: auto; flex: 0 0 auto; }
/* 좁은 칸이라 사진을 낮추고 글을 살린다 */
.card-side .card-thumb { height: clamp(120px, 11vw, 156px); flex: 0 0 auto; }
.card-side .card-title { font-size: var(--t-body); }
/* 제목이 한 줄인 카드와 두 줄인 카드의 높이가 20px 어긋났다.
   두 줄 자리를 늘 잡아 두 장이 같은 높이로 선다 (격자 카드와 같은 규칙). */
.card-side .card-title a {
  -webkit-line-clamp: 2;
  min-height: calc(2em * var(--lh-head));
}
.card-side .card-excerpt { -webkit-line-clamp: 2; }
.rail-item { flex: 1 1 auto; display: grid; grid-template-columns: 112px 1fr; gap: 14px;
  align-items: start; padding: 14px 12px; border-radius: 14px; transition: background var(--dur-1); }
.rail-body { display: block; min-width: 0; }
.rail-item:hover { background: rgba(255,255,255,.05); }
.rail-item + .rail-item { border-top: 1px solid var(--line); }
.rail-thumb { width: 112px; height: 72px; border-radius: 10px; overflow: hidden; background: #0a0f1e; }
.rail-thumb img { width: 100%; height: 100%; object-fit: cover; transition: transform .5s var(--ease); }
.rail-item:hover .rail-thumb img { transform: scale(1.05); }
.rail-title { font-size: var(--t-body); font-weight: 500; line-height: 1.45; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* 발췌 두 줄 — 지금 화면이 주던 정보를 되살린다 */
.rail-excerpt {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  margin-top: 6px; font-size: var(--t-cap); line-height: 1.55; color: var(--text-2);
}
/* 태그 — 지금 화면 오른쪽 목록이 보여주던 것이다 */
/* 레일 분류 알약 — 왼쪽 큰 카드와 같은 요소 구성으로 맞춘다 */
.rail-cats { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.rail-cats .pill-cat { padding: 5px 11px; }
.rail-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px;
  font-size: var(--t-cap); color: var(--accent-3); }
.rail-tags span { white-space: nowrap; }
.rail-meta { display: block; margin-top: 6px; font-size: var(--t-cap); color: var(--text-2); font-variant-numeric: tabular-nums; }
/* 여백은 .rail 의 gap(20) 하나가 정한다. margin-top 을 또 주면 34 가 되어
   카드 사이(20)와 버튼 위(34)가 어긋난다. */
.rail-cta { width: 100%; justify-content: center; flex: 0 0 auto; }

/* ══════════════════════ 카테고리 섹션 ══════════════════════
   좌측 sticky 를 버린다. 300px 을 떼가면 카드가 3열로 안 들어가 2+1 로 접히고,
   그때마다 좌측 아래에 빈 칸이 남는다 (지금 화면의 마지막 400px 도 같은 원인).
   제목 줄을 가로로 눕히면 카드가 폭을 다 쓰고 352px — 랜딩 카드(360)와 같은 자리에 선다. */
.cat-sec { padding-block: clamp(40px, 6vw, 88px); }
.cat-sec + .cat-sec { border-top: 1px solid var(--line); }
.cat-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 24px; flex-wrap: wrap; margin-bottom: clamp(20px, 2.6vw, 32px);
}
/* 제목은 카드보다 위지만 카드를 누르지 않는다. 카드가 주인공인 화면이라
   --t-h2(42) 대신 한 단계 낮춘다 (better-typography 원칙 6). */
.cat-head .sec-title { font-size: var(--t-h3); }
/* 설명이 억지로 두 줄로 접히면 제목과의 리듬이 깨진다.
   제목 덩어리가 "더 읽기" 버튼에 밀려 좁아진 탓이라, 남는 폭을 다 쓰게 한다. */
.cat-head > div:first-child { flex: 1 1 auto; min-width: 0; }
/* text-wrap: pretty 는 마지막 줄에 한 단어만 남는 것을 피하려고 **일부러
   폭을 덜 쓴다**. 그래서 한 줄에 들어갈 글이 두 줄로 접혔다. auto 가 폭을
   가장 잘 쓴다 — 어절은 body 의 word-break: keep-all 이 이미 지킨다. */
.cat-head .sec-desc { margin-top: 10px; max-width: none; text-wrap: auto; }

.grid { display: grid; gap: 20px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1023px) { .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 639px)  { .grid { grid-template-columns: 1fr; } }

/* ══════════════════════ 리빌 (GSAP 이 끄고 켠다) ══════════════════════ */
/* CSS 로 숨기지 않는다. JS 가 실패하거나 크롤러가 오면 그대로 보여야 한다.
   시작 상태는 GSAP 이 gsap.from 으로 잡는다. */
.reveal { will-change: transform, opacity; }

/* --text-3 는 12.5px 글자에서 3.74:1 로 기준(4.5)에 못 미친다. 한 단계 올린다. */
footer { border-top: 1px solid var(--line); padding-block: 56px; text-align: center; color: var(--text-2); font-size: var(--t-cap); }

/* ══════════════════ 헤더 ══════════════════
   랜딩 SiteHeader 와 같은 동작. 맨 위에서는 본문 정렬선을 쓰고,
   스크롤하면 가운데로 좁아지며 유리 알약이 된다. 내리면 감추고 올리면 나온다. */
:root {
  --header-top-pad: max(clamp(18px, 2.4vw, 30px), calc(env(safe-area-inset-top, 0px) + 10px));
  --header-top-pad-scrolled: max(clamp(14px, 1.6vw, 22px), calc(env(safe-area-inset-top, 0px) + 10px));
}
#siteHeader {
  position: fixed; inset-inline: 0; top: 0; z-index: 50;
  transition: transform .5s var(--ease);
}
#siteHeader[data-hidden="true"] { transform: translateY(-120%); }
.head-shell {
  margin-inline: auto;
  max-width: var(--measure); padding-inline: var(--gutter);
  padding-top: var(--header-top-pad); padding-bottom: clamp(18px, 2.4vw, 30px);
  transition: max-width .5s var(--ease), padding .5s var(--ease);
}
#siteHeader[data-scrolled="true"] .head-shell {
  max-width: min(760px, calc(100% - 2 * var(--gutter)));
  padding-inline: 0;
  padding-top: var(--header-top-pad-scrolled); padding-bottom: clamp(14px, 1.6vw, 22px);
}
.head-row { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 24px; padding-inline: 20px; }
/* 유리 판. 랜딩은 React Bits GlassSurface(SVG 굴절)를 쓰는데,
   여기서는 그 판이 하는 일(뒤를 눌러 링크를 읽히게)만 CSS 로 낸다. */
/* 랜딩의 GlassSurface 판과 **같은 값**이다 (실측).
   랜딩은 흐림이 아니라 SVG 굴절 필터를 쓴다 — React 없이는 못 옮기므로
   그 자리만 흐림으로 낸다. 나머지(바탕·테두리 없음·안팎 그림자·둥글기·전환)는
   실측한 값 그대로다. 전에는 1px 흰 테두리를 두르고 그림자가 없어
   같은 판이 다르게 보였다. */
.head-glass {
  position: absolute; inset: 0; z-index: -1; border-radius: 999px;
  background: rgba(0,0,0,.55);
  /* 랜딩의 굴절 필터(feDisplacementMap)를 그대로 옮겨 봤고, 되돌렸다.
     맵을 판 크기에 맞춰 다시 만들어도(랜딩이 하는 그대로) 굴절 결과가
     알약 밖으로 새어 나왔다 — backdrop-filter: url() 이 밝고 대비 큰 배경
     위에서 깨진다. 랜딩은 헤더 뒤가 어두운 히어로라 그 깨짐이 안 보일 뿐이다.
     목록 화면은 헤더 아래로 밝은 썸네일이 지나간다.
     **깨진 굴절보다 차분한 흐림이 낫다.** 이식할 때 React 로 GlassSurface 를
     쓰면 그때 굴절로 돌아간다. */
  backdrop-filter: blur(3px) saturate(1);
  -webkit-backdrop-filter: blur(3px) saturate(1);
  border: 0;
  box-shadow:
    inset 0 0 2px 1px rgba(255,255,255,.35),
    inset 0 0 10px 4px rgba(255,255,255,.15),
    0 4px 16px rgba(17,17,26,.05), 0 8px 24px rgba(17,17,26,.05), 0 16px 56px rgba(17,17,26,.05),
    inset 0 4px 16px rgba(17,17,26,.05), inset 0 8px 24px rgba(17,17,26,.05), inset 0 16px 56px rgba(17,17,26,.05);
  opacity: 0; transition: opacity .26s cubic-bezier(0,0,.2,1);
}
#siteHeader[data-scrolled="true"] .head-glass { opacity: 1; }
.head-logo { display: flex; align-items: center; padding-block: 12px; min-width: 44px; justify-content: center; }
.head-logo img { height: 2.2em; width: auto; font-size: var(--t-body); }
/* 랜딩 헤더와 **같은 값**이다 (blocks/nav-links.tsx: text-sm · font-medium · px-4 · gap-2).
   시안이 16px/400·활성 600 이라 같은 헤더가 두 화면에서 딴판으로 보였다.
   활성은 굵기로 가르지 않는다 — 밑줄이 그 일을 한다. */
/* 링크를 감싼다 — 랜딩도 각 링크를 <div> 로 감싸서 <a> 가 flex 자식이 아니다.
   flex 자식이 되면 display:inline 이 block 으로 바뀌어 상하 패딩이 높이를 밀고
   헤더가 두꺼워진다 (24 → 36). */
.nav-item { display: block; }
.head-nav { position: relative; display: flex; align-items: center; gap: 8px; font-size: 14px; }
/* 랜딩은 px-4 py-2 text-sm — 좌우 16 · 상하 8 · 줄간 20 · 높이 32 다.
   시안은 min-height 44 에 줄간이 본문(1.72)을 물려받아 44 였다.
   마우스에서는 랜딩과 같게 하고, 터치에서만 44 를 준다. */
.head-nav a {
  /* 랜딩은 display:inline 이라 상하 패딩(8)이 줄 높이를 밀지 않는다 —
     그래서 내비 높이가 24 다. 링크를 .nav-item 으로 감싸 flex 자식이 아니게
     해야 inline 이 유지된다. */
  position: relative; display: inline; padding: 8px 16px; line-height: 20px;
  font-weight: 500; border-radius: 999px;
  color: var(--text-2); transition: color .2s;
}
@media (pointer: coarse) { .head-nav a { min-height: 44px; } }
/* 맨 위에서는 뒤에 배경이 밝아 회색 링크가 묻힌다. 유리 판이 깔리면 되돌린다. */
/* 링크는 .nav-item / .nav-has-sub 로 감싸여 있다. '.head-nav > a' 는
   아무것도 안 잡고 '.nav-has-sub > a' 는 블로그만 잡아서, 소개·연락처
   화면에서도 블로그만 흰색으로 남아 있었다. 하위 목록 안만 빼고 전부 본다. */
#siteHeader:not([data-scrolled="true"]) .head-nav a:not(.nav-sub a) { color: var(--text); }
.head-nav a:hover { color: var(--text); }
.head-nav a[aria-current] { color: var(--text); }

/* ── 밑줄 하나가 옮겨 다닌다 ──
   회색 알약을 각 링크에 깔면 '눌린 칸'처럼 보이고, 항목마다 따로 있어서
   사이를 건너가지 못한다. 밑줄은 하나만 두고 JS 가 위치·폭을 준다.
   left 가 아니라 transform 으로 옮긴다 — 레이아웃을 다시 잡지 않는다. */
.nav-ink {
  position: absolute; top: 0; left: 0; height: 2px; width: 1px;
  border-radius: 2px; background: var(--accent-3);
  transform-origin: left center; transform: translateX(0) scaleX(0);
  opacity: 0; pointer-events: none;
  transition: transform .34s var(--ease), opacity .2s var(--ease);
}
.head-nav[data-ink="on"] .nav-ink { opacity: 1; }

/* ── '블로그' 하위 목록 ──
   원래 헤더에 있던 것을 되살린다 (existing-css-js 의 .mp-blog-dropdown).
   여는 데 120ms, 닫는 데 250ms 를 두는 것도 원래 동작 그대로다 —
   지나가다 스치는 것만으로 열리면 안 되고, 항목으로 내려가는 도중에
   닫히면 못 누른다. */
/* inline-flex 로 두면 그 안의 <a> 가 flex 자식이 되어 blockify 된다 —
   상하 패딩이 높이를 밀어 헤더가 두꺼워진다. block 으로 둔다. */
.nav-has-sub { position: relative; display: block; }
.nav-sub {
  position: absolute; top: calc(100% + var(--sub-offset, 12px)); left: 50%; z-index: 60;
  min-width: 184px; padding: 0; border-radius: 14px;
  /* 항목이 판 끝까지 닿게 하고, 모서리는 판이 자른다 */
  overflow: hidden;
  display: flex; flex-direction: column;
  background: rgba(8, 12, 26, .72);
  backdrop-filter: blur(22px) saturate(160%); -webkit-backdrop-filter: blur(22px) saturate(160%);
  border: 1px solid rgba(255,255,255,.10);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 20px 48px -16px rgba(0,0,0,.75);
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateX(-50%) translateY(6px);
  transition: opacity .25s var(--ease), transform .25s var(--ease), visibility .25s;
}
.nav-has-sub[data-open="true"] .nav-sub {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}
/* 링크와 목록 사이의 빈 틈에서 마우스가 빠져 닫히는 것을 막는 다리 */
.nav-has-sub[data-open="true"]::after {
  content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  width: 200px; height: var(--sub-offset, 14px);
}

/* 헤더가 알약으로 좁아지면 링크가 알약 **안쪽**에 있다. 링크 기준 12px 아래는
   아직 알약 안이라 목록이 헤더를 파고들었다 (실측: 링크 아래 13px 이 알약 바닥).
   알약을 벗어난 뒤에 열리게 한다 — 다리(::after)도 같이 늘려야 마우스가
   내려가는 도중에 닫히지 않는다. */
#siteHeader[data-scrolled="true"] .nav-has-sub { --sub-offset: 26px; }
/* 크기는 이 화면의 것이다. 지금 화면 값(13.5px)을 베끼지 않는다 —
   내비 글자(14)보다 한 단계 아래인 --t-cap 이 이 자리의 크기다. */
/* .head-nav a 의 border-radius: 999px 를 물려받아 마우스 표시가 알약이
   됐다. 목록 항목은 줄이지 알약이 아니다 — 직선으로 판을 꽉 채운다. */
.nav-sub a {
  min-height: 0; padding: 11px 20px; border-radius: 0;
  font-size: var(--t-cap); line-height: 1.5;
  font-weight: 500; color: var(--text-2); white-space: nowrap;
  transition: background .18s, color .18s;
}
.nav-sub a:hover { background: rgba(40,100,255,.16); color: var(--text); }

/* 고정 헤더가 본문을 덮지 않게 자리를 만든다 */
.head-spacer { height: calc(var(--header-top-pad) + 44px + clamp(18px, 2.4vw, 30px)); }

@media (max-width: 639px) {
  .head-nav { gap: 0; font-size: var(--t-cap); }
  /* 터치 영역 44×44. "홈" 처럼 짧은 라벨은 글자 폭만으로는 30px 밖에 안 된다. */
  .head-nav a { padding-inline: 8px; min-width: 44px; justify-content: center; }
  /* 원래 헤더도 768 아래에서는 호버 목록을 띄우지 않았다 (햄버거 안에 폈다).
     이 시안에는 햄버거가 없으므로 접어 둔다. */
  .nav-sub { display: none; }
}

/* ══════════════════ 찾기 줄 ══════════════════ */
.finder { padding-block: clamp(20px, 3vw, 40px) clamp(20px, 2.6vw, 32px); }
.finder-label { font-size: var(--t-cap); font-weight: 700; color: var(--text-2); letter-spacing: .04em; }
.finder-row { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; margin-top: 14px; }
.finder .chips { flex: 1 1 auto; }
.finder .search { flex: 0 1 380px; }

/* ══════════════════ 머리 ══════════════════ */
.lead { padding-block: 0 clamp(32px, 5vw, 64px); }
/* 칩줄이 있던 자리다. 칩을 걷어내고 제목과 검색만 남긴다 —
   /post 는 '전체'를 고른 상태가 아니라 블로그의 첫 화면이다. */
.lead-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; flex-wrap: wrap;
  padding-block: clamp(20px, 3vw, 40px) clamp(18px, 2.2vw, 28px);
}
/* 카드가 주인공인 화면이라 --t-h2(42) 대신 한 단계 낮춘다 (카테고리 제목과 같은 크기). */
.lead-head .sec-title { font-size: var(--t-h3); }
.lead-head .search { flex: 0 1 380px; }

/* ══════════════════ 푸터 ══════════════════ */
.foot-mark { font-size: 16px; color: var(--text-2); }
.foot-copy { margin-top: 18px; }
.foot-link { color: var(--accent-3); text-decoration: underline;
  text-underline-offset: 3px; text-decoration-thickness: from-font;
  /* 터치 영역 44px. 글자만으로는 86×14 라 손가락으로 누르기 어렵다. */
  display: inline-flex; align-items: center; min-height: 44px; padding-inline: 8px; }

/* ══════════════ 목록 페이지 머리 ══════════════
   /post-all/ · /category/* · /tag/* · 검색이 공유한다.
   "전체 글 233" 처럼 이름과 수가 한 줄에 온다 — 수는 A2Z 가 아니라
   본문 서체 + tabular-nums 로 둔다. 숫자가 바뀌어도 자리가 안 흔들린다. */
/* 설명이 있는 화면(카테고리·태그)과 없는 화면(전체 글)이 **같은 구조**여야 한다.

   전에는 설명을 제목 옆에 눕혀서 격자 시작 위치만 맞췄다. 수치로는 2px 이지만
   화면에서는 한쪽에만 오른쪽에 문단이 있어 다른 페이지로 보였다.

   설명은 제목 아래가 제 자리다. 대신 **설명이 없는 화면에도 그 자리를 비워 둔다** —
   그래야 페이지를 옮겨도 격자가 안 튄다. 모바일에서 이미 쓰던 수법을 전체로 넓힌다. */
.list-head {
  padding-block: clamp(24px, 3vw, 40px) clamp(20px, 2.4vw, 28px);
}
.list-title {
  font-size: var(--t-h2); line-height: var(--lh-head); letter-spacing: -.01em;
  display: flex; align-items: baseline; gap: .5em; flex-wrap: wrap;
}
.list-count {
  font-family: "Pretendard Variable", Pretendard, sans-serif;
  font-size: .62em; font-weight: 600; color: var(--accent-3);
  font-variant-numeric: tabular-nums;
}
/* 62ch 는 화면 절반밖에 안 됐다. 본문 폭을 다 쓰되 너무 길어지지만 않게 한다. */
.list-desc {
  margin: var(--gap-lead) 0 0; max-width: min(100%, 92ch);
  font-size: var(--t-sub); line-height: var(--lh-sub); color: var(--text-2);
  /* 자르지 않는다. 원문 설명은 그 분류가 무엇을 다루는지 알려주는 글이다 —
     잘라내면 문장이 끊긴다. 설명이 없는 화면이 두 줄을 비워 두므로
     두 줄짜리 설명에서는 격자가 같은 자리에 선다. */
  text-wrap: pretty;
}
/* 설명이 없는 화면은 그만큼을 여백으로 비운다 */
.list-head::after {
  content: ""; display: block;
  height: calc(var(--t-sub) * var(--lh-sub) * 2 + var(--gap-lead));
}
.list-head:has(.list-desc)::after { display: none; }

/* 검색은 제목 옆에 검색창이 온다 */
.list-head--search { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.list-head--search .search { flex: 0 1 380px; }

/* 검색어 표시 — 지금 화면의 큰 가운데 글자 */
.search-hero { padding-block: clamp(40px, 7vw, 96px) clamp(24px, 3vw, 40px); text-align: center; }
.search-term { font-size: var(--t-display); line-height: var(--lh-head); letter-spacing: -.02em; }

/* ══════════════ 페이지 넘김 ══════════════ */
/* ══════════════ 페이지 이동 ══════════════
   지금 화면과 같은 자리·구조다: 가운데 한 줄, 숫자 알약, 그 옆에 "다음 페이지".
   실측값 — 둥글기 10 · 안여백 7/14 · 글자 14 · 활성은 반투명 파랑 배경에
   파란 글자(꽉 찬 파랑이 아니다). 색만 리뉴얼 토큰으로 바꾼다. */
.pager {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 8px; padding-block: clamp(32px, 4vw, 56px);
}
/* 규격은 이 화면의 것이다 — 알약(999)·칩과 같은 높이·--t-cap.
   지금 화면 값(둥글기 10 · 7/14 · 14px)을 그대로 쓰지 않는다.
   가져오는 것은 "가운데 한 줄에 숫자와 다음 페이지가 선다" 는 구성뿐이다. */
.page-num, .page-next {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 36px; padding: 0 15px; border-radius: 999px;
  font-size: var(--t-cap); font-weight: 600; color: var(--text-2);
  background: rgba(255,255,255,.04); border: 1px solid var(--line);
  transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1);
}
.page-num:hover, .page-next:hover { color: var(--text); background: rgba(255,255,255,.08); }
/* 지금 보고 있는 쪽 — 칩 활성과 같은 표시다 (한 화면에서 같은 뜻은 같은 모양) */
.page-num[aria-current] { background: var(--accent); border-color: var(--accent); color: #fff; }
.page-nums { display: inline-flex; align-items: center; gap: 8px; }
.page-prev[aria-disabled], .page-next[aria-disabled] { opacity: .35; pointer-events: none; }
.page-prev, .page-next {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 36px; padding: 0 15px; border-radius: 999px;
  font-size: var(--t-cap); font-weight: 600; color: var(--accent-3);
  background: rgba(255,255,255,.04); border: 1px solid var(--line);
  transition: background var(--dur-1), color var(--dur-1);
}
.page-prev:hover, .page-next:hover { color: var(--text); background: rgba(255,255,255,.08); }
.grid-empty { grid-column: 1 / -1; padding: 48px 0; text-align: center; color: var(--text-2); }
.page-gap { padding-inline: 6px; font-size: var(--t-cap); color: var(--text-2); }
/* 터치에서는 44×44. 한 자리 숫자는 폭이 모자란다. */
@media (pointer: coarse) { .page-num, .page-next, .page-prev { min-height: 44px; min-width: 44px; } }

  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding-block: clamp(36px, 5vw, 64px); font-variant-numeric: tabular-nums;
}
.pager a, .pager-now {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding-inline: 10px; border-radius: 12px;
  font-size: var(--t-cap); font-weight: 600; color: var(--text-2);
  transition: background var(--dur-1), color var(--dur-1);
}
.pager a:hover { background: rgba(255,255,255,.06); color: var(--text); }
.pager-now { background: var(--accent); color: #fff; }
.pager-gap { padding-inline: 6px; color: var(--text-2); }
.pager-next { margin-left: auto; }


/* 콘텐츠로 바로가기 — 초점이 오기 전에는 화면 밖에 있다 */
.skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 999;
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 20px; border-radius: 999px;
  background: var(--accent); color: #fff; font-size: var(--t-cap); font-weight: 600;
  transform: translateY(-200%); transition: transform .2s var(--ease);
}
.skip-link:focus { transform: translateY(0); }

/* 푸터 — 지금 화면과 같이 로고와 LinkedIn 을 둔다 */
.foot-logo { display: inline-flex; justify-content: center; }
.foot-logo img { height: 52px; width: auto; }
.foot-social { margin-top: 18px; }
/* 아이콘 자리에 글자를 두지 않는다 — 운영 푸터도 아이콘이다.
   그림은 24px 이지만 누르는 자리는 44×44 다. */
.foot-social a {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  color: var(--text-2); transition: color var(--dur-1);
}
.foot-social svg { display: block; }
.foot-social a:hover { color: var(--text); }

/* ══════════════════════ 소개 · 연락처 ══════════════════════
   목록·글 화면과 같은 정렬선(.wrap) · 같은 유리 · 같은 사다리를 쓴다.
   문구·구성·순서는 운영 화면 원문 그대로다. */
.page-hero { padding-block: clamp(28px, 4vw, 56px) clamp(40px, 6vw, 80px); }
.page-hero-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(24px, 4vw, 56px); align-items: center;
}
.page-hero-media {
  margin: 0; border-radius: var(--card-radius); overflow: hidden; --edge-scale: 1.6;
}
.page-hero-media img { width: 100%; height: auto; display: block; }
.page-hero-body { display: flex; flex-direction: column; align-items: flex-start; gap: var(--gap-lead); }

/* 제목은 랜딩 사다리의 --t-h2 자리다. 줄간격은 제목 값을 쓴다 —
   본문 값(1.72)을 물려받으면 큰 글자의 줄 사이가 벌어져 덩어리로 안 읽힌다. */
.page-title {
  font-size: var(--t-h2); line-height: var(--lh-head); letter-spacing: -.01em;
  color: var(--text); text-wrap: auto;
}
.page-lead {
  font-size: var(--t-lead); line-height: var(--lh-sub); color: var(--text-2);
  max-width: 62ch; text-wrap: auto;
}

.page-sec { padding-block: 0 clamp(56px, 8vw, 112px); }
.page-sec--first { padding-top: clamp(28px, 4vw, 56px); }
.page-sec .sec-title { margin-bottom: clamp(20px, 2.6vw, 32px); }
.page-sec--first .page-lead { margin-top: var(--gap-lead); }

.page-cols {
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  /* 좌우 판의 아래가 안 맞으면 한쪽만 붕 뜬 것으로 보인다. 같은 높이로 세운다.
     (실측: 연락처가 좌 260 / 우 310 으로 50px 어긋나 있었다) */
  gap: 20px; align-items: stretch; margin-top: clamp(24px, 3vw, 40px);
}
.page-cols--contact { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.page-side { display: flex; flex-direction: column; gap: 20px; }
/* 옆 칸이 한 장뿐이면 그 한 장이 칸을 채운다 */
.page-side > .page-panel { flex: 1 1 auto; }

/* ── 판 안의 글자 크기 규칙 ──
   두 단계만 쓴다: **제목 --t-h3, 나머지 전부 --t-body.**
   위계는 크기가 아니라 굵기와 색이 만든다.
   전에는 설명·주석에 --t-sub 를 썼는데, 이 사다리에서 --t-sub(17)는
   --t-body(16)보다 **크다**. 그래서 설명이 이름보다, 주석이 본문보다
   커져 한 판 안에서 글자가 들쭉날쭉했다. */
.page-panel {
  border-radius: var(--card-radius); --edge-scale: 1.2;
  padding: clamp(22px, 2.6vw, 34px);
  font-size: var(--t-body); line-height: var(--lh-body); color: var(--text-2);
}
.page-panel > * + * { margin-top: var(--gap-lead); }

/* 판 안의 작은 제목. 눈썹(대문자 자간)을 쓰지 않는다 — 제목이 제 무게로 선다. */
.panel-title {
  display: block;
  font-family: a2z, "Pretendard Variable", Pretendard, sans-serif;
  font-size: var(--t-h3); line-height: var(--lh-head); font-weight: 700;
  color: var(--text); letter-spacing: -.01em;
}
/* 제목 → 내용 간격. **한 군데서만 정한다** — 판마다 다르면 안 된다.
   10px 은 붙어 보인다. 이메일·LinkedIn 카드가 갖고 있던 18px 이 기준이다. */
.page-panel .panel-title + * { margin-top: 18px; }
.page-panel .panel-title:not(:first-child) { margin-top: var(--gap-action); }

/* --text-3(#5a6a8a)는 이 판 위에서 3.6:1 로 기준(4.5)에 못 미친다.
   보조 문장이라도 읽을 수 없으면 없는 것과 같다 — 한 단계 올린다. */
.page-note { font-size: var(--t-body); color: var(--text-2); }
.topic-line { color: var(--text); font-weight: 500; }

/* 이력 — 이름과 설명을 두 층으로 나눈다. 한 줄에 이어 붙이면 아홉 줄이
   같은 무게로 흘러 무엇이 회사고 무엇이 한 일인지 안 갈린다. */
/* 아홉 줄을 한 줄로 세우면 755px 이 되어 왼쪽 칸이 오른쪽보다 434px 길어졌다.
   두 줄로 깔면 그 차이가 사라지고, 항목 하나하나도 눈에 더 잘 들어온다. */
.cv {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 24px;
}
.cv li { padding: 14px 0; border-top: 1px solid var(--line); }
.cv li:nth-child(-n+2) { border-top: 0; padding-top: 0; }
@media (max-width: 639px) {
  .cv { grid-template-columns: 1fr; }
  .cv li:nth-child(2) { border-top: 1px solid var(--line); padding-top: 14px; }
}
.cv-name {
  display: block; color: var(--text); font-weight: 600;
  font-size: var(--t-body); line-height: 1.4;
}
.cv-desc {
  display: block; margin-top: 4px;
  font-size: var(--t-body); line-height: var(--lh-sub); color: var(--text-2);
}

.bullets { margin: 0; padding-left: 1.2em; }
.bullets li { margin-top: 8px; }
.bullets li::marker { color: var(--text-3); }

.page-figure { margin: 0; border-radius: 14px; overflow: hidden; }
.page-figure img { width: 100%; height: auto; display: block; }

/* 연락처 카드 — 누를 수 있는 판이라 호버가 반응한다 */
/* gap 을 따로 주지 않는다. 주면 위 공통 규칙에 더해져 판마다 값이 갈린다. */
.contact-card { display: flex; flex-direction: column; }
.contact-value {
  font-size: var(--t-body); color: var(--accent-3);
  overflow-wrap: break-word; word-break: keep-all;
}
.contact-card:hover .contact-value { color: var(--text); }

@media (max-width: 899px) {
  .page-hero-grid, .page-cols, .page-cols--contact { grid-template-columns: 1fr; }
}
